GCP Pub/Sub push 至 IAP 保护的 Endpoint

众所周知 IAP(Identity Aware Proxy)对 AppEngine 开启后, 所有 GAE 的 service 都需要先通过 OAuth2 鉴权才能访问. 普通用户在浏览器打开 IAP 保护的 GAE 地址就会被跳转到登录页面去登录谷歌账号. 那问题来了, Pub/Sub 的消息如果要推送到一个 IAP 保护的地址应该怎么操作.

创建 subscription 的时候如果选择了 Push 方式, 下面就会有一个 Enable Authentication 的勾选框. 填好 Endpoint(必须得是谷歌可以验证其所属的地址, 比如 GAE) 后再选择一个用来产生 JWT 签名的服务账号. 命令行操作可以看 https://cloud.google.com/pubsub/docs/push#setting_up_for_push_authentication

(命令行操作还需要手动给予 Pub/Sub 自动创建的服务账号额外权限, 详见上面的文档链接)

之后就像授权普通用户访问 IAP 那样给 JWT 签名用的服务账号添加 IAP-secured Web App User 权限. 然后来试一试.

随便发个消息, 以为已经弄好了, 结果消息一直没有 ACK.

更气人的是看不到 IAP 的访问日志, 日志里只有 IAP 设置的修改.

这一部分日志属于 API 内部日志, 不由用户代码产生, 默认不记录这些日志, 要先去 Audit Logs (在网页控制台搜索这一页面)打开 IAP 日志.

Logging 中按以下 Query 来查询日志

resource.type="gae_app"

可以在左边的 Log Fields 看到一个 module_id 是 null 的东西

这个没有名字的模块就是 IAP 的访问日志, 由于它没有名字不能单独筛选, 只能混在所有 module 的日志里一起看.

然后就会看到一个双感叹号的条目, 赫然写着 'Permission Denied'. 这说明 Pub/Sub 发出的请求没有通过 IAP 验证.

在 StackOverflow 搜索这个问题只有一个结果, 评论区有人提到创建 subscription 时的 Audience 是必填项, 其值为 IAP 自己的 Client ID(不是用于签 JWT 的服务账号的 OAuth2 Client ID) https://stackoverflow.com/questions/57817374/google-pub-sub-push-message-not-working-for-iap-enabled-app-engine#comment104348926_58151897

我们回到 IAP 的文档去看 Programmatic Authentication 一节再去看一下 https://cloud.google.com/iap/docs/authentication-howto#accessing_the_application

自己获取 JWT 的 curl 命令是这样的

curl --verbose \
      --data client_id=DESKTOP_CLIENT_ID \
      --data client_secret=DESKTOP_CLIENT_SECRET \
      --data refresh_token=REFRESH_TOKEN \
      --data grant_type=refresh_token \
      --data audience=IAP_CLIENT_ID \
      https://oauth2.googleapis.com/token

配文: 'IAP_CLIENT_ID is the primary client ID used to access your application, and DESKTOP_CLIENT_ID and DESKTOP_CLIENT_SECRET are the client ID and secret you created when you set up the client ID above'

DESKTOP_CLIENT_ID 就是用来产生 JWT 用的服务账号的 Client ID, 而 IAP_CLIENT_ID 是系统给出的 IAP 自己的 Client ID.

回到 Credentials 页面, 除了每个服务账号对应的 Client IDs, 还会有一个系统创建 Client 的叫做 IAP-App-Engine-app

把 IAP-App-Engine-app 对应的 Client ID 填到 subscription 的 audience 就可以让 Sub/Pub 正确推送到目标 GAE 了.

不得不说, 谷歌的文档写的是真的烂.