diff --git a/README.md b/README.md index 215c184b89e0643d54725d606595fb34f16d5fe9..dccba4782fffde089694cc3402d7f07685de50d5 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,8 @@ The Google Cloud template uses some global configuration used throughout all job | ------------------------ | -------------------------------------- | ----------------- | | `GCP_CLI_IMAGE` | the Docker image used to run Google Cloud CLI commands| `google/cloud-sdk:latest` | | :lock: `GCP_KEY_FILE` | Default [Service Account key file](https://cloud.google.com/bigquery/docs/authentication/service-account-file) | _none_ | +| `GCP_OIDC_PROVIDER` | Default Workload Identity Provider associated with GitLab to [authenticate with OpenID Connect](https://docs.gitlab.com/ee/ci/cloud_services/google_cloud/) | none| +| `GCP_OIDC_ACCOUNT` | Default Service Account to which impersonate with OpenID Connect authentication | none | | `GCP_BASE_APP_NAME` | Base application name | `$CI_PROJECT_NAME` ([see GitLab doc](https://docs.gitlab.com/ee/ci/variables/predefined_variables.html)) | | `GCP_SCRIPTS_DIR` | Directory where Google Cloud scripts (deploy & cleanup) are located | `.` _(root project dir)_ | @@ -83,6 +85,27 @@ Here are some advices about your **secrets** (variables marked with a :lock:): it will then be possible to mask it and the template will automatically decode it prior to using it. 3. Don't forget to escape special characters (ex: `$` -> `$$`). +### Federated authentication using OpenID Connect + +The GCP template supports a [federated authentication using OpenID Connect](https://docs.gitlab.com/ee/ci/cloud_services/google_cloud/). + +If you wish to use this authentication mode, please follow carefully [the GitLab guide](https://docs.gitlab.com/ee/ci/cloud_services/google_cloud/), then configure appropriately the related variables: + +* `GPC_OIDC_PROVIDER` / `GPC_OIDC_ACCOUNT` for any global/common access, +* `GPC_<env>_OIDC_PROVIDER` / `GPC_<env>_OIDC_ACCOUNT` if you wish to use separate settings with any of your environments. + +The `GPC_OIDC_PROVIDER` & `GPC_<env>_OIDC_PROVIDER` variable shall be of the form: + +``` +projects/<PROJECT_NUMBER>/locations/global/workloadIdentityPools/<POOL_ID>/providers/<PROVIDER_ID> +``` + +The following commands may help you retrieve the different values: + +- `gcloud projects describe $GCP_PROJECT --format="value(projectNumber)"` will return the `PROJECT_NUMBER` value +- `gcloud iam workload-identity-pools list --location=global --format="value(name)"` will list you POOL_IDs available on your `GCP_PROJECT` +- `gcloud iam workload-identity-pools providers list --workload-identity-pool=<my-pool> --location=global --format="value(name)"` will return the list of available `PROVIDER_ID` for one `POOL_ID` + ### Deployment and cleanup jobs The GitLab CI template for Google Cloud requires you to provide a shell script that fully implements your application @@ -171,8 +194,14 @@ Here are variables supported to configure review environments: | `GCP_REVIEW_APP_NAME` | Application name for `review` env | `"${GCP_BASE_APP_NAME}-${CI_ENVIRONMENT_SLUG}"` (ex: `myproject-review-fix-bug-12`) | | `GCP_REVIEW_ENVIRONMENT_SCHEME`| The review environment protocol scheme.<br/>_For static environment URLs declaration_ | `https` | | `GCP_REVIEW_ENVIRONMENT_DOMAIN`| The review environment domain.<br/>_For static environment URLs declaration_ | _none_ | +| `GCP_REVIEW_OIDC_PROVIDER` | Workload Identity Provider associated with GitLab to [authenticate with OpenID Connect](https://docs.gitlab.com/ee/ci/cloud_services/google_cloud/) on `review` environment | none| +| `GCP_REVIEW_OIDC_ACCOUNT` | Service Account to which impersonate with OpenID Connect authentication on `review` environment | none | + + + Note: If you're managing your environment URLs statically, review environment URLs will be built as `${AWS_REVIEW_ENVIRONMENT_SCHEME}://${$CI_PROJECT_NAME}-${CI_ENVIRONMENT_SLUG}.${AWS_REVIEW_ENVIRONMENT_DOMAIN}` + #### Integration environment The integration environment is the environment associated to your integration branch (`develop` by default). @@ -187,6 +216,9 @@ Here are variables supported to configure the integration environment: | :lock: `GCP_INTEG_KEY_FILE`|[Service Account key file](https://cloud.google.com/bigquery/docs/authentication/service-account-file) to authenticate on `integration` env _(only define if different from default)_ | `$GCP_KEY_FILE` | | `GCP_INTEG_APP_NAME` | Application name for `integration` env | `${GCP_BASE_APP_NAME}-integration` | |Â `GCP_INTEG_ENVIRONMENT_URL`| The integration environment url (ex: `https://my-application-integration.nonpublic.domain.com`).<br/>_For static environment URLs declaration_ | _none_ | +| `GCP_INTEG_OIDC_PROVIDER` | Workload Identity Provider associated with GitLab to [authenticate with OpenID Connect](https://docs.gitlab.com/ee/ci/cloud_services/google_cloud/) on `integration` environment | none| +| `GCP_INTEG_OIDC_ACCOUNT` | Service Account to which impersonate with OpenID Connect authentication on `integration` environment | none | + #### Staging environment @@ -203,6 +235,9 @@ Here are variables supported to configure the staging environment: | :lock: `GCP_STAGING_KEY_FILE`|[Service Account key file](https://cloud.google.com/bigquery/docs/authentication/service-account-file) to authenticate on `staging` env _(only define if different from default)_ | `$GCP_KEY_FILE` | | `GCP_STAGING_APP_NAME` | Application name for `staging` env | `${GCP_BASE_APP_NAME}-staging` | |Â `GCP_STAGING_ENVIRONMENT_URL` | The staging environment url (ex: `https://my-application-staging.nonpublic.domain.com`).<br/>_For static environment URLs declaration_ | _none_ | +| `GCP_STAGING_OIDC_PROVIDER` | Workload Identity Provider associated with GitLab to [authenticate with OpenID Connect](https://docs.gitlab.com/ee/ci/cloud_services/google_cloud/) on `staging` environment | none| +| `GCP_STAGING_OIDC_ACCOUNT` | Service Account to which impersonate with OpenID Connect authentication on `staging` environment | none | + #### Production environment @@ -219,7 +254,8 @@ Here are variables supported to configure the production environment: | `GCP_PROD_APP_NAME` | Application name for `production` env | `$GCP_BASE_APP_NAME` | | `GCP_PROD_ENVIRONMENT_URL`|Â The production environment url (ex: `https://my-application.public.domain.com`).<br/>_For static environment URLs declaration_ | _none_ | | `AUTODEPLOY_TO_PROD` | Set this variable to auto-deploy to production. If not set deployment to production will be `manual` (default behaviour). | _none_ (disabled) | - +| `GCP_PROD_OIDC_PROVIDER` | Workload Identity Provider associated with GitLab to [authenticate with OpenID Connect](https://docs.gitlab.com/ee/ci/cloud_services/google_cloud/) on `production ` environment | none| +| `GCP_PROD_OIDC_ACCOUNT` | Service Account to which impersonate with OpenID Connect authentication on `production ` environment | none | ## Examples ### Google AppEngine application diff --git a/kicker.json b/kicker.json index f96825dd76ecb22da332b7de8d3b94681faeb5ae..8fc0f66ddeb4058d81c5bca6e73abf74b5895dbc 100644 --- a/kicker.json +++ b/kicker.json @@ -15,6 +15,26 @@ "secret": true, "mandatory": true }, + { + "name": "GCP_WORKLOAD_IDENTITY_PROVIDER", + "description": "Default [Workload Identity Provider](https://docs.gitlab.com/ee/ci/cloud_services/google_cloud/) associated with GitLab to authenticate\n\n(has format `projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID`)", + "advanced": true + }, + { + "name": "GCP_SERVICE_ACCOUNT", + "description": "Default Service Account to which impersonate with WIF authentication", + "advanced": true + }, + { + "name": "GCP_OIDC_PROVIDER", + "description": "Global Workload Identity Provider associated with GitLab to [authenticate with OpenID Connect](https://docs.gitlab.com/ee/ci/cloud_services/google_cloud/)", + "advanced": true + }, + { + "name": "GCP_OIDC_ACCOUNT", + "description": "Global Service Account to which impersonate with OpenID Connect authentication", + "advanced": true + }, { "name": "GCP_BASE_APP_NAME", "description": "Base application name", @@ -27,6 +47,7 @@ "default": ".", "advanced": true } + ], "features": [ { @@ -57,6 +78,16 @@ "name": "GCP_REVIEW_KEY_FILE", "description": "Service Account key file to authenticate on review env (only define if different from global)", "secret": true + }, + { + "name": "GCP_REVIEW_OIDC_PROVIDER", + "description": "Workload Identity Provider associated with GitLab to [authenticate with OpenID Connect](https://docs.gitlab.com/ee/ci/cloud_services/google_cloud/) on `review` environment\n\n_(only define if different from global)_", + "advanced": true + }, + { + "name": "GCP_REVIEW_OIDC_ACCOUNT", + "description": "Service Account to which impersonate with OpenID Connect authentication on `review` environment", + "advanced": true } ] }, @@ -84,6 +115,16 @@ "name": "GCP_INTEG_KEY_FILE", "description": "Service Account key file to authenticate on integration env (only define if different from global)", "secret": true + }, + { + "name": "GCP_INTEG_OIDC_PROVIDER", + "description": "Workload Identity Provider associated with GitLab to [authenticate with OpenID Connect](https://docs.gitlab.com/ee/ci/cloud_services/google_cloud/) on `integration` environment\n\n_(only define if different from global)_", + "advanced": true + }, + { + "name": "GCP_INTEG_OIDC_ACCOUNT", + "description": "Service Account to which impersonate with OpenID Connect authentication on `integration` environment", + "advanced": true } ] }, @@ -111,6 +152,16 @@ "name": "GCP_STAGING_KEY_FILE", "description": "Service Account key file to authenticate on staging env (only define if different from global)", "secret": true + }, + { + "name": "GCP_STAGING_OIDC_PROVIDER", + "description": "Workload Identity Provider associated with GitLab to [authenticate with OpenID Connect](https://docs.gitlab.com/ee/ci/cloud_services/google_cloud/) on `staging` environment\n\n_(only define if different from global)_", + "advanced": true + }, + { + "name": "GCP_STAGING_OIDC_ACCOUNT", + "description": "Service Account to which impersonate with OpenID Connect authentication on `staging` environment", + "advanced": true } ] }, @@ -143,6 +194,16 @@ "name": "GCP_PROD_KEY_FILE", "description": "Service Account key file to authenticate on production env (only define if different from global)", "secret": true + }, + { + "name": "GCP_PROD_OIDC_PROVIDER", + "description": "Workload Identity Provider associated with GitLab to [authenticate with OpenID Connect](https://docs.gitlab.com/ee/ci/cloud_services/google_cloud/) on `production` environment\n\n_(only define if different from global)_", + "advanced": true + }, + { + "name": "GCP_PROD_OIDC_ACCOUNT", + "description": "Service Account to which impersonate with OpenID Connect authentication on `production` environment", + "advanced": true } ] } diff --git a/templates/gitlab-ci-gcloud.yml b/templates/gitlab-ci-gcloud.yml index 6c5c4567bd6f755631c889af3bdd3839993639e8..bddba5535698066c248e56e0340ce3d088cc23ff 100644 --- a/templates/gitlab-ci-gcloud.yml +++ b/templates/gitlab-ci-gcloud.yml @@ -29,10 +29,11 @@ variables: GCP_CLI_IMAGE: "google/cloud-sdk:latest" GCP_SCRIPTS_DIR: "." - + GCP_BASE_APP_NAME: "$CI_PROJECT_NAME" GCP_REVIEW_ENVIRONMENT_SCHEME: "https" + # default production ref name (pattern) PROD_REF: '/^(master|main)$/' # default integration ref name (pattern) @@ -256,6 +257,38 @@ stages: awk '{while(match($0,"[$]{[^}]*}")) {var=substr($0,RSTART+2,RLENGTH -3);val=ENVIRON[var];gsub(/["\\]/,"\\\\&", val);gsub("\n", "\\n", val);gsub("\r", "\\r", val);gsub("[$]{"var"}",val)}}1' } + + # Google Cloud Authentication + function gcp_auth() { + gcp_key_file="$1" + oidc_provider="$2" + oidc_account="$3" + + log_info "--- \\e[32moidc_provider\\e[0m (env: \\e[33;1m${oidc_provider}\\e[0m)" + log_info "--- \$oidc_account: \\e[33;1m${oidc_account}\\e[0m" + + if [[ "$oidc_provider" ]] + then + # Use Workload Identity Federation to authenticate + # see: https://docs.gitlab.com/ee/ci/cloud_services/google_cloud/ + log_info "Authenticating with OpenID Connect..." + assert_defined "$oidc_account" 'Missing required OpenID Connect service account' + echo "${CI_JOB_JWT_V2}" > /tmp/.ci_job_jwt_file + gcloud iam workload-identity-pools create-cred-config "$oidc_provider" \ + --service-account="$oidc_account" \ + --output-file=/tmp/.gcp_temp_cred.json \ + --credential-source-file=/tmp/.ci_job_jwt_file + gcloud auth login --cred-file=/tmp/.gcp_temp_cred.json + else + # Use gcp_key_file to authenticate + log_info "Authenticating with Service Account key file..." + assert_defined "$gcp_key_file" 'Missing required GCP key file (JSON)' + as_content "$gcp_key_file" > /tmp/gcp_key.json + gcloud auth activate-service-account --key-file /tmp/gcp_key.json + fi + } + + # application deployment function function deploy() { export env=$1 @@ -324,6 +357,7 @@ stages: fi } + # export tool functions (might be used in after_script) export -f log_info log_warn log_error assert_defined awkenvsubst @@ -346,7 +380,7 @@ stages: # Deploy job prototype # Can be extended to define a concrete environment # -# @arg ENV_TYPE : environment type +# @arg ENV_TYPE : environment type # @arg ENV_APP_NAME : env-specific application name # @arg ENV_APP_SUFFIX: env-specific application suffix # @arg ENV_PROJECT : env-specific GCP Project ID @@ -354,15 +388,14 @@ stages: # @arg ENV_URL : env-specific application url .gcp-deploy: extends: .gcp-base - stage: deploy + stage: deploy variables: ENV_APP_SUFFIX: "-$CI_ENVIRONMENT_SLUG" before_script: - *gcp-scripts - install_ca_certs "${CUSTOM_CA_CERTS:-$DEFAULT_CA_CERTS}" - - assert_defined "${ENV_KEY_FILE:-$GCP_KEY_FILE}" 'Missing required GCP key file (JSON)' - - as_content "${ENV_KEY_FILE:-$GCP_KEY_FILE}" > /tmp/gcp.key - - gcloud auth activate-service-account --key-file /tmp/gcp.key + - gcp_auth "${ENV_KEY_FILE:-$GCP_KEY_FILE}" "${ENV_OIDC_PROVIDER:-$GCP_OIDC_PROVIDER}}" "${ENV_OIDC_ACCOUNT:-$GCP_OIDC_ACCOUNT}" + script: - deploy "$ENV_TYPE" "${ENV_APP_NAME:-${GCP_BASE_APP_NAME}${ENV_APP_SUFFIX}}" "$ENV_PROJECT" "$ENV_URL" artifacts: @@ -392,9 +425,7 @@ stages: before_script: - *gcp-scripts - install_ca_certs "${CUSTOM_CA_CERTS:-$DEFAULT_CA_CERTS}" - - assert_defined "${ENV_KEY_FILE:-$GCP_KEY_FILE}" 'Missing required GCP key file (JSON)' - - as_content "${ENV_KEY_FILE:-$GCP_KEY_FILE}" > /tmp/gcp.key - - gcloud auth activate-service-account --key-file /tmp/gcp.key + - gcp_auth "${ENV_KEY_FILE:-$GCP_KEY_FILE}" "${ENV_OIDC_PROVIDER:-$GCP_OIDC_PROVIDER}}" "${ENV_OIDC_ACCOUNT:-$GCP_OIDC_ACCOUNT}" script: - delete "$ENV_TYPE" "${ENV_APP_NAME:-${GCP_BASE_APP_NAME}${ENV_APP_SUFFIX}}" "$ENV_PROJECT" environment: @@ -409,6 +440,8 @@ gcp-review: ENV_TYPE: review ENV_APP_NAME: "$GCP_REVIEW_APP_NAME" ENV_PROJECT: "$GCP_REVIEW_PROJECT" + ENV_OIDC_PROVIDER: "$GCP_REVIEW_OIDC_PROVIDER" + ENV_OIDC_ACCOUNT: "$GCP_REVIEW_OIDC_ACCOUNT" ENV_KEY_FILE: "$GCP_REVIEW_KEY_FILE" ENV_URL: "${GCP_REVIEW_ENVIRONMENT_SCHEME}://${CI_PROJECT_NAME}-${CI_ENVIRONMENT_SLUG}.${GCP_REVIEW_ENVIRONMENT_DOMAIN}" environment: @@ -432,7 +465,10 @@ gcp-cleanup-review: ENV_TYPE: review ENV_APP_NAME: "$GCP_REVIEW_APP_NAME" ENV_PROJECT: "$GCP_REVIEW_PROJECT" + ENV_OIDC_PROVIDER: "$GCP_REVIEW_OIDC_PROVIDER" + ENV_OIDC_ACCOUNT: "$GCP_REVIEW_OIDC_ACCOUNT" ENV_KEY_FILE: "$GCP_REVIEW_KEY_FILE" + ENV_URL: "${GCP_REVIEW_ENVIRONMENT_URL}" environment: name: review/$CI_COMMIT_REF_NAME action: stop @@ -453,6 +489,8 @@ gcp-integration: ENV_TYPE: integration ENV_APP_NAME: "$GCP_INTEG_APP_NAME" ENV_PROJECT: "$GCP_INTEG_PROJECT" + ENV_OIDC_PROVIDER: "$GCP_INTEG_OIDC_PROVIDER" + ENV_OIDC_ACCOUNT: "$GCP_INTEG_OIDC_ACCOUNT" ENV_KEY_FILE: "$GCP_INTEG_KEY_FILE" ENV_URL: "${GCP_INTEG_ENVIRONMENT_URL}" environment: @@ -469,6 +507,8 @@ gcp-staging: ENV_TYPE: staging ENV_APP_NAME: "$GCP_STAGING_APP_NAME" ENV_PROJECT: "$GCP_STAGING_PROJECT" + ENV_OIDC_PROVIDER: "$GCP_STAGING_OIDC_PROVIDER" + ENV_OIDC_ACCOUNT: "$GCP_STAGING_OIDC_ACCOUNT" ENV_KEY_FILE: "$GCP_STAGING_KEY_FILE" ENV_URL: "${GCP_STAGING_ENVIRONMENT_URL}" environment: @@ -487,6 +527,8 @@ gcp-production: ENV_APP_SUFFIX: "" # no suffix for prod ENV_APP_NAME: "$GCP_PROD_APP_NAME" ENV_PROJECT: "$GCP_PROD_PROJECT" + ENV_OIDC_PROVIDER: "$GCP_PROD_OIDC_PROVIDER" + ENV_OIDC_ACCOUNT: "$GCP_PROD_OIDC_ACCOUNT" ENV_KEY_FILE: "$GCP_PROD_KEY_FILE" ENV_URL: "${GCP_PROD_ENVIRONMENT_URL}" environment: