AzureDNS
cert-manager 可以创建然后删除 Azure DNS 中的 DNS-01 记录,但它需要先对 Azure 进行身份验证。 有四种可用的身份验证方法
使用 AAD 工作负载身份的托管身份
ℹ️ 此功能在 cert-manager
>= v1.11.0
中可用。📖 阅读 AKS + LoadBalancer + Let's Encrypt 教程 以获取此身份验证方法的端到端示例。
Azure AD 工作负载身份(预览版)在 Azure Kubernetes 服务 (AKS) 上允许 cert-manager 使用 Kubernetes ServiceAccount 令牌对 Azure 进行身份验证,然后在 Azure DNS 中管理 DNS-01 记录。这是推荐的身份验证方法,因为它比其他方法更安全且更易于维护。
重新配置集群
在您的集群上启用工作负载身份联合功能。如果您有 Azure AKS 集群,可以使用以下命令
az aks update \--name ${CLUSTER} \--enable-oidc-issuer \--enable-workload-identity # ℹ️ This option is currently only available when using the aks-preview extension.
ℹ️ 如果您没有使用 Azure AKS,则可以 在其他托管和自托管集群上安装 Azure 工作负载身份扩展。
📖 阅读 在 Azure Kubernetes 服务 (AKS) 集群上部署和配置工作负载身份 以获取有关
--enable-workload-identity
功能的更多信息。
重新配置 cert-manager
为 Azure 工作负载身份 Webhook 的注意,标记 cert-manager 控制器 Pod 和 ServiceAccount,这将导致 cert-manager 控制器 Pod 具有一个包含 Kubernetes ServiceAccount 令牌的额外卷,它将用于对 Azure 进行身份验证。
如果您使用 Helm 安装了 cert-manager,则可以使用 Helm 值配置标签
# values.yamlpodLabels:azure.workload.identity/use: "true"serviceAccount:labels:azure.workload.identity/use: "true"
如果成功,cert-manager Pod 将设置一些新的环境变量,并将 Azure 工作负载身份 ServiceAccount 令牌作为投影卷
kubectl describe pod -n cert-manager -l app.kubernetes.io/component=controller
Containers:...cert-manager-controller:...Environment:...AZURE_CLIENT_ID:AZURE_TENANT_ID: f99bd6a4-665c-41cf-aff1-87a89d5c62d4AZURE_FEDERATED_TOKEN_FILE: /var/run/secrets/azure/tokens/azure-identity-tokenAZURE_AUTHORITY_HOST: https://login.microsoftonline.com/Mounts:/var/run/secrets/azure/tokens from azure-identity-token (ro)Volumes:...azure-identity-token:Type: Projected (a volume that contains injected data from multiple sources)TokenExpirationSeconds: 3600
创建托管身份
为了使 cert-manager 使用 Azure API 并操作 Azure DNS 区域中的记录,它需要一个 Azure 帐户,而最好的帐户类型称为“托管身份”。此帐户没有密码或 API 密钥,它设计用于机器而不是人类使用。
选择托管身份名称并创建托管身份
export IDENTITY_NAME=cert-manageraz identity create --name "${IDENTITY_NAME}"
授予其修改 DNS 区域记录的权限
export IDENTITY_CLIENT_ID=$(az identity show --name "${IDENTITY_NAME}" --query 'clientId' -o tsv)az role assignment create \--role "DNS Zone Contributor" \--assignee IDENTITY_CLIENT_ID \--scope $(az network dns zone show --name $DOMAIN_NAME -o tsv --query id)
📖 阅读 什么是 Azure 资源的托管身份? 以概述托管身份及其用途。
📖 阅读 Azure 内置角色 以了解“DNS 区域贡献者”角色。
📖 阅读有关
az identity
命令 的更多信息。
添加联合身份
现在将联合身份与您之前创建的托管身份相关联。cert-manager 将使用短期的 Kubernetes ServiceAccount 令牌对 Azure 进行身份验证,并且它将能够模拟您在上一步中创建的托管身份。
export SERVICE_ACCOUNT_NAME=cert-manager # ℹ️ This is the default Kubernetes ServiceAccount used by the cert-manager controller.export SERVICE_ACCOUNT_NAMESPACE=cert-manager # ℹ️ This is the default namespace for cert-manager.export SERVICE_ACCOUNT_ISSUER=$(az aks show --resource-group $AZURE_DEFAULTS_GROUP --name $CLUSTER --query "oidcIssuerProfile.issuerUrl" -o tsv)az identity federated-credential create \--name "cert-manager" \--identity-name "${IDENTITY_NAME}" \--issuer "${SERVICE_ACCOUNT_ISSUER}" \--subject "system:serviceaccount:${SERVICE_ACCOUNT_NAMESPACE}:${SERVICE_ACCOUNT_NAME}"
--subject
: 是 Kubernetes ServiceAccount 的区别名称。--issuer
: 是 Azure 将从中下载 JWT 签名证书和其他元数据的 URL
📖 阅读有关 工作负载身份联合 在 Microsoft 身份平台文档中的内容。
📖 阅读有关
az identity federated-credential
命令 的更多信息。
配置 ClusterIssuer
例如
apiVersion: cert-manager.io/v1kind: ClusterIssuermetadata:name: letsencrypt-stagingspec:acme:server: https://acme-staging-v02.api.letsencrypt.org/directoryemail: $EMAIL_ADDRESSprivateKeySecretRef:name: letsencrypt-stagingsolvers:- dns01:azureDNS:hostedZoneName: $AZURE_ZONE_NAMEresourceGroupName: $AZURE_RESOURCE_GROUPsubscriptionID: $AZURE_SUBSCRIPTION_IDenvironment: AzurePublicCloudmanagedIdentity:clientID: $IDENTITY_CLIENT_ID
以下变量需要填写。
# An email address to which Let's Encrypt will send renewal reminders.export EMAIL_ADDRESS=<email-address># The Azure DNS zone in which the DNS-01 records will be created and deleted.export AZURE_ZONE_NAME=<domain.example.com># The Azure resource group containing the DNS zone.export AZURE_RESOURCE_GROUP=<azure-resource-group># The Azure billing account name and ID for the DNS zone.export AZURE_SUBSCRIPTION=<azure-billing-account-name>export AZURE_SUBSCRIPTION_ID=$(az account show --name $AZURE_SUBSCRIPTION --query 'id' -o tsv)
⚠️ 使用 ClusterIssuer 和 Issuer 资源的“环境凭据”
这种身份验证方法是 cert-manager 所谓的“环境凭据”的示例。环境凭据默认情况下为 ClusterIssuer 资源启用,但默认情况下为 Issuer 资源禁用。这是为了防止没有权限的用户(有权创建 Issuer 资源)使用 cert-manager 偶然访问的凭据来颁发证书。ClusterIssuer 资源是集群范围的(不是命名空间的),只有平台管理员应该被授予创建它们的权限。
如果您正在使用此身份验证机制并且环境凭据未启用,您将看到此错误
error instantiating azuredns challenge solver: ClientID is not set but neither --cluster-issuer-ambient-credentials nor --issuer-ambient-credentials are set.
⚠️ 可以(但不建议)为
Issuer
资源启用此身份验证机制,方法是在 cert-manager 控制器上设置--issuer-ambient-credentials
标志为 true。
使用 AAD Pod 身份的托管身份
⚠️ Azure Kubernetes 服务中的开源 Azure AD Pod 托管身份 (预览版) 自 2022 年 10 月 24 日起已弃用。改用工作负载身份。
AAD Pod 身份 允许将 托管身份 分配给 Pod。这消除了将显式凭据添加到集群以创建所需 DNS 记录的需要。
注意:使用 Pod 身份时,即使允许将多个身份分配给单个 Pod,但目前 cert-manager 不支持此功能,因为它无法识别要使用的身份。
首先应该创建一个身份,该身份有权为 DNS 区域做出贡献。
- 使用
azure-cli
和jq
的示例创建
# Choose a unique Identity name and existing resource group to create identity in.IDENTITY=$(az identity create --name $IDENTITY_NAME --resource-group $IDENTITY_GROUP --output json)# Gets principalId to use for role assignmentPRINCIPAL_ID=$(echo $IDENTITY | jq -r '.principalId')# Used for identity bindingCLIENT_ID=$(echo $IDENTITY | jq -r '.clientId')RESOURCE_ID=$(echo $IDENTITY | jq -r '.id')# Get existing DNS Zone IdZONE_ID=$(az network dns zone show --name $ZONE_NAME --resource-group $ZONE_GROUP --query "id" -o tsv)# Create role assignmentaz role assignment create --role "DNS Zone Contributor" --assignee $PRINCIPAL_ID --scope $ZONE_ID
- 使用 Terraform 的示例创建
variable resource_group_name {}variable location {}variable dns_zone_id {}# Creates Identityresource "azurerm_user_assigned_identity" "dns_identity" {name = "cert-manager-dns01"resource_group_name = var.resource_group_namelocation = var.location}# Creates Role Assignmentresource "azurerm_role_assignment" "dns_contributor" {scope = var.dns_zone_idrole_definition_name = "DNS Zone Contributor"principal_id = azurerm_user_assigned_identity.dns_identity.principal_id}# Client Id Used for identity bindingoutput "identity_client_id" {value = azurerm_user_assigned_identity.dns_identity.client_id}# Resource Id Used for identity bindingoutput "identity_resource_id" {value = azurerm_user_assigned_identity.dns_identity.id}
接下来,我们需要确保我们已使用他们的演练安装了 AAD Pod 身份。这将安装分配身份所需的 CRD 和部署。
现在我们可以使用以下清单作为示例创建身份资源和绑定
apiVersion: "aadpodidentity.k8s.io/v1"kind: AzureIdentitymetadata:annotations:# recommended to use namespaced identites https://azure.github.io/aad-pod-identity/docs/configure/match_pods_in_namespace/aadpodidentity.k8s.io/Behavior: namespacedname: certman-identitynamespace: cert-manager # change to your preferred namespacespec:type: 0 # MSIresourceID: <Identity_Id> # Resource Id From Previous stepclientID: <Client_Id> # Client Id from previous step---apiVersion: "aadpodidentity.k8s.io/v1"kind: AzureIdentityBindingmetadata:name: certman-id-bindingnamespace: cert-manager # change to your preferred namespacespec:azureIdentity: certman-identityselector: certman-label # This is the label that needs to be set on cert-manager pods
接下来,我们需要确保 cert-manager Pod 有一个相关的标签来使用 Pod 身份绑定。这可以通过编辑部署并将以下内容添加到 .spec.template.metadata.labels
字段来完成
spec:template:metadata:labels:aadpodidbinding: certman-label # must match selector in AzureIdentityBinding
或者使用 helm 值 podLabels
podLabels:aadpodidbinding: certman-label
最后,当我们创建证书颁发机构时,我们只需要为 DNS 区域指定 hostedZoneName
、resourceGroupName
和 subscriptionID
字段。以下示例
apiVersion: cert-manager.io/v1kind: Issuermetadata:name: example-issuerspec:acme:...solvers:- dns01:azureDNS:subscriptionID: AZURE_SUBSCRIPTION_IDresourceGroupName: AZURE_DNS_ZONE_RESOURCE_GROUPhostedZoneName: AZURE_DNS_ZONE# Azure Cloud Environment, default to AzurePublicCloudenvironment: AzurePublicCloud
这种身份验证机制是 cert-manager 认为的“环境凭据”。默认情况下,cert-manager Issuer
禁用环境凭据的使用。这是为了确保没有权限的用户(有权创建颁发机构)无法使用 cert-manager 偶然访问的任何凭据来颁发证书。要为 Issuer
启用此身份验证机制,您需要在 cert-manager 控制器上将 --issuer-ambient-credentials
标志设置为 true。(有一个相应的 --cluster-issuer-ambient-credentials
标志,该标志默认设置为 true
)。
如果您正在使用此身份验证机制并且环境凭据未启用,您将看到此错误
error instantiating azuredns challenge solver: ClientID is not set but neither --cluster-issuer-ambient-credentials nor --issuer-ambient-credentials are set.
这些对于启用 Azure 托管身份是必要的。
使用 AKS Kubelet 身份的托管身份
在 Azure 中创建 AKS 集群时,可以选择使用分配给 kubelet 的托管身份。此身份分配给 AKS 集群中的底层节点池,然后可以由 cert-manager Pod 用于对 Azure Active Directory 进行身份验证。
此方法有一些注意事项,主要为
- 授予此身份的任何权限也将可供 Kubernetes 集群中运行的所有容器访问。
- 使用 AKS 扩展(例如
Kube Dashboard
、Virtual Node
或HTTP Application Routing
(查看完整列表 这里))将创建分配给节点池的额外身份。如果您的节点池分配了多个身份,则需要指定clientID
或resourceID
来选择正确的身份。
要设置此项,首先您需要通过查询 AKS 集群来检索 kubelet 使用的身份。然后可以使用它在 DNS 区域中创建相应的权限。
- 使用
azure-cli
的示例命令
# Get AKS Kubelet IdentityPRINCIPAL_ID=$(az aks show -n $CLUSTERNAME -g $CLUSTER_GROUP --query "identityProfile.kubeletidentity.objectId" -o tsv)# Get existing DNS Zone IdZONE_ID=$(az network dns zone show --name $ZONE_NAME --resource-group $ZONE_GROUP --query "id" -o tsv)# Create role assignmentaz role assignment create --role "DNS Zone Contributor" --assignee $PRINCIPAL_ID --scope $ZONE_ID
- 示例 terraform
variable dns_zone_id {}# Creating the AKS cluster, abbreviated.resource "azurerm_kubernetes_cluster" "cluster" {...# Creates Identity associated to kubeletidentity {type = "SystemAssigned"}...}resource "azurerm_role_assignment" "dns_contributor" {scope = var.dns_zone_idrole_definition_name = "DNS Zone Contributor"principal_id = azurerm_kubernetes_cluster.cluster.kubelet_identity[0].object_idskip_service_principal_aad_check = true # Allows skipping propagation of identity to ensure assignment succeeds.}
然后在创建 cert-manager 发行器时,我们需要指定 hostedZoneName
、resourceGroupName
和 subscriptionID
字段用于 DNS 区域。
如果将多个托管身份分配给节点池,我们还需要指定 managedIdentity.clientID
或 managedIdentity.resourceID
。
可以通过运行以下命令获取 managedIdentity.clientID
的值
az aks show -n $CLUSTERNAME -g $CLUSTER_GROUP --query "identityProfile.kubeletidentity.clientId" -o tsv
以下示例
apiVersion: cert-manager.io/v1kind: Issuermetadata:name: example-issuerspec:acme:...solvers:- dns01:azureDNS:subscriptionID: AZURE_SUBSCRIPTION_IDresourceGroupName: AZURE_DNS_ZONE_RESOURCE_GROUPhostedZoneName: AZURE_DNS_ZONE# Azure Cloud Environment, default to AzurePublicCloudenvironment: AzurePublicCloud# optional, only required if node pools have more than 1 managed identity assignedmanagedIdentity:# client id of the node pool managed identity (can not be set at the same time as resourceID)clientID: YOUR_MANAGED_IDENTITY_CLIENT_ID# resource id of the managed identity (can not be set at the same time as clientID)# resourceID: YOUR_MANAGED_IDENTITY_RESOURCE_ID
服务主体
为 Kubernetes 集群配置 AzureDNS DNS01 挑战需要在 Azure 中创建服务主体。
要创建服务主体,可以使用以下脚本(需要 azure-cli
和 jq
)
# Choose a name for the service principal that contacts azure DNS to present# the challenge.$ AZURE_CERT_MANAGER_NEW_SP_NAME=NEW_SERVICE_PRINCIPAL_NAME# This is the name of the resource group that you have your dns zone in.$ AZURE_DNS_ZONE_RESOURCE_GROUP=AZURE_DNS_ZONE_RESOURCE_GROUP# The DNS zone name. It should be something like domain.com or sub.domain.com.$ AZURE_DNS_ZONE=AZURE_DNS_ZONE$ DNS_SP=$(az ad sp create-for-rbac --name $AZURE_CERT_MANAGER_NEW_SP_NAME --output json)$ AZURE_CERT_MANAGER_SP_APP_ID=$(echo $DNS_SP | jq -r '.appId')$ AZURE_CERT_MANAGER_SP_PASSWORD=$(echo $DNS_SP | jq -r '.password')$ AZURE_TENANT_ID=$(echo $DNS_SP | jq -r '.tenant')$ AZURE_SUBSCRIPTION_ID=$(az account show --output json | jq -r '.id')
出于安全目的,使用 RBAC 来确保您适当地维护对 Azure 中资源的访问控制是合适的。本教程生成的此服务主体仅对指定资源组中的 DNS 区域具有细粒度访问权限。它需要此权限才能对区域读写 _acme_challenge TXT 记录。
降低服务主体的权限。
$ az role assignment delete --assignee $AZURE_CERT_MANAGER_SP_APP_ID --role Contributor
授予对 DNS 区域的访问权限。
$ DNS_ID=$(az network dns zone show --name $AZURE_DNS_ZONE --resource-group $AZURE_DNS_ZONE_RESOURCE_GROUP --query "id" --output tsv)$ az role assignment create --assignee $AZURE_CERT_MANAGER_SP_APP_ID --role "DNS Zone Contributor" --scope $DNS_ID
检查权限。作为以下命令的结果,我们希望在权限数组中只看到一个具有“DNS 区域贡献者”角色的对象。
$ az role assignment list --all --assignee $AZURE_CERT_MANAGER_SP_APP_ID
在 Kubernetes 上应该创建一个包含服务主体密码的密钥,以方便将挑战呈现给 Azure DNS。您可以使用以下命令创建密钥
$ kubectl create secret generic azuredns-config --from-literal=client-secret=$AZURE_CERT_MANAGER_SP_PASSWORD
获取配置发行者的变量。
$ echo "AZURE_CERT_MANAGER_SP_APP_ID: $AZURE_CERT_MANAGER_SP_APP_ID"$ echo "AZURE_CERT_MANAGER_SP_PASSWORD: $AZURE_CERT_MANAGER_SP_PASSWORD"$ echo "AZURE_SUBSCRIPTION_ID: $AZURE_SUBSCRIPTION_ID"$ echo "AZURE_TENANT_ID: $AZURE_TENANT_ID"$ echo "AZURE_DNS_ZONE: $AZURE_DNS_ZONE"$ echo "AZURE_DNS_ZONE_RESOURCE_GROUP: $AZURE_DNS_ZONE_RESOURCE_GROUP"
要配置发行者,请用先前脚本中的值替换大写字母变量。您可以从 Azure 门户获取订阅 ID。
apiVersion: cert-manager.io/v1kind: Issuermetadata:name: example-issuerspec:acme:...solvers:- dns01:azureDNS:clientID: AZURE_CERT_MANAGER_SP_APP_IDclientSecretSecretRef:# The following is the secret we created in Kubernetes. Issuer will use this to present challenge to Azure DNS.name: azuredns-configkey: client-secretsubscriptionID: AZURE_SUBSCRIPTION_IDtenantID: AZURE_TENANT_IDresourceGroupName: AZURE_DNS_ZONE_RESOURCE_GROUPhostedZoneName: AZURE_DNS_ZONE# Azure Cloud Environment, default to AzurePublicCloudenvironment: AzurePublicCloud