Authenticating service-to-service (Cloud Run (fully managed))

This page applies to Cloud Run (fully managed).

Service-to-service authentication is the ability for one service, which can be a Cloud Run service, to invoke a Cloud Run (fully managed) service.

If your architecture is using multiple services, these services will likely need to communicate with each other.

You can use synchronous or asynchronous service-to-service communication:

For asynchronous communication, use

  • Cloud Tasks for one to one asynchronous communication
  • Pub/Sub for one to many asynchronous communication
  • Cloud Scheduler for regularly scheduled asynchronous communication.

For synchronous communication, one service invokes another one over HTTP using its endpoint URL. In this use case, it's a good idea to ensure that each service is only able to make requests to specific services. For instance, if you have a login service, it should be able to access the user-profiles service, but it probably shouldn't be able to access the search service.

First, you'll need to configure the receiving service to accept requests from the calling service:

  1. Grant the Cloud Run Invoker (roles/run.invoker) role to the calling service identity on the receiving service. By default, this identity is PROJECT_NUMBER-compute@developer.gserviceaccount.com.

Console UI

  1. Go to the Google Cloud Console:

    Go to Google Cloud Console

  2. Select the receiving service.

  3. Click Show Info Panel in the top right corner to show the Permissions tab.

  4. In the Add members field, enter the identity of the calling service.

  5. Select the Cloud Run Invoker role from the Select a role drop-down menu.

  6. Click Add.

GCloud

Use the gcloud run services add-iam-policy-binding command:

gcloud run services add-iam-policy-binding RECEIVING_SERVICE \
  --member='serviceAccount:CALLING_SERVICE_IDENTITY' \
  --role='roles/run.invoker'

where RECEIVING_SERVICE is the name of the receiving service, and CALLING_SERVICE_IDENTITY is the email address of the service account.

In the calling service, you'll need to:

  1. Create a Google-signed OAuth ID token with the audience (aud) set to the URL of the receiving service. This value must contain the schema prefix (http:// or https://) and custom domains are currently not supported for the aud value.

  2. Include the ID token in an Authorization: Bearer ID_TOKEN header in the request to the service.

Nodejs
// Make sure to `npm install --save request-promise` or add the dependency to your package.json
const request = require('request-promise');

const receivingServiceURL = ...

// Set up metadata server request
// See https://cloud.google.com/compute/docs/instances/verifying-instance-identity#request_signature
const metadataServerTokenURL = 'http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience=';
const tokenRequestOptions = {
    uri: metadataServerTokenURL + receivingServiceURL,
    headers: {
        'Metadata-Flavor': 'Google'
    }
};

// Fetch the token, then provide the token in the request to the receiving service
request(tokenRequestOptions)
  .then((token) => {
    return request(receivingServiceURL).auth(null, null, true, token)
  })
  .then((response) => {
    res.status(200).send(response);
  })
  .catch((error) => {
    res.status(400).send(error);
  });
    
Python
# Requests is already installed, no need to add it to requirements.txt
import requests

receiving_service_url = ...

# Set up metadata server request
# See https://cloud.google.com/compute/docs/instances/verifying-instance-identity#request_signature
metadata_server_token_url = 'http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience='

token_request_url = metadata_server_token_url + receiving_service_url
token_request_headers = {'Metadata-Flavor': 'Google'}

# Fetch the token
token_response = requests.get(token_request_url, headers=token_request_headers)
jwt = token_response.content.decode("utf-8")

# Provide the token in the request to the receiving service
receiving_service_headers = {'Authorization': f'bearer {jwt}'}
service_response = requests.get(receiving_service_url, headers=receiving_service_headers)

return service_response.content
    
Go
import (
	"fmt"
	"net/http"

	"cloud.google.com/go/compute/metadata"
)

// makeGetRequest makes a GET request to the specified Cloud Run endpoint in
// serviceURL (must be a complete URL) by authenticating with the ID token
// obtained from the Metadata API.
func makeGetRequest(serviceURL string) (*http.Response, error) {
	// query the id_token with ?audience as the serviceURL
	tokenURL := fmt.Sprintf("/instance/service-accounts/default/identity?audience=%s", serviceURL)
	idToken, err := metadata.Get(tokenURL)
	if err != nil {
		return nil, fmt.Errorf("metadata.Get: failed to query id_token: %+v", err)
	}
	req, err := http.NewRequest("GET", serviceURL, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", idToken))
	return http.DefaultClient.Do(req)
}
Java
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

public class Authentication {

  // Instantiate OkHttpClient
  private static final OkHttpClient ok =
      new OkHttpClient.Builder()
          .readTimeout(10, TimeUnit.SECONDS)
          .writeTimeout(10, TimeUnit.SECONDS)
          .build();

  // makeGetRequest makes a GET request to the specified Cloud Run endpoint,
  // serviceUrl (must be a complete URL), by authenticating with the Id token
  // obtained from the Metadata API.
  public static Response makeGetRequest(String serviceUrl) throws IOException {
    Request.Builder serviceRequest = new Request.Builder().url(serviceUrl);

    // Set up metadata server request
    // https://cloud.google.com/compute/docs/instances/verifying-instance-identity#request_signature
    String tokenUrl =
        String.format(
            "http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience=%s",
            serviceUrl);
    Request tokenRequest =
        new Request.Builder().url(tokenUrl).addHeader("Metadata-Flavor", "Google").get().build();
    // Fetch the token
    try (Response tokenResponse = ok.newCall(tokenRequest).execute()) {
      String token = tokenResponse.body().string();
      // Provide the token in the request to the receiving service
      serviceRequest.addHeader("Authorization", "Bearer " + token);
      System.out.println("Id token query succeeded.");
    } catch (IOException e) {
      System.out.println("Id token query failed: " + e);
    }

    return ok.newCall(serviceRequest.get().build()).execute();
  }
}

For an end-to-end walkthrough of an application using this service-to-service authentication technique, follow the securing Cloud Run services tutorial.

Calling from outside GCP

If you're invoking a service from a compute instance that doesn't have access to compute metadata (e.g. your own server), you'll have to manually generate the proper token:

  1. Self-sign a service account JWT with the target_audience claim set to the URL of the receiving service.

  2. Exchange the self-signed JWT for a Google-signed ID token, which should have the aud claim set to the above URL.

  3. Include the ID token in an Authorization: Bearer ID_TOKEN header in the request to the service.

Although Identity-Aware Proxy is not yet supported for Cloud Run (fully managed), you can examine the Identity-Aware Proxy sample code for code examples of the steps above.