阿里云服务网格(Service Mesh,简称ASM)提供一个全托管式的服务网格平台,兼容社区Istio开源服务网格,用于简化服务的治理,包括服务调用之间的流量路由与拆分管理、服务间通信的认证安全以及网格可观测性能力,从而极大地减轻开发与运维的工作负担。产品文档主页:https://help.aliyun.com/zh/asm/product-overview/what-is-asm?spm=a2c4g.11186623.0.0.3dcb29aae25AFJ
本文演示如何使用cert-manager对接ACME CA,为ASM网关签发浏览器信任的HTTPS证书。
ACME是什么?
ACME的定义可以参考RFC 8555: Automatic Certificate Management Environment (ACME)
简而言之,ACME就是一种可以自动化处理X.509证书签发请求的协议。通过这个协议,可以对证书申请者的身份进行验证,从而为其签发证书。
当前有很多公共的ACME CA。Let's Encrypt就是一个非营利性的公共ACME CA,它可以签发出一般浏览器信任的证书。入门指南
本文也将使用let's Encrypt来做演示。
cert-manager中对于ACME的说明
cert-manager的ACME Issuer会向ACME CA Server注册一个账户。当你创建一个ACME Issuer,cert-manager将会生成一个私钥,用来和ACME Server通信。公共ACME Server颁发的证书通常是被客户端信任的。这意味着:客户端浏览器访问时,会信任你当前的URL。
提示一下:
用公共CA为自己的网站颁发证书,主要是为了让浏览器相信:当前Server就是为这个域名提供服务。所以公共CA需要确认:申请证书的Server就是这个域名的拥有者。
Solving Challenges
Challenges是ACME中的一个概念。申请证书时,为了让ACME CA Server确认当前客户端(证书申请者)确实拥有这个域名,客户端一般需要完成一些挑战(challenges)。这是为了确保让不拥有这个域名的人申请到这个证书,防止出现域名欺骗的问题。cert-manager目前提供了两种验证challenge - HTTP01和DNS01 challenge。
HTTP01 challenge是通过server提供计算出的密钥来完成的,该密钥应该存在于 HTTP URL 端点上并且可以通过互联网进行路由。这个URL需要使用当前申请的证书的域名。一旦ACME Server可以通过互联网在这个URL中获取到这个key,ACME server就会确认“你是当前域名的所有者”。当你创建了一个HTTP01 challenge,cert-manager会自动配置你集群的Ingress,来将这个URL的请求路由到一个小的web server上,这个web server上会存有这个key。
DNS01 challenge时通过提供DNS TXT记录中存在的一个计算出来的密钥来完成的。一旦这个TXT记录在互联网上传播开来,ACME server就可以通过DNS查找来获取这个key,并确认当前客户端确实拥有这个域名。拥有了正确的权限后,cert-manager将自动为给定的DNS提供商提供此TXT记录。
使用前提
- 已添加ACK集群到ASM实例,且ASM实例版本为1.16及以上。具体操作,请参见添加集群到ASM实例。
- 已部署入口网关服务,并暴露80和443端口。具体操作,请参见已部署入口网关服务。
- 已部署httpbin应用。具体操作,请参见部署httpbin应用。
- 已经安装cert-manager,具体操作,请参见使用cert-manager管理网关的证书中安装步骤。
- 已经在ASM网关上启用Ingress API支持,具体操作,请参见如何使用ASM网关作为Ingress Controller暴露集群内的服务中的步骤一。
步骤一:准备公网域名
要使用Let's Encrypt为域名签发证书,首先需要有一个公网域名,并且需要您将公网域名指向要使用的ASM网关。具体流程需要查看您的DNS提供商的文档。如果您使用的是阿里云提供的DNS服务,可以参考:如何添加域名解析记录_云解析DNS(DNS)-阿里云帮助中心。
步骤二:创建对接Let's Encrypt的Issuer资源
使用数据面集群的kubeconfig,创建如下资源:
apiVersion: cert-manager.io/v1 kind: Issuer metadata: name: letsencrypt-prod-issuer namespace: istio-system spec: acme: email: 'test@mail.com' # 这个不是必填字段,但是建议填写。ACME Server可能通过该邮箱向您发送与证书相关的重要通知。 privateKeySecretRef: name: letsencrypt-prod server: https://acme-v02.api.letsencrypt.org/directory solvers: - http01: ingress: ingressClassName: istio
上述issuer资源指定了一个http01类型的solver,使用Ingress API,ingressClassName是istio。下面步骤中将会解释这个solver如何生效。
说明:
cert-manager支持使用Ingress API和Gateway API两种类型的solver。ASM对这两种API都提供了支持。
本例中使用Ingress API。
等待issuer就绪,可以使用如下命令查看issuer状态:
k -n istio-system get issuer letsencrypt-prod-issuer NAME READY AGE letsencrypt-prod-issuer True 8m3s
步骤三:为ASM网关签发证书
使用数据面集群的kubeconfig,创建如下资源:
apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: istio-ingressgateway-certs namespace: istio-system spec: dnsNames: - ${测试域名} # test.com issuerRef: group: cert-manager.io kind: Issuer name: letsencrypt-prod-issuer secretName: istio-ingressgateway-certs
等待certificate就绪,可以用如下命令查看certificate状态:
kubectl -n istio-system get certificate istio-ingressgateway-certs NAME READY SECRET AGE istio-ingressgateway-certs True istio-ingressgateway-certs 59m
证书签发的过程解析
创建certificate资源之后,cert-manager就会使用上一步创建的issuer为这个域名签发证书。这个过程中,我们上一步配置的solver就会生效。
Let's Encrypt将会使用HTTP01 challenge来确认:当前server是这个域名的所有者。为此,Let's Encrypt将会发送一个HTTP请求到这个域名,然后它需要获取到一个合法的返回值,才可以完成验证。
可是在这个例子中,我们的网关上只有httpbin的路由规则,并没有任何和challenge有关的配置。Let's Encrypt是否真的发送了challenge请求?以及请求是如何被响应的呢?
首先可以确认的是:Let's Encrypt确实发送了challenge请求,这一点可以通过网关日志来确认。使用如下命令查看网关日志:
kubectl -n istio-system logs ${网关pod名称} | grep letsencrypt | tail -1 {"authority_for":"xxxxxxx","bytes_received":"0","bytes_sent":"87","downstream_local_address":"xx.xx.xx.xx:80","downstream_remote_address":"xx.xx.xx.xx:57101","duration":"0","istio_policy_status":"-","method":"GET","path":"/.well-known/acme-challenge/JfKvfdSNmkR7UqmCQU0OSkJC3EsnP4ZUiCc28OLLLxA","protocol":"HTTP/1.1","request_id":"e6806d08-0469-4383-be8e-4d7506b39ec5","requested_server_name":"-","response_code":"200","response_flags":"-","route_name":"-","start_time":"2024-04-08T12:04:06.153Z","trace_id":"-","upstream_cluster":"outbound|8089||cm-acme-http-solver-c4ch9.istio-system.svc.cluster.local","upstream_host":"xx.xx.xx.xx:8089","upstream_local_address":"xx.xx.xx.xx:55886","upstream_response_time":"0","upstream_service_time":"0","upstream_transport_failure_reason":"-","user_agent":"Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)","x_forwarded_for":"xx.xx.xx.xx"}
可以看到网关上确实存在Let's Encrypt发出的请求,并且该请求被正常返回了。这是为什么呢?
此时自然可以想到前面在issuer中配置了ingressClassName为istio。其实,cert-manager会自动创建一个ingressClassName为istio的ingress资源,将challenge请求转发给cert-manager对应的solver来完成验证。
可是在certificate就绪之后,使用kubectl -n istio-system get ingress
无法看到相关的ingress资源,这是为什么呢?
这是因为cert-manager在完成证书签发后,会自动删除solver相关的资源,其中就包括ingress、service、deployment等资源。
步骤四:验证Let's Encrypt签发的证书
创建如下网关规则,在网关的443端口上配置上一步生成的证书:
apiVersion: networking.istio.io/v1beta1 kind: Gateway metadata: name: httpbin-https namespace: default spec: selector: istio: ingressgateway servers: - hosts: - ${测试域名} port: name: https number: 443 protocol: HTTPS tls: credentialName: istio-ingressgateway-certs mode: SIMPLE
修改原有的httpbin-vs虚拟服务,为如下内容:
apiVersion: networking.istio.io/v1beta1 kind: VirtualService metadata: name: httpbin-vs namespace: default spec: gateways: - httpbin - httpbin-https # 新增这一行 hosts: - '*' http: - name: test route: - destination: host: httpbin.default.svc.cluster.local port: number: 8000
配置完成之后,在浏览器中访问https://${测试域名}
,可以看到如下形式的浏览器地址栏,点击小锁图标,可以看到显示“连接安全”:
此时浏览器已经信任您的证书。
说明
实际场景中,您需要确认您当前的证书颁发机构是否支持ACME协议。如果支持,则ASM网关可以通过cert-manager自动从CA处获取证书。比如Sectigo目前已经支持了ACME协议,其原理与本文所述类似,您可以参考Overview :: Sectigo Certificate Manager Documentation。