Learn how to invoke authenticated Cloud Functions

1. Introduction

Overview

Cloud Functions is a lightweight compute solution for developers to create single-purpose, stand-alone functions that can be triggered using HTTPS or respond to CloudEvents without needing to manage a server or runtime environment.

There are two main approaches to controlling invocations to Cloud Functions: securing access based on identity and securing access using network-based access controls. This codelab focuses on the first approach and walks you through 3 scenarios for securing access based on identity to invoke a Function:

  1. Use your gcloud identity token to invoke a Function for local development & testing purposes
  2. Impersonate a service account when developing and testing locally to use same the credentials as in production
  3. Use Google client libraries to handle authentication to Google Cloud APIs, e.g. when a service needs to invoke a Function

What you'll learn

  • How to configure authentication on a Cloud Function and verify authentication has been properly configured
  • Invoke an authenticated function from a local development environment by providing the token for your gcloud identity
  • How to create a service account and grant it the appropriate role to invoke a function
  • How to impersonate a service from a local development environment that has the appropriate roles for invoking a function

2. Setup and Requirements

Prerequisites

  • You are logged into the Cloud Console
  • You have previously deployed an HTTP triggered 2nd gen Cloud Function
  • (optional) For the 3rd scenario, this codelab uses Node.js and npm as the example, but you can use any runtime that's supported by the Google Auth client libraries.

Activate Cloud Shell

  1. From the Cloud Console, click Activate Cloud Shell d1264ca30785e435.png.

84688aa223b1c3a2.png

If this is your first time starting Cloud Shell, you're presented with an intermediate screen describing what it is. If you were presented with an intermediate screen, click Continue.

d95252b003979716.png

It should only take a few moments to provision and connect to Cloud Shell.

7833d5e1c5d18f54.png

This virtual machine is loaded with all the development tools needed. It offers a persistent 5 GB home directory and runs in Google Cloud, greatly enhancing network performance and authentication. Much, if not all, of your work in this codelab can be done with a browser.

Once connected to Cloud Shell, you should see that you are authenticated and that the project is set to your project ID.

  1. Run the following command in Cloud Shell to confirm that you are authenticated:
gcloud auth list

Command output

 Credentialed Accounts
ACTIVE  ACCOUNT
*       <my_account>@<my_domain.com>

To set the active account, run:
    $ gcloud config set account `ACCOUNT`
  1. Run the following command in Cloud Shell to confirm that the gcloud command knows about your project:
gcloud config list project

Command output

[core]
project = <PROJECT_ID>

If it is not, you can set it with this command:

gcloud config set project <PROJECT_ID>

Command output

Updated property [core/project].

3. Create and test an authenticated Cloud Function

Requiring authentication means that the principle invoking the function must have the Cloud Functions Invoker (and Cloud Run Invoker for 2nd gen) roles; otherwise, the function will return a 403 Forbidden error. This codelab will show how to grant the appropriate Invoker roles to a principle.

Setup local environment variables for simplified gcloud commands

First, you will create a few environment variables to improve the readability of the gcloud commands used in this codelab.

REGION=us-central1
PROJECT_ID=$(gcloud config get-value project)

Create the source code for the function

Although this codelab uses Node.js, you can use any runtime that's supported by the Google Auth client libraries.

First, create a directory and cd into that directory.

mkdir auth-function-codelab && cd $_

Then, create the package.json file.

touch package.json

echo '{
  "dependencies": {
    "@google-cloud/functions-framework": "^2.1.0"
  }
}
' > package.json

Next, create the index.js source file.

touch index.js

echo 'const functions = require("@google-cloud/functions-framework");

functions.http("helloWorld", (req, res) => {
 res.send(`Hello ${req.query.name || req.body.name || "World"}!`);
});' > index.js

Create the authenticated function

Here are the steps for creating an authenticated function for the nodejs18 runtime. However, you can use any runtime that's supported by the Google Auth client libraries.

FUNCTION_NAME=authenticated-function-codelab
ENTRY_POINT=helloWorld

gcloud functions deploy $FUNCTION_NAME  \
--gen2 \
--region $REGION \
--source . \
--trigger-http \
--runtime nodejs18 \
--entry-point $ENTRY_POINT \
--no-allow-unauthenticated

and then you can save the Function URL as an environment variable to use later.

FUNCTION_URL="$(gcloud functions describe $FUNCTION_NAME --gen2 --region us-central1 --format='get(serviceConfig.uri)')"

Verify the function requires authentication by attempting to invoke as an anonymous caller

You'll invoke the function without authentication to verify that you receive an expected 403 error.

From a command line, run the following curl command:

curl -i $FUNCTION_URL

You will see the following result:

<html><head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<title>403 Forbidden</title>
</head>
<body text=#000000 bgcolor=#ffffff>
<h1>Error: Forbidden</h1>
<h2>Your client does not have permission to get URL <code>/</code> from this server.</h2>
<h2></h2>
</body></html>

Now you are ready to walk through 3 scenarios where you can invoke your Function by providing authentication.

4. Scenario 1: Use your gcloud identity token

As a developer, you'll want a way to test your function while you are developing it locally. In this section, you'll perform a quick test to verify the function is properly authenticated using your own identity.

Verify you are authenticated using gcloud by running the following command:

gcloud auth list

You should see an asterisk next to your active identity, for example:

Credentialed Accounts
ACTIVE  ACCOUNT

*       <my_account>@<my_domain.com>

To set the active account, run:
    $ gcloud config set account `ACCOUNT`

You can find more information on setting up gcloud init and gcloud auth login in the docs.

Next, invoke the Function and pass it your identity token.

curl $FUNCTION_URL -H "Authorization: bearer $(gcloud auth print-identity-token)"

Now you will see the result:

Hello World!

Troubleshooting

If you receive a 403 Forbidden error, make sure your identity has the Cloud Functions Invoker role or Cloud Run Invoker role for 2nd gen functions. You can use the IAM console to verify roles given to a principal.

Although using your own identity token is a quick way to test your function during development, the caller of your authenticated function will need the appropriate roles; otherwise, the caller will receive a 403 Forbidden error.

You'll want to follow the principle of least privilege by limiting the number of identities and service accounts that have roles to invoke the function. In the next scenario, you'll learn how to create a new service account and grant it the appropriate roles to invoke the function.

5. Scenario 2: Impersonate a service account

In this scenario, you'll impersonate (i.e. assume the permissions of) a service account to invoke a function when developing and testing locally. By impersonating a service account, you can test your function the same credentials as in production.

By doing this, you'll not only verify roles, but also follow the principle of least privilege by not having to grant the Cloud Function Invoker role to other identities just for local testing purposes.

For purposes of this codelab, you'll create a new service account that only has roles to invoke the function you created in this codelab.

Create a new service account

First, you'll create a couple additional environment variables to represent the service accounts used in the gcloud commands.

SERVICE_ACCOUNT_NAME="invoke-functions-codelab"
SERVICE_ACCOUNT_ADDRESS=$SERVICE_ACCOUNT_NAME@$PROJECT_ID.iam.gserviceaccount.com

Next, you'll create the service account.

gcloud iam service-accounts create $SERVICE_ACCOUNT_NAME \
  --display-name="Cloud Function Authentication codelab"

And grant the service account Cloud Function invoker role:

gcloud functions add-iam-policy-binding FUNCTION_NAME \
  --region=us-central1 --gen2 \
  --member serviceAccount:$SERVICE_ACCOUNT_ADDRESS \
  --role='roles/cloudfunctions.invoker'

Invoke the function by impersonating the service account

For this, you'll impersonate the newly created service account by obtaining its ID token.

Add required roles for impersonation

For impersonating a service account, your user account needs to have the Service Account Token Creator (roles/iam.serviceAccountTokenCreator) role to generate an ID token for the service account.

You can run the following commands to grant your active user account this role:

ACCOUNT_EMAIL=$(gcloud auth list --filter=status:ACTIVE --format="value(account)")

gcloud iam service-accounts add-iam-policy-binding $SERVICE_ACCOUNT_ADDRESS  \
  --member user:$ACCOUNT_EMAIL \
  --role='roles/iam.serviceAccountTokenCreator'

Use the ID token of the service account

Now you can invoke the Function by passing the ID token of the service account.

curl $FUNCTION_URL -H "Authorization: bearer $(gcloud auth print-identity-token --impersonate-service-account $SERVICE_ACCOUNT_ADDRESS)" 

And you will see the following:

WARNING: This command is using service account impersonation. All API calls will be executed as [invoke-functions-codelab@<project-id>.iam.gserviceaccount.com].
Hello World!

6. Scenario 3: Use Google client libraries

For this last portion of the codelab, you will run a small service locally to generate an ID token for a service account and then programmatically call the Function using the Google Auth client libraries and Application Default Credentials (ADC). You can read more about Google client libraries in the client libraries explained section of the docs.

Using ADC is especially important when you want to write and test your Function locally (e.g. on your laptop, in Cloud Shell, etc.) while interacting with other Google Cloud resources (e.g. Cloud Storage, Vision API, etc.) For this example, you'll see how to have a service invoke another Function that requires authentication. For more info on ADC and local development, see the blog post How to develop and test your Cloud Functions locally | Google Cloud Blog

Run the gcloud command to impersonate a service account

ADC automatically finds credentials based on the application environment and uses those credentials to authenticate to Google Cloud APIs. The –impersonate-service-account flag allows you to impersonate a service account by using its identity for authentication against Google Cloud APIs.

To impersonate a service account, you can run the following command:

gcloud auth application-default login --impersonate-service-account=$SERVICE_ACCOUNT_ADDRESS

Now you are running gcloud commands as that service account, instead of your identity.

Create and run a service to invoke an authenticated function

Each runtime has its own Google Auth client library that you can install. This codelab walks you through creating and running a Node.js app locally.

Here are the steps for Node.js:

  1. Create a new Node.js app
npm init
  1. Install the Google Auth client library
npm install google-auth-library
  1. Create an index.js file
  2. Retrieve the URL of your Cloud Function, which you will add to your code in the following step.
echo $FUNCTION_URL
  1. Add the following code to index.js. Make sure to change the targetAudience variable to your Cloud Function URL.

index.js

// Cloud Functions uses your function's url as the `targetAudience` value

const targetAudience = '<YOUR-CLOUD-FUNCTION-URL>';

// For Cloud Functions, endpoint(`url`) and `targetAudience` should be equal

const url = targetAudience;

const { GoogleAuth } = require('google-auth-library');
const auth = new GoogleAuth();

async function request() {
    console.info(`request ${url} with target audience ${targetAudience}`);

    // this call retrieves the ID token for the impersonated service account
    const client = await auth.getIdTokenClient(targetAudience);

    const res = await client.request({ url });
    console.info(res.data);
}

request().catch(err => {
    console.error(err.message);
    process.exitCode = 1;
});
  1. Run the app
node index.js

And you should see the resulting "Hello World!"

Troubleshooting

If you see an error Permission ‘iam.serviceAccounts.getOpenIdToken' denied on resource (or it may not exist)., please wait a few minutes for the Service Account Token Creator role to propagate.

If you received the error Cannot fetch ID token in this environment, use GCE or set the GOOGLE_APPLICATION_CREDENTIALS environment variable to a service account credentials JSON file, you may have forgotten to run the command

gcloud auth application-default login --impersonate-service-account=$SERVICE_ACCOUNT_ADDRESS

7. Congratulations!

Congratulations for completing the codelab!

We recommend reviewing the documentation on how to secure Cloud Functions.

We also recommend this blog post on local development with Cloud Functions to learn how to develop and test your Cloud Function in your local developer environment.

What we've covered

  • How to configure authentication on a Cloud Function and verify authentication has been properly configured
  • Invoke an authenticated function from a local development environment by providing the token for your gcloud identity
  • How to create a service account and grant it the appropriate role to invoke a function
  • How to impersonate a service from a local development environment that has the appropriate roles for invoking a function

8. Clean up

To avoid inadvertent charges, (for example this Cloud Function is inadvertently invoked more times than your monthly Cloud Function invokement allocation in the free tier), you can either delete the Cloud Function or delete the project you created in Step 2.

To stop impersonating the service account, you can re-login using your identity:

gcloud auth application-default login

To delete the Cloud Function, go to the Cloud Function Cloud Console at https://console.cloud.google.com/functions/ Make sure the project you created in step 2 is the currently selected project.

Select the my-authenticated-function you deployed earlier. Then hit Delete.

If you choose to delete the entire project, you can go to https://console.cloud.google.com/cloud-resource-manager, select the project you created in Step 2, and choose Delete. If you delete the project, you'll need to change projects in your Cloud SDK. You can view the list of all available projects by running gcloud projects list.