Lambda + API Gateway 创建需 API Key 验证的 API

希望在标题上尽量包含更多的信息,原本命题为: Lambda + API Gateway 创建需 API Key 验证的 API(Docker + Python + Terraform), 但是觉得太长了,于是只取了前半部份。仍然要在开头部分强调一下本文件打算要实现什么

  1. 在 AWS  用 Lambda  和  API Gateway 创建 API
  2. 创建的 API 是 public 的,需要用 x-api-key 来验证
  3. Lambda 的实现代码打包在了一个 Docker 镜像中
  4. 整个 AWS 的基础架构(包括 ECR, Lambda, API Gateway 及权限等)是由 Terraform 脚本创建管理的

目标明确,我们直冲到代码的目录结构来,项目目录为 api-gateway-demo, Github 上的链接为 api-gateway-demo. 后面详叙还会把其中每一个文件的内部给列出来

由于创建 Lambda 的时候需经指定 Docker 镜像 的 hash, 而非 tag 名称,所以执行分以下几步

  1. Terraform 创建 ECR
  2. 创建 Docker 镜像并推送到上一步创建的 ECR 中
  3. 创建 Lambda 及 API Gateway 诸要素

也就是为什么要把其中三个 *.tf 文件放在一个子目录中去的缘故。也有人通过  Terraform 的 null_resource 配置 provisioner "local-exec" 在新建好 ECR 后自动创建 Docker 镜像及推送到 ECR 中去,但本文还是让事情更简单一些

创建 ECR

需要进到 terraform  目录,用到 ecr.tfmain.tf, 并把对模块 lambda-api-gateway 的引用用  count = 0 禁用掉。

ecr.tf

main.tf

执行 terraform apply 后得到新建 ECR 的 url 如

$ terraform apply --auto-approve
......
Outputs:
ecr-url = "123456789088.dkr.ecr.us-east-1.amazonaws.com/apidemo-lambda"

创建并推着 Docker 镜像

进到 python 目录, 先浏览一下其下的三个文件

app.py

其中用来处理两种类型的请求,分别是

  1. GET /jobs/{jobId}
  2. POST /jobs

由 resource 和 http_method 来路由请求到不同的方法,返回数据的格式特别要注意,必须是一个 API Gateway 能理解的格式,如上面的包含 header, statusCode, 和 body 的 Python 字典。API Gatewy 收到 Lambda 的返回,提取出相应的字段组装成一个 HTTP 响应包,如果 Lambda 端随意就可能见到 "malformed Lambda proxy response" 的问题。

requirements.txt

其中定义了本 Python 项目用到的第三方包(要是用到的话)

Dockerfile

基本镜像用 AWS 官方提供的,它为我们设定了下列内容

  1. "WorkingDir": "/var/task"
  2. "Env": ["LAMBDA_TASK_ROOT"="/var/task"]
  3. "Entrypoint": ["/lambda-entrypoint.sh"]         我们的 app.handler 将作为它的参数

执行命令

$ aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 123456789088.dkr.ecr.us-east-1.amazonaws.com
Login Succeeded
$ docker build -t 123456789088.dkr.ecr.us-east-1.amazonaws.com/apidemo-lambda:1.0.0 .
$ docker push 123456789088.dkr.ecr.us-east-1.amazonaws.com/apidemo-lambda:1.0.0

注:关于创建 Lambda Docker 镜像及本地测试请参见:Creating Lambda container images, 此非本文的内容 

创建 Lambda 及 API Gateway 基础设施

现在再回到  terraform 目录,执行前需把 main.tf 中设置 count = 1 开启对模块 lambda-api-gateway 的调用。照旧,先看下其中三个文件的内容

lambda-api-gateway/iam.tf

在 Lambda 最基本的角色权限,能够在 CloudWatch 中创建 Log Group, Log Stream, 并往上写日志

lambda-api-gateway/lambda.tf

创建一个 Lambda 并关联相应的 IAM role 和 Docker 镜像

lambda-api-gateway/api-gateway.tf

终于来到本文最重要也是最复杂的地方了, 当然是最需要加以解释的,由后面的效果有助我们理解这一大段 Terraform 脚本。基本上就是创建一个使用 Docker 镜像的 Lambda, 在 API Gateway 中创建了一个 API demoapi, 因为是 REST API, 相应的资源是 /jobs/jobs/{jobId},并设置为 Lambda 的触发器,调用 API 需要在头上加上 x-api-key

API 定义参照 REST API, 什么是资源,什么又是资源上的操作方法,然后把操作方法代理到相应的 Lambda,这一步叫做集成(Integration)。通过 API Gateway 想要调用 Lambda,需要在 Lambda 那一段加上相应的调用权限,最后通过使用计划(Usage Plan) 的方式把对资源的操作与 API Key 关联了起来。这一套建立起来之后,再最后就是部署到某个环境中去,API Gateway 就会为所定义的 API 生成一个 URL,开始使用了。

最终效果及测试

执行 terraform apply --auto-approve, 如果一切顺利的话(运气不好的话,terraform 执行中可能用依赖的问题,解决办法可多执行几次,或加上 depends_on),就可以开始调用前面定义的 APIs 了

可以在 API Gateway 的 Resources 里去测试这两个 API,但这儿会跳过 API key 的验证。所以应该找到 Stages  里 API 的 URL 来测试

在这个页面我们找到 endpoint 是 https://q0dejwgby4.execute-api.us-east-1.amazonaws.com/stg/*, 用 curl 命令来测试下

不提供 x-api-key 就不允许调用相应的 API, 得到  403: Forbidden 的消息。那么  x-api-key 从哪儿来的呢?我们前面自己定义的,可在 API Keys 里找到

这也验证了 API Key 是生效了的,当我们对 aws_api_gateway_method 设置了 api_key_required 时,相应的资源上就会标记为 API Key  Required.

API Gateway 的 API Key 不仅仅是用来允不允许对某个资源的访问,还能用来限制对 API 的访问配额,所以是通过 aws_api_gateway_usage_plan 让 API 定义与 API Key 进行关联的。它可以控制对某个 API 一秒之内最多访问的次数,或一天(周) 之内最多访问多少次,前面的 Terraform 脚本中没有进行这样的配置限制。

最后别忘了 Lambda + API Gateway 中的 Lambda 这一主要劳动力,看看它发生了什么变化,这得上一张大图

每一个 REST API 对应一个 Lambda 的触发器,并有相应的权限,同时 API Key 在这里也能看到,所以对像 POST /jobsGET /jobs/{jobId} 可以使用不同的 API Key.

如果在 API Gateway 的 Resource 中新加了一个  API, 也部署了,但在 Lambda 端未加上相应的权限,调用时也是得到 401: Forbidden

记得前面我们用 Terraform 生成的 API Gateway Resources 中,请求方法的 Method Response 显示为

而通过 AWS 控制台页面创建的一个请求方法的  Method Response 有些不一样,是这样子的

那么这有什么影响呢?我也不确定,反正 Integration Request 那个卡片里的 Type 都是 LAMBDA_PROXY。之前碰过好像 Terraform 创建的 Resource 会出现 401: Forbidden 的情况,但后来又消失了。想要达成与 Web 控制台创建的一样效果的话,在 Terraform 中还要加上以下的 aws_api_gateway_method_responseaws_api_gateway_integration_response 两个声明 

这两个一加,在执行 Terraform 时更容易出现依赖的问题。一个办法是这两个语句可以在后期补上,再执行  Terraform 脚本,或让它们去依赖 aws_api_gateway_deployment

另外在 Lambda 中处理请求与响应时还有不少东西需要不断深入,比如说

  1. 像  /job/{jobId} 中 pathParameter 怎么从  event 中取
  2. 像 ?key1=value1 中的 queryParameter 怎么从 event 中取
  3. post body 如何从 event 中取,以及它与请求时的 Content-Type 的关系
  4. post body 中如何获得 multipart/form-data 文件上传表单数据及文件内容
  5. 如何处理请求与响应数据的压缩,Content-Encoding, Accept-Encoding
  6. 如何进行文件下载,Content-Type: application/octet-stream, Content-Disposition: attachment; filename="abc.zip" 

Lambda + API Gateway 的  {"message":"Forbidden"} 的情况很容易让人抓狂,其中有一个原因居然与本地 DNS 缓冲有关,必要时须清一下它

 Mac OS X Yosemite and later:

sudo killall -HUP mDNSResponder

Windows:

ipconfig /flushdns

链接:

  1. API GATEWAY INVOKING LAMBDA FUNCTION WITH TERRAFORM - LAMBDA CONTAINER
  2. Using AWS Lambda with API Gateway and Terraform
  3. Document how to use a LAMBDA_PROXY #10494
  4. How do I troubleshoot HTTP 403 Forbidden errors from API Gateway?
  5. How do I resolve API Gateway "malformed Lambda proxy response" errors or 502 status codes?
  6. Output format of a Lambda function for proxy integration

本文链接 https://yanbin.blog/lambda-api-gateway-with-api-key/, 来自 隔叶黄莺 Yanbin Blog

[版权声明] Creative Commons License 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。

Subscribe
Notify of
guest

3 Comments
Inline Feedbacks
View all comments
trackback

[…] on Lambda + API Gateway 创建需 API Key 验证的 APIv2 用于 HTTP/Websocket, 用来调用 Lambda 还得研究一下,它不需要逐步定义 […]

bbbush
bbbush
3 years ago

只是调用 lambda 的话我记得 v2 只要两个 resource (integration + lambda permission)