新着情報: プロジェクトの最新情報をTwitterMastodonで入手しましょう

AzureDNS

cert-manager は Azure DNS で DNS-01 レコードを作成および削除できますが、最初に Azure に認証する必要があります。利用可能な認証方法は 4 つあります。

AAD ワークロード ID を使用したマネージド ID

ℹ️ この機能は、cert-manager >= v1.11.0 で利用可能です。

📖 この認証方法のエンドツーエンドの例については、AKS + LoadBalancer + Let's Encrypt チュートリアルを参照してください。

Azure Kubernetes Service (AKS) 上の Azure AD ワークロード ID (プレビュー) を使用すると、cert-manager は Kubernetes ServiceAccount トークンを使用して Azure に認証し、Azure DNS で DNS-01 レコードを管理できます。これは、他の方法よりも安全で管理が容易であるため、推奨される認証方法です。

クラスターの再構成

クラスターでワークロード ID フェデレーション機能を有効にします。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 ワークロード ID 拡張機能をインストールできます。

📖 --enable-workload-identity 機能の詳細については、Azure Kubernetes Service (AKS) クラスターにワークロード ID をデプロイおよび構成するを参照してください。

cert-manager の再構成

Azure ワークロード ID Webhook が注目するように、cert-manager コントローラー Pod と ServiceAccount にラベルを付けます。これにより、cert-manager コントローラー Pod に、Azure での認証に使用する Kubernetes ServiceAccount トークンを含む追加ボリュームが作成されます。

Helm を使用して cert-manager をインストールした場合、ラベルは Helm の値を使用して構成できます。

# values.yaml
podLabels:
azure.workload.identity/use: "true"
serviceAccount:
labels:
azure.workload.identity/use: "true"

成功すると、cert-manager Pod にいくつかの新しい環境変数が設定され、Azure ワークロード ID ServiceAccount トークンが projected ボリュームとして設定されます。

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

📖 Kubernetes 用 Azure AD ワークロード ID における Mutating Admission Webhook の役割についてお読みください。

マネージド ID の作成

cert-manager が Azure API を使用して Azure DNS ゾーン内のレコードを操作するには、Azure アカウントが必要です。使用する最適なアカウントの種類は「マネージド ID」と呼ばれます。このアカウントにはパスワードや API キーは付属しておらず、人間ではなくマシンで使用するように設計されています。

マネージド ID 名を選択し、マネージド ID を作成します。

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)

📖 マネージド ID とその用途の概要については、Azure リソースのマネージド ID とは何ですか? をお読みください。

📖 「DNS ゾーン共同作成者」ロールの詳細については、Azure の組み込みロールをお読みください。

📖 az identity コマンドの詳細をお読みください。

フェデレーション ID の追加

次に、以前に作成したマネージド ID にフェデレーション ID を関連付けます。cert-manager は、有効期間の短い Kubernetes ServiceAccount トークンを使用して Azure に認証し、前の手順で作成したマネージド ID を偽装できるようになります。

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 ID プラットフォームのドキュメントの ワークロード ID フェデレーションについてお読みください。

📖 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 ID を使用したマネージド ID

⚠️ Azure Kubernetes Service のオープンソース Azure AD ポッドマネージド ID (プレビュー) は、2022 年 10 月 24 日で非推奨になりました。代わりにワークロード ID を使用してください。

AAD Pod ID を使用すると、マネージド ID をポッドに割り当てることができます。これにより、必要な DNS レコードを作成するために、明示的な認証情報をクラスターに追加する必要がなくなります。

注: ポッド ID を使用する場合、単一のポッドに複数の ID を割り当てることが許可されていますが、現在、cert-manager は、使用する ID を識別できないため、これをサポートしていません。

まず、DNS ゾーンへの投稿を許可するアクセス権を持つ ID を作成する必要があります。

  • 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 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 Identity がインストールされていることを確認する必要があります。これにより、ID の割り当てに必要な CRD とデプロイメントがインストールされます。

次に、次のマニフェストを例として使用して、ID リソースとバインディングを作成できます。

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

次に、ポッド ID バインディングを使用するための関連ラベルが cert-manager ポッドにあることを確認する必要があります。これは、デプロイメントを編集し、次のものを .spec.template.metadata.labels フィールドに追加することで実行できます。

spec:
template:
metadata:
labels:
aadpodidbinding: certman-label # must match selector in AzureIdentityBinding

または、helm 値 podLabels を使用します。

podLabels:
aadpodidbinding: certman-label

最後に、証明書発行者を作成する場合、DNS ゾーンの hostedZoneNameresourceGroupName、および subscriptionID フィールドのみを指定する必要があります。以下の例を参照してください。

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 マネージド ID を有効にするために必要です。

AKS Kubelet IDを使用したマネージドID

AzureでAKSクラスターを作成する際、kubeletに割り当てられるマネージドIDを使用するオプションがあります。このIDはAKSクラスター内の基盤となるノードプールに割り当てられ、cert-managerポッドがAzure Active Directoryへの認証に使用できます。

このアプローチにはいくつかの注意点があります。主なものは以下のとおりです。

  • このIDに付与されたすべての権限は、Kubernetesクラスター内で実行されているすべてのコンテナからもアクセス可能になります。
  • Kube DashboardVirtual NodeHTTP Application RoutingなどのAKS拡張機能(詳細なリストはこちら)を使用すると、ノードプールに割り当てられる追加のIDが作成されます。ノードプールに複数のIDが割り当てられている場合は、適切なIDを選択するためにclientIDまたはresourceIDを指定する必要があります。

これを設定するには、まずAKSクラスターをクエリして、kubeletが使用しているIDを取得する必要があります。その後、これを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 issuerを作成する際に、DNSゾーンのhostedZoneNameresourceGroupNamesubscriptionIDフィールドを指定する必要があります。

また、複数のマネージドIDがノードプールに割り当てられている場合は、managedIdentity.clientIDまたはmanagedIdentity.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ゾーン共同作成者」ロールを持つオブジェクトがpermissions配列に1つだけ表示されることを期待します。

$ az role assignment list --all --assignee $AZURE_CERT_MANAGER_SP_APP_ID

Azure DNSにチャレンジを提示するために、サービスプリンシパルのパスワードを含むシークレットをKubernetes上に作成する必要があります。次のコマンドでシークレットを作成できます。

$ kubectl create secret generic azuredns-config --from-literal=client-secret=$AZURE_CERT_MANAGER_SP_PASSWORD

issuerを構成するための変数を取得します。

$ 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"

issuerを構成するには、大文字の変数を前のスクリプトの値に置き換えます。サブスクリプションIDはAzureポータルから取得できます。

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