×

通配符证书通过使用单个证书保护给定域的所有一级子域来简化操作,但其他用例可能需要为每个域使用单个证书。

了解如何使用Red Hat OpenShift 的 cert-manager OperatorLet’s Encrypt为使用自定义域创建的路由动态颁发证书。

先决条件

  • 一个 ROSA 集群(HCP 或 Classic)

  • 具有 `cluster-admin` 权限的用户帐户

  • OpenShift CLI(`oc`)

  • Amazon Web Services (AWS) CLI(`aws`)

  • 一个唯一的域名,例如 `*.apps.example.com`

  • 上述域名的 Amazon Route 53 公共托管区域

设置您的环境

  1. 配置以下环境变量

    $ export DOMAIN=apps.example.com (1)
    $ export EMAIL=[email protected] (2)
    $ export AWS_PAGER=""
    $ export CLUSTER=$(oc get infrastructure cluster -o=jsonpath="{.status.infrastructureName}"  | sed 's/-[a-z0-9]\{5\}$//')
    $ export OIDC_ENDPOINT=$(oc get authentication.config.openshift.io cluster -o json | jq -r .spec.serviceAccountIssuer | sed  's|^https://||')
    $ export REGION=$(oc get infrastructure cluster -o=jsonpath="{.status.platformStatus.aws.region}")
    $ export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
    $ export SCRATCH="/tmp/${CLUSTER}/dynamic-certs"
    $ mkdir -p ${SCRATCH}
    1 替换为您要用于 `IngressController` 的自定义域名。
    2 替换为您希望 Let’s Encrypt 使用的电子邮件地址,用于发送有关您证书的通知。
  2. 确保所有字段正确输出,然后再转到下一部分

    $ echo "Cluster: ${CLUSTER}, Region: ${REGION}, OIDC Endpoint: ${OIDC_ENDPOINT}, AWS Account ID: ${AWS_ACCOUNT_ID}"

    上一个命令输出的“集群”可能是您的集群名称、集群的内部 ID 或集群的域名前缀。如果您更倾向于使用其他标识符,您可以手动运行以下命令设置此值

    $ export CLUSTER=my-custom-value

准备您的 AWS 账户

当 cert-manager 向 Let’s Encrypt(或其他 ACME 证书颁发机构)请求证书时,Let’s Encrypt 服务器使用 *挑战* 验证您是否控制该证书中的域名。在本教程中,您使用的是DNS-01 挑战,它通过在该域名下的 TXT 记录中放置特定值来证明您控制该域名的 DNS。所有这些都是由 cert-manager 自动完成的。要允许 cert-manager 权限修改您域名的 Amazon Route 53 公共托管区域,您需要创建一个具有特定策略权限和信任关系的 Identity Access Management (IAM) 角色,以允许访问 pod。

本教程中使用的公共托管区域与 ROSA 集群位于同一个 AWS 账户中。如果您的公共托管区域位于不同的账户中,则需要执行一些额外的步骤来进行跨账户访问

  1. 检索 Amazon Route 53 公共托管区域 ID

    此命令查找与您之前作为 `DOMAIN` 环境变量指定的自定义域名匹配的公共托管区域。您可以通过运行 `export ZONE_ID=<zone_ID>` 手动指定 Amazon Route 53 公共托管区域,将 `<zone_ID>` 替换为您特定的 Amazon Route 53 公共托管区域 ID。

    $ export ZONE_ID=$(aws route53 list-hosted-zones-by-name --output json \
      --dns-name "${DOMAIN}." --query 'HostedZones[0]'.Id --out text | sed 's/\/hostedzone\///')
  2. 为 cert-manager Operator 创建一个 AWS IAM 策略文档,该文档仅提供更新指定公共托管区域的功能

    $ cat <<EOF > "${SCRATCH}/cert-manager-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/${ZONE_ID}"
        },
        {
          "Effect": "Allow",
          "Action": "route53:ListHostedZonesByName",
          "Resource": "*"
        }
      ]
    }
    EOF
  3. 使用您在上一步中创建的文件创建 IAM 策略

    $ POLICY_ARN=$(aws iam create-policy --policy-name "${CLUSTER}-cert-manager-policy" \
      --policy-document file://${SCRATCH}/cert-manager-policy.json \
      --query 'Policy.Arn' --output text)
  4. 为 cert-manager Operator 创建一个 AWS IAM 信任策略

    $ cat <<EOF > "${SCRATCH}/trust-policy.json"
    {
     "Version": "2012-10-17",
     "Statement": [
     {
     "Effect": "Allow",
     "Condition": {
       "StringEquals" : {
         "${OIDC_ENDPOINT}:sub": "system:serviceaccount:cert-manager:cert-manager"
       }
     },
     "Principal": {
       "Federated": "arn:aws:iam::$AWS_ACCOUNT_ID:oidc-provider/${OIDC_ENDPOINT}"
     },
     "Action": "sts:AssumeRoleWithWebIdentity"
     }
     ]
    }
    EOF
  5. 使用您在上一步中创建的信任策略为 cert-manager Operator 创建一个 IAM 角色

    $ ROLE_ARN=$(aws iam create-role --role-name "${CLUSTER}-cert-manager-operator" \
       --assume-role-policy-document "file://${SCRATCH}/trust-policy.json" \
       --query Role.Arn --output text)
  6. 将权限策略附加到角色

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

安装 cert-manager Operator

  1. 创建一个项目以将 cert-manager Operator 安装到其中

    $ oc new-project cert-manager-operator

    不要尝试在您的集群中使用多个 cert-manager Operator。如果您在集群中安装了社区 cert-manager Operator,则必须先卸载它,然后再安装 Red Hat OpenShift 的 cert-manager Operator。

  2. 安装 Red Hat OpenShift 的 cert-manager Operator

    $ cat << EOF | oc apply -f -
    apiVersion: operators.coreos.com/v1
    kind: OperatorGroup
    metadata:
      name: openshift-cert-manager-operator-group
      namespace: cert-manager-operator
    spec:
      targetNamespaces:
      - cert-manager-operator
    ---
    apiVersion: operators.coreos.com/v1alpha1
    kind: Subscription
    metadata:
      name: openshift-cert-manager-operator
      namespace: cert-manager-operator
    spec:
      channel: stable-v1
      installPlanApproval: Automatic
      name: openshift-cert-manager-operator
      source: redhat-operators
      sourceNamespace: openshift-marketplace
    EOF

    此 Operator 需要几分钟才能安装并完成设置。

  3. 验证 cert-manager Operator 是否正在运行

    $ oc -n cert-manager-operator get pods
    示例输出
    NAME                                                        READY   STATUS    RESTARTS   AGE
    cert-manager-operator-controller-manager-84b8799db5-gv8mx   2/2     Running   0          12s
  4. 使用您之前创建的 AWS IAM 角色注释 cert-manager pod 使用的服务帐户

    $ oc -n cert-manager annotate serviceaccount cert-manager eks.amazonaws.com/role-arn=${ROLE_ARN}
  5. 通过运行以下命令重新启动现有的 cert-manager 控制器 pod

    $ oc -n cert-manager delete pods -l app.kubernetes.io/name=cert-manager
  6. 修补 Operator 的配置以使用外部名称服务器,以防止 DNS-01 挑战解析问题

    $ oc patch certmanager.operator.openshift.io/cluster --type merge \
      -p '{"spec":{"controllerConfig":{"overrideArgs":["--dns01-recursive-nameservers-only","--dns01-recursive-nameservers=1.1.1.1:53"]}}}'
  7. 通过运行以下命令创建一个 `ClusterIssuer` 资源以使用 Let’s Encrypt

    $ cat << EOF | oc apply -f -
    apiVersion: cert-manager.io/v1
    kind: ClusterIssuer
    metadata:
      name: letsencrypt-production
    spec:
      acme:
        server: https://acme-v02.api.letsencrypt.org/directory
        email: ${EMAIL}
        # This key doesn't exist, cert-manager creates it
        privateKeySecretRef:
          name: prod-letsencrypt-issuer-account-key
        solvers:
        - dns01:
            route53:
             hostedZoneID: ${ZONE_ID}
             region: ${REGION}
             secretAccessKeySecretRef:
               name: ''
    EOF
  8. 验证 `ClusterIssuer` 资源是否已准备好

    $ oc get clusterissuer.cert-manager.io/letsencrypt-production
    示例输出
    NAME                     READY   AGE
    letsencrypt-production   True    47s

创建自定义域 Ingress Controller

  1. 创建和配置证书资源以为主自定义域 Ingress Controller 供应证书

    以下示例使用单个域证书。SAN 和通配符证书也受支持。

    $ cat << EOF | oc apply -f -
    apiVersion: cert-manager.io/v1
    kind: Certificate
    metadata:
      name: custom-domain-ingress-cert
      namespace: openshift-ingress
    spec:
      secretName: custom-domain-ingress-cert-tls
      issuerRef:
         name: letsencrypt-production
         kind: ClusterIssuer
      commonName: "${DOMAIN}"
      dnsNames:
      - "${DOMAIN}"
    EOF
  2. 验证证书是否已颁发

    Let’s Encrypt 颁发此证书需要几分钟时间。如果超过 5 分钟,请运行 `oc -n openshift-ingress describe certificate.cert-manager.io/custom-domain-ingress-cert` 以查看 cert-manager 报告的任何问题。

    $ oc -n openshift-ingress get certificate.cert-manager.io/custom-domain-ingress-cert
    示例输出
    NAME                         READY   SECRET                           AGE
    custom-domain-ingress-cert   True    custom-domain-ingress-cert-tls   9m53s
  3. 创建一个新的 `IngressController` 资源

    $ cat << EOF | oc apply -f -
    apiVersion: operator.openshift.io/v1
    kind: IngressController
    metadata:
      name: custom-domain-ingress
      namespace: openshift-ingress-operator
    spec:
      domain: ${DOMAIN}
      defaultCertificate:
        name: custom-domain-ingress-cert-tls
      endpointPublishingStrategy:
        loadBalancer:
          dnsManagementPolicy: Unmanaged
          providerParameters:
            aws:
              type: NLB
            type: AWS
          scope: External
        type: LoadBalancerService
    EOF

    此 `IngressController` 示例将在您的 AWS 账户中创建一个可访问互联网的网络负载均衡器 (NLB)。要改为配置内部 NLB,请在创建 `IngressController` 资源之前将 `.spec.endpointPublishingStrategy.loadBalancer.scope` 参数设置为 `Internal`。

  4. 验证您的自定义域 IngressController 是否已成功创建外部负载均衡器

    $ oc -n openshift-ingress get service/router-custom-domain-ingress
    示例输出
    NAME                           TYPE           CLUSTER-IP      EXTERNAL-IP                                                                     PORT(S)                      AGE
    router-custom-domain-ingress   LoadBalancer   172.30.174.34   a309962c3bd6e42c08cadb9202eca683-1f5bbb64a1f1ec65.elb.us-east-1.amazonaws.com   80:31342/TCP,443:31821/TCP   7m28s
  5. 准备一份文档,其中包含启用自定义域名 Ingress Controller 的 DNS 解析所需的 DNS 更改。

    $ INGRESS=$(oc -n openshift-ingress get service/router-custom-domain-ingress -ojsonpath="{.status.loadBalancer.ingress[0].hostname}")
    $ cat << EOF > "${SCRATCH}/create-cname.json"
    {
      "Comment":"Add CNAME to custom domain endpoint",
      "Changes":[{
          "Action":"CREATE",
          "ResourceRecordSet":{
            "Name": "*.${DOMAIN}",
          "Type":"CNAME",
          "TTL":30,
          "ResourceRecords":[{
            "Value": "${INGRESS}"
          }]
        }
      }]
    }
    EOF
  6. 将您的更改提交到 Amazon Route 53 以进行传播。

    $ aws route53 change-resource-record-sets \
      --hosted-zone-id ${ZONE_ID} \
      --change-batch file://${SCRATCH}/create-cname.json

    虽然通配符 CNAME 记录避免了为使用自定义域名 Ingress Controller 部署的每个新应用程序创建新记录的需要,但这些应用程序使用的证书**不是**通配符证书。

为自定义域名路由配置动态证书

现在您可以公开指定域名任何一级子域上的集群应用程序,但连接不会使用与应用程序域名匹配的 TLS 证书进行保护。为确保这些集群应用程序拥有每个域名名的有效证书,请配置 cert-manager 为在此域名下创建的每个新路由动态颁发证书。

  1. 创建 cert-manager 管理 OpenShift 路由证书所需的必要 OpenShift 资源。

    此步骤创建一个新的部署(因此是一个 pod),专门监控集群中带注释的路由。如果在新的路由中找到issuer-kindissuer-name注释,它会向颁发者(此处为 ClusterIssuer)请求一个对该路由唯一的新的证书,并且该证书将遵守创建路由时指定的 hostname。

    如果集群无法访问 GitHub,您可以将原始内容保存在本地并运行oc apply -f localfilename.yaml -n cert-manager

    $ oc -n cert-manager apply -f https://github.com/cert-manager/openshift-routes/releases/latest/download/cert-manager-openshift-routes.yaml

    此步骤还会创建以下其他 OpenShift 资源

    • ClusterRole - 授予权限以监视和更新整个集群中的路由

    • ServiceAccount - 使用权限运行新创建的 pod

    • ClusterRoleBinding - 绑定这两个资源

  2. 确保新的cert-manager-openshift-routes pod 运行成功。

    $ oc -n cert-manager get pods
    示例结果
    NAME                                             READY   STATUS    RESTARTS   AGE
    cert-manager-866d8f788c-9kspc                    1/1     Running   0          4h21m
    cert-manager-cainjector-6885c585bd-znws8         1/1     Running   0          4h41m
    cert-manager-openshift-routes-75b6bb44cd-f8kd5   1/1     Running   0          6s
    cert-manager-webhook-8498785dd9-bvfdf            1/1     Running   0          4h41m

部署示例应用程序

现在已经配置了动态证书,您可以部署一个示例应用程序来确认在公开新路由时是否已配置和信任证书。

  1. 为您的示例应用程序创建一个新项目。

    $ oc new-project hello-world
  2. 部署一个 hello world 应用程序。

    $ oc -n hello-world new-app --image=docker.io/openshift/hello-openshift
  3. 创建一个路由以从集群外部公开应用程序。

    $ oc -n hello-world create route edge --service=hello-openshift hello-openshift-tls --hostname hello.${DOMAIN}
  4. 验证路由的证书是否不受信任。

    $ curl -I https://hello.${DOMAIN}
    示例输出
    curl: (60) SSL: no alternative certificate subject name matches target host name 'hello.example.com'
    More details here: https://curl.se/docs/sslcerts.html
    
    curl failed to verify the legitimacy of the server and therefore could not
    establish a secure connection to it. To learn more about this situation and
    how to fix it, please visit the web page mentioned above.
  5. 为路由添加注释以触发 cert-manager 为自定义域名配置证书。

    $ oc -n hello-world annotate route hello-openshift-tls cert-manager.io/issuer-kind=ClusterIssuer cert-manager.io/issuer-name=letsencrypt-production

    证书创建需要 2-3 分钟。cert-manager 运算符将在证书接近过期时自动管理证书的续订。

  6. 验证路由的证书现在是否受信任。

    $ curl -I https://hello.${DOMAIN}
    示例输出
    HTTP/2 200
    date: Thu, 05 Oct 2023 23:45:33 GMT
    content-length: 17
    content-type: text/plain; charset=utf-8
    set-cookie: 52e4465485b6fb4f8a1b1bed128d0f3b=68676068bb32d24f0f558f094ed8e4d7; path=/; HttpOnly; Secure; SameSite=None
    cache-control: private

动态证书配置故障排除

创建证书时,验证过程通常需要 2-3 分钟才能完成。

如果在证书创建步骤中,为路由添加注释没有触发证书创建,请针对每个certificatecertificaterequestorderchallenge资源运行oc describe以查看可以帮助识别问题原因的事件或原因。

$ oc get certificate,certificaterequest,order,challenge

有关故障排除,您可以参考此关于调试证书的有用指南

您还可以使用cmctl CLI 工具执行各种证书管理活动,例如检查证书状态和测试续订。