新:在TwitterMastodon上获取项目更新。

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.yaml
podLabels:
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-87a89d5c62d4
AZURE_FEDERATED_TOKEN_FILE: /var/run/secrets/azure/tokens/azure-identity-token
AZURE_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

📖 阅读有关 Azure AD 工作负载身份对 Kubernetes 中的变异准入 Webhook 的作用

创建托管身份

为了使 cert-manager 使用 Azure API 并操作 Azure DNS 区域中的记录,它需要一个 Azure 帐户,而最好的帐户类型称为“托管身份”。此帐户没有密码或 API 密钥,它设计用于机器而不是人类使用。

选择托管身份名称并创建托管身份

export IDENTITY_NAME=cert-manager
az 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/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
spec:
acme:
server: https://acme-staging-v02.api.letsencrypt.org/directory
email: $EMAIL_ADDRESS
privateKeySecretRef:
name: letsencrypt-staging
solvers:
- dns01:
azureDNS:
hostedZoneName: $AZURE_ZONE_NAME
resourceGroupName: $AZURE_RESOURCE_GROUP
subscriptionID: $AZURE_SUBSCRIPTION_ID
environment: AzurePublicCloud
managedIdentity:
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-clijq 的示例创建
# 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 assignment
PRINCIPAL_ID=$(echo $IDENTITY | jq -r '.principalId')
# Used for identity binding
CLIENT_ID=$(echo $IDENTITY | jq -r '.clientId')
RESOURCE_ID=$(echo $IDENTITY | jq -r '.id')
# Get existing DNS Zone Id
ZONE_ID=$(az network dns zone show --name $ZONE_NAME --resource-group $ZONE_GROUP --query "id" -o tsv)
# Create role assignment
az 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 Identity
resource "azurerm_user_assigned_identity" "dns_identity" {
name = "cert-manager-dns01"
resource_group_name = var.resource_group_name
location = var.location
}
# Creates Role Assignment
resource "azurerm_role_assignment" "dns_contributor" {
scope = var.dns_zone_id
role_definition_name = "DNS Zone Contributor"
principal_id = azurerm_user_assigned_identity.dns_identity.principal_id
}
# Client Id Used for identity binding
output "identity_client_id" {
value = azurerm_user_assigned_identity.dns_identity.client_id
}
# Resource Id Used for identity binding
output "identity_resource_id" {
value = azurerm_user_assigned_identity.dns_identity.id
}

接下来,我们需要确保我们已使用他们的演练安装了 AAD Pod 身份。这将安装分配身份所需的 CRD 和部署。

现在我们可以使用以下清单作为示例创建身份资源和绑定

apiVersion: "aadpodidentity.k8s.io/v1"
kind: AzureIdentity
metadata:
annotations:
# recommended to use namespaced identites https://azure.github.io/aad-pod-identity/docs/configure/match_pods_in_namespace/
aadpodidentity.k8s.io/Behavior: namespaced
name: certman-identity
namespace: cert-manager # change to your preferred namespace
spec:
type: 0 # MSI
resourceID: <Identity_Id> # Resource Id From Previous step
clientID: <Client_Id> # Client Id from previous step
---
apiVersion: "aadpodidentity.k8s.io/v1"
kind: AzureIdentityBinding
metadata:
name: certman-id-binding
namespace: cert-manager # change to your preferred namespace
spec:
azureIdentity: certman-identity
selector: 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 区域指定 hostedZoneNameresourceGroupNamesubscriptionID 字段。以下示例

apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: example-issuer
spec:
acme:
...
solvers:
- dns01:
azureDNS:
subscriptionID: AZURE_SUBSCRIPTION_ID
resourceGroupName: AZURE_DNS_ZONE_RESOURCE_GROUP
hostedZoneName: AZURE_DNS_ZONE
# Azure Cloud Environment, default to AzurePublicCloud
environment: 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 DashboardVirtual NodeHTTP Application Routing(查看完整列表 这里))将创建分配给节点池的额外身份。如果您的节点池分配了多个身份,则需要指定 clientIDresourceID 来选择正确的身份。

要设置此项,首先您需要通过查询 AKS 集群来检索 kubelet 使用的身份。然后可以使用它在 DNS 区域中创建相应的权限。

  • 使用 azure-cli 的示例命令
# Get AKS Kubelet Identity
PRINCIPAL_ID=$(az aks show -n $CLUSTERNAME -g $CLUSTER_GROUP --query "identityProfile.kubeletidentity.objectId" -o tsv)
# Get existing DNS Zone Id
ZONE_ID=$(az network dns zone show --name $ZONE_NAME --resource-group $ZONE_GROUP --query "id" -o tsv)
# Create role assignment
az 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 kubelet
identity {
type = "SystemAssigned"
}
...
}
resource "azurerm_role_assignment" "dns_contributor" {
scope = var.dns_zone_id
role_definition_name = "DNS Zone Contributor"
principal_id = azurerm_kubernetes_cluster.cluster.kubelet_identity[0].object_id
skip_service_principal_aad_check = true # Allows skipping propagation of identity to ensure assignment succeeds.
}

然后在创建 cert-manager 发行器时,我们需要指定 hostedZoneNameresourceGroupNamesubscriptionID 字段用于 DNS 区域。

如果将多个托管身份分配给节点池,我们还需要指定 managedIdentity.clientIDmanagedIdentity.resourceID

可以通过运行以下命令获取 managedIdentity.clientID 的值

az aks show -n $CLUSTERNAME -g $CLUSTER_GROUP --query "identityProfile.kubeletidentity.clientId" -o tsv

以下示例

apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: example-issuer
spec:
acme:
...
solvers:
- dns01:
azureDNS:
subscriptionID: AZURE_SUBSCRIPTION_ID
resourceGroupName: AZURE_DNS_ZONE_RESOURCE_GROUP
hostedZoneName: AZURE_DNS_ZONE
# Azure Cloud Environment, default to AzurePublicCloud
environment: AzurePublicCloud
# optional, only required if node pools have more than 1 managed identity assigned
managedIdentity:
# 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-clijq

# 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/v1
kind: Issuer
metadata:
name: example-issuer
spec:
acme:
...
solvers:
- dns01:
azureDNS:
clientID: AZURE_CERT_MANAGER_SP_APP_ID
clientSecretSecretRef:
# The following is the secret we created in Kubernetes. Issuer will use this to present challenge to Azure DNS.
name: azuredns-config
key: client-secret
subscriptionID: AZURE_SUBSCRIPTION_ID
tenantID: AZURE_TENANT_ID
resourceGroupName: AZURE_DNS_ZONE_RESOURCE_GROUP
hostedZoneName: AZURE_DNS_ZONE
# Azure Cloud Environment, default to AzurePublicCloud
environment: AzurePublicCloud