Commit 4f2c9432 authored by Vincent BONHOMME's avatar Vincent BONHOMME
Browse files

feat(gcp): setup GCP credentials through ADC

parent eb47b3c3
Loading
Loading
Loading
Loading
+68 −0
Original line number Diff line number Diff line
@@ -572,3 +572,71 @@ variables:
  ANSIBLE_VAULT_PASSWORD: "@url@http://vault-secrets-provider/api/secrets/b7ecb6ebabc231/my-infra/ansible?field=vault.password"
  ANSIBLE_PRIVATE_KEY: "@url@http://vault-secrets-provider/api/secrets/b7ecb6ebabc231/my-infra/ansible?field=priv_key"
```

### Google Cloud variant

This variant uses [Application Default Credentials][gcp-adc] through the `GOOGLE_APPLICATION_CREDENTIALS` variable using Workload Identity federation.

List of requirements before using this variant:

1. You must have a Workload Identity Federation Pool and Provider configured,
2. You must have a Service Account with the `roles/iam.workloadIdentityUser` IAM role
   granted to the Workload Identity [principal][gcp-iam-principals] matching your Gitlab project or group,
3. Optionally, you can set the `GOOGLE_CLOUD_PROJECT` template variable
   to define the default Google Cloud project.

The Gitlab documentation has some [details about Workload Identity Federation integration][gcp-gitlab-wif].

This [blog post about OIDC impersonation through Workload Identify Federation][gcp-wif-example] might also be of help.

[gcp-adc]: https://cloud.google.com/docs/authentication/client-libraries
[gcp-provider]: https://registry.terraform.io/providers/hashicorp/google/latest/docs/guides/provider_reference#running-terraform-outside-of-google-cloud
[gcp-iam-principals]: https://cloud.google.com/iam/docs/principal-identifiers
[gcp-gitlab-wif]: https://docs.gitlab.com/ee/ci/cloud_services/google_cloud/
[gcp-wif-example]: https://blog.salrashid.dev/articles/2021/understanding_workload_identity_federation/#oidc-impersonated

#### Configuration

The variant requires the additional configuration parameters:

| Input / Variable                                          | Description                                                                                                                                                                                                          | Default value    |
|-----------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------|
| `gcp-oidc-aud` / `GCP_OIDC_AUD`                           | The `aud` claim for the JWT token                                                                                                                                                                                    | `$CI_SERVER_URL` |
| `gcp-oidc-provider` / `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` / `GCP_OIDC_ACCOUNT`                   | Default Service Account to which impersonate with OpenID Connect authentication                                                                                                                                      | _none_           |
| `gcp-review-oidc-provider` / `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 _(only define to override default)_      | _none_           |
| `gcp-review-oidc-account` / `GCP_REVIEW_OIDC_ACCOUNT`     | Service Account to which impersonate with OpenID Connect authentication on `review` environment _(only define to override default)_                                                                                  | _none_           |
| `gcp-integ-oidc-provider` / `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 _(only define to override default)_ | _none_           |
| `gcp-integ-oidc-account` / `GCP_INTEG_OIDC_ACCOUNT`       | Service Account to which impersonate with OpenID Connect authentication on `integration` environment _(only define to override default)_                                                                             | _none_           |
| `gcp-staging-oidc-provider` / `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 _(only define to override default)_     | _none_           |
| `gcp-staging-oidc-account` / `GCP_STAGING_OIDC_ACCOUNT`   | Service Account to which impersonate with OpenID Connect authentication on `staging` environment _(only define to override default)_                                                                                 | _none_           |
| `gcp-prod-oidc-provider` / `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 _(only define to override default)_  | _none_           |
| `gcp-prod-oidc-account` / `GCP_PROD_OIDC_ACCOUNT`         | Service Account to which impersonate with OpenID Connect authentication on `production` environment _(only define to override default)_                                                                              | _none_           |

#### Example

With a common default `GCP_OIDC_PROVIDER` and `GCP_OIDC_ACCOUNT` configuration for non-prod environments, and a specific one for production:

```yaml
include:
  # main template
  - component: gitlab.com/to-be-continuous/ansible/gitlab-ci-ansible@6.4.0
  # Google Cloud variant
  - component: gitlab.com/to-be-continuous/ansible/gitlab-ci-ansible-gcp@6.4.0
    inputs:
      # common OIDC config for non-prod envs
      gcp-oidc-provider: "projects/<gcp_nonprod_proj_id>/locations/global/workloadIdentityPools/<pool_id>/providers/<provider_id>"
      gcp-oidc-account: "<name>@$<gcp_nonprod_proj_id>.iam.gserviceaccount.com"
      # specific OIDC config for prod
      gcp-prod-oidc-provider: "projects/<gcp_prod_proj_id>/locations/global/workloadIdentityPools/<pool_id>/providers/<provider_id>"
      gcp-prod-oidc-account: "<name>@$<gcp_prod_proj_id>.iam.gserviceaccount.com"
```

Then in your playbook you can use [module defaults](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_module_defaults.html) to configure the [GCP module](https://galaxy.ansible.com/ui/repo/published/google/cloud) to use ADC for authentication:

```yaml
module_defaults:
  group/gcp:
    project: <your-project-id>
    auth_kind: "application"
```
+62 −0
Original line number Diff line number Diff line
@@ -408,6 +408,68 @@
          "secret": true
        }
      ]
    },
    {
      "id": "gcp-auth-provider",
      "name": "Google Cloud",
      "description": "This variant uses [Application Default Credentials][gcp-adc] through the `GOOGLE_APPLICATION_CREDENTIALS` variable using Workload Identity federation.",
      "template_path": "templates/gitlab-ci-ansible-gcp.yml",
      "variables": [
        {
          "name": "GCP_OIDC_AUD",
          "description": "The `aud` claim for the JWT token _(only required for [OIDC authentication](https://docs.gitlab.com/ee/ci/cloud_services/aws/))_",
          "default": "$CI_SERVER_URL",
          "advanced": true
        },
        {
          "name": "GCP_OIDC_ACCOUNT",
          "description": "Default Service Account to which impersonate with OpenID Connect authentication"
        },
        {
          "name": "GCP_OIDC_PROVIDER",
          "description": "Default Workload Identity Provider associated with GitLab to [authenticate with OpenID Connect](https://docs.gitlab.com/ee/ci/cloud_services/google_cloud/)"
        },
        {
          "name": "GCP_REVIEW_OIDC_ACCOUNT",
          "description": "Service Account to which impersonate with OpenID Connect authentication on `review` environment",
          "advanced": 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",
          "advanced": true
        },
        {
          "name": "GCP_INTEG_OIDC_ACCOUNT",
          "description": "Service Account to which impersonate with OpenID Connect authentication on `integration` environment",
          "advanced": 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",
          "advanced": true
        },
        {
          "name": "GCP_STAGING_OIDC_ACCOUNT",
          "description": "Service Account to which impersonate with OpenID Connect authentication on `staging` environment",
          "advanced": 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",
          "advanced": true
        },
        {
          "name": "GCP_PROD_OIDC_ACCOUNT",
          "description": "Service Account to which impersonate with OpenID Connect authentication on `production` environment",
          "advanced": 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",
          "advanced": true
        }
      ]
    }
  ]
}
+115 −0
Original line number Diff line number Diff line
# =====================================================================================================================
# === Google Cloud template variant
# =====================================================================================================================
spec:
  inputs:
    gcp-oidc-aud:
      description: The `aud` claim for the JWT token
      default: $CI_SERVER_URL
    gcp-oidc-account:
      description: Default Service Account to which impersonate with OpenID Connect authentication
      default: ''
    gcp-oidc-provider:
      description: Default Workload Identity Provider associated with GitLab to [authenticate with OpenID Connect](https://docs.gitlab.com/ee/ci/cloud_services/google_cloud/)
      default: ''
    gcp-review-oidc-account:
      description: Service Account to which impersonate with OpenID Connect authentication on `review` environment
      default: ''
    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
      default: ''
    gcp-integ-oidc-account:
      description: Service Account to which impersonate with OpenID Connect authentication on `integration` environment
      default: ''
    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
      default: ''
    gcp-staging-oidc-account:
      description: Service Account to which impersonate with OpenID Connect authentication on `staging` environment
      default: ''
    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
      default: ''
    gcp-prod-oidc-account:
      description: Service Account to which impersonate with OpenID Connect authentication on `production` environment
      default: ''
    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
      default: ''
---
variables:
  GCP_OIDC_AUD: $[[ inputs.gcp-oidc-aud ]]
  GCP_OIDC_ACCOUNT: $[[ inputs.gcp-oidc-account ]]
  GCP_OIDC_PROVIDER: $[[ inputs.gcp-oidc-provider ]]
  GCP_REVIEW_OIDC_ACCOUNT: $[[ inputs.gcp-review-oidc-account ]]
  GCP_REVIEW_OIDC_PROVIDER: $[[ inputs.gcp-review-oidc-provider ]]
  GCP_INTEG_OIDC_ACCOUNT: $[[ inputs.gcp-integ-oidc-account ]]
  GCP_INTEG_OIDC_PROVIDER: $[[ inputs.gcp-integ-oidc-provider ]]
  GCP_STAGING_OIDC_ACCOUNT: $[[ inputs.gcp-staging-oidc-account ]]
  GCP_STAGING_OIDC_PROVIDER: $[[ inputs.gcp-staging-oidc-provider ]]
  GCP_PROD_OIDC_ACCOUNT: $[[ inputs.gcp-prod-oidc-account ]]
  GCP_PROD_OIDC_PROVIDER: $[[ inputs.gcp-prod-oidc-provider ]]

.gcp-provider-auth:
  before_script:
    - echo "Installing GCP authentication with env GOOGLE_APPLICATION_CREDENTIALS file"
    - echo $GCP_JWT > "$CI_BUILDS_DIR/.auth_token.jwt"
    - |-
      if [[ "$ENV_TYPE" ]]
      then
        case "$ENV_TYPE" in
        review*)
          env_prefix=REVIEW;;
        integ*)
          env_prefix=INTEG;;
        staging*)
          env_prefix=STAGING;;
        prod*)
          env_prefix=PROD;;
        *)
          ;;
        esac
        env_oidc_provider=$(eval echo "\$GCP_${env_prefix}_OIDC_PROVIDER")
        env_oidc_account=$(eval echo "\$GCP_${env_prefix}_OIDC_ACCOUNT")
      fi
      oidc_provider="${env_oidc_provider:-$GCP_OIDC_PROVIDER}"
      oidc_account="${env_oidc_account:-$GCP_OIDC_ACCOUNT}"
    - |-
      cat << EOF > "$CI_BUILDS_DIR/google_application_credentials.json"
      {
        "type": "external_account",
        "audience": "//iam.googleapis.com/${oidc_provider}",
        "subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
        "token_url": "https://sts.googleapis.com/v1/token",
        "credential_source": {
          "file": "$CI_BUILDS_DIR/.auth_token.jwt"
        },
        "service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${oidc_account}:generateAccessToken"
      }
      EOF
    - export GOOGLE_APPLICATION_CREDENTIALS="$CI_BUILDS_DIR/google_application_credentials.json"

.ansible-deploy:
  id_tokens:
    GCP_JWT:
      aud: "$GCP_OIDC_AUD"
  before_script:
    - !reference [.ansible-scripts]
    - !reference [.gcp-provider-auth, before_script]
    - install_ca_certs "${CUSTOM_CA_CERTS:-$DEFAULT_CA_CERTS}"
    - cd $ANSIBLE_PROJECT_DIR
    - assert_defined "${ENV_INVENTORY:-${ANSIBLE_DEFAULT_INVENTORY}}" 'Missing required Ansible inventory'
    - assert_defined "${ENV_PLAYBOOK_FILE}" 'Missing required Ansible playbook'

.ansible-cleanup:
  id_tokens:
    GCP_JWT:
      aud: "$GCP_OIDC_AUD"
  before_script:
    - !reference [.ansible-scripts]
    - !reference [.gcp-provider-auth, before_script]
    - install_ca_certs "${CUSTOM_CA_CERTS:-$DEFAULT_CA_CERTS}"
    - cd $ANSIBLE_PROJECT_DIR
    - assert_defined "${ENV_INVENTORY:-${ANSIBLE_DEFAULT_INVENTORY}}" 'Missing required Ansible inventory'
    - assert_defined "${ENV_CLEANUP_PLAYBOOK_FILE:-${ENV_PLAYBOOK_FILE}}" 'Missing required Ansible playbook'
    - assert_defined "$ENV_CLEANUP_TAGS" 'Missing required Ansible cleanup tags'