使用Google Cloud Run中的默认凭据进行域范围的委派

时间:2020-02-27 14:57:06

标签: node.js service-accounts google-cloud-run google-auth-library-nodejs

我正在使用自定义服务帐户(在deploy命令中使用--service-account参数)。该服务帐户已启用域范围的委派,并已安装在G Apps管理员面板中。

我尝试了以下代码:

app.get('/test', async (req, res) => {
    const auth = new google.auth.GoogleAuth()
    const gmailClient = google.gmail({ version: 'v1' })
    const { data } = await gmailClient.users.labels.list({ auth, userId: 'user@domain.com' })
    return res.json(data).end()
})

如果我在计算机上运行它(将GOOGLE_APPLICATION_CREDENTIALS环境变量设置为分配给Cloud Run服务的同一服务帐户的路径),它可以工作,但是当它在Cloud Run中运行时,我得到这个回应:

{
  "code" : 400,
  "errors" : [ {
    "domain" : "global",
    "message" : "Bad Request",
    "reason" : "failedPrecondition"
  } ],
  "message" : "Bad Request"
}

我看到了this针对同一问题的解决方案,但这是针对Python的,我不知道如何使用Node库复制该行为。

2 个答案:

答案 0 :(得分:1)

经过几天的研究,我终于找到了一个可行的解决方案(移植Python实现):

async function getGoogleCredentials(subject: string, scopes: string[]): Promise<JWT | OAuth2Client> {
    const auth = new google.auth.GoogleAuth({
        scopes: ['https://www.googleapis.com/auth/cloud-platform'],
    })
    const authClient = await auth.getClient()

    if (authClient instanceof JWT) {
        return (await new google.auth.GoogleAuth({ scopes, clientOptions: { subject } }).getClient()) as JWT
    } else if (authClient instanceof Compute) {
        const serviceAccountEmail = (await auth.getCredentials()).client_email
        const unpaddedB64encode = (input: string) =>
            Buffer.from(input)
                .toString('base64')
                .replace(/=*$/, '')
        const now = Math.floor(new Date().getTime() / 1000)
        const expiry = now + 3600
        const payload = JSON.stringify({
            aud: 'https://accounts.google.com/o/oauth2/token',
            exp: expiry,
            iat: now,
            iss: serviceAccountEmail,
            scope: scopes.join(' '),
            sub: subject,
        })

        const header = JSON.stringify({
            alg: 'RS256',
            typ: 'JWT',
        })

        const iamPayload = `${unpaddedB64encode(header)}.${unpaddedB64encode(payload)}`

        const iam = google.iam('v1')
        const { data } = await iam.projects.serviceAccounts.signBlob({
            auth: authClient,
            name: `projects/-/serviceAccounts/${serviceAccountEmail}`,
            requestBody: {
                bytesToSign: unpaddedB64encode(iamPayload),
            },
        })
        const assertion = `${iamPayload}.${data.signature!.replace(/=*$/, '')}`

        const headers = { 'content-type': 'application/x-www-form-urlencoded' }
        const body = querystring.encode({ assertion, grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer' })
        const response = await fetch('https://accounts.google.com/o/oauth2/token', { method: 'POST', headers, body }).then(r => r.json())

        const newCredentials = new OAuth2Client()
        newCredentials.setCredentials({ access_token: response.access_token })
        return newCredentials
    } else {
        throw new Error('Unexpected authentication type')
    }
}

答案 1 :(得分:0)

您可以在这里执行以下操作,在yaml文件中将ENV变量定义为described in this documentation,以将GOOGLE_APPLICATION_CREDENTIALS设置为JSON密钥的路径。

然后使用诸如上面提到的here这样的代码。

const authCloudExplicit = async ({projectId, keyFilename}) => {
  // [START auth_cloud_explicit]
  // Imports the Google Cloud client library.
  const {Storage} = require('@google-cloud/storage');

  // Instantiates a client. Explicitly use service account credentials by
  // specifying the private key file. All clients in google-cloud-node have this
  // helper, see https://github.com/GoogleCloudPlatform/google-cloud-node/blob/master/docs/authentication.md
  // const projectId = 'project-id'
  // const keyFilename = '/path/to/keyfile.json'
  const storage = new Storage({projectId, keyFilename});

  // Makes an authenticated API request.
  try {
    const [buckets] = await storage.getBuckets();

    console.log('Buckets:');
    buckets.forEach(bucket => {
      console.log(bucket.name);
    });
  } catch (err) {
    console.error('ERROR:', err);
  }
  // [END auth_cloud_explicit]
};

或采用与上述here类似的方法。

'use strict';

const {auth, Compute} = require('google-auth-library');


async function main() {
  const client = new Compute({
    serviceAccountEmail: 'some-service-account@example.com',
  });
  const projectId = await auth.getProjectId();
  const url = `https://dns.googleapis.com/dns/v1/projects/${projectId}`;
  const res = await client.request({url});
  console.log(res.data);
}

main().catch(console.error);