Unable to connect to Google Cloud Run

Hi,

I am trying to connect my Vercel app to another service running on Google Cloud Run. I have followed the guide here, but I am having trouble getting it to work.

I think I need to pass a token ID to my rest API request, which would identify the service to the backend (see here).

However, how do I do this in practice? The following works locally:

  const auth = new GoogleAuth({
    scopes: "https://www.googleapis.com/auth/cloud-platform",
    // Pass the project ID explicitly to avoid the need to grant `roles/browser` to the service account
    // or enable Cloud Resource Manager API on the project.
    projectId: GCP_PROJECT_ID,
  });

  const backend_url = "...";
  const client = await auth.getIdTokenClient(backend_url);
  const response = await client.request({ url: backend_url });

However, this is relying on the fact that I have run (locally) the command gcloud auth login, so that the credentials are present in my machine and the call to GoogleAuth goes through.

How to do the same on a vercel function?
The official docs suggest doing something like

  const auth = ExternalAccountClient.fromJSON({
    type: "external_account",
    audience: `//iam.googleapis.com/projects/${GCP_PROJECT_NUMBER}/locations/global/workloadIdentityPools/${GCP_WORKLOAD_IDENTITY_POOL_ID}/providers/${GCP_WORKLOAD_IDENTITY_POOL_PROVIDER_ID}`,
    subject_token_type: "urn:ietf:params:oauth:token-type:jwt",
    token_url: "https://sts.googleapis.com/v1/token",
    service_account_impersonation_url: `https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${GCP_SERVICE_ACCOUNT_EMAIL}:generateAccessToken`,
    subject_token_supplier: {
      // Use the Vercel OIDC token as the subject token.
      getSubjectToken: getVercelOidcToken,
    },
  });

But the resulting auth object does not have a getIdTokenClient method, so it’s unclear how to make progress now.

I have also tried calling auth.getServiceAccountEmail() to see if it would show the vercel@${PROJECT_ID}.iam.gserviceaccount.com which I expect, but it just returns null instead.

I have been trying to do this for multiple hours now. Can someone help shed some light on this, or point to an working example?

Thank you!

Up :slight_smile:

Does anyone have any information? How do you usually connect to cloud run backends on google cloud form a vercel website?

hey @ant0198

It sounds like these environment variables might be missing from your Vercel project.

If you’ve checked that they exist within project settings and have confirmed that the credentials are correct we can pull these same variables into your local dev environment:

vercel pull

# or to pull prod env vars:
vercel pull --environment=production

View vercel pull docs

This should allow you to authenticate using the same credentials as your deployed app when testing locally instead of using gcloud auth login.

There’s already a reference to GCP_PROJECT_ID so I’m assuming that some environment variables have been defined further up in the codebase. If not we might need to add variable definitions similar to the example in our docs:

const GCP_PROJECT_ID = process.env.GCP_PROJECT_ID;
const GCP_PROJECT_NUMBER = process.env.GCP_PROJECT_NUMBER;
const GCP_SERVICE_ACCOUNT_EMAIL = process.env.GCP_SERVICE_ACCOUNT_EMAIL;
const GCP_WORKLOAD_IDENTITY_POOL_ID = process.env.GCP_WORKLOAD_IDENTITY_POOL_ID;
const GCP_WORKLOAD_IDENTITY_POOL_PROVIDER_ID =
  process.env.GCP_WORKLOAD_IDENTITY_POOL_PROVIDER_ID;

This allows us to initialise ExternalAccountClient as outlined in the official docs

import { ExternalAccountClient } from 'google-auth-library';

// Initialize the External Account Client
const authClient = ExternalAccountClient.fromJSON({
  type: 'external_account',
  audience: `//iam.googleapis.com/projects/${GCP_PROJECT_NUMBER}/locations/global/workloadIdentityPools/${GCP_WORKLOAD_IDENTITY_POOL_ID}/providers/${GCP_WORKLOAD_IDENTITY_POOL_PROVIDER_ID}`,
  subject_token_type: 'urn:ietf:params:oauth:token-type:jwt',
  token_url: 'https://sts.googleapis.com/v1/token',
  service_account_impersonation_url: `https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${GCP_SERVICE_ACCOUNT_EMAIL}:generateAccessToken`,
  subject_token_supplier: {
    // Use the Vercel OIDC token as the subject token
    getSubjectToken: getVercelOidcToken,
  },
});

While I wasn’t able to find a more specific example for your use case you might be able to take inspiration from the official google-auth-library samples such as credentials.js.

2 Likes

Hi,

Thanks a lot for your answer! :slight_smile:

I did have the environment variables, and the ExternalAccountClient works properly - the problem is that I need to get a tokenID in order to connect to the cloud run backend, so I need something like getIdTokenClient. This method exists under GoogleAuth but not under ExternalAccountClient.

The code sample you linked to uses GoogleAuth, not the ExternalAccountClient class. I couldn’t find any examples of ExternalAccountClient unfortunately.

Up :slight_smile:

I would be grateful for any pointers to code or documentation. What I am trying to do should be relatively easy - connect from a vercel function to a Cloud run backend.

I now tried the following:

    const authClient = new GoogleAuth({
      scopes: "https://www.googleapis.com/auth/cloud-platform",
      // Pass the project ID explicitly to avoid the need to grant roles/browser
      // to the service account or enable Cloud Resource Manager API on the
      // project.
      projectId: GCP_PROJECT_ID,
      credentials: {
        type: "external_account",
        audience: //iam.googleapis.com/projects/${GCP_PROJECT_NUMBER}/locations/global/workloadIdentityPools/${GCP_WORKLOAD_IDENTITY_POOL_ID}/providers/${GCP_WORKLOAD_IDENTITY_POOL_PROVIDER_ID},
        subject_token_type: "urn:ietf:params:oauth:token-type:jwt",
        token_url: "https://sts.googleapis.com/v1/token",
        service_account_impersonation_url: https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${GCP_SERVICE_ACCOUNT_EMAIL}:generateAccessToken,
        subject_token_supplier: {
          // Use the Vercel OIDC token as the subject token.
          getSubjectToken: getVercelOidcToken,
        },
      },
    });
    const idTokenClientTest = await authtest.getIdTokenClient(backend_url);

But it fails with the following error: “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.
at C.getIdTokenClient (/var/task/…/app/api/backend/route.js:13:33960)”

And I am not sure what’s going on.

Note that I am able to get an access token (await getAccessToken() works), but how to get an id token now which is required to connect to cloud run?

Thank you! :slight_smile:

Can you initialize the IdTokenClient in another way?

It seems that the constructor accepts the credentials property, which happens to be the interface as ExternalAccountClient credentials method. I’m not sure about the rest of the properties though.

new IdTokenClient({
  credentials: auth.credentials,
  targetAudience: '',
  idTokenProvider: {
    fetchIdToken: async (aud: string) => ''
  }
})

Actually, can I ask what service you’re trying to access? I think most services in GCP will work using the access tokens that are minted from the external account client.

Thank you!

Let me try to use new IdTokenClient and I will report back :slight_smile:

Actually, can I ask what service you’re trying to access? I think most services in GCP will work using the access tokens that are minted from the external account client.

I am trying to access a Cloud Run server which is running my code. IIUC one needs a token ID to connect to it.

1 Like

Hey @ant0198,

I took a quick look at Cloud Run and I suspect you can actually configure it to directly recognize and trust Vercel OIDC Tokens.

In other words you might not need to connect to GCP STS to exchange the Vercel token for credentials, you can simply authenticate with your Cloud Run HTTPS endpoint with authorization: Bearer ${await getVercelOidcToken()} header.

I haven’t tried this myself, but I suspect you can grant the Vercel service account access to Cloud Run Invoker.

Hi,

I am still not sure about the intended workflow (everything is very confusing), but I managed to authenticate using the following:

  1. Create the External client as shown above
  2. Use the impersonated class to impersonate a service account
  3. Use this impersonated class to collect a token ID
  4. Manually use this token id in the header request
const client = ExternalAccountClient.fromJSON({
    type: "external_account",
    audience: `//iam.googleapis.com/projects/${GCP_PROJECT_NUMBER}/locations/global/workloadIdentityPools/${GCP_WORKLOAD_IDENTITY_POOL_ID}/providers/${GCP_WORKLOAD_IDENTITY_POOL_PROVIDER_ID}`,
    subject_token_type: "urn:ietf:params:oauth:token-type:jwt",
    token_url: "https://sts.googleapis.com/v1/token",
    service_account_impersonation_url: `https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${GCP_SERVICE_ACCOUNT_EMAIL}:generateAccessToken`,
    subject_token_supplier: {
      // Use the Vercel OIDC token as the subject token.
      getSubjectToken: getVercelOidcToken,
    },
  });
  const targetClient = new Impersonated({
      sourceClient: externalClient,
      targetPrincipal: GCP_SERVICE_ACCOUNT_EMAIL,
      lifetime: 30,
      delegates: [],
      targetScopes: ["https://www.googleapis.com/auth/cloud-platform"],
    });
const idToken = await targetClient.fetchIdToken(backend_url);
const response = await fetch(backend_url, {
      method: "GET",
      headers: {
        Authorization: `Bearer ${idToken}`,
      },
    });
console.log(await response.json());

This seems to work correctly!

Awesome @ant0198, that is great to know. Thanks for posting the solution.

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.