为API Gateway使用自定义域名和mTLS双向证书认证

一、背景

通常使用的HTTPS接口采用的是服务器端Transport Layer Security(以下简称TLS)认证。TLS认证默认是由服务器提供证书,服务器端证书经过全球数家证书颁发机构和顶级CA签署认证的证书,来标识服务器自己的真实、正确的身份。在亚马逊云科技的云平台上,这一服务器端证书通常由ACM生成并管理,配置到Amazon CloudFront、Amazon ELB、Amazon API Gateway(以下简称API Gateway)等服务上。

为了进一步增强安全性,TLS协议建立连接时候,服务器可以要求客户端也提供自己X.509格式的证书用于验明身份。由于服务器和客户端双方都需要提供证书,这种认证方式被称为 mutual Transport Layer Security (以下简称mTLS),也称为双向TLS。

双向TLS证书验证在金融、物联网、IoT等公开网络环境中发生在线交易、在线交互等场景下获得普遍应用。在企业内部应用系统中也经常作为强制验证客户端身份真实性的一种手段。使用mTLS后,即便拥有正确的用户名和密码,也会因为没有通过证书验证而不能建立网络连接,此时用户名和密码将不会在网络层被发送。应用mTLS额外提升了接口安全能力。

在API Gateway上开启mTLS认证没有额外费用。双向TLS证书验证与使用用户名、密码和token的认证手段并不矛盾和冲突,mTLS工作于更基础的网络层。通过证书校验,网络通道建立后,就会进行应用层的身份认证。对于API Gateway,建议在API Gateway使用包括但是不限于IAM身份验证、Lambda认证转发器和JSON Web Tokens (JWTs)等认证手段进行身份认证。

在API Gateway上开启mTLS认证主要步骤是:

  • 准备:发布一个正常接受访问的API Gateway Stages(阶段),并准备好ACM证书,自定义域名
  • 生成证书:生成mTLS使用的证书
  • 创建Endpoint:使用自定义域名功能,通过绑定别名和来自Amazon Certificate Manager(ACM)的证书,生成一个仅支持的mTLS的新的Endpoint
  • 绑定:然后通过API Mapping方式将原API Gateway与新的mTLS认证的Endpoint进行关联
  • 加固:关闭原有API Gateway的Endpoint的公开访问

本文介绍如何为API Gateway开启mTLS认证。

二、使用mTLS认证

开始配置前,请确认API Gateway本身工作正常。然后开始配置。

1、生成并上传证书

双向证书认证要求证书为X.509证书。证书可以自行签署,或者通过Amazon Private Certificate Authority(以下简称Private CA)生成。

截止本文编写时(2023年1月),亚马逊云科技中国区尚未发布Private CA功能,因此本文使用Openssl自行签署。

(1)生成CA

生成证书需要Linux环境和openssl组件,先生成自有CA,然后自己为自己颁发证书。全过程需要生成以下文件:

  • RootCA.key (root CA private key)
  • RootCA.pem (root CA public key)
  • my_client.csr (client certificate signing request)
  • my_client.key (client certificate private key)
  • my_client.pem (client certificate public key)

首先生成CA私有证书文件。

openssl genrsa -out RootCA.key 4096

由此将在当前目录下获得RootCA.key文件。接下来生成CA Public Key。执行如下命令:

openssl req -new -x509 -days 3650 -key RootCA.key -out RootCA.pem

执行以上命令后,提示输入证书所需要的Country NameStatecityCompanysectionnameEmail等信息。如下返回信息。

[root@ip-172-31-32-113 ~]# openssl req -new -x509 -days 3650 -key RootCA.key -out RootCA.pem
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:CN
State or Province Name (full name) []:BEIJING
Locality Name (eg, city) [Default City]:BEIJING
Organization Name (eg, company) [Default Company Ltd]:BITIPCMAN
Organizational Unit Name (eg, section) []:DEV
Common Name (eg, your name or your server's hostname) []:lxy
Email Address []:aobao32@163.com
[root@ip-172-31-32-113 ~]#

完毕后,在当前目录下生成RootCA.pem文件。

(2)生成客户端证书

首先生成客户端私钥。执行如下命令:

openssl genrsa -out my_client.key 2048

由此获得my_client.key文件。为此证书生成Certificate Signing Request (CSR)。执行如下命令:

openssl req -new -key my_client.key -out my_client.csr

在发出证书签署申请时候,也要逐步输入Country NameStatecityCompanysectionnameEmail等信息。当提示A challenge password时候,请注意这是可选步骤,直接按回车保持为空不需要输入。如下返回信息。

[root@ip-172-31-32-113 ~]# openssl req -new -key my_client.key -out my_client.csr
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:CN
State or Province Name (full name) []:BEIJING
Locality Name (eg, city) [Default City]:BEIJING
Organization Name (eg, company) [Default Company Ltd]:BITIPCMAN
Organizational Unit Name (eg, section) []:DEV
Common Name (eg, your name or your server's hostname) []:dev01
Email Address []:dev01@bitipcman.com

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
[root@ip-172-31-32-113 ~]#

至此在当前目录下获得my_client.csr文件。

(3)签署证书

现在针对上一步的客户端证书签名请求,签署客户端证书,生成客户端公钥。执行如下命令:

openssl x509 -req -in my_client.csr -CA RootCA.pem -CAkey RootCA.key -set_serial 01 -out my_client.pem -days 3650 -sha256

这样即可针对刚才的客户端证书生成签名。返回结果如下:

root@ip-172-31-32-113 ~]# openssl x509 -req -in my_client.csr -CA RootCA.pem -CAkey RootCA.key -set_serial 01 -out my_client.pem -days 3650 -sha256
Signature ok
subject=/C=CN/ST=BEIJING/L=BEIJING/O=BITIPCMAN/OU=DEV/CN=dev01/emailAddress=dev01@bitipcman.com
Getting CA Private Key
[root@ip-172-31-32-113 ~]#

由此在当前目录下获得my_client.pem文件。后续在调用API Gateway的客户端程序中,就需要使用client.pem这个文件。

(4)上传CA到S3存储桶用于API Gateway加载

API Gateway在开启mTLS时候需要检查证书的CA的公有证书,因此这里需要将上一个步骤生成的RootCA.pem上传到某个S3存储桶中,然后在API Gateway上加载这个证书文件。

为此,创建一个S3存储桶,选择与API Gateway相同的region,存储桶取名为apigateway-mtls-mycert-zhy。将上一步生成的RootCA.pem复制为一个新文件,名叫truststore.pem,然后将其上传到S3存储桶内。

以下过程也可以通过AWSCLI执行,命令如下。

cp RootCA.pem truststore.pem
aws s3 cp truststore.pem s3://apigateway-mtls-mycert-zhy/

2、为API Gateway设置设置自定义域名和mTLS

为配置mTLS,需要开启custom domain name也就是自定义域名功能。

进入API Gateway的界面,点击左侧菜单custom domain name,点击右侧的创建按钮。

在创建自定义域名的界面上,输入自定义域名,TLS协议选择为1.2版本(推荐),在Mutual TLS authentication开关设置为启用,在Truststore URI位置输入上一步的S3存储桶名称和文件路径s3://apigateway-mtls-mycert-zhy/truststore.pem。然后向下卷动页面。如下截图。

Certificate type位置上,选择Public ACM certificate,然后从下拉框中选择事先在本Region配置好的ACM证书。最后点击右角的Create domain name创建自定义域名。

现在可以看到新生成了一个新的API Gateway Endpoint,和原来没有使用mTLS的Endpoint是独立的两个端点。由此即可配置自己的域名,设置一个新的CNAME,将别名指向过来。

如果您的域名是在Amazon Route53解析的,请在Route53上设置CNAME。如果您的域名是在第三方域名服务商解析的,请在域名服务商提供的域名解析控制面板上设置。设置CNAME过程本文不再描述,请参考相关文档完成。

3、设置自定义域名的Mapping

在创建自定义域名完成后,还要设置API Mapping。进入自定义域名界面,点击右下的第二个标签页API mappings标签页,点击右边的按钮Configure API mappings按钮。如下截图。

在创建新的Endpoint的位置上,从下拉框选择中选择要绑定的API Gateway的名字,从Stages下拉框中选择之前配置好的prod,在映射为新的API地方输入相同的名字prod,然后点击右下角的创建按钮。这里可以看到界面上提示原有的API Gateway的公开对外访问还没有关闭,这个信息可以暂时忽略,本文后续步骤会关闭不带mTLS的访问。如下截图。

创建Mapping完成。提示原有的API Gateway的公开对外访问还没有关闭,这个信息可以暂时忽略,本文后续步骤会关闭不带mTLS的访问。如下截图。

接下来发起访问测试。

4、测试访问

使用CURL的参数--key--cert带着证书访问新创建的自定义域名。命令如下:

curl --key my_client.key --cert my_client.pem https://mtlsdemo.bitipcman.com/prod/pets/

测试可看到访问正常。当去掉证书之后,再次使用CURL访问,可以看到提示SSL错误无法建立连接。如下截图。

由此表示配置正常。

5、关闭原有的不带CNAME不带mTLS的Endpoint

前文已经讲解,新创建的带有mTLS的自定义域名是一个全新的Endpoint,还需要把原先的Endpoint设置为禁止公开访问。

方法是进入原先的API Gateway的界面,进入左侧的Settings设置界面,再右侧找到Default endpoint的选项,将默认为Enable的开关修改为Disable。如下截图。

注意此选项变更后,必须要重新部署新Stages才可以生效。界面上也有如下提示:

This is an API level setting and affects all stages of your API. After you enable or disable the default endpoint, deploy your API to any one stage for the update to take effect.

如果不发起新的部署,原有的API Gateway Endpoint依然是无需mTLS即可访问的。发起新的部署可以在API Gateway图形界面控制台完成,也可以通过AWSCLI发起,命令如下:

aws apigateway update-rest-api \
    --rest-api-id abcdef123 \
    --patch-operations op=replace,path=/disableExecuteApiEndpoint,value='True'
    
aws apigateway create-deployment \
    --rest-api-id abcdef123 \
    --stage-name prod

在发布新的Stage后,再次通过curl访问原有API Gateway,则提示没有权限。

{"message":"Forbidden"}

由此表明,旧的API Gateway已经被关闭。目前所有访问只能通过开启了mTLS的自定义域名来访问。至此所有配置完成。

三、参考文档

Introducing mutual TLS authentication for Amazon API Gateway

https://aws.amazon.com/cn/blogs/compute/introducing-mutual-tls-authentication-for-amazon-api-gateway/

为 REST API 配置双向 TLS 身份验证

https://docs.amazonaws.cn/apigateway/latest/developerguide/rest-api-mutual-tls.html

禁用 REST API 的默认终端节点

https://docs.amazonaws.cn/apigateway/latest/developerguide/rest-api-disable-default-endpoint.html