Using SSL certificates in Kubernetes ingress via cert-manager

之前將 SSL 憑證交給 Cloudflare 託管,要使用該服務就必須開啟 Cloudflare proxy 功能,但會造成網路效能降低([[cloudflare-proxy-slow-issue]])。 改用憑證管理工具 cert-managerLet's Encrypt 申請憑證。

Install cert-manager

使用 helm 來安裝 cert-manager 並指定版本為 v1.5.1。

1bash$: helm repo add jetstack https://charts.jetstack.io
2bash$: helm repo update
3bash$: helm install --namespace cert-manager --create-namespace cert-manager jetstack/cert-manager --version v1.5.1 --set installCRDs=true

Get Cloudflare token

在 cert-manager acme 中有兩種申請憑證的方案,這邊採用 DNS01 方案去申請域名的 wildcard certificate。

  • HTTP01:透過 HTTP 請求來認證網站
  • DNS01:透過 DNS 供應商來認證網站

使用 DNS01 的方案,所以需要在 Cloudflare 上建立 token 來做認證。進入 Cloudflare User Profile,依照下面配置建立 token。

1Permissions:
2  * Zone - DNS - Edit
3  * Zone - Zone - Read
4Zone Resources:
5  * Include - All Zones

Create Issuer / ClusterIssuer

建立 ACME 的發證服務,其中 kind 類型有 Issuer、ClusterIssuer,差別在於 IssuerCertificate 必須在同一個 namespace 下;而 ClusterIssuerCertificate 可以在不同 namespace 下。

Cloudflare

Let's Encrypt 有提供測試環境 Staging Environment,在使用額度的部分會比較多。

  • production environment server: https://acme-v02.api.letsencrypt.org/directory
  • staging environment server: https://acme-staging-v02.api.letsencrypt.org/directory
 1apiVersion: v1
 2kind: Secret
 3metadata:
 4  name: cloudflare-api-token-secret
 5  namespace: cert-manager
 6type: Opaque
 7stringData:
 8  api-token: pleace-change-cloudflare-secret-token
 9---
10apiVersion: cert-manager.io/v1
11kind: ClusterIssuer
12metadata:
13  name: letsencrypt-dns01
14  namespace: cert-manager
15spec:
16  acme:
17    email: user@example.com
18    server: https://acme-v02.api.letsencrypt.org/directory
19    privateKeySecretRef:
20      name: letsencrypt-dns01
21    solvers:
22    - dns01:
23        cloudflare:
24          email: my-cloudflare-acc@example.com
25          apiTokenSecretRef:
26            name: cloudflare-api-token-secret
27            key: api-token

AWS route53

 1apiVersion: cert-manager.io/v1
 2kind: ClusterIssuer
 3metadata:
 4  name: letsencrypt-dns01
 5  namespace: cert-manager
 6spec:
 7  acme:
 8    email: my-route53@example.com
 9    privateKeySecretRef:
10      name: letsencrypt-dns01
11    server: https://acme-v02.api.letsencrypt.org/directory
12    solvers:
13      - selector:
14          dnsZones:
15            - "example1.com"
16        dns01:
17          route53:
18            region: ap-southeast-1
19            hostedZoneID: please-change-hosted-zone-id
20      - selector:
21          dnsZones:
22            - "example2.com"
23        dns01:
24          route53:
25            region: ap-southeast-1
26            hostedZoneID: please-change-hosted-zone-id

透過下面指令可以查詢服務狀況。

1bash$ kubectl get cert-manager -n cert-manager
2NAME                                              READY   AGE
3clusterissuer.cert-manager.io/letsencrypt-dns01   True    62m

Create Certificate

cert-manager 的 Certificate 會更新憑證並且將憑證保存至定義的 secret 中。 在 yaml 中定義好預期要申請的域名後,會跟 Issuer、ClusterIssuer 發起申請憑證。

 1apiVersion: cert-manager.io/v1
 2kind: Certificate
 3metadata:
 4  name: example1.com
 5spec:
 6  secretName: example1.com
 7  duration: 2160h # 90d
 8  renewBefore: 360h # 15d
 9  issuerRef:
10    name: letsencrypt-dns01
11    kind: ClusterIssuer
12  dnsNames:
13    - 'example1.com'
14    - '*.example1.com'
15---
16apiVersion: cert-manager.io/v1
17kind: Certificate
18metadata:
19  name: example2.com
20spec:
21  secretName: example2.com
22  duration: 2160h # 90d
23  renewBefore: 360h # 15d
24  issuerRef:
25    name: letsencrypt-dns01
26    kind: ClusterIssuer
27  dnsNames:
28    - 'example2.com'
29    - '*.example2.com'

查詢服務狀況。第一次發起申請憑證的時候,狀態從 False -> True 大約等了 10 分鐘。

 1bash$ kubectl get cert-manager
 2NAME                                                                  STATE   AGE
 3order.acme.cert-manager.io/example.com-certificate-bk8vh-2305456588   valid   54m
 4
 5NAME                                                               APPROVED   DENIED   READY   ISSUER              REQUESTOR                                         AGE
 6certificaterequest.cert-manager.io/example.com-certificate-bk8vh   True                True    letsencrypt-dns01   system:serviceaccount:cert-manager:cert-manager   54m
 7
 8NAME                                                  READY   SECRET                       AGE
 9certificate.cert-manager.io/example.com-certificate   True    example.com-certificate   54m
10
11NAME                                              READY   AGE
12clusterissuer.cert-manager.io/letsencrypt-dns01   True    63m

查詢憑證狀況。

 1bash$ kubectl get secret
 2NAME                                                TYPE                                  DATA   AGE
 3example.com-certificate                             kubernetes.io/tls                     2      70m
 4
 5bash$ kubectl describe secret/example.com-certificate
 6Name:         example.com-certificate
 7Namespace:    default
 8Labels:       <none>
 9Annotations:  cert-manager.io/alt-names: *.example.com,example.com
10              cert-manager.io/certificate-name: example.com-certificate
11              cert-manager.io/common-name: example.com
12              cert-manager.io/ip-sans:
13              cert-manager.io/issuer-group:
14              cert-manager.io/issuer-kind: ClusterIssuer
15              cert-manager.io/issuer-name: letsencrypt-dns01
16              cert-manager.io/uri-sans:
17
18Type:  kubernetes.io/tls
19
20Data
21====
22tls.crt:  5615 bytes
23tls.key:  1675 bytes

Modified Nginx ingress

確認申請憑證完成後,修改 ingress 的配置,這邊我的 ingress 是使用 ingress-nginx 搭配 AWS 的 network load balance。

 1apiVersion: networking.k8s.io/v1
 2kind: Ingress
 3...
 4spec:
 5  tls:
 6    - hosts:
 7      - justin.example.com
 8      secretName: acme-letsencrypt-certificate
 9  rules:
10    - host: "example.com-certificate"
11      http:
12        paths:
13          - path: /
14            pathType: Prefix
15            backend:
16              service:
17                name: golang
18                port:
19                  number: 80
comments powered by Disqus