cert-manager で証明書管理

前回の「ArgoCD と Istio Ingress Gateway」と、前々回の「 Istio 導入への道 – Ingress Gateway で TLS Termination 編 」で TLS の証明書を手動で取得して Secret として登録したが、登録もさることながら更新が大変です。これを cert-manager にやってもらうことにします。

cert-manager のインストール

ドキュメントにある通りです。

$ kubectl apply -f [https://github.com/jetstack/cert-manager/releases/download/v0.14.1/cert-manager.yaml](https://github.com/jetstack/cert-manager/releases/download/v0.14.1/cert-manager.yaml)

沢山のリソースが作成されます。

customresourcedefinition.apiextensions.k8s.io/certificaterequests.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/certificates.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/challenges.acme.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/clusterissuers.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/issuers.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/orders.acme.cert-manager.io created
namespace/cert-manager created
serviceaccount/cert-manager-cainjector created
serviceaccount/cert-manager created
serviceaccount/cert-manager-webhook created
clusterrole.rbac.authorization.k8s.io/cert-manager-cainjector created
clusterrole.rbac.authorization.k8s.io/cert-manager-controller-certificates created
clusterrole.rbac.authorization.k8s.io/cert-manager-controller-issuers created
clusterrole.rbac.authorization.k8s.io/cert-manager-view created
clusterrole.rbac.authorization.k8s.io/cert-manager-controller-orders created
clusterrole.rbac.authorization.k8s.io/cert-manager-controller-challenges created
clusterrole.rbac.authorization.k8s.io/cert-manager-controller-ingress-shim created
clusterrole.rbac.authorization.k8s.io/cert-manager-controller-clusterissuers created
clusterrole.rbac.authorization.k8s.io/cert-manager-edit created
clusterrolebinding.rbac.authorization.k8s.io/cert-manager-cainjector created
clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-challenges created
clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-issuers created
clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-certificates created
clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-orders created
clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-clusterissuers created
clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-ingress-shim created
role.rbac.authorization.k8s.io/cert-manager-cainjector:leaderelection created
role.rbac.authorization.k8s.io/cert-manager:leaderelection created
rolebinding.rbac.authorization.k8s.io/cert-manager-cainjector:leaderelection created
rolebinding.rbac.authorization.k8s.io/cert-manager:leaderelection created
service/cert-manager created
service/cert-manager-webhook created
deployment.apps/cert-manager-cainjector created
deployment.apps/cert-manager created
deployment.apps/cert-manager-webhook created
mutatingwebhookconfiguration.admissionregistration.k8s.io/cert-manager-webhook created
validatingwebhookconfiguration.admissionregistration.k8s.io/cert-manager-webhook created

cert-manager ネームスペースに cert-manager, cert-manager-cainjector (CA Injector), cert-manager-webhook Deployment がデプロイされています。

$ kubectl get pods --namespace cert-manager
NAME                                       READY   STATUS    RESTARTS   AGE
cert-manager-665f89d4d6-vfpdx              1/1     Running   0          83m
cert-manager-cainjector-78c8947f5c-r8rsd   1/1     Running   0          83m
cert-manager-webhook-84f59fdf49-l59ld      1/1     Running   0          83m

リソースの登録

ここでは Let’s Encrypt の証明書を dns01 で取得します。DNS には Route53 を使います。Kubernetes クラスタは相変わらず Minikube なので IAM Role ではなく IAM User を作成して Access Key ID と Secret Access Key を使います。

Secret Access Key を K8s Secret に登録

route53-credentials-secret という名前の Secret を cert-manager ネームスペースに登録します。キーは secret-access-key とします。

$ kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
  name: route53-credentials-secret
  namespace: cert-manager
type: Opeque
stringData:
  secret-access-key: ai/Vdmhv2qhekGe1nE1u39HC48LIAwtBap+5TP81
EOF

Issuer の登録

IssuerClusterIssuer があり、Issuer は namespace に閉じたリソースです。ネームスペースを跨いで利用する場合は ClusterIssuer を使います。Issuer には Let’s Encrypt などを使用するのに必要な情報をセットします。今回は Let’s Encrypt の dns01Route53 ですから、

Let’s Encrypt 用に

  • メールアドレス
  • ACME サーバーの URL
  • Private Key の保存先としての Secret 名

Route53 用に

  • Region 名
  • Access Key ID
  • Secret Access Key を登録した Secret 名

を設定します。

$ kubectl apply -f - <<EOF
apiVersion: cert-manager.io/v1alpha2
kind: ClusterIssuer
metadata:
  name: letsencrypt
  namespace: cert-manager
spec:
  acme:
    # The ACME server URL
    server: https://acme-v02.api.letsencrypt.org/directory
    # Email address used for ACME registration
    email: username@gmail.com
    # Name of a secret used to store the ACME account private key
    privateKeySecretRef:
      name: letsencrypt
    solvers:
    - dns01:
        route53:
          region: ap-northeast-1
          accessKeyID: AKIA5Z6TEOZE8VOPCH6K
          secretAccessKeySecretRef:
            name: route53-credentials-secret
            key: secret-access-key
EOF

solvers はリストで複数のプロバイダを登録できます。ドメインによって DNS プロバイダが違う場合や Route53 用の AWS Account が違う場合、または dns01 ではなく web01 を使う場合などの混在が可能です。

Certificate の登録

issuerRef でどの Issuer を使うのかを指定します。ClusterIssuer を使ったため default ネームスペースからでも発行可能です。secretName で指定した Secret に秘密鍵と証明書が保存されます。

$ kubectl apply -f - <<EOF
apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
  name: wildcard-local-1q77-com
  namespace: default
spec:
  secretName: wildcard-local-1q77-com
  dnsNames:
  - '*.local.1q77.com'
  - local.1q77.com
  issuerRef:
    name: letsencrypt
    kind: ClusterIssuer
EOF

これで次のような CommonName, SANs の証明書を取得することができます。

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            04:94:64:04:e7:54:ba:6a:b5:cf:30:3a:fd:e3:d3:7f:95:af
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=US, O=Let's Encrypt, CN=Let's Encrypt Authority X3
        Validity
            Not Before: Mar 28 08:21:40 2020 GMT
            Not After : Jun 26 08:21:40 2020 GMT
        Subject: CN=*.local.1q77.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:c5:5f:18:bd:46:7e:91:6c:d3:0e:04:0e:fc:6c:
                    ef:9d:25:98:10:e9:c1:a1:68:3c:23:5e:13:98:cd:
                    2d:60:4e:5d:06:87:f3:10:c1:86:cb:4e:7d:cf:b4:
                    fe:9f:20:3a:88:5a:4d:0a:ff:01:34:23:74:a4:22:
                    a8:c3:32:74:b1:23:25:f2:f1:11:f1:7c:77:ee:7e:
                    41:d9:7b:4f:ac:ca:ea:c0:6d:f1:46:bf:d1:c3:06:
                    fa:45:66:dc:ee:03:e9:25:23:46:c9:54:57:88:eb:
                    35:53:f5:ea:db:5c:09:d3:fa:a5:98:34:2d:c6:50:
                    aa:80:ef:25:72:48:04:9b:48:4d:bb:dc:f8:9a:56:
                    dc:f5:e4:f6:b4:34:d2:d0:a8:54:ce:77:4a:d5:83:
                    60:e3:16:20:6e:12:6b:d5:0c:86:d2:3c:5a:ba:64:
                    5f:cf:05:cb:db:0a:64:35:3d:e9:8d:18:65:2b:fd:
                    11:fe:32:c5:5e:29:44:f6:85:61:4c:ae:9d:33:f6:
                    e1:d8:9a:4c:2d:9e:fa:58:ff:0b:45:89:61:1c:3d:
                    cf:8c:58:b5:c6:76:ca:95:e3:6d:14:ef:4e:81:8d:
                    dd:a0:85:52:4b:b3:61:e4:c0:f5:be:12:c7:7e:35:
                    ab:36:b7:ea:54:55:2d:8d:a5:3f:cc:3e:84:a5:84:
                    4d:a1
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment
            X509v3 Extended Key Usage: 
                TLS Web Server Authentication, TLS Web Client Authentication
            X509v3 Basic Constraints: critical
                CA:FALSE
            X509v3 Subject Key Identifier: 
                A8:0D:95:E2:F2:CA:5A:11:4A:DD:83:70:A6:07:77:83:12:36:27:7A
            X509v3 Authority Key Identifier: 
                keyid:A8:4A:6A:63:04:7D:DD:BA:E6:D1:39:B7:A6:45:65:EF:F3:A8:EC:A1

            Authority Information Access: 
                OCSP - URI:http://ocsp.int-x3.letsencrypt.org
                CA Issuers - URI:http://cert.int-x3.letsencrypt.org/

            X509v3 Subject Alternative Name: 
                DNS:*.local.1q77.com, DNS:local.1q77.com
            X509v3 Certificate Policies: 
                Policy: 2.23.140.1.2.1
                Policy: 1.3.6.1.4.1.44947.1.1.1
                  CPS: http://cps.letsencrypt.org

Certificate リソースは次のような状態。

$ kubectl get certificate
NAME                      READY   SECRET                    AGE
wildcard-local-1q77-com   True    wildcard-local-1q77-com   4h36m
$ kubectl describe certificate                
Name:         wildcard-local-1q77-com
Namespace:    default
Labels:       <none>
Annotations:  kubectl.kubernetes.io/last-applied-configuration:
                {"apiVersion":"cert-manager.io/v1alpha2","kind":"Certificate","metadata":{"annotations":{},"name":"wildcard-local-1q77-com","namespace":"d...
API Version:  cert-manager.io/v1alpha3
Kind:         Certificate
Metadata:
  Creation Timestamp:  2020-03-28T08:59:27Z
  Generation:          2
  Resource Version:    1027821
  Self Link:           /apis/cert-manager.io/v1alpha3/namespaces/default/certificates/wildcard-local-1q77-com
  UID:                 f5e5d067-2fac-4fdd-8a4b-59ba26916137
Spec:
  Dns Names:
    *.local.1q77.com
    local.1q77.com
  Issuer Ref:
    Kind:       ClusterIssuer
    Name:       letsencrypt
  Secret Name:  wildcard-local-1q77-com
Status:
  Conditions:
    Last Transition Time:  2020-03-28T09:21:41Z
    Message:               Certificate is up to date and has not expired
    Reason:                Ready
    Status:                True
    Type:                  Ready
  Not After:               2020-06-26T08:21:40Z
Events:                     

Certificate を作成すると CertificateRequest リソースが作成されます。何か問題がある場合はこの中身をみると原因が分かったりします。

$ kubectl get certificaterequest
NAME                                 READY   AGE
wildcard-local-1q77-com-4173496889   True    4h17m
$ kubectl describe certificaterequest     
Name:         wildcard-local-1q77-com-4173496889
Namespace:    default
Labels:       <none>
Annotations:  cert-manager.io/certificate-name: wildcard-local-1q77-com
              cert-manager.io/private-key-secret-name: wildcard-local-1q77-com
              kubectl.kubernetes.io/last-applied-configuration:
                {"apiVersion":"cert-manager.io/v1alpha2","kind":"Certificate","metadata":{"annotations":{},"name":"wildcard-local-1q77-com","namespace":"d...
API Version:  cert-manager.io/v1alpha3
Kind:         CertificateRequest
Metadata:
  Creation Timestamp:  2020-03-28T09:17:50Z
  Generation:          1
  Owner References:
    API Version:           cert-manager.io/v1alpha2
    Block Owner Deletion:  true
    Controller:            true
    Kind:                  Certificate
    Name:                  wildcard-local-1q77-com
    UID:                   f5e5d067-2fac-4fdd-8a4b-59ba26916137
  Resource Version:        1027815
  Self Link:               /apis/cert-manager.io/v1alpha3/namespaces/default/certificaterequests/wildcard-local-1q77-com-4173496889
  UID:                     8c73b0a5-172e-4fee-bed1-5208406d3e50
Spec:
  Csr:  LS0tLS1C (中略... PEM がさらに Base64 されて表示されている) U1QtLS0tLQo=
  Issuer Ref:
    Kind:  ClusterIssuer
    Name:  letsencrypt
Status:
  Certificate:  LS0tLS1CRU (中略... PEM がさらに Base64 されて表示されている) LS0tLS0K
  Conditions:
    Last Transition Time:  2020-03-28T09:21:40Z
    Message:               Certificate fetched from issuer successfully
    Reason:                Issued
    Status:                True
    Type:                  Ready
Events:                    <none>

Certificate リソースで指定した Secret に証明書と秘密鍵が入っています。

$ kubectl get secret wildcard-local-1q77-com
NAME                      TYPE                DATA   AGE
wildcard-local-1q77-com   kubernetes.io/tls   3      4h37m
$ kubectl describe secret wildcard-local-1q77-com
Name:         wildcard-local-1q77-com
Namespace:    default
Labels:       <none>
Annotations:  cert-manager.io/alt-names: *.local.1q77.com,local.1q77.com
              cert-manager.io/certificate-name: wildcard-local-1q77-com
              cert-manager.io/common-name: *.local.1q77.com
              cert-manager.io/ip-sans: 
              cert-manager.io/issuer-kind: ClusterIssuer
              cert-manager.io/issuer-name: letsencrypt
              cert-manager.io/uri-sans: 

Type:  kubernetes.io/tls

Data
====
ca.crt:   0 bytes
tls.crt:  3582 bytes
tls.key:  1675 bytes

Certificate 設定をミスってた時に CertificateRequest で確認されたメッセージです。

status:
  conditions:
  - lastTransitionTime: "2020-03-28T08:59:27Z"
    message: 'The CSR PEM requests a commonName that is not present in the list of
      dnsNames. If a commonName is set, ACME requires that the value is also present
      in the list of dnsNames: "local.1q77.com" does not exist in [*.local.1q77.com]'
    reason: Failed
    status: "False"
    type: Ready
  failureTime: "2020-03-28T08:59:27Z"

自動更新

自動更新までは動作確認できていませんが、ドキュメントには次のように書いてあります。

Once our certificate has been obtained, cert-manager will periodically check its validity and attempt to renew it if it gets close to expiry. cert-manager considers certificates to be close to expiry when the ‘Not After’ field on the certificate is less than the current time plus 30 days.

証明書取得後、cert-manager は定期的に有効性を確認し、期限が近づくと更新を試みます。cert-manager は Not After フィールドの日付が現在時刻プラス30日よりも近い場合に期限切れ間近と判断する。

Built with Hugo
テーマ StackJimmy によって設計されています。