0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Red Hat Cert-Manager Opertor を ROSA 上で使用してみる

Last updated at Posted at 2024-06-10

Cert-Manager Operator を使った証明書の更新方法は、ドキュメントに載っているのですが、ccoctl という OpenShift の独自ツールを使用して IAM RoleIAM Policy を作成するようになっています。

できるだけシンプルに導入したいので、そこは標準の AWS CLI を利用する事にして、それ以外は基本、ドキュメントに従って導入して行きます。

ここでは、GUI から Cert-Manager Operator をインストールしていますが、CLIからも導入できます。(ちょっと手数が多くなります)

今回の環境

今回の手順は以下の環境で作成しました。

Kubernetes のディストリビューションは、OpenShift の AWS 上の Managed Service である ROSA のバージョン 4.14.27を使用 (ROSA の最新アーキテクチャ版である ROSA HCP(Hosted Control Plane) を使用)
Cert-Manger Operator は、Community 版とRed Hat版があるが Red Hat 版を使用。
・証明書を発行する対象ドメインは、ocp4.work
・証明書を発行する対象ドメイン ocp4.workは、Route53 を権威 DNSサーバーにする
ROSAocp4.work の権威DNSである Route53 は、同じ AWS Account 内に存在
・証明書の CA は Let's Encrypt とする。

AWS の Public 向けの証明書を発行する ACM (AWS Certificate Manager)は、AWSのサービスに対してだけ証明書を発行する特殊なサービスなので、Cert-Manager と組み合わせて使用する事はできません。Cert-Manager は複数のCAをサポートしていますが、今回は Let's Encrypt を使用します。Cert-Manager がサポートする CAは、ドキュメントのこの辺りから書かれています。

環境の準備

以下の環境を使用します

・Amazon Linux 2
・oc コマンド が実行できる環境 (インストールするための こちらにスクリプトを作りました )
・AWS CLIがセットアップ済み (Amazon Linux 2 に aws コマンドは入って居るので aws configure で構成)
・jq コマンドがインストール済み ( sudo yum install -y jq)

必要な情報の収集

今回は、Cert-Manager が、Let's Encrypt の Challenge に対して自動的に Route53 のレコードを書き替えて対応するようにします。そのために、AWS の IAM Role/IAM Policy を作成したり、対象ドメイン(この例では ocp4.work)をホストしている Route53の情報が必要になります。

AWS CLI の構成をしておきます。

$ aws configure
AWS Access Key ID [None]: ASBDERFFI4B4VVUXXYYZZ
AWS Secret Access Key [None]: xXv7/X4FsXlLaTEteaabbccddjwV+6M7SHm02
Default region name [None]: ap-northeast-1
Default output format [None]: 
$ 

作業ディレクトリの作成

export WORK_DIR=~/work
mkdir -pv $WORK_DIR

rosa list cluster で表示される作成済みの ROSA クラスターの名前

CLUSTER_NAME="rosa-cluster"   

rh-oidc.s3.us-east-1.amazonaws.com/20s0h5421tff8a6tqsoh2hk78m1ocp0o のような値

export OIDC_PROVIDER=$(oc get authentication.config.openshift.io cluster -o json \
| jq -r .spec.serviceAccountIssuer| sed -e "s/^https:\/\///")   

AWS Account ID の値。 201068234712 のような値

export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)  

Let's Encrypt に対して証明書を申請する際に使う有効なメールアドレス

export LETSENCRYPT_EMAIL=test@test.com  

この例での証明書発行をリクエストするドメイン名は ocp4.work です。自分で取得するドメイン名に読み替えて下さい。
このドメインを Route53 でホストします。

export HOSTED_ZONE_REGION=ap-northeast-1     
export DOMAIN=ocp4.work

HOSTED_ZONE_ID は、証明書発行ドメインをホストしている Route53 の Zone IDです。Z01429121KPW1GKICNEO2 のような値です。

export HOSTED_ZONE_ID=$(aws route53 list-hosted-zones --query "HostedZones[].[Name,Id,Config.PrivateZone,ResourceRecordSetCount]" --output text | grep $DOMAIN | awk '{print $2}' | sed -e "s/.*\///")

HOSTED_ZONE_ID は、上記の例のように AWS CLIで取得するか、Route53 の画面に表示されています。

image.png

AWS Role と Policy の作成

Cert-Manager Operator は、証明書の発行や更新のために AWS の Route53 にアクセスして編集などの作業するので、AWS の STS(Secure Token Service) を使用して必要な権限を一時的に取得します。その時にどの対象からのリクエストを信頼して、どのような許可を与えるかを AWS の IAM Policy や IAM Role を使って事前に定義しておく必要があります。

route53 用 許可ポリシーの json ファイルの作成します。

cat <<EOF > $WORK_DIR/cert-manager-r53-policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "route53:GetChange",
      "Resource": "arn:aws:route53:::change/*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "route53:ChangeResourceRecordSets",
        "route53:ListResourceRecordSets"
      ],
      "Resource": "arn:aws:route53:::hostedzone/*"
    },
    {
      "Effect": "Allow",
      "Action": "route53:ListHostedZonesByName",
      "Resource": "*"
    }
  ]
}
EOF

上記の Policy を良く見ると、"Action" として Route53 への
幾つかの権限を与えている事がわかります。

作成したjson ファイルを使用して、AWS上に許可ポリシーを作成します。

POLICY=$(aws iam create-policy --policy-name "${CLUSTER_NAME}-cert-manager-r53-policy" \
   --policy-document file://$WORK_DIR/cert-manager-r53-policy.json \
   --query 'Policy.Arn' --output text) || echo $POLICY

変数にきちんと値が返っているか確認します。

$ echo $POLICY
arn:aws:iam::452752386616:policy/rosa-cluster-cert-manager-r53-policy
$

次に、信頼ポリシー用のjson ファイルを作成します。

この Policy は、cert-manager という project 名の cert-manager という Service Account を条件(Condition) に設定している事に注意して下さい。

導入する project を変更した場合は、内容を変更する必要があります。

cat <<EOF > $WORK_DIR/TrustPolicy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::${AWS_ACCOUNT_ID}:oidc-provider/${OIDC_PROVIDER}"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "${OIDC_PROVIDER}:sub": [
            "system:serviceaccount:cert-manager:cert-manager"
          ]
        }
      }
    }
  ]
}
EOF

上記の Policy を良く見ると AWS_ACCOUNT_IDPrincipal として指定されていたり、さらに条件 (Condition) として、OIDC_RPOVIDER の値や、Service Account 名や project 名 等も指定されている事がわかります。これらの条件に一致しない相手でないと信頼されない(=Tokenが発行されない)事になります。

作成した 信頼ポリシー(Trust Policy) のファイルを指定して、AWS上にIAM Roleを作成します。

ROLE=$(aws iam create-role \
  --role-name "${CLUSTER_NAME}-cert-manager-operator" \
  --assume-role-policy-document file://$WORK_DIR/TrustPolicy.json \
  --query "Role.Arn" --output text)

上手くできているか変数の値を確認します。

$ echo $ROLE
arn:aws:iam::081920418460:role/myhcpcluster-cert-manager-operator
$ 

作成したIAM Role に、一番はじめに作成した 許可ポリシーも Attach します。

aws iam attach-role-policy \
    --role-name "${CLUSTER_NAME}-cert-manager-operator" \
    --policy-arn $POLICY

これで、IAM Role (${CLUSTER_NAME}-cert-manager-operator) に、許可ポリシーと信頼ポリシーがアタッチされました。

Cert-Manager Operator のインストール

Cert-Manager は CLI からもインストールする事ができますが、ここでは OpenShift Console の GUI 上の OperatorHub からインストールします。

Cert-Manager は、Community 版と、Red Hat 版があるので、Red Hat 版を選択します。

image.png

image.png

role ARN では、前のステップで作成した AWS の IAM Role の ARN arn:aws:iam::081920418460:role/myhcpcluster-cert-manager-operator を入力します。(ここに IAM Role の ARN を入力すると Service Account に annotation が付くのかと思ったのですが、そういう訳ではありませんでした。用途がちょっと謎です)

また、ここでは Update approval は、Automatic を選択し、承認なしで自動でアップデートされるようにしました。
image.png

以下の表示が出ればインストールは完了しているはずです。
image.png

CLI に戻り、以下で導入を確認します。

 oc get pods -n cert-manager

こんな風に3つのPodが動いてればOKです。

$ oc get pods -n cert-manager
NAME                                       READY   STATUS    RESTARTS   AGE
cert-manager-85dfdd4f8b-zgvc8              1/1     Running   0          65m
cert-manager-cainjector-56b774cdf8-bt4tm   1/1     Running   0          66m
cert-manager-webhook-8f694d645-tfcsn       1/1     Running   0          66m
$ 

以下のような service account が作られているはずです。

$ oc get sa -n cert-manager | grep cert
cert-manager              1         3h38m
cert-manager-cainjector   1         3h38m
cert-manager-webhook      1         3h38m
$ 

cert-manager という Service Account に、前のステップで作成した IAM Role 名を入れた annotation を付けます。

$ echo $ROLE
arn:aws:iam::081920418460:role/myhcpcluster-cert-manager-operator
$
oc annotate -n cert-manager serviceaccount cert-manager  eks.amazonaws.com/role-arn=$ROLE
$ oc describe -n cert-manager sa cert-manager  | grep Annotation
Annotations:         eks.amazonaws.com/role-arn: arn:aws:iam::081920418460:role/myhcpcluster-cert-manager-operator
$ 

この annotation を付ける事で、この Service Account (cert-manager) は、作業に必要な AWS の IAMRole を STSを使って手に入れる(AssumeRole)事ができるようになります。

Service Accountannotation を付けたので、新しい権限で Pod を再起動します。以下のコマンドで delete すると再起動します。

oc delete pods -l app.kubernetes.io/name=cert-manager -n cert-manager

CertMnager リソースの編集

証明書を保管するための project を作成します。

CERT_PROJECT=letencrypt
oc new-project $CERT_PROJECT

この手順は、ドキュメントのこちら を参考にしています。

CertManager リソースを編集して --issuer-ambient-credentials 引数をつけます。

oc patch certmanager/cluster --type=merge -p='{"spec":{"controllerConfig":{"overrideArgs":["--issuer-ambient-credentials"]}}}'

ClusterIssure リソースの作成

ClusterIssure リソースを作成します。

envsubst  <<EOF | oc apply -f -
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencryptissuer
  namespace: $CERT_PROJECT
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: $LETSENCRYPT_EMAIL
    # This key doesn't exist, cert-manager creates it
    privateKeySecretRef:
      name: prod-letsencrypt-issuer-account-key
    solvers:
    - dns01:
        route53:
         hostedZoneID: $HOSTED_ZONE_ID
         region: $HOSTED_ZONE_REGION
EOF

公式ドキュメントspec.acme.server field の値は、 https://acme-staging-v02.api.letsencrypt.org/directory となっており staging 用の Let's Encrypt 証明書を発行するようになっています。staging 用証明書の CA が普通の環境の Trusted Store には登録されてないため、curl 等では -k オプションを使ってアプリケーションのテストする必要があります。

一方、ここで作成する 非staging 環境用 (本場用:spec.acme.server field の値が https://acme-v02.api.letsencrypt.org/directory) の証明書は、一週間に5回以上、同じドメインの証明書をリクエストをすると Rate Limit にひっかかり新規の発行ができなくなります。

そのため、実験で繰り返す事が予想される場合は、本番用の証明書を発行するClusterIssureリソースの他に、staging 用の証明書を作成するClusterIssureリソース (spec.acme.server の値は、 https://acme-staging-v02.api.letsencrypt.org/directory)も作成しておく事をおすすめします。

Read が True になるまで待ちます。

$ oc get  clusterissuer letsencryptissuer
NAME                READY   AGE
letsencryptissuer           90s
$ 
$ oc get  clusterissuer letsencryptissuer
NAME                READY   AGE
letsencryptissuer   True    5m34s
$ 

Certificate リソースの作成

Certficate リソースを作成します。Certificate リソースを作成する事で、証明書の発行をリクエストします。

前段で作成した ClusterIssureletsencryptissuer は、証明書のリクエスト先 CA (Certificate Auhtority) としてこのリソースの中に指定されています。

envsubst  <<EOF | oc apply -f -
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: customdomain-cert
  namespace: $CERT_PROJECT
spec:
  isCA: false
  secretName: custom-domain-certificate-tls
  issuerRef:
     name: letsencryptissuer
     kind: ClusterIssuer
  commonName: "*.$DOMAIN"
  dnsNames:
  - "*.$DOMAIN"
EOF

状態確認をします。True になるまで待ちます。

$  oc get -n $CERT_PROJECT certificate customdomain-cert 
NAME                READY   SECRET                          AGE
customdomain-cert   False   custom-domain-certificate-tls   21s
$ 
$  oc get -n $CERT_PROJECT certificate customdomain-cert 
NAME                READY   SECRET                          AGE
customdomain-cert   True    custom-domain-certificate-tls   21s
$ 

ここで作成された証明書は上記の表示にあるように custom-domain-certificate-tls という Secret になります。

debugの方法

数分で作成される場合もありますが、場合によっては Certificate の発行が待たされている場合があります。
状況は Pod のログを見る事で確認できます。

$ oc get -n cert-manager pods | grep cert-manager
NAME                                       READY   STATUS    RESTARTS   AGE
cert-manager-5cb58c6b55-r4hcj              1/1     Running   0          56m
cert-manager-cainjector-7889dc796c-mjqqm   1/1     Running   0          60m
cert-manager-webhook-85f99cb964-2qk6n      1/1     Running   0          60m
$ 

cert-manager-<ランダム>Pod のログを確認します。ここでは、一番上の Pod のログを確認します。

$ oc logs -f cert-manager-5cb58c6b55-r4hcj 
<省略>
I0102 03:10:10.041269       1 conditions.go:192] Found status change for Certificate "customdomain-cert" condition "Issuing": "True" -> "False"; setting lastTransitionTime to 2023-01-02 03:10:10.041262527 +0000 UTC m=+54.359300594
I0102 03:10:10.055415       1 trigger_controller.go:179] cert-manager/certificates-trigger "msg"="Backing off from issuance due to previously failed issuance(s). Issuance will next be attempted at 2023-01-02 04:10:10.000000666 +0000 UTC m=+3654.318038737" "key"="cert-manager/customdomain-cert
I0102 03:10:10.088248       1 trigger_controller.go:179] cert-manager/certificates-trigger "msg"="Backing off from issuance due to previously failed issuance(s). Issuance will next be attempted at 2023-01-02 04:10:10.000000521 +0000 UTC m=+3654.318038589" "key"="cert-manager/customdomain-cert"

[実践編] IngressController の証明書を Cert-Manager Operator が管理する証明書に置きかえる

ここでは実践編として、ROSA に追加で作成した Ingresscontroller (name: additional) が使用している証明書の Secret を、Cert-Manager Operator が使用している Secert と置き換えてみます。こちらの記事の環境を前提にしています。

前提条件

以下のように既に Secondary の IngressController である additional という IngressController がデプロイされています。

$ oc get ingresscontroller -n openshift-ingress-operator
NAME         AGE
additional   46m
default      8h
$ 

この IngressController の使用している defaultCertifidcate は、my-tls という名前になっています。これを Cert-Manager が作成したものに置き換えてみます。

$ oc get ingresscontroller additional -n openshift-ingress-operator -o yaml | grep defaultCertificate: -A1
  defaultCertificate:
    name: my-tls
$ 

現在の証明遺書の確認

まず、現在の IngressController を使っているアプリの現在の証明書の期限を確認しておきます。

$ echo | openssl s_client -connect hello.ocp4.work:443 2>&1 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | openssl x509 -text 2>/dev/null | grep -e "Public-Key" -e "Not"
            Not Before: Jun 10 10:59:15 2024 GMT
            Not After : Sep  8 10:59:14 2024 GMT
                Public-Key: (2048 bit)
$ 

まず、openshift-ingress project 内に 新しい証明書の Secret を作成します。

証明書の Secret を生成する project は、IngressController が Router pod を生成する openshift-ingress project である必要があります。

Certificate リソースによってリクエストされた 証明書(Secret) は、Certificateリソースと同じprojectに作成されます。

envsubst  <<EOF | oc apply -f -
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: my-tls2
  namespace: openshift-ingress
spec:
  isCA: false
  secretName: my-tls2
  issuerRef:
     name: letsencryptissuer
     kind: ClusterIssuer
  commonName: "*.$DOMAIN"
  dnsNames:
  - "*.$DOMAIN"
EOF

作成を確認します。

$ oc get -n openshift-ingress certificate my-tls2 
NAME      READY   SECRET    AGE
my-tls2   True    my-tls2   14s
$ 

Secret の作成を確認します。

$ oc get secret my-tls2 -n openshift-ingress
NAME      TYPE                DATA   AGE
my-tls2   kubernetes.io/tls   2      36s
$ 

次にこの Secret を IngressController に指定します。

証明書の Secret を作成したのは、openshift-ingress project ですが、Secret 名を指定する IngressController リソースは openshift-ingress-operator に存在します。

oc edit ingresscontroller additional -n openshift-ingress-operator

spec.defaultCertificate.name の部分に、先ほど作成した Secret の名前を指定します。project 名は指定する field は無いので指定できません。Router Pod が作成される openshif-ingress project 内の Secret である必要があります。

apiVersion: operator.openshift.io/v1
kind: IngressController
metadata:
  creationTimestamp: "2024-06-07T00:37:53Z"
  finalizers:
  - ingresscontroller.operator.openshift.io/finalizer-ingresscontroller
  generation: 5
  name: acme
  namespace: openshift-ingress-operator
  resourceVersion: "363927"
  uid: 3851a50e-66b8-4218-9266-9811aae54055
spec:
  clientTLS:
    clientCA:
      name: ""
    clientCertificatePolicy: ""
  defaultCertificate:
    name: my-tls2 # ここを作成した Secret 名 (my-tls2)に
  domain: acme.rosa-1w8dvp.18to.p1.openshiftapps.com
  endpointPublishingStrategy:
    loadBalancer:
      dnsManagementPolicy: Manage
      ...

更新の確認

証明書が置き換えられているかどうかを確認します。

openshift-ingress project の Pod が証明書の更新のために再起動するはずなので、少し待ちます。

$ oc get pods -n openshift-ingress
NAME                                 READY   STATUS        RESTARTS   AGE
router-additional-6667c9f8d6-5gr5l   1/1     Running       1          104m
router-additional-6667c9f8d6-bmtrl   1/1     Terminating   1          104m
router-additional-6b6b6464d6-f4m9k   1/1     Running       0          15s
router-additional-6b6b6464d6-n8mqq   1/1     Running       0          13s
router-default-578b69d8f4-k99j7      1/1     Running       1          9h
router-default-578b69d8f4-tq9m8      1/1     Running       1          9h
$ 

再起動が完了したら、証明書の日付を確認してみます。

$ echo | openssl s_client -connect hello.$DOMAIN:443 2>&1 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | openssl x509 -text 2>/dev/null | grep -e "Public-Key" -e "Not"   
            Not Before: Jun 10 12:56:20 2024 GMT
            Not After : Sep  8 12:56:19 2024 GMT
                Public-Key: (2048 bit)
$ 

有効期限の日付は同じですが、時間が以前と変わっているので無事置き換わった事がわかります。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?