#chiroito ’s blog

Java を中心とした趣味の技術について

OpenShift 4.21で証明書を自動化

OpenShift を素の状態で使い始めると、API サーバや Ingress には OpenShift 内部の仕組みで発行された証明書が使われます。これでもクラスタ内部では問題ありませんが、外部クライアントから素直に信頼される証明書に置き換えたい場面は多くあります。この記事では、OpenShift 4.21.8 に cert-manager を導入し、Cloudflare の DNS01 challenge を使って Let’s Encrypt から証明書を発行し、それを API 用と Ingress 用に適用する流れをまとめます。

今回の構成では Cloudflare を DNS に使います。cert-manager の Cloudflare 連携では API Token を使う方法が案内されており、Let’s Encrypt のワイルドカード証明書も DNS01 で扱う前提です。

OpenShift 上で使う cert-manager は、Red Hat が提供する cert-manager Operator for Red Hat OpenShift を使います。この Operator は OpenShift に最初から入っているわけではないため、Web コンソールからインストールします。また、コミュニティ版の cert-manager Operator を併用しないようにする必要があります。

前提

今回の前提は次のとおりです。

  • DNS は Cloudflare
    • OpenShiftのドメインは o.chiroito.dev
  • OpenShift 4.21.8

YAMLの中にあるドメイン名やトークン、メールアドレスは適宜変更してください。

cert-manager Operator の導入

最初に OpenShift の Web コンソールから cert-manager Operator for Red Hat OpenShift をインストールします。手順としては、Web コンソールに cluster-admin 権限でログインし、Operator のカタログから該当の Operator を選んでインストールします。今回の構成では複数 namespace で Certificate を扱うため、インストールモードは AllNamespaces を選んでおくのが無難です。

ここはこの記事の本題ではないので詳細な画面操作までは追いませんが、少なくとも次の状態になっていれば先へ進めます。

  • cert-manager Operator がインストール済み
  • cert-manager namespace に関連 Pod が起動している

Cloudflare の API Token を作成する

Cloudflare 側では cert-manager が DNS レコードを書き換えられるように API Token を作成します。 Cloudflare の画面では、Profile から API Tokens に進み、Create Token を選択します。Edit zone DNS のテンプレートを使ってもよいですし、Create Custom Token から自分で設定しても構いません。必要なのは次の 2 権限と 1 つの Zone Resources 設定です。

権限

  • Zone - DNS - Edit
  • Zone - Zone - Read

Zone Resources

  • Include - All Zones

作成後に表示されるトークンはその場で控えておきます。

Cloudflare の認証情報を Secret として作成する

次に Cloudflare の API Token を Secret として登録します。ここで使う Secret は、後で ClusterIssuer の apiTokenSecretRef から参照されます。

apiVersion: v1
kind: Secret
metadata:
  name: cloudflare-api-token-secret
  namespace : cert-manager
type: Opaque
stringData:
  api-token: <API Token>

Let’s Encrypt 用の ClusterIssuer を作成する

続いて ACME の発行元を定義します。今回の YAML では、ステージング用の example-issuer と本番用の prod-issuer を分けています。privateKeySecretRef は ACME アカウント鍵を保存するための Secret 名で、cert-manager が自動生成して管理します。

まずは Let’s Encrypt のステージング環境です。

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: example-issuer
spec:
  acme:
    email: <メールアドレス>
    privateKeySecretRef:
      name: example-issuer-account-key
    server: 'https://acme-staging-v02.api.letsencrypt.org/directory'
    solvers:
      - dns01:
          cloudflare:
            apiTokenSecretRef:
              key: api-token
              name: cloudflare-api-token-secret

次に本番環境です。

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: prod-issuer
spec:
  acme:
    email: <メールアドレス>
    privateKeySecretRef:
      name: prod-issuer-account-key
    server: 'https://acme-v02.api.letsencrypt.org/directory'
    solvers:
      - dns01:
          cloudflare:
            apiTokenSecretRef:
              key: api-token
              name: cloudflare-api-token-secret

ClusterIssuer を使っているのは、openshift-configopenshift-ingress の両方から同じ発行元を参照したいためです。cert-manager の Certificate は、issuerRef.kindClusterIssuer にすると cluster-scoped な発行元を参照できます。

最初はステージング側で疎通確認をし、問題がなければ prod-issuer を使います。

Certificate を作成する

OpenShift の API 用証明書と Ingress 用証明書をそれぞれ Certificate として定義します。spec.secretName は cert-manager が自動作成して管理する Secret 名です。Certificate が属する namespace にその Secret が作られるため、API 用は openshift-config、Ingress 用は openshift-ingress に作ります。

まずは API 用です。

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: api-tls-cert
  namespace: openshift-config
spec:
  commonName: api.o.chiroito.dev
  dnsNames:
    - api.o.chiroito.dev
  isCA: false
  issuerRef:
    kind: ClusterIssuer
    name: prod-issuer
  secretName: api-secret

次に Ingress 用です。

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: ingress-tls-cert
  namespace: openshift-ingress
spec:
  commonName: apps.o.chiroito.dev
  dnsNames:
    - apps.o.chiroito.dev
    - '*.apps.o.chiroito.dev'
  isCA: false
  issuerRef:
    kind: ClusterIssuer
    name: prod-issuer
  secretName: ingress-secret

Ingress 側で *.apps.o.chiroito.dev を入れているのは、OpenShift の Route が通常 アプリ名-namespace.apps... のようなホスト名で公開されるためです。

Certificate を作成したあとは、まず cert-manager 側で発行が成功しているかを確認します。

oc get certificate -A
oc describe certificate api-tls-cert -n openshift-config
oc describe certificate ingress-tls-cert -n openshift-ingress

Secret が生成されていることも確認します。

oc get secret api-secret -n openshift-config
oc get secret ingress-secret -n openshift-ingress

OpenShift 側に Secret をひも付ける

証明書を発行しただけでは、まだ OpenShift の API サーバや Ingress はそれを使いません。最後に、cert-manager が作成した Secret を OpenShift 側の設定に参照させます。API サーバの named certificate は APIServer/clusterspec.servingCerts.namedCertificates で設定し、Ingress の既定証明書は IngressController/defaultspec.defaultCertificate.name で設定します。

API サーバ側は次のとおりです。

apiVersion: config.openshift.io/v1
kind: APIServer
metadata:
  name: cluster
spec:
  audit:
    profile: Default
  servingCerts:
    namedCertificates:
      - names:
          - api.o.chiroito.dev
        servingCertificate:
          name: api-secret

kube-apiserver のロールアウト状況を確認します。

oc get clusteroperators kube-apiserver
oc get apiserver cluster -o yaml

Ingress 側は次の設定です。

apiVersion: operator.openshift.io/v1
kind: IngressController
metadata:
  name: default
  namespace: openshift-ingress-operator
spec:
  defaultCertificate:
    name: ingress-secret
(略)

IngressController の defaultCertificateopenshift-ingress namespace にある Secret を参照します。Route 側で個別の証明書を指定していない場合、この既定証明書が使われます。OpenShift は既定では内部生成のワイルドカード証明書を使いますが、ここを差し替えることで外部から信頼される証明書に置き換えられます。

defaultCertificate の参照が反映されているかを確認します。

oc get ingresscontroller/default -n openshift-ingress-operator -o yaml

最後にクライアント側から証明書を確認します。API であれば https://api.o.chiroito.dev:6443、Ingress であればWebコンソールにブラウザで証明書チェーンを確認すると分かりやすいです。

参考資料