Deploying Google Cloud Functions using GitHub Actions and Workload Identity Authentication
In this article, we will cover how to create a Workload Identity that works with GitHub Actions while deploying a simple Google Cloud Functions.
GitHub has recently introduced the OIDC tokens as part of GitHub Actions Workflow, so now you can authenticate from GitHub Actions to Google Cloud using Workload Identity Federation, and no longer use JSON service account keys.
Prerequisites
- A GitHub account with a repository created
- A Google Cloud account with Billing enabled
- The Google Cloud CLI installed and authenticated
Create a Hello World function
For the purposes of testing, we will create a basic HTTP triggered function that prompts the user with "Hello World".
1. Using your favorite editor, create an index.js
with this helloWorld
function:
/**
* Responds to any HTTP request.
*
* @param {!express:Request} req HTTP request context.
* @param {!express:Response} res HTTP response context.
*/
exports.helloWorld = (req, res) => {
let message = req.query.message || req.body.message || "Hello World!";
res.status(200).send(message);
};
2. Commit and push the code to your GitHub repository.
git add index.js
git commit -m "Adding helloWorld function"
git push
Create a Google Cloud Project
1. If you don't have one already, create a new project on the Google Cloud Platform.
2. Take a note of the Project ID, in this example github-actions-example-342323
.
3. Visit the Navigation Menu
, then Billing
to make sure there's a billing account linked to the project.
Create a Workload Identity on Google Cloud
While most of the configuration can be made on Google Cloud Console, from now on, we will only use the gcloud
command to make the chances needed.
1. To make things easier, we will set temporary variables in our terminal session:
ForSERVICE_ACCOUNT
,WORKLOAD_IDENTITY_POOL
andWORKLOAD_IDENTITY_PROVIDER
, you can use the same values as I did, or come up with your own.
export GITHUB_REPO=$YOUR_GITHUB_REPO$ (e.g. user/project)
export PROJECT_ID=$YOUR_PROJECT_ID$
export SERVICE_ACCOUNT=github-actions-service-account
export WORKLOAD_IDENTITY_POOL=gh-pool
export WORKLOAD_IDENTITY_PROVIDER=gh-provider
2. Now, using the gcloud
command, set the project:
gcloud config set project $PROJECT_ID
3. Enable the APIs for Cloud Functions, Cloud Build and IAM Credential:
gcloud services enable \
(out) cloudfunctions.googleapis.com \
(out) cloudbuild.googleapis.com \
(out) iamcredentials.googleapis.com
4. Create a Service Account that will be used by GitHub Actions:
gcloud iam service-accounts create $SERVICE_ACCOUNT \
(out) --display-name="GitHub Actions Service Account"
5. Bind the Service Account to the Roles in the Services it must interact:
gcloud projects add-iam-policy-binding $PROJECT_ID \
(out) --member="serviceAccount:$SERVICE_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com" \
(out) --role="roles/cloudfunctions.developer"
gcloud projects add-iam-policy-binding $PROJECT_ID \
(out) --member="serviceAccount:$SERVICE_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com" \
(out) --role="roles/iam.serviceAccountUser"
6. Create a Workload Identity Pool for GitHub:
gcloud iam workload-identity-pools create $WORKLOAD_IDENTITY_POOL \
(out) --location="global" \
(out) --display-name="GitHub pool"
7. Create a Workload Identity Provider for GitHub:
gcloud iam workload-identity-pools providers create-oidc $WORKLOAD_IDENTITY_PROVIDER \
(out) --location="global" \
(out) --workload-identity-pool=$WORKLOAD_IDENTITY_POOL \
(out) --display-name="GitHub provider" \
(out) --attribute-mapping="google.subject=assertion.sub,attribute.actor=assertion.actor,attribute.repository=assertion.repository" \
(out) --issuer-uri="https://token.actions.githubusercontent.com"
8. Retrieve the Workload Identity Pool ID:
WORKLOAD_IDENTITY_POOL_ID=$(gcloud iam workload-identity-pools \
(out) describe $WORKLOAD_IDENTITY_POOL \
(out) --location="global" \
(out) --format="value(name)")
9. Allow authentications from the Workload Identity Provider originating from your repository:
gcloud iam service-accounts add-iam-policy-binding \
(out) $SERVICE_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com \
(out) --role="roles/iam.workloadIdentityUser" \
(out) --member="principalSet://iam.googleapis.com/${WORKLOAD_IDENTITY_POOL_ID}/attribute.repository/${GITHUB_REPO}"
10. Finally, extract the Workload Identity Provider resource name:
WORKLOAD_IDENTITY_PROVIDER_LOCATION=$(gcloud iam workload-identity-pools providers \
(out) describe $WORKLOAD_IDENTITY_PROVIDER \
(out) --location="global" \
(out) --workload-identity-pool=$WORKLOAD_IDENTITY_POOL \
(out) --format="value(name)")
Create a GitHub Actions Workflow
We will create a simple workflow that can be triggered by new changes on the main
branch, pull requests or manual dispatch. This workflow will create a new Google Cloud Function called helloWorld
(if one doesn't exist already) and will run a simple curl
test on the function URL.
When omitting some attributes undergoogle-github-actions/deploy-cloud-functions@v0
, it assumes the Cloud Function name is the same as the entry point, in ourindex.js
file noteexport.helloWorld
. Also, it defaults the region tous-central1
, andevent_trigger_type
to HTTP. Read more about how to change these inputs here.
1. Head over to your GitHub Project, go to Actions, then click on set up a workflow yourself:
2. Before we edit the main.yml
file, let's recover some variables from our terminal session:
echo $WORKLOAD_IDENTITY_PROVIDER_LOCATION
(out)(e.g.) projects/21710762087/locations/global/workloadIdentityPools/gh-pool/providers/gh-provider
echo $SERVICE_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com
(out)(e.g.) github-actions-service-account@github-actions-example-342323.iam.gserviceaccount.com
3. Now, using the values from above, replace them in the file below and submit the changes on GitHub by clicking on the Start commit button.
# This is a basic workflow to help you get started with Actions
name: CD
# Controls when the workflow will run
on:
# Triggers the workflow on push or pull request events but only for the main branch
push:
branches: [main]
pull_request:
branches: [main]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "deploy"
deploy:
# The type of runner that the job will run on
runs-on: ubuntu-latest
# Add "id-token" with the intended permissions.
permissions:
contents: "read"
id-token: "write"
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
- id: "auth"
name: "Authenticate to Google Cloud"
uses: "google-github-actions/auth@v0"
with:
# Replace with your Workload Identity Provider Location
workload_identity_provider: "$WORKLOAD_IDENTITY_PROVIDER_LOCATION"
# Replace with your GitHub Service Account
service_account: "$SERVICE_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com"
- id: "deploy"
uses: "google-github-actions/deploy-cloud-functions@v0"
with:
# Name of the Cloud Function, same as the entry point name
name: "helloWorld"
# Runtime to use for the function
runtime: "nodejs16"
# Example of using the output
- id: "test"
run: 'curl "${{ steps.deploy.outputs.url }}"'
4. Almost immediately after submitting you can see a new workflow run under the Actions tab. If everything goes well your function will be deployed:
That's it! Now, any new changes in your repository will trigger a new deploy action.
Tip: Allowing anyone to access a Google Cloud Function
You may have noticed that if the Cloud Function was deploy for the first time using google-github-actions/deploy-cloud-functions@v0
, it will give you an 403 Forbidden
when you try to access its URL.
If it's your intention to make it public, and available to everyone with access to your function URL, you can simply fix it by adding allUsers
as cloudfunctions.invoker
to your function:
gcloud functions add-iam-policy-binding helloWorld \
(out) --member="allUsers" \
(out) --role="roles/cloudfunctions.invoker"
Ta-da!