在不使用 Google 的 SDK 的情况下访问 Google CloudTasks API
Accessing Google CloudTasks API without using Google's SDKs
我正在尝试使用 Cloudflare workers 的 Google cloudtasks。
这是一个 JS 环境,仅限于 web-workers 标准减去了一些 Cloudflare 没有实现的东西。
底线 - 我无法在该环境中使用 Google 提供的 SDK。
我正在尝试使用简单的提取来调用 API,但在身份验证部分总是失败。
"parameters": {
...
"key": {
"description": "API key. Your API key identifies your project and provides you with API access, quota, and reports. Required unless you provide an OAuth 2.0 token.",
"location": "query",
"type": "string"
}
}
所以我尝试使用 ?key=MY_API_KEY
查询参数调用 api
没用。
我还尝试使用服务帐户生成令牌,下载 json 文件 this library
没用。
我尝试按照 this guide 生成 oauth 访问令牌,这是错误消息告诉我我需要的。但是
- 运行 命令
gcloud auth application-default print-access-token
返回错误:
WARNING: Compute Engine Metadata server unavailable onattempt 1 of 3. Reason: timed out
WARNING: Compute Engine Metadata server unavailable onattempt 2 of 3. Reason: timed out
WARNING: Compute Engine Metadata server unavailable onattempt 3 of 3. Reason: [Errno 64] Host is down
WARNING: Authentication failed using Compute Engine authentication due to unavailable metadata server.
ERROR: (gcloud.auth.application-default.print-access-token) Could not automatically determine credentials. Please set GOOGLE_APPLICATION_CREDENTIALS or explicitly create credentials and re-run the application. For more information, please see https://cloud.google.com/docs/authentication/getting-started
上面的 env 变量已正确设置为服务帐户 json 文件。
- 即使它有效,我也不明白我应该如何从我的代码中使用它,而它使用 cli 工具
gcloud
所以我的问题是 - 如何从 Cloudflare 工作人员(网络工作人员 javascript env.)访问 Google 的云 API,我特别感兴趣在 Cloudtasks 中,无需使用任何 CLI 工具或 Google SDK。
更具体地说 - 如何生成所需的 oauth2 访问令牌?
基于@john-hanley blog post 我能够使以下代码工作:
const fetch = require('node-fetch'); //in cloudflare workers env. this is not needed. 'fetch' is globally available
const jwt = require('jsonwebtoken');
const q = require('querystring');
async function main() {
const project = 'xxxx';
const location = 'us-central1';
const scopes = "https://www.googleapis.com/auth/cloud-platform"
const queue = 'queueName';
const parent = `projects/${project}/locations/${location}/queues/${queue}`
const url = `https://cloudtasks.googleapis.com/v2/${parent}/tasks`
const sjwt = await createSignedJwt(json.private_key, json.private_key_id, json.client_email, scopes);
const {token} = await exchangeJwtForAccessToken(sjwt)
const headers = { Authorization: `Bearer ${token}` }
const body = Buffer.from(JSON.stringify({"c":"b"})).toString('base64'); //Note this is not a string!
const task = { //in my case the task is HTTP request that google will send to my service outside GCP. You can create an appEngine task instead
httpRequest: {
"url": "https://where-google-should-send-the-task.com",
"httpMethod": "POST",
"headers": {
"Content-Type": "application/json; charset=UTF-8",
"Authorization": "only-if-you-need-it"
},
"body": body
}
};
const request = {
parent: parent,
task: task
};
const response = await fetch(url, {
method: "POST",
body: JSON.stringify(request),
headers
})
const res = await response.json()
console.log(res)
}
async function createSignedJwt (pkey, pkey_id, email, scope) {
const authUrl = "https://oauth2.googleapis.com/token"
const options = {
algorithm: "RS256",
keyid: pkey_id,
expiresIn: 3600,
audience: authUrl,
issuer: email
// header: { //this is not needed because it's jsonwebtoken's default behavior to add the correct typ when the payload is a json
// "typ": "JWT"
// }
}
const payload = {
"scope": scope
}
return jwt.sign(payload, pkey, options)
}
/**
* This function takes a Signed JWT and exchanges it for a Google OAuth Access Token
*/
async function exchangeJwtForAccessToken(signedJwt) {
const authUrl = "https://oauth2.googleapis.com/token"
const params = {
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": signedJwt
}
const body = q.stringify(params);
const res = await fetch(authUrl, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body
})
if (!res.ok) {
return {
error: "Could not fetch access token. " + await res.text()
}
}
const resJson = await res.json();
return {
token: resJson.access_token
}
}
// for convenience, I'm placing the JSON here. For production it should be stored in secret-manager or injected via environment variable
const json = {
"type": "service_account",
"project_id": "xxxx",
"private_key_id": "xxxx",
"private_key": "-----BEGIN PRIVATE KEY-----xxxx-----END PRIVATE KEY-----\n",
"client_email": "xxxx@xxxx.iam.gserviceaccount.com",
"client_id": "xxxx",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/xxxx%40xxxx.iam.gserviceaccount.com"
}
main()
我正在尝试使用 Cloudflare workers 的 Google cloudtasks。 这是一个 JS 环境,仅限于 web-workers 标准减去了一些 Cloudflare 没有实现的东西。 底线 - 我无法在该环境中使用 Google 提供的 SDK。 我正在尝试使用简单的提取来调用 API,但在身份验证部分总是失败。
"parameters": {
...
"key": {
"description": "API key. Your API key identifies your project and provides you with API access, quota, and reports. Required unless you provide an OAuth 2.0 token.",
"location": "query",
"type": "string"
}
}
所以我尝试使用 ?key=MY_API_KEY
查询参数调用 api
没用。
我还尝试使用服务帐户生成令牌,下载 json 文件 this library 没用。
我尝试按照 this guide 生成 oauth 访问令牌,这是错误消息告诉我我需要的。但是
- 运行 命令
gcloud auth application-default print-access-token
返回错误:
上面的 env 变量已正确设置为服务帐户 json 文件。WARNING: Compute Engine Metadata server unavailable onattempt 1 of 3. Reason: timed out WARNING: Compute Engine Metadata server unavailable onattempt 2 of 3. Reason: timed out WARNING: Compute Engine Metadata server unavailable onattempt 3 of 3. Reason: [Errno 64] Host is down WARNING: Authentication failed using Compute Engine authentication due to unavailable metadata server. ERROR: (gcloud.auth.application-default.print-access-token) Could not automatically determine credentials. Please set GOOGLE_APPLICATION_CREDENTIALS or explicitly create credentials and re-run the application. For more information, please see https://cloud.google.com/docs/authentication/getting-started
- 即使它有效,我也不明白我应该如何从我的代码中使用它,而它使用 cli 工具
gcloud
所以我的问题是 - 如何从 Cloudflare 工作人员(网络工作人员 javascript env.)访问 Google 的云 API,我特别感兴趣在 Cloudtasks 中,无需使用任何 CLI 工具或 Google SDK。 更具体地说 - 如何生成所需的 oauth2 访问令牌?
基于@john-hanley blog post 我能够使以下代码工作:
const fetch = require('node-fetch'); //in cloudflare workers env. this is not needed. 'fetch' is globally available
const jwt = require('jsonwebtoken');
const q = require('querystring');
async function main() {
const project = 'xxxx';
const location = 'us-central1';
const scopes = "https://www.googleapis.com/auth/cloud-platform"
const queue = 'queueName';
const parent = `projects/${project}/locations/${location}/queues/${queue}`
const url = `https://cloudtasks.googleapis.com/v2/${parent}/tasks`
const sjwt = await createSignedJwt(json.private_key, json.private_key_id, json.client_email, scopes);
const {token} = await exchangeJwtForAccessToken(sjwt)
const headers = { Authorization: `Bearer ${token}` }
const body = Buffer.from(JSON.stringify({"c":"b"})).toString('base64'); //Note this is not a string!
const task = { //in my case the task is HTTP request that google will send to my service outside GCP. You can create an appEngine task instead
httpRequest: {
"url": "https://where-google-should-send-the-task.com",
"httpMethod": "POST",
"headers": {
"Content-Type": "application/json; charset=UTF-8",
"Authorization": "only-if-you-need-it"
},
"body": body
}
};
const request = {
parent: parent,
task: task
};
const response = await fetch(url, {
method: "POST",
body: JSON.stringify(request),
headers
})
const res = await response.json()
console.log(res)
}
async function createSignedJwt (pkey, pkey_id, email, scope) {
const authUrl = "https://oauth2.googleapis.com/token"
const options = {
algorithm: "RS256",
keyid: pkey_id,
expiresIn: 3600,
audience: authUrl,
issuer: email
// header: { //this is not needed because it's jsonwebtoken's default behavior to add the correct typ when the payload is a json
// "typ": "JWT"
// }
}
const payload = {
"scope": scope
}
return jwt.sign(payload, pkey, options)
}
/**
* This function takes a Signed JWT and exchanges it for a Google OAuth Access Token
*/
async function exchangeJwtForAccessToken(signedJwt) {
const authUrl = "https://oauth2.googleapis.com/token"
const params = {
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": signedJwt
}
const body = q.stringify(params);
const res = await fetch(authUrl, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body
})
if (!res.ok) {
return {
error: "Could not fetch access token. " + await res.text()
}
}
const resJson = await res.json();
return {
token: resJson.access_token
}
}
// for convenience, I'm placing the JSON here. For production it should be stored in secret-manager or injected via environment variable
const json = {
"type": "service_account",
"project_id": "xxxx",
"private_key_id": "xxxx",
"private_key": "-----BEGIN PRIVATE KEY-----xxxx-----END PRIVATE KEY-----\n",
"client_email": "xxxx@xxxx.iam.gserviceaccount.com",
"client_id": "xxxx",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/xxxx%40xxxx.iam.gserviceaccount.com"
}
main()