Let's Encryptの証明書生成およびSecretの作成をcert-managerで行い、Spring Bootアプリに設定するメモです。
Azure DNSでDNS Challengeを使います。
目次
- cert-managerのインストール
- Azure CLIでService Principalを作成
- Let's EncryptのIssuer作成
- Certificateの作成
- 発行したTLS証明書をSpring Bootアプリに設定
cert-managerのインストール
https://cert-manager.io/docs/installation/kubernetes の通りインストールします。
kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v0.16.0/cert-manager.yaml
Podを確認。
$ kubectl get pods -n cert-manager
NAME READY STATUS RESTARTS AGE
cert-manager-cainjector-6d444776dd-f7wxx 1/1 Running 0 2m58s
cert-manager-fbd8f9986-wbsmw 1/1 Running 0 2m58s
cert-manager-webhook-86689cb4c9-4tnwm 1/1 Running 0 2m58s
Azure CLIでService Principalを作成
https://cert-manager.io/docs/configuration/acme/dns01/azuredns の通り作成します。
Resource Group maki-lolとDNS Zone maki.lol は用意済み。

az login
でブラウザ経由でログイン。
AZURE_CERT_MANAGER_NEW_SP_NAME=cert-manager
AZURE_DNS_ZONE_RESOURCE_GROUP=maki-lol
AZURE_DNS_ZONE=maki.lol
DNS_SP=$(az ad sp create-for-rbac --name ${AZURE_CERT_MANAGER_NEW_SP_NAME})
AZURE_CERT_MANAGER_SP_APP_ID=$(echo ${DNS_SP} | jq -r '.appId')
AZURE_CERT_MANAGER_SP_PASSWORD=$(echo ${DNS_SP} | jq -r '.password')
AZURE_TENANT_ID=$(echo ${DNS_SP} | jq -r '.tenant')
AZURE_SUBSCRIPTION_ID=$(az account show | jq -r '.id')
Service PrincipalにはDNS Zone Contributor Roleのみ付けます。
az role assignment delete --assignee ${AZURE_CERT_MANAGER_SP_APP_ID} --role Contributor
DNS_ID=$(az network dns zone show --name ${AZURE_DNS_ZONE} --resource-group ${AZURE_DNS_ZONE_RESOURCE_GROUP} --query "id" --output tsv)
az role assignment create --assignee ${AZURE_CERT_MANAGER_SP_APP_ID} --role "DNS Zone Contributor" --scope ${DNS_ID}
Let's EncryptのIssuer作成
Let's EncryptのIssuerリソースを作ります。NamespaceレベルのIssuerとClusterレベルのClusterIssuerがありますが、
今回はClusterIssuerリソースを作成します。
Service PrincipalのClient Secret保存するSecretを作成します。
cat <<EOF > azuredns-config.yml
kind: Secret
apiVersion: v1
metadata:
name: azuredns-config
namespace: cert-manager
stringData:
client-secret: ${AZURE_CERT_MANAGER_SP_PASSWORD}
EOF
Azure DNSを使ったClusterIssuerを作成します。
cat <<EOF > letsencrypt-cluster-issuer.yml
apiVersion: cert-manager.io/v1alpha2
kind: ClusterIssuer
metadata:
name: letsencrypt
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: makingx+cert-manager@gmail.com
privateKeySecretRef:
name: letsencrypt
solvers:
- dns01:
azuredns:
clientID: ${AZURE_CERT_MANAGER_SP_APP_ID}
clientSecretSecretRef:
name: azuredns-config
key: client-secret
subscriptionID: ${AZURE_SUBSCRIPTION_ID}
tenantID: ${AZURE_TENANT_ID}
resourceGroupName: ${AZURE_DNS_ZONE_RESOURCE_GROUP}
hostedZoneName: ${AZURE_DNS_ZONE}
environment: AzurePublicCloud
EOF
kubectl applyでこれらを適用します。
kubectl apply -f azuredns-config.yml -f letsencrypt-cluster-issuer.yml
ClusterIssuerリソースが作成され、READYであることを確認します。
$ kubectl get clusterissuer
NAME READY AGE
letsencrypt True 15s
Certificateの作成
次のこのIssuerからCertificateリソースを作成します。
*.demo.maki.lolとdemo.maki.lolに対する証明書を発行します。
cat <<EOF > demo-maki-lol.yml
apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
name: demo-maki-lol
namespace: default
spec:
secretName: demo-maki-lol-tls
issuerRef:
name: letsencrypt
kind: ClusterIssuer
dnsNames:
- '*.demo.maki.lol'
- demo.maki.lol
EOF
kubectl applyでこれらを適用します。
kubectl apply -f demo-maki-lol.yml
2~3分経つとCertificateリソースがREADYになります。
$ kubectl get certificate -n default
NAME READY SECRET AGE
demo-maki-lol True demo-maki-lol-tls 2m29s
TLSのSecretも作成されます。
$ kubectl get secret -n default
NAME TYPE DATA AGE
...
demo-maki-lol-tls kubernetes.io/tls 2 7m6s
...
TLS証明書の中身を見てみます。
$ kubectl get secret demo-maki-lol-tls -o go-template='{{index .data "tls.crt" | base64decode}}' | openssl x509 -noout -text
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
03:1f:52:6c:2a:21:51:fa:07:37:2c:37:02:90:34:24:a0:a2
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=US, O=Let's Encrypt, CN=Let's Encrypt Authority X3
Validity
Not Before: Jul 26 03:02:03 2020 GMT
Not After : Oct 24 03:02:03 2020 GMT
Subject: CN=*.demo.maki.lol
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:ab:c8:4f:a6:cf:92:12:1e:10:2e:6c:50:20:8d:
97:3e:ae:45:e5:37:5d:20:eb:36:88:36:53:01:ff:
cb:98:61:da:02:37:a2:70:b6:c6:3c:16:42:8d:02:
16:71:94:77:70:b3:6d:07:c0:8c:37:29:ad:e9:51:
37:eb:5c:4b:9d:33:fd:d1:e5:7c:f1:8f:35:5d:18:
b0:23:45:ee:37:ab:58:8f:ca:f9:41:e0:f4:d6:c6:
d1:00:f4:a7:92:13:3e:f0:8c:0b:c8:2e:15:33:37:
04:7c:26:e8:44:b6:9f:10:4f:c2:57:a0:fb:05:a3:
c7:a7:94:7b:2b:78:fc:15:71:08:91:1e:04:ce:7c:
46:82:82:09:94:e9:30:98:62:7b:d3:d7:67:d1:4a:
5b:89:b2:81:bf:70:44:db:8c:4c:e0:d3:41:38:1b:
8f:24:c9:b2:54:18:2d:b3:a6:64:52:45:7e:42:d3:
d6:7f:35:3f:eb:04:4a:dd:0b:62:c5:ba:d0:9d:8a:
ff:3c:6c:a2:7e:3a:2f:9b:c1:a6:92:95:67:64:8e:
d1:46:da:2d:19:ac:d9:16:dc:4f:fb:c7:6a:a6:d7:
4c:3f:7f:0f:cc:ed:6a:55:77:e8:29:95:8b:3e:83:
90:e0:77:0f:69:53:4b:60:87:09:e9:5e:34:7b:bd:
f1:0d
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:
21:E0:6A:FF:D0:43:73:09:CF:64:40:E0:7F:26:1B:C8:7A:BB:65:27
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:*.demo.maki.lol, DNS:demo.maki.lol
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
1.3.6.1.4.1.11129.2.4.2:
......v.^.s..V...6H}.I.2z.........u..qEX...s.H.......G0E.!...
.F.[>.K.(.....)[...9=.o....{ . =v..f...t>....i!.L..|..sV....w...u......... N.f.+..% gk..p..IS-...^...s.H.......F0D. o..T.~%..|U,E$.....J=$l.......... *....h.8"i.j/.U......s.i.."?..<.
Signature Algorithm: sha256WithRSAEncryption
5c:a0:d7:58:4d:ff:63:bc:6d:d7:71:1f:49:6b:e7:ae:e9:8a:
00:e0:dc:b3:77:e1:7c:93:46:f7:29:fd:14:04:45:99:f9:d0:
0b:de:e0:8d:43:65:db:76:6e:22:ef:e6:ea:71:5f:e0:44:1d:
0d:b4:27:f1:8e:84:5b:24:e7:e3:9c:78:46:2e:6a:de:59:a6:
72:fc:02:2b:81:05:d6:eb:a7:08:69:62:81:91:dd:f3:79:92:
35:3a:51:0f:70:8e:80:53:0c:32:f1:24:a9:b7:9d:d1:e0:c4:
82:b1:0f:aa:71:19:1b:26:48:ee:a0:3d:1a:06:f2:d9:5e:bb:
67:70:71:99:72:24:ca:08:e9:b5:4f:e6:ac:98:c7:20:05:e6:
3b:d8:d9:0a:64:31:e4:47:93:be:ac:69:b2:36:d1:ec:ac:96:
81:5e:2f:63:73:66:5b:b9:bf:93:16:d5:2f:a5:31:e7:58:22:
55:b1:58:28:8a:54:c1:86:31:7d:58:11:c0:b6:37:7b:23:9e:
e6:fe:31:84:bc:02:d3:65:ca:c2:74:4e:d2:21:ab:34:d0:2d:
60:7a:18:7b:1b:02:e6:f1:27:fa:cd:5d:eb:09:d3:5e:25:91:
af:99:a7:f4:1f:1f:98:e3:80:37:4e:c0:27:f4:c4:66:a9:f7:
94:57:49:81
IssuerがLet's Encryptで、Subject Alternative NameがDNS:*.demo.maki.lol, DNS:demo.maki.lolになっています。
発行したTLS証明書をSpring Bootアプリに設定
https://blog.ik.am/entries/488 の記事のようにPEM形式をJKS形式に変換し、Spring BootアプリをTLS対応させます。
cat <<'EOF' > hello-cnb.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-cnb
spec:
replicas: 1
selector:
matchLabels:
app: hello-cnb
template:
metadata:
labels:
app: hello-cnb
spec:
initContainers:
- image: openjdk:11-jdk-slim
name: pem-to-keystore
volumeMounts:
- name: keystore-volume
mountPath: /keystores
- name: demo-maki-lol-tls
mountPath: /demo-maki-lol-tls
env:
- name: SERVER_SSL_KEY_PASSWORD
valueFrom:
secretKeyRef:
name: hello-cnb
key: server.ssl.key-password
command:
- sh
- -ce
- |
openssl pkcs12 -export \
-name hello-cnb \
-in /demo-maki-lol-tls/tls.crt \
-inkey /demo-maki-lol-tls/tls.key \
-out /keystores/hello-cnb.p12 \
-password pass:foobar
keytool -importkeystore \
-destkeystore /keystores/hello-cnb.jks \
-srckeystore /keystores/hello-cnb.p12 \
-deststoretype pkcs12 \
-srcstoretype pkcs12 \
-alias hello-cnb \
-deststorepass ${SERVER_SSL_KEY_PASSWORD} \
-destkeypass ${SERVER_SSL_KEY_PASSWORD} \
-srcstorepass foobar \
-srckeypass foobar \
-noprompt
containers:
- image: making/hello-cnb:latest
name: hello-cnb
ports:
- containerPort: 8443
volumeMounts:
- name: keystore-volume
mountPath: /keystores
readOnly: true
env:
- name: INFO_MESSAGE
value: Hello World!
- name: JAVA_OPTS
value: "-XX:ReservedCodeCacheSize=32M -Xss512k"
- name: BPL_JVM_THREAD_COUNT
value: "20"
- name: BPL_JVM_HEAD_ROOM
value: "5"
- name: SERVER_PORT
value: "8443"
- name: SERVER_SSL_ENABLED
value: "true"
- name: SERVER_SSL_PROTOCOL
value: TLSv1.2
- name: SERVER_SSL_KEY_STORE
value: file:///keystores/hello-cnb.jks
- name: SERVER_SSL_KEY_STORE_TYPE
value: PKCS12
- name: SERVER_SSL_KEY_ALIAS
value: hello-cnb
- name: SERVER_SSL_KEY_PASSWORD
valueFrom:
secretKeyRef:
name: hello-cnb
key: server.ssl.key-password
- name: SERVER_SSL_KEY_STORE_PASSWORD
valueFrom:
secretKeyRef:
name: hello-cnb
key: server.ssl.key-password
- name: SERVER_HTTP2_ENABLED
value: "true"
resources:
limits:
memory: "256Mi"
requests:
memory: "256Mi"
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8443
scheme: HTTPS
initialDelaySeconds: 10
timeoutSeconds: 3
failureThreshold: 3
periodSeconds: 5
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8443
scheme: HTTPS
initialDelaySeconds: 20
timeoutSeconds: 1
periodSeconds: 10
failureThreshold: 1
volumes:
- name: keystore-volume
emptyDir: {}
- name: demo-maki-lol-tls
secret:
secretName: demo-maki-lol-tls
---
kind: Service
apiVersion: v1
metadata:
name: hello-cnb
spec:
type: LoadBalancer
selector:
app: hello-cnb
ports:
- protocol: TCP
port: 443
targetPort: 8443
EOF
kubectl applyでこれらを適用します。
kubectl create secret generic hello-cnb \
--dry-run -o yaml \
--from-literal server.ssl.key-password=thisisasecret \
> hello-cnb-secret.yml
kubectl apply -f hello-cnb.yml -f hello-cnb-secret.yml
/etc/hostsに
<hello-cnb ServiceのExternal IP> hello-cnb.demo.maki.lol
を追加して、 https://hello-cnb.demo.maki.lol/actuator/info にアクセス。
