Kubernetes,  Security

Installation the Cert-Manager on Kubernetes

  • 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

  • 参见: https://cert-manager.io/docs/configuration/selfsigned/#deployment
  • 场景:适用于开发环境测试,不适合生产环境。
  • 提示:定义 issuer 并不会生成证书到 secret, 必须声明 Certificate 才会生成证书到 secret。
  • 警告:使用自签名证书的客户端在没有事先拥有证书的情况下无法信任它们,当客户端与服务器位于不同的命名空间时,这可能难以管理。解决参见:https://cert-manager.io/docs/configuration/selfsigned/#caveats
  • 注意:使用此 selfSigned 类型的 Issuer 签发证书,它每次都会创建一个新的自签名证书,而不是使用一个预先存在的固定 CA 来签发证书。
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

  • 参见: https://cert-manager.io/docs/configuration/ca/
  • 场景:适用于想要自建 PKI 的公司或组织的生产环境,完全私有控制,但有运维管理门槛。
  • 注意:之后签发实体证书时需要固定 Root 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 不便管理也不合理。

留言

您的电子邮箱地址不会被公开。