Keyless Google Cloud Authentication to push your containers to Artifact Registry from GitHub Actions
In November 2021, GitHub announced the GA support of OpenID Connect (OIDC) with GitHub actions for secure deployments to cloud, which uses short-lived tokens that are automatically rotated for each deployment. There are different providers supported like Azure, AWS, Google Cloud, etc.
Amazing!
More security in CI/CD pipelines with GitHub actions!
Seamless authentication between Cloud Providers and GitHub without the need for storing any long-lived cloud secrets in GitHub
Cloud Admins can rely on the security mechanisms of their cloud provider to ensure that GitHub Actions workflows get the minimal access for cloud resources. There is no duplication of secret management in GitHub and the cloud.
With Google Cloud you could use the google-github-actions/auth
GitHub action which leverages Workload Identity Federation as a keyless API authentication.
The GitHub Action
google-github-actions/auth
exchanges a GitHub Actions OIDC token into a Google Cloud access token using Workload Identity Federation. This obviates the need to export a long-lived Google Cloud service account key and establishes a trust delegation relationship between a particular GitHub Actions workflow invocation and permissions on Google Cloud.Traditionally, applications running outside Google Cloud can use service account keys to access Google Cloud resources. However, service account keys are powerful credentials, and can present a security risk if they are not managed correctly.
Sounds exciting, let’s see in actions this Workload Identity Federation setup in the context of GitHub Actions!
Define the common variables:
PROJECT_ID=FIXME
gcloud config set project ${PROJECT_ID}
Configure the Workload Identity pool and provider
Create the Workload Identity pool:
WI_POOL_NAME=container-builder-from-gha
gcloud iam workload-identity-pools create ${WI_POOL_NAME} \
--location global \
--display-name ${WI_POOL_NAME}
WI_POOL_ID=$(gcloud iam workload-identity-pools describe ${WI_POOL_NAME} \
--location global \
--format='get(name)')
Create a Workload Identity Provider with GitHub Actions in that pool:
WI_ATTRIBUTE_MAPPING_SCOPE=repository
gcloud iam workload-identity-pools providers create-oidc ${WI_POOL_NAME} \
--location global \
--workload-identity-pool ${WI_POOL_NAME} \
--display-name ${WI_POOL_NAME} \
--attribute-mapping "google.subject=assertion.${WI_ATTRIBUTE_MAPPING_SCOPE},attribute.actor=assertion.actor,attribute.aud=assertion.aud,attribute.${WI_ATTRIBUTE_MAPPING_SCOPE}=assertion.${WI_ATTRIBUTE_MAPPING_SCOPE}" \
--issuer-uri "https://token.actions.githubusercontent.com"
WI_PROVIDER_ID=$(gcloud iam workload-identity-pools providers describe ${WI_POOL_NAME} \
--location global \
--workload-identity-pool ${WI_POOL_NAME} \
--format='get(name)')
Important notes here:
I’m using the attribute mapping as repository
(GitHub repository), other options are for example: sub
(GitHub repository and branch) or repository_owner
(GitHub organization or user name). In my use case, I find repository
, granular and secure enough: I just want to allow the authentication for this specific GitHub repository: my-user-name-or-org/my-repo
, for any branches or PRs.
You can find more information about the attribute mapping and conditions here. Also, according to Google Cloud best practices, it’s recommended to use the system-generated and immutable IDs such as repository_id
, rather than repository
name (same approach with repository_owner_id
versus repository_owner
).
Configure the Google Service Account
Create the Google Service Account that will be used in by the GitHub Actions:
GSA_NAME="container-builder-from-gha"
gcloud iam service-accounts create ${GSA_NAME}
GSA_ID="${GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com"
Allow authentications from the Workload Identity Provider to impersonate the Service Account created above:
GH_REPO_NAME="FIXME-YOUR-REPO-ORG/FIXME-YOUR-REPO-NAME"
gcloud iam service-accounts add-iam-policy-binding ${GSA_ID} \
--role "roles/iam.workloadIdentityUser" \
--member "principalSet://iam.googleapis.com/${WI_POOL_ID}/attribute.${WI_ATTRIBUTE_MAPPING_SCOPE}/${GH_REPO_NAME}"
Grant the Service Account with the role to push in the registry:
gcloud artifacts repositories add-iam-policy-binding ${REGISTRY_NAME} \
--location ${REGISTRY_LOCATION} \
--member "serviceAccount:${GSA_ID}" \
--role roles/artifactregistry.writer
Configure the GitHub Actions definition
Add the required values in GitHub Actions as Secrets:
gh secret set PROJECT_ID -b"${PROJECT_ID}"
gh secret set GSA_ID -b"${GSA_ID}"
gh secret set WI_PROVIDER_ID -b"${WI_PROVIDER_ID}"
gh secret set REGISTRY_NAME -b"${REGISTRY_NAME}"
gh secret set REGISTRY_LOCATION -b"${REGISTRY_LOCATION}"
And finally, create the associated GitHub Actions definition to build and push a container image to Artifact Registry using this Workload Identity Federation setup:
...
jobs:
job:
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v3
- uses: google-github-actions/auth@v1
with:
workload_identity_provider: '${{ secrets.WI_PROVIDER_ID }}'
service_account: '${{ secrets.GSA_ID }}'
- uses: google-github-actions/setup-gcloud@v1
with:
version: latest
- name: prepare environment variables
run: |
imageName=${{ secrets.REGISTRY_LOCATION }}-docker.pkg.dev/${{ secrets.PROJECT_ID }}/${{ secrets.REGISTRY_NAME }}/my-sample-app
echo "IMAGE_NAME=$imageName" >> $GITHUB_ENV
- name: sign-in to artifact registry
run: |
gcloud auth configure-docker ${{ secrets.REGISTRY_LOCATION }}-docker.pkg.dev --quiet
- name: build the container
run: |
docker build \
--tag ${IMAGE_NAME}:latest \
app/
- name: push the container to GAR
run: |
docker push \
${IMAGE_NAME}:latest
And that’s it, that’s a wrap! No excuses anymore to use the not-recommended and not-secure approach with the Service Account key files! WIF FTW!
That’s now a great way to enforce your Organization Policy: constraints/iam.disableServiceAccountKeyCreation
. 😉
You can find the complete GitHub Actions definition here: sail-sharp/.github/workflows/push-tag.yml.
Hope you enjoyed that one, stay safe out there! 🛡️🚀
Originally posted at mathieu-benoit.github.io.