返回 Skill 列表
extension
分类: 开发与工程无需 API Key

kv-cert

Azure Key Vault证书管理。触发条件:当用户请求在Azure Key Vault中创建/查看/管理SSL证书,将证书导入到App Service,使用SSL绑定自定义域名,授予KV RBAC权限,或解决KV证书问题时(例如,'创建KV证书','向应用服务添加SSL','绑定自定义域名','授予KV权限','KV 证书','创建证书','绑定域名','SSL绑定')

person作者: jakexiaohubgithub

Azure Key Vault Certificate Management

Manage SSL certificates in Azure Key Vault and bind them to App Service custom domains.

RBAC Permissions

KV uses RBAC authorization. Key roles:

| Role | Purpose | Role Definition ID | |---|---|---| | Key Vault Certificate User | Read certs | db79e9a7-68ee-4b58-9aeb-b90e7c24fcba | | Key Vault Certificates Officer | Create/import/delete certs | a4417e6f-fecd-4de8-b567-7b0420556985 | | Key Vault Secrets User | Read secrets (cert private keys are stored as secrets) | 4633458b-17de-408a-b874-0445c86b69e6 |

Grant RBAC via REST API

az role assignment create may fail with MissingSubscription in some CLI versions. Use REST API instead:

# Get current user Object ID
user_oid=$(az ad signed-in-user show --query id -o tsv)

# Get KV resource ID
kv_id=$(az keyvault show --name <kv-name> --resource-group <rg> --query id -o tsv)

# Grant Key Vault Certificates Officer
az rest --method PUT \
  --url "https://management.azure.com${kv_id}/providers/Microsoft.Authorization/roleAssignments/$(python -c 'import uuid; print(uuid.uuid4())')?api-version=2022-04-01" \
  --body "{
    \"properties\": {
      \"roleDefinitionId\": \"/subscriptions/<sub-id>/providers/Microsoft.Authorization/roleDefinitions/a4417e6f-fecd-4de8-b567-7b0420556985\",
      \"principalId\": \"${user_oid}\",
      \"principalType\": \"User\"
    }
  }"

RBAC propagation takes 1-5 minutes.

Grant App Service WebApp SP access to KV

App Service reads KV certs via the Azure WebApp 1st-party Service Principal (Application ID: abfa0a7c-a6b6-4736-8310-5855508787cd, Display Name: Microsoft Azure App Service), NOT the UAMI bound to the App Service.

CRITICAL: Application ID ≠ Object ID. RBAC principalId must use Object ID, not Application ID. Using Application ID silently assigns to the wrong principal.

Grant it Key Vault Certificate User + Key Vault Secrets User roles:

azure_webapp_app_id="abfa0a7c-a6b6-4736-8310-5855508787cd"
# Resolve Object ID (different per tenant — never hardcode)
azure_webapp_sp_oid=$(az ad sp show --id "${azure_webapp_app_id}" --query id -o tsv)
# Use the REST API pattern above with principalId="${azure_webapp_sp_oid}" and principalType ServicePrincipal
# Roles needed: db79e9a7-... (Certificate User) + 4633458b-... (Secrets User)

Common Queries

# List certificates in KV
az keyvault certificate list --vault-name <kv-name> \
  --query "[].{name:name, thumbprint:x509ThumbprintHex}" -o table

# Show certificate details
az keyvault certificate show --vault-name <kv-name> --name <cert-name> \
  --query "{
    name:name, thumbprint:x509ThumbprintHex,
    subject:policy.x509CertificateProperties.subject,
    notBefore:attributes.notBefore, expires:attributes.expires,
    issuer:policy.issuerParameters.name,
    sansCount:length(policy.x509CertificateProperties.subjectAlternativeNames.dnsNames)
  }" -o json

# Show SAN list
az keyvault certificate show --vault-name <kv-name> --name <cert-name> \
  --query "policy.x509CertificateProperties.subjectAlternativeNames.dnsNames" -o json

# List configured issuers
az keyvault certificate issuer list --vault-name <kv-name> -o table

Create Certificate via OneCert CA

1. Prepare Policy JSON

{
  "issuerParameters": { "name": "OneCertV2-PublicCA" },
  "keyProperties": { "exportable": true, "keySize": 2048, "keyType": "RSA", "reuseKey": false },
  "lifetimeActions": [{ "action": { "actionType": "AutoRenew" }, "trigger": { "lifetimePercentage": 80 } }],
  "secretProperties": { "contentType": "application/x-pkcs12" },
  "x509CertificateProperties": {
    "ekus": ["1.3.6.1.5.5.7.3.1", "1.3.6.1.5.5.7.3.2"],
    "keyUsage": ["digitalSignature", "keyEncipherment"],
    "subject": "CN=*.example.dev.copilotlumina.com",
    "subjectAlternativeNames": {
      "dnsNames": [
        "*.example.dev.copilotlumina.com",
        "*.sub1.example.dev.copilotlumina.com"
      ]
    },
    "validityInMonths": 6
  }
}

Key fields:

  • issuerParameters.name — must be a CA configured in the KV (az keyvault certificate issuer list)
  • subject — Certificate CN; also add it to SAN dnsNames (modern clients only check SAN)
  • lifetimeActions.AutoRenew — auto-renew at 80% lifetime
  • ekus1.3.6.1.5.5.7.3.1 = Server Auth, 1.3.6.1.5.5.7.3.2 = Client Auth
  • contentTypeapplication/x-pkcs12 = PFX format with private key

2. Create

az keyvault certificate create \
  --vault-name <kv-name> --name <cert-name> \
  --policy @cert-policy.json

3. Copy policy from existing cert

# Export policy
az keyvault certificate show --vault-name <source-kv> --name <source-cert> \
  --query "policy" -o json > cert-policy.json
# Edit: change subject/CN, dnsNames, remove read-only fields (attributes, id)
# Create with new policy
az keyvault certificate create --vault-name <target-kv> --name <new-cert> --policy @cert-policy.json

App Service: Import KV Certificate & Bind Custom Domains

Step 1: Import certificate

thumbprint=$(az webapp config ssl import \
  --name <webapp-name> --resource-group <rg> \
  --key-vault <kv-name> --key-vault-certificate-name <cert-name> \
  --query thumbprint -o tsv)

Step 2: Add custom hostname

az webapp config hostname add \
  --webapp-name <webapp-name> --resource-group <rg> \
  --hostname "*.example.dev.copilotlumina.com"

Prerequisite: DNS CNAME to <webapp>.azurewebsites.net, or TXT record asuid.<hostname> for domain verification.

Step 3: Bind SSL (SNI)

az webapp config ssl bind \
  --name <webapp-name> --resource-group <rg> \
  --certificate-thumbprint <thumbprint> --ssl-type SNI

Batch operation pattern

thumbprint=$(az webapp config ssl import --name ${webapp} --resource-group ${rg} \
  --key-vault ${kv} --key-vault-certificate-name ${cert} --query thumbprint -o tsv)

for hostname in "${hostnames[@]}"; do
  az webapp config hostname add --webapp-name ${webapp} --resource-group ${rg} \
    --hostname "${hostname}" 2>/dev/null || true
  az webapp config ssl bind --name ${webapp} --resource-group ${rg} \
    --certificate-thumbprint ${thumbprint} --ssl-type SNI 2>/dev/null || true
done

Verify App Service Bindings

# List custom hostnames
az webapp config hostname list --webapp-name <webapp> --resource-group <rg> -o table

# List SSL certificates
az webapp config ssl list --resource-group <rg> \
  --query "[].{name:name, thumbprint:thumbprint, subject:subjectName, expires:expirationDate}" -o table

# Show hostname + SSL binding status
az webapp show --name <webapp> --resource-group <rg> \
  --query "hostNameSslStates[].{name:name, sslState:sslState, thumbprint:thumbprint}" -o table

Common Issues

| Problem | Solution | |---|---| | az role assignment createMissingSubscription | Use az rest --method PUT REST API instead | | ForbiddenByConnection | KV has public network access disabled — enable temporarily or use Private Endpoint/VPN | | ForbiddenByRbac | Missing RBAC role — grant via REST API (see above) | | CN not in SAN → domain invalid | Always include CN in SAN dnsNames; modern clients only check SAN | | App Service can't read KV cert | Grant Key Vault Certificate User + Key Vault Secrets User to WebApp 1st-party SP. Use Object ID (via az ad sp show --id "abfa0a7c-..." --query id), NOT Application ID |

Network Access

If KV has public network access disabled (ForbiddenByConnection):

  • Temporarily enable public access (Portal → KV → Networking)
  • Or use Private Endpoint / VPN
  • AKS pods cannot reach KV private endpoint unless a Private DNS Zone for privatelink.vaultcore.azure.net is linked to the AKS VNet