ScopeLens上云记录: Cert-manager管理TLS证书(4)

Jiang
10 min readJul 10, 2021
Photo by Michael Dziedzic on Unsplash

Introduction

2210年几乎所有的网站都配备了TLS证书保证消息的安全传输,出去面试也会有不少面试官喜欢考查候选人关于HTTPS的特点及握手流程的知识点。如果你对TLS的重要性或工作原理感兴趣,文末提供了一个我认为讲解得非常透彻的YouTube视频供读者参考,或者也推荐简单阅读《HTTP权威指南》相应章节。

个人建站最常用的证书签发机构当属Let’s Encrypt,最根本的原因是免费,而且支持泛域名,但著名的90天有效期实在是让网站维护者烦恼。

https://cert-manager.io/docs/

Cert-manager解决的问题就是证书的自动申请与续签。如图,它帮你为指定的域名申请证书,并将证书和密钥导出Kubernetes Secret资源。Cert-manager也会在证书到期之前申请续签证书,以保证TLS证书的持续可用。

本文的主要目的还是以应用为主,把能让我们部署的服务用上HTTPS作为目标(当然是因为我也讲不清楚)。文末给出的引用文章里包含了免费证书签发与校验原理的简单解释。因为域名托管在了Route53上,因此这里选择DNS-01校验方式,此方法不需要服务使用Ingress,且支持泛域名证书。

Installation

Helm install

这里采用Helm安装,安装到名为cert-manager的namespace下。

helm repo add jetstack https://charts.jetstack.io
helm repo update
helm install cert-manager jetstack/cert-manager --namespace cert-manager --create-namespace --version v1.3.1 --set installCRDs=true

Credentials

不太清楚为什么cert-manager需要一个IAM user控制Route53记录,也许还有更安全的做法。总之这里使用了管理员权限的用户,将其AccessID和SecretKey放入了Secret资源内,待 cert-manager使用:

apiVersion: v1
kind: Secret
metadata:
namespace: cert-manager
name: aws-credential
type: Opaque
stringData:
accessKey: <>
secretKey: <>

Set up an IAM Role

下一步如同之前配置External DNS那样,我们再创建一个能控制Route 53的IAM Role,它将会被赋予到cert-manager的ServiceAccount上。这是因为我们要采用的DNS-01校验方式涉及到添加DNS记录的步骤,包括添加一条_acme-challenge的TXT记录,后面Let’s Encrypt 将向 DNS 系统查询该记录,如果找到匹配项,就可以颁发证书。该角色的权限范围如下:

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "route53:GetChange",
"Resource": "arn:aws:route53:::change/*"
},
{
"Effect": "Allow",
"Action": [
"route53:ChangeResourceRecordSets",
"route53:ListResourceRecordSets"
],
"Resource": "arn:aws:route53:::hostedzone/*"
},
{
"Effect": "Allow",
"Action": "route53:ListHostedZonesByName",
"Resource": "*"
}
]
}

前往AWS IAM网页控制台,创建一份策略,把该文本粘贴进去,并取一个名字,例如官方文档将其命名为AllowExternalDNSUpdates,保存后会得到它的ARN,形如arn:aws:iam::12345678:policy/AllowExternalDNSUpdates

接着借助eksctl来创建一个IAM Roles for Service Accounts,实现Kubernetes的Service Account资源与IAM Role的映射。

eksctl create iamserviceaccount --cluster=<CLUSTER-NAME> --name=external-dns --attach-policy-arn=<IAM-POLICY-ARN> --override-existing-serviceaccounts --approve

此时查看IAM控制台,会发现多了一个由eksctl创建的角色,把它的ARN先记下来

创建之后继续在IAM控制台修改该角色的信任关系,包含两点:

  • 把default改成cert-manager:system:serviceaccount:default:cert-manager→system:serviceaccount:cert-manager:cert-manager;
  • 添加用户的信任关系:另该角色与上面配置的用户(这里是管理员)构建信任关系;
    {
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::YYY:user/YYY"(注意是user的ARN)
},
"Action": "sts:AssumeRole",
"Condition": {}
}

Service annotation

这一步还是类似External DNS时给ServiceAccount添加带有ARN的annotation,但是我们不会去直接修改这个资源,而是通过helm template将该信息注入进去。

先创建一个包含上面的角色ARN在内的待注入信息的文件,命名为cert-manager.values.yaml

securityContext:
enabled: "true"
serviceAccount:
annotations:
eks.amazonaws.com/role-arn: <IAM-POLICY-ARN>

再执行下面的命令注入进去

helm template cert-manager jetstack/cert-manager --namespace cert-manager --version v1.3.1 -f cert-manager/cert-manager.values.yaml | kubectl apply -f -

Creating an Issuer

Cert-manager有IssuerClusterIssuer 两种用于申请证书的资源,从名字就可以看出二者的scope不一样,前者是受命名空间限制的资源,后者申请的证书整个集群都可以使用。这里选择创建一个ClusterIssuer

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
email: <EMAIL>
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt-prod-account-key
solvers:
- selector:
dnsZones:
- "<DOMAIN-NAME>"
- "*.<DOMAIN-NAME>"
dns01:
route53:
region: <REGION>
hostedZoneID: <HOSTED-ZONE-ID> # optional, see policy above
accessKeyID: <ACCESS-KEY-ID>
secretAccessKeySecretRef:
name: aws-credential
key: secretKey
role: arn:aws:iam::<ACCOUNT-ID>:role/<ROLENAME-OF-CERT-MANAGER>
  • 示例中申请的证书是letsencrypt-prod;
  • dnsZones同时配置了一级域名和泛二级域名;
  • dns01.route53下都是集群的信息与密钥,以获取Route53控制权限。

Apply a certificate

创建Certificate资源,它的作用是利用Issuer去申请证书并生成保存了证书与密钥的Secret资源。

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: ingress-cert
namespace: istio-system
spec:
secretName: ingress-cert
privateKey:
algorithm: ECDSA
size: 256
duration: 2160h
renewBefore: 360h
issuerRef:
kind: ClusterIssuer
name: letsencrypt-prod
dnsNames:
- <DOMAIN-NAME>
- "*.<DOMAIN-NAME>"
  • namespace:Certificate是受限于命名空间的资源,这里将它放在了istio-system命名空间下,因为这里将要把它给Istio的IngressGateway去使用。读者也应当将其放在与自己配置的Ingress相同的命名空间下。关于Istio的配置请关注后续文章;
  • privateKey:下面可以指定密钥的算法与长度,ECDSA是一种还不能破解的加密算法;
  • duration与renewBefore:分别指定了证书的有效期和更新证书的时间,2160h就是Let’s Encrypt签发的证书的有效期90天,renewBefore指的是到期前360h开始尝试申请新证书。

Troubleshoot

至此如果无误的话,等个几分钟,执行命令kubectl get Certificate -n <NAMESPACE>查看证书可用状态,如果有True的字样说明你的网站已经是HTTPS可用的了。

但实际上,在执行上面的配置过程中有哪步缺了漏了的话,都可能会导致证书不能被正式签发的情况,而且错误排查起来很困难,这是因为证书申请涉及到了很多串联起来的CRD的工作,错误可能发生在其中任何一环,同时阻塞了后面的所有过程。

CRD的工作等待链是Challenge←Order←CertificateRequest←Certificate,如果你等了半天发现还是False,请以从左到右的顺序挨个kubectl describe来排查错误。

Conclusion

Ingress引用到证书的Secret之后,我们的网站便可以通过HTTPS进行访问,当然要记得在安全组开放443端口。

下一篇文章终于要讲到一点Istio的相关应用,这里会使用到它的IngressGateway作为网站入口。希望不鸽。

Reference

  1. A complete overview of SSL/TLS and its cryptographic system
  2. 《HTTP权威指南》
  3. 手把手教你使用 cert-manager 签发免费证书

--

--