cert-managerとHashiCorp Vaultを使用して証明書を管理する方法
Jintao Zhang
March 2, 2023
cert-managerが解決する問題
2017年にJETSTACKによってオープンソース化されたcert-managerは、CNCF(Cloud Native Computing Foundation)に寄贈され、サンドボックスレベルのプロジェクトとなりました。2022年10月にはCNCFのインキュベーションプロジェクトとなりました。
cert-manager
は、KubernetesとOpenShift内のx.509証明書を自動的に管理できます。これにより、証明書と証明書署名要求がKubernetes上でサポートされる最初のリソースタイプとなり、このプロセスはCRDによって実装されています。さらに、cert-managerは開発者がアプリケーションのアクセスセキュリティを向上させるために迅速に証明書を申請できるようにします。
それでは、cert-managerが登場する前にKubernetesでどのように証明書を管理していたかを見てみましょう。
Kubernetesでの証明書管理方法
Kubernetesでは、主に2つのネイティブな方法でデータを保存できます:
- ConfigMap
- Secret
ただし、ConfigMap内のすべての情報はプレーンテキストです。そのため、比較的一般的な設定情報を保存するには適していますが、証明書のような機密情報を保存するには適していません。
Kubernetesが設計された際、証明書などの関連情報を保存するためにSecret
を使用することが推奨され、これに対するサポートも提供されています。kubectl create secret tls
を使用して簡単に証明書情報を保存できます。例えば:
➜ ~ kubectl create secret tls moelove-tls --cert=./cert.pem --key=./cert-key.pem secret/moelove-tls created ➜ ~ kubectl get secret moelove-tls -oyaml apiVersion: v1 data: tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNFekNDQWJtZ0F3SUJBZ0lVVHhCTC9aQkdpOEJCOUFVN2JRWi9jK3c2L1Rzd0NnWUlLb1pJemowRUF3SXcKVFRFTE1Ba0dBMVVFQmhNQ1EwNHhFREFPQmdOVkJBY1RCMEpsYVdwcGJtY3hGVEFUQmdOVkJBb1RERTF2WlV4dgpkbVVnU1U1R1R6RVZNQk1HQTFVRUF4TU1iVzlsYkc5MlpTNXBibVp2TUI0WERUSXlNVEF4T1RBM01UY3dNRm9YCkRUSXpNVEF4T1RBM01UY3dNRm93VFRFTE1Ba0dBMVVFQmhNQ1EwNHhFREFPQmdOVkJBY1RCMEpsYVdwcGJtY3gKRlRBVEJnTlZCQW9UREUxdlpVeHZkbVVnU1U1R1R6RVZNQk1HQTFVRUF4TU1iVzlsYkc5MlpTNXBibVp2TUZrdwpFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRVVTcEFjNGE1UXQwQ0NVa2hGSGY3WnZvR1FReVVPUUxSClJhZG0rSUUrV1ZkOThyWkc5NFpob08ybDZSWkY2MnVPN3FpZ2VsaUJwY0FGQ3FzWU9HNnVLcU4zTUhVd0RnWUQKVlIwUEFRSC9CQVFEQWdXZ01CMEdBMVVkSlFRV01CUUdDQ3NHQVFVRkJ3TUJCZ2dyQmdFRkJRY0RBakFNQmdOVgpIUk1CQWY4RUFqQUFNQjBHQTFVZERnUVdCQlFnS01icnBUb3k4NVcvRy9hMGZtYzlDMUJRbURBWEJnTlZIUkVFCkVEQU9nZ3h0YjJWc2IzWmxMbWx1Wm04d0NnWUlLb1pJemowRUF3SURTQUF3UlFJZ1EzTzhJZ0N2MlRkNUhhV00KcE1LWmRCLzNXdEMreERlSVdPbER6L2hCdzE0Q0lRRExQNG0weFpmSkJvRGc5cERocThGdHN5VDdVZVhVdlZGQQpsS0tReFZNOXFBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= tls.key: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUsyZjZHQlNZQ0R4eVoycnB2bVZ1YW5MNDhxeW9SK1NiWmxiQzNqSUZybzhvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFVVNwQWM0YTVRdDBDQ1VraEZIZjdadm9HUVF5VU9RTFJSYWRtK0lFK1dWZDk4clpHOTRaaApvTzJsNlJaRjYydU83cWlnZWxpQnBjQUZDcXNZT0c2dUtnPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo= kind: Secret metadata: creationTimestamp: "2022-10-19T07:24:26Z" name: moelove-tls namespace: default resourceVersion: "2103326" uid: 14f86514-a1d1-4d99-b000-9ed8b5189d56 type: kubernetes.io/tls
上記のコマンドにより、Kubernetes内にmoelove-tls
という名前のkubernetes.io/tls
タイプのSecretリソースが作成されます。
このリソースは、アプリケーションが証明書情報を使用する必要がある場合に直接参照できます。ほとんどの場合、Ingress Controllerのシーンで使用されます。例えば:
➜ ~ kubectl create ingress moelove-ing --rule="moelove.info/=moelove:8080,tls=moelove-tls" ingress.networking.k8s.io/moelove-ing created ➜ ~ kubectl get ing moelove-ing -oyaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: creationTimestamp: "2022-10-19T07:32:43Z" generation: 1 name: moelove-ing namespace: default resourceVersion: "2104268" uid: b90f09f7-8036-4b9f-9744-a247141ea8da spec: rules: - host: moelove.info http: paths: - backend: service: name: moelove port: number: 8080 path: / pathType: Exact tls: - hosts: - moelove.info secretName: moelove-tls status: loadBalancer: {}
上記のコマンドにより、moelove-ing
という名前のIngressリソースが作成されます。そのドメイン名はmoelove.info
と宣言され、moelove-tls
を使用してこのドメイン名に証明書保護が追加されます。対応するIngress ControllerコンポーネントがIngressリソースを取得すると、コンポーネントは自動的にこのドメイン名の証明書を設定し、ウェブサイトのセキュリティを向上させます。
遭遇した問題
証明書の発行が煩雑
上記の内容では、証明書の発行方法をデモンストレーションしていません。興味があれば、OpenSSL Documentationを確認してください。証明書の発行プロセスでは、多くの概念を理解する必要があります。さらに、署名プロセスはKubernetesクラスタの外部で行われ、「宣言的」な設定方法では具体的に何が起こったかを理解することはできません。特に、証明書には多くの異なる暗号化アルゴリズムや設定などがあります。
そのため、デフォルトの方法を使用する場合、生成された証明書とキーをKubernetes Secretsに保存することしかできません。
証明書の更新/再署名が煩雑
証明書には有効期限があることは周知の事実です。証明書が期限切れになるか、失効する前に、新しい証明書を準備し、新しい証明書の有効期限が古いものよりも後である必要があります。
Kubernetes Secretsでの証明書管理には改善の余地があります:
-
有効期限の自動チェックがない:Kubernetesには、証明書が期限切れかどうかに関係なく、任意の証明書を保存できます。
-
無効なデータのチェックがない:Kubernetes Secretsに保存されたデータが破損しているか無効である場合、Kubernetesでは特別な処理が行われません。
セキュリティの欠如
Kubernetes Secretsに保存された証明書とキー情報は、base64エンコードされているだけです。そのため、データを取得した誰でもbase64デコードして実際のデータを取得できます。例えば:
➜ ~ kubectl get secrets moelove-tls -o jsonpath='{ .data.tls\.crt }' |base64 -d -----BEGIN CERTIFICATE----- MIICEzCCAbmgAwIBAgIUTxBL/ZBGi8BB9AU7bQZ/c+w6/TswCgYIKoZIzj0EAwIw TTELMAkGA1UEBhMCQ04xEDAOBgNVBAcTB0JlaWppbmcxFTATBgNVBAoTDE1vZUxv dmUgSU5GTzEVMBMGA1UEAxMMbW9lbG92ZS5pbmZvMB4XDTIyMTAxOTA3MTcwMFoX DTIzMTAxOTA3MTcwMFowTTELMAkGA1UEBhMCQ04xEDAOBgNVBAcTB0JlaWppbmcx FTATBgNVBAoTDE1vZUxvdmUgSU5GTzEVMBMGA1UEAxMMbW9lbG92ZS5pbmZvMFkw EwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUSpAc4a5Qt0CCUkhFHf7ZvoGQQyUOQLR Radm+IE+WVd98rZG94ZhoO2l6RZF62uO7qigeliBpcAFCqsYOG6uKqN3MHUwDgYD VR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNV HRMBAf8EAjAAMB0GA1UdDgQWBBQgKMbrpToy85W/G/a0fmc9C1BQmDAXBgNVHREE EDAOggxtb2Vsb3ZlLmluZm8wCgYIKoZIzj0EAwIDSAAwRQIgQ3O8IgCv2Td5HaWM pMKZdB/3WtC+xDeIWOlDz/hBw14CIQDLP4m0xZfJBoDg9pDhq8FtsyT7UeXUvVFA lKKQxVM9qA== -----END CERTIFICATE-----
上記のコマンドにより、証明書に関連する元のデータを取得できます。
一方、証明書とキーデータを更新したい場合、二次確認プロセスなしで直接更新できます。
これは、ほとんどのシナリオでのセキュリティポリシーと一致しません。
次に、cert-managerがこれらの問題をどのように解決するかを見てみましょう。
cert-managerがこれらの問題を解決する方法
自動発行
cert-managerはCRDを通じて開発および拡張され、Issuers
とClusterIssuers
リソースを追加および実装し、証明書のCA(認証局)を表します。
また、さまざまな組み込みタイプをサポートし、外部コンポーネントと簡単に統合できます。例えば:
-
SelfSigned:自己署名証明書
-
CA:発行のためのCAを提供
-
Vault:HashiCorp Vaultを使用して発行
-
Venafi:Venafiを使用して発行
-
External:署名のためにいくつかの外部コンポーネントを使用。例えば:
-
ACME(Automated Certificate Management Environment)
これらのコンポーネントを使用して、簡単に証明書を発行できます。以降の内容では、Vaultを例として具体的に紹介します。
自動更新/再署名
cert-managerでは、cmctl
を使用して手動で証明書を更新できます。同時に、cert-managerは証明書の有効期限と完全性を自動的にチェックします。
証明書が期限切れになったり、証明書データが不完全な場合、自動的に証明書の再発行をトリガーし、労力とメンテナンスコストを節約できます。
セキュリティ保証
cert-managerでは、CRD(CustomResourceDefinitions)を通じてsigners
リソースが追加され、証明書リクエストを確認し、Approved
またはDenied
することができます。Approveされた後にのみ有効になり、証明書が発行されます。これはより安全な方法です。
APISIX Ingress Controllerがcert-managerと統合する方法
インストール
Apache APISIX Ingress Controllerは、Ingress、カスタムリソース、およびGateway APIを通じてプロキシルールを設定できるKubernetes Ingress Controllerです。
次に、APISIX Ingress Controllerとcert-managerを統合して、エージェントのドメイン名にTLS証明書を追加し、セキュリティを向上させる方法をデモンストレーションします。
同時に、Vaultを使用して証明書を発行します。
APISIX Ingress Controllerのデプロイ
APISIX Ingress Controllerのデプロイは非常に簡単で、以下の手順を実行するだけです:
tao@moelove:~$ helm repo add apisix https://charts.apiseven.com tao@moelove:~$ helm repo add bitnami https://charts.bitnami.com/bitnami tao@moelove:~$ helm repo update tao@moelove:~$ helm install apisix apisix/apisix --set gateway.tls.enabled=true --set gateway.type=NodePort --set ingress-controller.enabled=true --set ingress-controller.config.apisix.serviceNamespace=apisix --namespace apisix --create-namespace --set ingress-controller.config.apisix.serviceName=apisix-admin --set ingress-controller.config.ingressPublishService="apisix/apisix-gateway" NAME: apisix LAST DEPLOYED: Wed Oct 19 21:33:37 2022 NAMESPACE: apisix STATUS: deployed REVISION: 1 TEST SUITE: None NOTES: 1. Get the application URL by running these commands: export NODE_PORT=$(kubectl get --namespace apisix -o jsonpath="{.spec.ports[0].nodePort}" services apisix-gateway) export NODE_IP=$(kubectl get nodes --namespace apisix -o jsonpath="{.items[0].status.addresses[0].address}") echo http://$NODE_IP:$NODE_PORT
すべてのPodが実行状態になると、デプロイが成功します。
tao@moelove:~$ kubectl -n apisix get pods NAME READY STATUS RESTARTS AGE apisix-777c9fdd67-rf8zs 1/1 Running 0 6m48s apisix-etcd-0 1/1 Running 0 6m48s apisix-etcd-1 1/1 Running 0 6m48s apisix-etcd-2 1/1 Running 0 6m48s apisix-ingress-controller-568544b554-k7nd4 1/1 Running 0 6m48s
Vaultのデプロイ
Vaultをデプロイする際も、Helmを使用できます。ここでは、--set "server.dev.enabled=true"
設定項目を追加して、デプロイ後に追加の操作なしで直接使用できるようにしました。(この設定は本番環境では使用しないでください。)
tao@moelove:~$ helm repo add hashicorp https://helm.releases.hashicorp.com tao@moelove:~$ helm install vault hashicorp/vault --set "injector.enabled=false" --set "server.dev.enabled=true" NAME: vault LAST DEPLOYED: Wed Oct 19 21:53:50 2022 NAMESPACE: default STATUS: deployed REVISION: 1 NOTES: Thank you for installing HashiCorp Vault! Now that you have deployed Vault, you should look over the docs on using Vault with Kubernetes available here: https://www.vaultproject.io/docs/ Your release is named "vault". To learn more about the release, try the following: $ helm status vault $ helm get manifest vault
デプロイが完了すると、PodがRunning状態になり、デプロイが完了したことが示されます。
tao@moelove:~$ kubectl get pods NAME READY STATUS RESTARTS AGE vault-0 1/1 Running 0 29s tao@moelove:~$ kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 84m vault ClusterIP 10.96.190.88 <none> 8200/TCP,8201/TCP 4m14s vault-internal ClusterIP None <none> 8200/TCP,8201/TCP 4m14s
次に、Vaultに入り、pki機能を有効にして、対応するポリシーを設定します。
tao@moelove:~$ kubectl exec -it vault-0 -- sh / $ vault secrets enable pki Success! Enabled the pki secrets engine at: pki/ / $ vault write pki/root/generate/internal common_name=moelove.info ttl=8760h Key Value --- ----- certificate -----BEGIN CERTIFICATE----- MIIDODCCAiCgAwIBAgIUds5uMJV9rOkwFEt6Xof5T2SVFccwDQYJKoZIhvcNAQEL ... VM4DRVgDkqY9JdHU -----END CERTIFICATE----- expiration 1668983612 issuer_id 8df13015-7c70-df9a-7bb7-9b3b4afe7f82 issuer_name n/a issuing_ca -----BEGIN CERTIFICATE----- MIIDODCCAiCgAwIBAgIUds5uMJV9rOkwFEt6Xof5T2SVFccwDQYJKoZIhvcNAQEL ... VM4DRVgDkqY9JdHU -----END CERTIFICATE----- key_id c9fcfcb0-3548-a9a7-e706-30510592c797 key_name n/a serial_number 76:ce:6e:30:95:7d:ac:e9:30:14:4b:7a:5e:87:f9:4f:64:95:15:c7 / $ / $ vault write pki/config/urls issuing_certificates="http://vault.default:8200/v1/pki/ca" crl_distribution_points="http://vault.default:8200/v1/pki/crl" Success! Data written to: pki/config/urls / $ vault write pki/roles/moelove-dot-info allowed_domains=moelove.info allow_subdomains=true max_ttl=72h Success! Data written to: pki/roles/moelove-dot-info / $ / $ vault policy write pki - <<EOF > path "pki*" { capabilities = ["read", "list"] } > path "pki/sign/moelove-dot-info" { capabilities = ["create", "update"] } > path "pki/issue/moelove-dot-info" { capabilities = ["create"] } > EOF Success! Uploaded policy: pki
次に、Kubernetes認証を設定します:
/ $ vault auth enable kubernetes Success! Enabled kubernetes auth method at: kubernetes/ / $ vault write auth/kubernetes/config kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443" Success! Data written to: auth/kubernetes/config / $ vault write auth/kubernetes/role/issuer bound_service_account_names=issuer bound_service_account_namespaces=default policies=pki ttl=20m Success! Data written to: auth/kubernetes/role/issuer
上記の操作が完了したら、次にcert-managerをデプロイします。
cert-managerのデプロイ
これで、Helmを使用してcert-managerをインストールできます。インストールプロセスは比較的簡単です。
tao@moelove:~$ helm repo add jetstack https://charts.jetstack.io tao@moelove:~$ helm repo update jetstack Hang tight while we grab the latest from your chart repositories... ...Successfully got an update from the "jetstack" chart repository Update Complete. ⎈Happy Helming!⎈ tao@moelove:~$ kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.10.0/cert-manager.crds.yaml customresourcedefinition.apiextensions.k8s.io/clusterissuers.cert-manager.io created customresourcedefinition.apiextensions.k8s.io/challenges.acme.cert-manager.io created customresourcedefinition.apiextensions.k8s.io/certificaterequests.cert-manager.io created customresourcedefinition.apiextensions.k8s.io/issuers.cert-manager.io created customresourcedefinition.apiextensions.k8s.io/certificates.cert-manager.io created customresourcedefinition.apiextensions.k8s.io/orders.acme.cert-manager.io created tao@moelove:~$ helm install \ > cert-manager jetstack/cert-manager \ > --namespace cert-manager \ > --create-namespace \ > --version v1.10.0 xNAME: cert-manager LAST DEPLOYED: Wed Oct 19 22:51:06 2022 NAMESPACE: cert-manager STATUS: deployed REVISION: 1 TEST SUITE: None NOTES: cert-manager v1.10.0 has been deployed successfully! In order to begin issuing certificates, you will need to set up a ClusterIssuer or Issuer resource (for example, by creating a 'letsencrypt-staging' issuer). More information on the different types of issuers and how to configure them can be found in our documentation: https://cert-manager.io/docs/configuration/ For information on how to configure cert-manager to automatically provision Certificates for Ingress resources, take a look at the `ingress-shim` documentation: https://cert-manager.io/docs/usage/ingress/
Podのステータスを確認します:
tao@moelove:~$ kubectl -n cert-manager get pods NAME READY STATUS RESTARTS AGE cert-manager-69b456d85c-znpq4 1/1 Running 0 117s cert-manager-cainjector-5f44d58c4b-wcd27 1/1 Running 0 117s cert-manager-webhook-566bd88f7b-7rptf 1/1 Running 0 117s
その後、設定と検証を開始できます。
設定と検証方法
証明書の設定と発行
tao@moelove:~$ kubectl create serviceaccount issuer serviceaccount/issuer created tao@moelove:~$ kubectl get secret NAME TYPE DATA AGE sh.helm.release.v1.vault.v1 helm.sh/release.v1 1 36m tao@moelove:~$ vim issuer-secret.yaml tao@moelove:~$ cat issuer-secret.yaml apiVersion: v1 kind: Secret metadata: name: issuer-token-moelove annotations: kubernetes.io/service-account.name: issuer type: kubernetes.io/service-account-token tao@moelove:~$ kubectl apply -f issuer-secret.yaml secret/issuer-token-moelove created tao@moelove:~$ kubectl get sa,secret NAME SECRETS AGE serviceaccount/default 0 118m serviceaccount/issuer 0 2m11s serviceaccount/vault 0 38m NAME TYPE DATA AGE secret/issuer-token-moelove kubernetes.io/service-account-token 3 35s secret/sh.helm.release.v1.vault.v1 helm.sh/release.v1 1 38m
Issuerの作成
この設定により、Vaultが証明書認証局として使用され、Vaultで設定されたロールとシークレットを参照して自動発行が行われます。
tao@moelove:~$ cat vault-issuer.yaml apiVersion: cert-manager.io/v1 kind: Issuer metadata: name: vault-issuer namespace: default spec: vault: server: http://vault.default path: pki/sign/moelove-dot-info auth: kubernetes: mountPath: /v1/auth/kubernetes role: moelove-dot-info secretRef: name: issuer-token-moelove key: token tao@moelove:~$ kubectl apply -f vault-issuer.yaml issuer.cert-manager.io/vault-issuer created
証明書の作成
この設定により、証明書が自動的に発行され、後続の使用中にmoelove-info-tls
を参照できます。
tao@moelove:~$ cat moelove-dot-info-cert.yaml apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: moelove-info namespace: default spec: secretName: moelove-info-tls issuerRef: name: vault-issuer commonName: www.moelove.info dnsNames: - www.moelove.info tao@moelove:~$ kubectl apply -f moelove-dot-info-cert.yaml certificate.cert-manager.io/moelove-info created
検証
次に、HTTPBINサービスをプロキシして検証します。
まず、HTTPBINアプリケーションを作成し、対応するServiceを作成します。
kubectl run httpbin --image kennethreitz/httpbin kubectl expose pod httpbin --port=80
次に、以下のリソースを定義してプロキシし、証明書を参照します:
# ApisixTlsオブジェクトを定義 apiVersion: apisix.apache.org/v2 kind: ApisixTls metadata: name: moelove spec: hosts: - moelove.info secret: name: moelove-info-tls --- # バックエンドにアクセスするためのルートを定義 apiVersion: apisix.apache.org/v2 kind: ApisixRoute metadata: name: moelove spec: http: - name: httpbin match: paths: - /* hosts: - moelove.info backends: - serviceName: httpbin servicePort: 80
これらのリソースをクラスタに適用します。その後、kubectl port-forward
を使用してAPISIXのポート443をローカルに転送し、テストアクセスを実行します:
$ ~ kubectl port-forward -n ingress-apisix svc/apisix-gateway 8443:443 & $ ~ curl -sk https://moelove.info:8443/ip --resolve 'moelove.info:8443:127.0.0.1' { "origin": "172.17.18.1" }
moelove.info
ドメイン名にHTTPS証明書が正しく設定され、APISIX Ingress Controllerを通じてプロキシが設定されていることがわかります。
結論
Kubernetesには、証明書を保存するための2つのデフォルトの方法があります:ConfigMapとSecretです。しかし、証明書の発行と更新/再署名は煩雑で、セキュリティも改善が必要です。
cert-managerはこれらの問題を解決し、Kubernetesエコシステム内の証明書発行/管理の事実上の標準となりつつあります。さらに、Vaultなどのツールと統合でき、より安全です。
Apache APISIX Ingress Controllerは、ユーザーフレンドリーなIngress Controllerを作成することを目指しており、早期に完全なcert-manager統合機能を追加しました。ユーザーは、Apache APISIX Ingress ControllerでVaultを使用してcert-managerを通じて証明書を発行し、アプリケーションにHTTPSプロキシを提供できます。