验证来自 Google Cloud Scheduler 的 HTTP 请求

Verify HTTP request from Google Cloud Scheduler

验证来自 Google Cloud scheduler 的 HTTP 请求的过程是什么?文档 (https://cloud.google.com/scheduler/docs/creating) 提到您可以创建一个以任何公开可用的 HTTP 端点为目标的作业,但没有提到服务器如何验证 cron/scheduler 请求。

[2019 年 5 月 28 日更新]

Google Cloud Scheduler 现在有两个命令行选项:

--oidc-service-account-email=<service_account_email>
--oidc-token-audience=<service_endpoint_being_called>

这些选项向 Cloud Scheduler 发出的请求添加额外的 header:

 Authorization: Bearer ID_TOKEN

您可以在端点代码中处理 ID_TOKEN 以验证谁在呼叫您的端点。

例如,您可以发出 HTTP 请求来解码 ID 令牌:

https://oauth2.googleapis.com/tokeninfo?id_token=ID_TOKEN

这将 return JSON 像这样:

{
  "aud": "https://cloudtask-abcdefabcdef-uc.a.run.app",
  "azp": "0123456789077420983142",
  "email": "cloudtask@development.iam.gserviceaccount.com",
  "email_verified": "true",
  "exp": "1559029789",
  "iat": "1559026189",
  "iss": "https://accounts.google.com",
  "sub": "012345678901234567892",
  "alg": "RS256",
  "kid": "0123456789012345678901234567890123456789c3",
  "typ": "JWT"
}

然后您可以检查服务帐户电子邮件是否与您授权 Cloud Scheduler 使用的电子邮件匹配,并且令牌尚未过期。

[结束更新]

您需要自己验证请求。

Google Cloud Scheduler 包括几个 Google 特定的 header,例如 User-Agent: Google-Cloud-Scheduler。请参阅下面的文档 link。

但是,任何人都可以伪造 HTTP headers。您需要创建一个自定义 something,将其作为 HTTP Header 或您知道如何验证的 HTTP body 包含在内。使用签名的 JWT 将安全且易于创建和验证。

创建 Google Cloud Scheduler 作业时,您可以控制 headersbody 字段。您可以将您的自定义 something 嵌入其中任何一个。

Scheduler Jobs

[更新]

这是一个使用 gcloud 的示例(Windows 命令行),以便您可以设置 HTTP headers 和 body。此示例在每个触发器上调用 Cloud Functions,展示如何包含 APIKEY。 Google 控制台还没有这个级别的支持。

gcloud beta scheduler ^
--project production ^
jobs create http myfunction ^
--time-zone "America/Los_Angeles" ^
--schedule="0 0 * * 0" ^
--uri="https://us-central1-production.cloudfunctions.net/myfunction" ^
--description="Job Description" ^
--headers="{ \"Authorization\": \"APIKEY=AUTHKEY\", \"Content-Type\": \"application/json\" }" ^
--http-method="POST" ^
--message-body="{\"to\":\"/topics/allDevices\",\"priority\":\"low\",\"data\":{\"success\":\"ok\"}}"

简答

如果您在 Google 云中托管您的应用程序,只需检查 header X-Appengine-Queuename 是否等于 __scheduler。但是,这是未记录的行为,有关更多信息,请阅读下文。

此外,如果可能,请使用 Pub/Sub 而不是 HTTP 请求,因为 Pub/Sub 是内部发送的(因此隐式验证来源)。


实验

正如我发现的那样 here,Google 剥离了某些 headers1 的请求,但不是全部2。让我们看看 Cloud Scheduler 是否有这样的 header。

1 例如你不能发送任何 X-Google-* headers(实验发现,read more

2 例如你可以发送 X-Appengine-* headers(实验发现)

实验中使用的Flask应用:

@app.route('/echo_headers')
def echo_headers():
    headers = {h[0]: h[1] for h in request.headers}
    print(headers)
    return jsonify(headers)

Cloud Scheduler 发送的请求 headers

{
  "Host": []
  "X-Forwarded-For": "0.1.0.2, 169.254.1.1",
  "X-Forwarded-Proto": "http",
  "User-Agent": "AppEngine-Google; (+http://code.google.com/appengine)",
  "X-Appengine-Queuename": "__scheduler",
  "X-Appengine-Taskname": [private]
  "X-Appengine-Taskretrycount": "1",
  "X-Appengine-Taskexecutioncount": "0",
  "X-Appengine-Tasketa": [private]
  "X-Appengine-Taskpreviousresponse": "0",
  "X-Appengine-Taskretryreason": "",
  "X-Appengine-Country": "ZZ",
  "X-Cloud-Trace-Context": [private]
  "X-Appengine-Https": "off",
  "X-Appengine-User-Ip": [private]
  "X-Appengine-Api-Ticket": [private]
  "X-Appengine-Request-Log-Id": [private]
  "X-Appengine-Default-Version-Hostname": [private]
}

证明 header X-Appengine-Queuename 被 GAE

剥离

限制

Google SLA 和折旧政策很可能不支持此方法,因为它没有记录。另外,我不确定当请求源在 Google Cloud 内时 header 是否无法伪造(也许它们在外层被剥离)。我已经在 GAE 中测试了一个应用程序,结果可能会或可能不会因其他部署选项而有所不同。总之,使用风险自负。

这个 header 应该有效:

map (key: string, value: string)

HTTP request headers.

This map contains the header field names and values. Headers can be set when the job is created.

Cloud Scheduler sets some headers to default values:

User-Agent: By default, this header is "AppEngine-Google; (+http://code.google.com/appengine)". This header can be modified, but Cloud Scheduler will append "AppEngine-Google; (+http://code.google.com/appengine)" to the modified User-Agent. X-CloudScheduler: This header will be set to true. X-CloudScheduler-JobName: This header will contain the job name. X-CloudScheduler-ScheduleTime: For Cloud Scheduler jobs specified in the unix-cron format, this header will contain the job schedule time in RFC3339 UTC "Zulu" format. If the job has an body, Cloud Scheduler sets the following headers:

Content-Type: By default, the Content-Type header is set to "application/octet-stream". The default can be overridden by explictly setting Content-Type to a particular media type when the job is created. For example, Content-Type can be set to "application/json". Content-Length: This is computed by Cloud Scheduler. This value is output only. It cannot be changed. The headers below are output only. They cannot be set or overridden:

X-Google-: For Google internal use only. X-AppEngine-: For Google internal use only. In addition, some App Engine headers, which contain job-specific information, are also be sent to the job handler.

An object containing a list of "key": value pairs. Example: { "name": "wrench", "mass": "1.3kg", "count": "3" }.

https://cloud.google.com/scheduler/docs/reference/rest/v1/projects.locations.jobs#appenginehttptarget

if request.META['HTTP_X_CLOUDSCHEDULER'] == 'true':
   print("True")