- In the Kubernetes world, almost all application component communications require certificates (single or two-way encryption), so installing the popular certificate automatic management service (certmanager) in the cluster seems to be a must.
Installation
Solution 1: with Manifest
curl -OL 'https://github.com/cert-manager/cert-manager/releases/download/v1.16.1/cert-manager.yaml'
# Replace to image registry of Aliyun(CN) (if necessary).
# Linux
sed -i 's#quay.io/jetstack/cert-manager-#registry.cn-shenzhen.aliyuncs.com/wl4g-k8s/cert-manager-#g' cert-manager.yaml
# MacOS
sed -io 's#quay.io/jetstack/cert-manager-#registry.cn-shenzhen.aliyuncs.com/wl4g-k8s/cert-manager-#g' cert-manager.yaml
kubectl create ns cert-manager
kubectl -n cert-manager apply -f cert-manager.yaml
Solution 2: with Helm
helm repo add jetstack https://charts.jetstack.io
helm repo update
# Delete the role/binding with last created(If necessary).
kubectl delete role -n kube-system cert-manager-cainjector:leaderelection
kubectl delete role -n kube-system cert-manager:leaderelection
kubectl delete rolebinding -n kube-system cert-manager-cainjector:leaderelection
kubectl delete rolebinding -n kube-system cert-manager:leaderelection
helm -n cert-manager upgrade --create-namespace -i \
cert-manager jetstack/cert-manager \
--set prometheus.enabled=true \
--set webhook.timeoutSeconds=4 \
--set installCRDs=true \
--version v1.16.1 \
--set image.repository=registry.cn-shenzhen.aliyuncs.com/wl4g-k8s/cert-manager-controller \
--set image.tag=v1.16.1 \
--set webhook.image.repository=registry.cn-shenzhen.aliyuncs.com/wl4g-k8s/cert-manager-webhook \
--set webhook.image.tag=v1.16.1 \
--set cainjector.image.repository=registry.cn-shenzhen.aliyuncs.com/wl4g-k8s/cert-manager-cainjector \
--set cainjector.image.tag=v1.16.1 \
--set acmesolver.image.repository=registry.cn-shenzhen.aliyuncs.com/wl4g-k8s/cert-manager-acmesolver \
--set acmesolver.image.tag=v1.16.1 \
--set startupapicheck.image.repository=registry.cn-shenzhen.aliyuncs.com/wl4g-k8s/cert-manager-startupapicheck \
--set startupapicheck.image.tag=v1.16.1
helm -n cert-manager list
Deployment of Issuer
Solution 1: SelfSigned
Issuer
kubectl -n cert-manager apply -f - <<'EOF'
apiVersion: cert-manager.io/v1
kind: Issuer # 只打算给 selfsigned-root-ca-cert 签发时使用, 因此没必要使用 ClusterIssuer
metadata:
name: selfsigned-issuer
spec:
selfSigned: {}
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: selfsigned-root-ca-cert
spec:
isCA: true # 生成的证书会包含 BasicConstraints: CA=TRUE 可以用来签发其他证书
secretName: selfsigned-root-ca-tls
issuerRef:
name: selfsigned-issuer
kind: Issuer
group: cert-manager.io
privateKey:
algorithm: ECDSA
size: 384
rotationPolicy: Always
commonName: "Security Department Example Enterprise Root CA"
duration: 2160h # 90d
renewBefore: 360h # 15d
usages:
- cert sign # Root CA is used to issue other certificates.
- crl sign
subject:
organizations:
- "Security Department of Example Enterprise, Inc."
organizationalUnits:
- "Security Operations"
countries:
- "US"
- "CN"
localities:
- "San Francisco"
provinces:
- "California"
- "Washington"
- "Shanghai"
- "Hongkong"
dnsNames:
- "localhost"
- "root-ca.example.com"
- "root-ca.example.internal"
ipAddresses:
- 127.0.0.1
emailAddresses:
- "pki-admin@example.com"
- "security-team@example.com"
uris:
- spiffe://cluster.local/ns/sandbox/sa/example
# Used to download CRL revocation lists.
#crlDistributionPoints:
#- "http://crl.example.com"
EOF
# Check the Issuer/ClusterIssuer ready status.
kubectl -n cert-manager get issuer
- 特点:设置为 isCA=true生成的 Secret 中也包含了 ca.crt, 但实际上通常就是 tls.crt, 这是因为 cert-manager 在创建 Secret 时,总是包含完整的证书链。 可使用如下命令验证:
export tmp_json=$(kubectl -n cert-manager get secret selfsigned-root-ca-tls -ojson)
echo $tmp_json | jq -r '.data["ca.crt"]' | base64 -d > /tmp/ca.crt
echo $tmp_json | jq -r '.data["tls.crt"]' | base64 -d > /tmp/tls.crt
diff /tmp/ca.crt /tmp/tls.crt && echo "It's same" || echo "It's not same" # OUTPUT: It's same
Solution 2: Self-Hosting (PKI) CA
Issuer
kubectl -n cert-manager apply -f - <<'EOF'
apiVersion: cert-manager.io/v1
kind: ClusterIssuer # 设想之后其他业务 Namespace 申请证书时使用统一的 Root CA, 因此需定义为 ClusterIssuer
metadata:
name: root-ca-issuer
spec:
ca:
secretName: selfsigned-root-ca-tls
# Used to download CRL revocation lists.
#crlDistributionPoints:
#- "http://crl.example.com"
EOF
kubectl -n cert-manager get clusterissuer,secret
Solution 3: ACME (Let’s encrypt + Cloudflware)
kubectl -n cert-manager apply -f - <<'EOF'
apiVersion: v1
kind: Secret
metadata:
name: cloudflare-api-token-secret
type: Opaque
stringData:
api-token: <Your_Cloudflare_API_Token>
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-cluster-issuer
spec:
acme:
email: my-example@gmail.com
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt-cluster-issuer
solvers:
- dns01: # see: https://cert-manager.io/docs/configuration/acme/dns01/#supported-dns01-providers
cloudflare:
email: 983708408@qq.com
apiTokenSecretRef:
name: cloudflare-api-token-secret
key: api-token
EOF
kubectl -n cert-manager get secret,issuer,clusterissuer
Deployment of Certificate
Example the Certificate
kubectl -n default apply -f - <<'EOF'
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: myapp-com-cert
spec:
isCA: false
# Later, ca.crt, tls.crt, and tls.key will be automatically signed and generated into this secret.
secretName: myapp-com-tls
issuerRef:
name: root-ca-issuer
kind: ClusterIssuer
group: cert-manager.io
privateKey:
algorithm: RSA # ECDSA
encoding: PKCS1
size: 2048
duration: 2160h # 90d
renewBefore: 360h # 15d
usages:
- server auth
- client auth
subject:
organizations:
- myapp.com
commonName: "The MyApp Enterprise, Inc."
dnsNames:
- 'localhost'
- 'blogs.myapp.com'
- 'iam.myapp.com'
- 'vaultwarden.myapp.com'
- 'pam.myapp.com'
- 'im.myapp.com'
- 'umami.myapp.com'
- 'mqtt.myapp.com'
- 'matrix.myapp.com'
- 'chat.myapp.com'
- 'excalidraw.myapp.com'
- 'gitea.myapp.com'
- 'gitlab.myapp.com'
- 'ruskdesk.myapp.com'
- 'yapi.myapp.com'
- 'nexus3.myapp.com'
- 'hedgedoc.myapp.com'
- 'outline.myapp.com'
emailAddresses:
- securityadmin@myapp.com
ipAddresses:
- 127.0.0.1
uris:
- spiffe://cluster.local/ns/sandbox/sa/example
EOF
kubectl -n default get certificate,secret | grep -E 'myapp|NAME'
#NAME READY SECRET AGE
#certificate.cert-manager.io/myapp-com-cert True myapp-com-tls 2m25s
#NAME TYPE DATA AGE
#secret/myapp-com-tls kubernetes.io/tls 3 2m25s
总结
- 如上部署
Issuer
的 Solution 1 和 Solution 2 需要配合,S1生成的 selfsigned-root-ca-tls
作为 S2 的 caBundle,之后业务/中间件服务申请实体证书时都使用 S2 创建的 ClusterIssuer/root-ca-issuer
,这样就能使用固定 Root CA 了。
- 否则如果直接使用 S1
selfsigned-issuer
申请实体证书,每次都会生成 CA,这导致没有固定的 Root CA 不便管理也不合理。