cert-manager で 自己署名証明書
はじめに
今回は cert-manager についてです。
cert-manager + Let's Encrypt を使った Ingress の証明書の払い出しを行おうと思ったのですが、Let's Encrypt にはチャレンジなる検証があり、インターネット公開されていないと色々と難しそう・・・。
というわけで、今回は cert-manager を使って自己署名証明書を作って Ingress に証明書を適用しようと思います。
環境情報
- Kubernetes:v1.20.6
- nginx-ingress:v0.47(github のコードは helm-chart-3.34.0)
- helm:v3.5.4
- cert-manager:v1.4.0
Ingress お試し用に Jenkins をデプロイします。PV はいつもどおり Ceph で。
※Web 画面をちょっと見たいだけなので、他の Web アプリでも代替できます
- Jenkins:2.289.1
- Ceph
https://qiita.com/t_ume/items/4ac37f746bf07146d5f9
※Node の前段に nginx を BareMetal LB として用意しています。
設定ファイルは以下を適用しています。
kubeadm でのクラスタ構築は以下を参考に。
user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
error_log /var/log/nginx/error.log warn;
events {
worker_connections 1024;
}
stream {
upstream kubernetes {
server 192.168.10.61:6443 max_fails=2 fail_timeout=30s;
server 192.168.10.62:6443 max_fails=2 fail_timeout=30s;
server 192.168.10.63:6443 max_fails=2 fail_timeout=30s;
}
server {
listen 6443;
proxy_pass kubernetes;
}
upstream ingress80 {
server 192.168.10.71:32080 max_fails=2 fail_timeout=30s;
server 192.168.10.72:32080 max_fails=2 fail_timeout=30s;
}
server {
listen 80;
proxy_pass ingress80;
}
upstream ingress443 {
server 192.168.10.71:32443 max_fails=2 fail_timeout=30s;
server 192.168.10.72:32443 max_fails=2 fail_timeout=30s;
}
server {
listen 443;
proxy_pass ingress443;
}
}
nginx-ingress
Ingress を導入していきます。既に何がしかの IngressController があればスキップでお願いします。helm でインストールしていきます。
# github からコード取得
$ git clone https://github.com/kubernetes/ingress-nginx.git -b helm-chart-3.34.0 --depth 1
# 作業ディレクトリに移動
$ cd ingress-nginx/charts/ingress-nginx/
前項の nginx の設定で http:32080、https:32443 でリバースプロキシしているので、値を values.yaml
に反映させます。
@@ -433,7 +433,7 @@ controller:
http: http
https: https
- type: LoadBalancer
+ type: NodePort
# type: NodePort
# nodePorts:
@@ -442,8 +442,8 @@ controller:
# tcp:
# 8080: 32808
nodePorts:
- http: ""
- https: ""
+ http: 32080
+ https: 32443
tcp: {}
udp: {}
それでは早速デプロイします。
$ kubectl create ns ingress-nginx
namespace/nginx-ingress created
$ helm install ingress-nginx . -n ingress-nginx
NAME: ingress-nginx
LAST DEPLOYED: Mon Jul 12 15:20:42 2021
NAMESPACE: ingress-nginx
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
・・・
$ kubectl get po -n ingress-nginx
NAME READY STATUS RESTARTS AGE
ingress-nginx-controller-d9458694b-cjmhw 1/1 Running 0 29s
現在のバージョンで ingress を設定中に以下エラーが起きたので対処します。
Error from server (InternalError): error when creating "ingress.yaml": Internal error occurred: failed calling webhook "validate.nginx.ingress.kubernetes.io": an error on the server ("") has prevented the request from succeeding
$ kubectl get -A ValidatingWebhookConfiguration
NAME WEBHOOKS AGE
ingress-nginx-admission 1 119s
$ kubectl delete ValidatingWebhookConfiguration ingress-nginx-admission
validatingwebhookconfiguration.admissionregistration.k8s.io "ingress-nginx-admission" deleted
cert-manager
Ingress の準備ができたので、本題の cert-manager をデプロイします。
こちらも helm からインストールします。
# 作業ディレクトリを移動(ここでは $HOME に)
$ cd ~
# github からコード取得
$ git clone https://github.com/jetstack/cert-manager.git -b v1.4.0 --depth 1
$ cd cert-manager/deploy/charts/cert-manager/
# バージョン指定するために、Chart.tempalte.yaml をコピーして編集
$ cp -p Chart{.template,}.yaml
cert-manager のバージョンを指定するため、appVersion を編集します。
@@ -2,7 +2,7 @@
name: cert-manager
# The version and appVersion fields are set automatically by the release tool
version: v0.1.0
-appVersion: v0.1.0
+appVersion: v1.4.0
description: A Helm chart for cert-manager
home: https://github.com/jetstack/cert-manager
icon: https://raw.githubusercontent.com/jetstack/cert-manager/master/logo/logo.png
準備ができたのでデプロイします。
$ kubectl create ns cert-manager
namespace/cert-manager created
# cert-manager で利用する CRD をデプロイする
# URL で指定している「v1.4.0」の箇所はデプロイする cert-manager のバージョンに合わせる
$ kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.4.0/cert-manager.crds.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
$ kubectl api-resources | grep -e NAME -e cert-manager
NAME SHORTNAMES APIVERSION NAMESPACED KIND
challenges acme.cert-manager.io/v1 true Challenge
orders acme.cert-manager.io/v1 true Order
certificaterequests cr,crs cert-manager.io/v1 true CertificateRequest
certificates cert,certs cert-manager.io/v1 true Certificate
clusterissuers cert-manager.io/v1 false ClusterIssuer
issuers cert-manager.io/v1 true Issuer
# Deploy cert-manager
$ helm install cert-manager . -n cert-manager
NAME: cert-manager
LAST DEPLOYED: Mon Jul 12 15:50:42 2021
NAMESPACE: cert-manager
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
cert-manager has been deployed successfully!
・・・
$ kubectl get po -n cert-manager
NAME READY STATUS RESTARTS AGE
cert-manager-59c55b4dbd-mpc6f 1/1 Running 0 9m50s
cert-manager-cainjector-5bccf4b7-tdtnm 1/1 Running 0 9m49s
cert-manager-webhook-6fd8ccc59d-hfsv6 1/1 Running 0 9m50s
自己署名証明書発行
cert-manager の準備ができたので自己署名証明書を発行していきます。
今回作成する認証局・証明書のイメージは以下の通りです。
まずは CA(認証局) から作成していきます。
Issuer(発行者)を作成し、ルート証明書を発行します。後続の 自己署名証明書発行の際にここで作成するルート証明書を利用します。
※Certificate
の duration
(有効期限)で 50 年を指定しています(h(時間)指定)。
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: selfsigned-issuer
spec:
selfSigned: {}
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: selfsigned-ca
namespace: sandbox
spec:
isCA: true
commonName: selfsigned-ca
duration: 438000h
secretName: selfsigned-ca-cert
privateKey:
algorithm: RSA
size: 2048
issuerRef:
name: selfsigned-issuer
kind: ClusterIssuer
group: cert-manager.io
---
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: ca-issuer
namespace: sandbox
spec:
ca:
secretName: selfsigned-ca
作業用の namespace を作成して、CA をデプロイします。
# 作業用 namespace の作成
$ kubectl create ns sandbox
namespace/sandbox created
$ kubectl apply -f ca.yaml
clusterissuer.cert-manager.io/selfsigned-issuer created
certificate.cert-manager.io/selfsigned-ca created
issuer.cert-manager.io/ca-issuer created
$ kubectl describe clusterissuers selfsigned-issuer
Name: selfsigned-issuer
Namespace:
Labels: <none>
Annotations: <none>
API Version: cert-manager.io/v1
Kind: ClusterIssuer
・・・
Spec:
Self Signed:
Status:
Conditions:
Last Transition Time: 2021-07-14T17:13:12Z
Observed Generation: 1
Reason: IsReady
Status: True
Type: Ready
Events: <none>
# ルート証明書の確認
# ※有効期限が発行日時から 50 年後になっている
$ kubectl describe certificate selfsigned-ca -n sandbox
Name: selfsigned-ca
Namespace: sandbox
Labels: <none>
Annotations: <none>
API Version: cert-manager.io/v1
Kind: Certificate
・・・
Spec:
Common Name: selfsigned-ca
Duration: 438000h0m0s
Is CA: true
Issuer Ref:
Group: cert-manager.io
Kind: ClusterIssuer
Name: selfsigned-issuer
Private Key:
Algorithm: RSA
Size: 2048
Secret Name: selfsigned-ca-cert
Status:
Conditions:
Last Transition Time: 2021-07-14T17:13:12Z
Message: Certificate is up to date and has not expired
Observed Generation: 1
Reason: Ready
Status: True
Type: Ready
Not After: 2071-07-02T17:06:27Z ★ 50 年後
Not Before: 2021-07-14T17:06:27Z
Renewal Time: 2054-11-05T09:06:27Z
Events: <none>
$ kubectl describe issuers ca-issuer -n sandbox
Name: ca-issuer
Namespace: sandbox
Labels: <none>
Annotations: <none>
API Version: cert-manager.io/v1
Kind: Issuer
・・・
Spec:
Ca:
Secret Name: selfsigned-ca-cert
Status:
Conditions:
Last Transition Time: 2021-07-14T17:13:12Z
Message: Signing CA verified
Observed Generation: 1
Reason: KeyPairVerified
Status: True
Type: Ready
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal KeyPairVerified 103s (x2 over 103s) cert-manager Signing CA verified
# Manifest ファイルの「secretName」で指定した名前で、
# Secret にルート証明書と秘密鍵が格納されている
$ kubectl describe secrets selfsigned-ca-cert -n sandbox
Name: selfsigned-ca-cert
Namespace: sandbox
Labels: <none>
Annotations: cert-manager.io/alt-names:
cert-manager.io/certificate-name: selfsigned-ca
cert-manager.io/common-name: selfsigned-ca
cert-manager.io/ip-sans:
cert-manager.io/issuer-group: cert-manager.io
cert-manager.io/issuer-kind: ClusterIssuer
cert-manager.io/issuer-name: selfsigned-issuer
cert-manager.io/uri-sans:
Type: kubernetes.io/tls
Data
====
ca.crt: 1099 bytes
tls.crt: 1099 bytes
tls.key: 1675 bytes
CA の準備ができたので早速 Jenkins 用の 自己署名証明書を発行していきます。
CA 作成時同様にマニフェストファイルを作成して、証明書を発行します。
※適用する URL はdnsNames(SAN)で指定しています。
参考:https://cert-manager.io/docs/reference/api-docs/#cert-manager.io/v1.Certificate
※subject
は適宜変更を。
※duration
を 1 年で指定しています
※issuerRef
で先程作成した CA の Issuer を指定する
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: jenkins
namespace: sandbox
spec:
subject:
organizations:
- MyOrg
countries:
- Japan
organizationalUnits:
- MyUnit
localities:
- Sapporo
provinces:
- Hokkaido
commonName: jenkins-cn
duration: 8760h
dnsNames:
- www.jenkins.internal
secretName: jenkins-certificate
issuerRef:
name: ca-issuer
kind: Issuer
group: cert-manager.io
privateKey:
algorithm: RSA
size: 2048
デプロイして 自己署名証明書を発行します。
$ kubectl apply -f jenkins.yaml -n sandbox
certificate.cert-manager.io/jenkins created
# 証明書の確認
$ kubectl describe certificate jenkins -n sandbox
Name: jenkins
Namespace: sandbox
Labels: <none>
Annotations: <none>
API Version: cert-manager.io/v1
Kind: Certificate
・・・
Spec:
Common Name: jenkins-cn
Dns Names:
www.jenkins.internal
Duration: 8760h0m0s
Issuer Ref:
Group: cert-manager.io
Kind: Issuer
Name: ca-issuer
Private Key:
Algorithm: RSA
Size: 2048
Secret Name: jenkins-certificate
Subject:
Countries:
Japan
Localities:
Sapporo
Organizational Units:
MyUnit
Organizations:
MyOrg
Provinces:
Hokkaido
Status:
Conditions:
Last Transition Time: 2021-07-14T17:25:35Z
Message: Certificate is up to date and has not expired
Observed Generation: 1
Reason: Ready
Status: True
Type: Ready
Not After: 2022-07-14T17:25:35Z
Not Before: 2021-07-14T17:25:35Z
Renewal Time: 2022-03-15T01:25:35Z
Revision: 1
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Issuing 35s cert-manager Issuing certificate as Secret does not exist
Normal Generated 35s cert-manager Stored new private key in temporary Secret resource "jenkins-j2vlp"
Normal Requested 35s cert-manager Created new CertificateRequest resource "jenkins-ln7qf"
Normal Issuing 35s cert-manager The certificate has been successfully issued
# 作成された Secret (証明書と鍵)を確認
$ kubectl describe secrets jenkins-certificate
Name: jenkins-certificate
Namespace: sandbox
Labels: <none>
Annotations: cert-manager.io/alt-names: www.jenkins.internal
cert-manager.io/certificate-name: jenkins
cert-manager.io/common-name: jenkins-cn
cert-manager.io/ip-sans:
cert-manager.io/issuer-group: cert-manager.io
cert-manager.io/issuer-kind: Issuer
cert-manager.io/issuer-name: ca-issuer
cert-manager.io/uri-sans:
Type: kubernetes.io/tls
Data
====
tls.crt: 1253 bytes
tls.key: 1679 bytes
ca.crt: 1099 bytes
Deploy Jenkins
証明書が発行できたので実際にコンテナを起動、Ingress に設定していきます。
# 作業ディレクトリを移動
$ cd ~
# github からコード取得
$ git clone https://github.com/jenkinsci/helm-charts.git -b jenkins-3.5.2 --depth 1
$ cd helm-charts/charts/jenkins/
values.yaml
を以下の様に編集します。
-
tag
でイメージを最新に変更 -
Ingress
を有効化 -
annotations
で IngressController を指定 -
hostName
で Ingress で受け取る FQDN を指定 -
secretName
で証明書の Secret を指定 -
storageClass
で PV のストレージクラスを指定
@@ -19,7 +19,7 @@ controller:
# Used for label app.kubernetes.io/component
componentName: "jenkins-controller"
image: "jenkins/jenkins"
- tag: "2.289.1-jdk11"
+ tag: "2.302"
imagePullPolicy: "Always"
imagePullSecretName:
# Optionally configure lifetime for controller-container
@@ -383,7 +383,7 @@ controller:
updateStrategy: {}
ingress:
- enabled: false
+ enabled: true
# Override for the default paths that map requests to the backend
paths: []
# - backend:
@@ -398,7 +398,9 @@ controller:
# For Kubernetes v1.19+, use 'networking.k8s.io/v1'
apiVersion: "extensions/v1beta1"
labels: {}
- annotations: {}
+ annotations:
+ kubernetes.io/ingress.class: nginx
+ ingressClassName: nginx
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
# For Kubernetes >= 1.18 you should specify the ingress-controller via the field ingressClassName
@@ -407,11 +409,11 @@ controller:
# Set this path to jenkinsUriPrefix above or use annotations to rewrite path
# path: "/jenkins"
# configures the hostname e.g. jenkins.example.com
- hostName:
+ hostName: www.jenkins.internal
tls:
- # - secretName: jenkins.cluster.local
- # hosts:
- # - jenkins.cluster.local
+ - secretName: jenkins-certificate
+ hosts:
+ - www.jenkins.internal
# often you want to have your controller all locked down and private
# but you still want to get webhooks from your SCM
@@ -724,7 +726,7 @@ persistence:
## set, choosing the default provisioner. (gp2 on AWS, standard on
## GKE, AWS & OpenStack)
##
- storageClass:
+ storageClass: rook-ceph-block
annotations: {}
accessMode: "ReadWriteOnce"
size: "8Gi"
Jenkins をデプロイします。
$ helm install jenkins . -n sandbox
$ kubectl get po -n sandbox
NAME READY STATUS RESTARTS AGE
jenkins-0 2/2 Running 0 82s
$ kubectl describe ingress jenkins -n sandbox
Name: jenkins
Namespace: sandbox
Address: 10.97.19.11
Default backend: default-http-backend:80 (<error: endpoints "default-http-backend" not found>)
TLS:
jenkins-certificate terminates www.jenkins.internal
Rules:
Host Path Backends
---- ---- --------
www.jenkins.internal
jenkins:8080 (172.16.86.144:8080)
Annotations: ingressClassName: nginx
kubernetes.io/ingress.class: nginx
meta.helm.sh/release-name: jenkins
meta.helm.sh/release-namespace: sandbox
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Sync 106s (x2 over 114s) nginx-ingress-controller Scheduled for sync
アクセス
実際にアクセスして確認してみます。
今回は DNS を利用していないので、hosts ファイルで名前解決します。
URL に対して LB のアドレスを指定して登録しておきます。
※下記のファイル名は「:」「¥」が全角になっているのでコピペ不可
・・・
192.168.10.51 www.jenkins.internal
作成したルート証明書をダウンロードしてクライアントにインストールします。
まずは作成したルート証明書をエクスポートします。
$ kubectl get secrets jenkins-certificate -o jsonpath='{.data.ca\.crt}' | base64 -d > ca.crt
$ cat ca.crt
-----BEGIN CERTIFICATE-----
MIIC/zCCAeegAwIBAgIRAI0d8EpGZX5+1eK7Wo6xOXIwDQYJKoZIhvcNAQELBQAw
・・・
-----END CERTIFICATE-----
作成できた証明書ファイルを SCP などでクライアントまでダウンロードし、証明書をインストールします。以下は Windows での手順です。参考までに。
- 証明書ファイルをダブルクリック
- 「全般」タブの「証明書のインストール」をクリック
- 「現在のユーザー」を選択して「次へ」
- 「証明書をすべて次のストアに配置する」を選択し、「参照」をクリック
- 表示されたダイアログで「信頼されたルート証明機関」を選択し「OK」
- 「次へ」⇒「完了」⇒「セキュリティ警告」が表示されるので「はい」⇒「OK」
-
certmgr.msc
を実行すると証明書マネージャーが起動するので「信頼されたルート証明機関」に「selfsigned-ca」が登録されていることを確認
ルート証明書がインストールできたので、指定した URL(ここではhttps://www.jenkins.internal/
)にアクセスすると証明書エラーが表示されずに https でアクセスできることが確認できるかと思います。
まとめ
cert-manager を使うと自己署名証明書(オレオレ証明書)も簡単に作成することができました。Ingress のマニフェストに Annotation を追加で付与することで、Ingress 登録時に証明書が自動的に作成される、ってこともできるそうです。
Securing Ingress Resources
https://cert-manager.io/docs/usage/ingress/
次はクラウドを使って Let's Encrypt にも調整したいですねー。
参考
Ingress Nginx
https://github.com/kubernetes/ingress-nginx
cert-manager
https://github.com/jetstack/cert-manager
cert-manager API-Refference
https://cert-manager.io/docs/reference/api-docs/#cert-manager.io%2Fv1
Discussion