🔐

cert-managerのワイルドカード証明書をreflectorで使い回す

に公開

複数namespaceでワイルドカード証明書を使いたい

kubernetesクラスタ上において cert-manager で生成したドメインごとの証明書を複数のnamespaceで利用したい場合、各namespace毎に証明書を生成して利用すればよい。
しかし、ワイルドカード証明書であれば1つの証明書で複数のサブドメインに対応できるため、同じ証明書を複数のnamespaceで使い回したい。利用の度にワイルドカード証明書を発行したくない。

cert-managerが生成する証明書はSecretとして保存されるが、Secretは作成されたnamespace内でのみ利用可能という制約がある。
すなわち、cert-managerのnamespaceに作成・保存されたワイルドカード証明書はcert-managerのnamespaceでしか利用できず、別のexample-app1 やexample-app2といったnamespaceで利用することができない。これが不便。

reflectorによる証明書の自動複製

この制約を回避する方法として、reflectorを利用してリソースを別のnamespaceに複製する方法がある。
secretも操作可能で、cert-managerでオリジナルとなる証明書を発行し、これをreflectorで必要となるnamespaceにコピーして利用する。
これにより、k8sクラスタ全体で一度発行したワイルドカード証明書を使いまわすことができるようになる。
cert-managerが証明書を随時更新した場合もreflectorが適宜反映してくれるので、証明書の更新毎の運用負荷が生じないのもうれしい。

実装手順

1. reflectorのセットアップ

reflectorはHelm提供されているのでこれを利用してインストールする。

reflector-values.yaml
replicaCount: 1
configuration:
  logging:
    minimumLevel: Information
  watcher:
    timeout: 60
helm repo add emberstack https://emberstack.github.io/helm-charts
helm repo update
helm install reflector emberstack/reflector \
  --namespace kube-system \
  --values reflector-values.yaml

2. cert-managerでワイルドカード証明書を生成

Let's Encryptを使用してワイルドカード証明書を生成する例は以下の通り。
ワイルドカード証明書を発行するにはDNSチャレンジ方式が必要。例えばcloudflareを利用していれば、cloudflareのapi tokenをsecretに格納しこれを指定すればよい。
これによりワイルドカード証明書が発行されて secretへと格納される。

# certificate.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: wildcard-cert
  namespace: cert-manager
spec:
  secretName: wildcard-cert-secret
  secretTemplate:
    annotations:
      reflector.v1.k8s.emberstack.com/reflection-allowed: "true"
      reflector.v1.k8s.emberstack.com/reflection-allowed-namespaces: ""
      reflector.v1.k8s.emberstack.com/reflection-auto-enabled: "true"
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  dnsNames:
  - "*.example.com"
---
# cluster-issuer.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: your-email@example.com
    privateKeySecretRef:
      name: letsencrypt-prod
    solvers:
    - dns01:
        cloudflare:
          apiTokenSecretRef:
            name: cloudflare-api-token-secret
            key: api-token
      selector:
        dnsZones:
        - example.com

特徴はCertificateリソースに spec.secretTemplate.annotations として
reflector.v1.k8s.emberstack.com/{reflection-allowed,reflection-allowed-namespaces,reflection-auto-enabled} のようなreflectorが利用する複製に関するアノテーションを指定しているところ。
アノテーションの意味は以下の通り:

  • reflection-allowed: "true": reflectorによる複製を許可
  • reflection-allowed-namespaces: "": 空文字列は全namespaceを意味、特定のnamespaceに限定する場合は "app1,app2" のように指定
  • reflection-auto-enabled: "true": reflectorによる自動複製を有効化

この設定であれば全namespaceに証明書が格納されたsecretが生成される。セキュリティ上、生成先を制御したい場合は allowed-namespacesを指定して制御する。

3. 動作確認

証明書が正常に複製されているか確認する:

# 元の証明書の確認
kubectl get secret wildcard-cert-secret -n cert-manager

# 複製された証明書の確認
kubectl get secret wildcard-cert-secret -n app1
kubectl get secret wildcard-cert-secret -n app2

# 証明書の詳細確認
kubectl describe secret wildcard-cert-secret -n app1

4. 手動reflection設定(オプション)

自動複製を使わない場合は、複製先のnamespaceで reflector.v1.k8s.emberstack.com/reflects アノテーションを指定したSecretを事前に作成する必要がある。
これにより、複製のタイミングなどを制御できるようになる。

apiVersion: v1
kind: Secret
metadata:
  name: wildcard-cert-secret
  namespace: app1
  annotations:
    reflector.v1.k8s.emberstack.com/reflects: "cert-manager/wildcard-cert-secret"
type: kubernetes.io/tls
data: {}

複製されたSecretの使用

各namespaceで複製された証明書を利用する場合は特に考慮すべき点は無い。

ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app-ingress
  namespace: app1
spec:
  tls:
  - hosts:
    - app1.example.com
    secretName: wildcard-cert-secret
  rules:
  - host: app1.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: app1-service
            port:
              number: 80

注意点とセキュリティ考慮事項

secretTemplateでreflectorの設定を反映する

kind: Certificateのリソースは証明書を生成する設定なので、このリソースをreflectorで複製しても意味はなく、Certificateリソースが発行してsecretリソースに格納するTLS証明書にアノテーションを付与してたい。
これにより、生成した証明書が各namespaceに配布されるようになる。

secretTemplateはまさにそのような目的の設定で、ここに指定されたアノテーションが生成されたsecretリソースのアノテーションに付与される。
これによりreflectorにより複製して欲しいリソースに対して適切にアノテーションが付与できる。

DNS設定要件

Cloudflareを使用する場合、以下の権限が必要:

  • Zone:Zone:Read
  • Zone:DNS:Edit

APIトークンは適切なゾーンにスコープを限定する。

Ingress annotationのサポート

Ingressにて証明書をアタッチして利用したい場合は、個別にcertを生成・reflectして利用する設定を行わなくてもよい。
Ingressに直接アノテーションを付与することで自動で発行して関連付けることができる。
ただし、これはワイルドカード証明書ではなくホスト毎に発行している場合のみなので注意。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app-ingress
  namespace: app1
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    cert-manager.io/secret-template: |
      {"annotations": {"reflector.v1.k8s.emberstack.com/reflection-allowed": "true", "reflector.v1.k8s.emberstack.com/reflection-allowed-namespaces": ""}}
spec:
  tls:
  - hosts:
    - app1.example.com
    secretName: app1-tls-secret
  rules:
  - host: app1.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: app1-service
            port:
              number: 80

参考

Discussion