关于 azure-ad-sso
azure-ad-sso 是用于在 Kubernetes 上为应用配置 Azure AD(Entra ID)OAuth2/OIDC 单点登录及组级访问控制的实施 Skill。它提供应用配置指引、通过 Key Vault 管理密钥的方案,以及故障排查资料。
主要功能
- 01Azure AD 应用注册指引
- 02OAuth2/OIDC 与 SSO 配置
- 03按组限制访问
- 04通过 CSI 管理 Key Vault 密钥
- 05应用配置与错误排查
适用场景
- 01为 Kubernetes 上的应用添加 SSO
- 02限制 Azure AD 组对应用的访问
- 03通过 Key Vault 存储应用密钥
- 04诊断重定向或组同步故障
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ julianobarbosa/ claude-code-skills --skill azure-ad-sso安装后在 Claude Code 中输入/azure-ad-sso确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/app-configs.mdazure-ad-sso-guide.mdtroubleshooting.md详细介绍
Azure AD SSO 集成技能
概述
本技能全面介绍如何为部署在 Kubernetes 集群上的应用实现 Azure AD(Entra ID)OAuth2/OIDC 单点登录,包括通过 Azure AD 组限制访问。
快速参考
支持的应用
| 应用 | 提供方 | 重定向 URI 模式 | 组同步 |
|---|---|---|---|
| DefectDojo | azuread-tenant-oauth2 | /complete/azuread-tenant-oauth2/ | 是 |
| Grafana | azuread | /login/azuread | 是 |
| ArgoCD | microsoft (Dex) | /api/dex/callback | 是 |
| Harbor | oidc | /c/oidc/callback | 是 |
| SonarQube | saml 或 oidc | /oauth2/callback/saml | 是 |
| OAuth2 Proxy | azure | /oauth2/callback | 是 |
| Keycloak | oidc | /realms/{realm}/broker/azure/endpoint | 是 |
身份验证流程选择
┌─────────────────────────────────────────────────────────────────┐
│ Access Control Decision │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Q: Who should access this application? │
│ │
│ ├─ Everyone in tenant ──► appRoleAssignmentRequired=false │
│ │ │
│ └─ Specific groups ────► appRoleAssignmentRequired=true │
│ + Assign groups to Enterprise App │
│ │
└─────────────────────────────────────────────────────────────────┘
实施工作流
阶段 1:Azure AD 应用注册
# 1. Create App Registration
APP_NAME="<application>-<environment>"
REDIRECT_URI="https://<app-domain>/complete/<provider>/"
APP_ID=$(az ad app create \
--display-name "$APP_NAME" \
--sign-in-audience "AzureADMyOrg" \
--web-redirect-uris "$REDIRECT_URI" \
--query appId -o tsv)
echo "Application (client) ID: $APP_ID"
# 2. Get Tenant ID
TENANT_ID=$(az account show --query tenantId -o tsv)
echo "Directory (tenant) ID: $TENANT_ID"
# 3. Create Client Secret
SECRET=$(az ad app credential reset \
--id $APP_ID \
--append \
--years 1 \
--query password -o tsv)
echo "Client Secret: $SECRET" # Save immediately!
阶段 2:启用组声明
# Enable security group claims in tokens
az ad app update --id $APP_ID --set groupMembershipClaims=SecurityGroup
# Add Group.Read.All permission (delegated)
az ad app permission add \
--id $APP_ID \
--api 00000003-0000-0000-c000-000000000000 \
--api-permissions 5f8c59db-677d-491f-a6b8-5f174b11ec1d=Scope
# Grant admin consent
az ad app permission admin-consent --id $APP_ID
阶段 3:按组限制访问(关键)
# Get Service Principal object ID
SP_ID=$(az ad sp list --filter "appId eq '$APP_ID'" --query "[0].id" -o tsv)
# Enable user assignment requirement
az ad sp update --id $SP_ID --set appRoleAssignmentRequired=true
# Get the group ID to restrict access
GROUP_ID=$(az ad group show --group "G-Usuarios-<App>-Admin" --query id -o tsv)
# Assign group to the application (only these users can login)
az rest --method POST \
--uri "https://graph.microsoft.com/v1.0/servicePrincipals/$SP_ID/appRoleAssignments" \
--headers "Content-Type=application/json" \
--body "{
\"principalId\": \"$GROUP_ID\",
\"principalType\": \"Group\",
\"appRoleId\": \"00000000-0000-0000-0000-000000000000\",
\"resourceId\": \"$SP_ID\"
}"
阶段 4:在 Key Vault 中存储密钥
az keyvault secret set \
--vault-name "<keyvault-name>" \
--name "<app>-azuread-client-secret" \
--value "$SECRET"
密钥管理
SecretProviderClass 模板
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: <app>-secrets
namespace: <namespace>
spec:
provider: azure
parameters:
usePodIdentity: "false"
useVMManagedIdentity: "true"
userAssignedIdentityID: "<managed-identity-client-id>"
keyvaultName: "<keyvault-name>"
tenantId: "<azure-tenant-id>"
objects: |
array:
- |
objectName: <app>-azuread-client-secret
objectType: secret
objectAlias: AZURE_AD_CLIENT_SECRET
secretObjects:
- secretName: <app>-azure-ad
type: Opaque
data:
- objectName: AZURE_AD_CLIENT_SECRET
key: client-secret
Pod 卷挂载
volumes:
- name: secrets-store
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: "<app>-secrets"
volumeMounts:
- name: secrets-store
mountPath: "/mnt/secrets-store"
readOnly: true
应用配置
DefectDojo
# Enable SSO
extraEnv:
- name: DD_SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_ENABLED
value: "True"
- name: DD_SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_KEY
value: "<client-id>"
- name: DD_SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_TENANT_ID
value: "<tenant-id>"
- name: DD_SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_SECRET
valueFrom:
secretKeyRef:
name: defectdojo
key: DD_SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_SECRET
# Group sync
- name: DD_SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_GET_GROUPS
value: "True"
- name: DD_SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_CLEANUP_GROUPS
value: "True"
- name: DD_SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_GROUPS_FILTER
value: "^G-Usuarios-DefectDojo-.*"
# CRITICAL: For apps behind reverse proxy
- name: DD_SECURE_PROXY_SSL_HEADER
value: "True"
Grafana
grafana.ini:
auth.azuread:
enabled: true
name: Azure AD
allow_sign_up: true
client_id: "<client-id>"
client_secret: "${GF_AUTH_AZUREAD_CLIENT_SECRET}"
scopes: openid email profile
auth_url: https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/authorize
token_url: https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token
allowed_groups: "<admin-group-id> <viewer-group-id>"
role_attribute_path: contains(groups[*], '<admin-group-id>') && 'Admin' || 'Viewer'
ArgoCD(通过 Dex)
configs:
cm:
dex.config: |
connectors:
- type: microsoft
id: microsoft
name: Azure AD
config:
clientID: "<client-id>"
clientSecret: $dex.azure.clientSecret
tenant: "<tenant-id>"
redirectURI: https://<argocd-domain>/api/dex/callback
groups:
- <admin-group-id>
rbac:
policy.csv: |
g, <admin-group-id>, role:admin
Harbor
externalURL: https://harbor.<domain>
core:
oidc:
name: "azure"
endpoint: "https://login.microsoftonline.com/<tenant-id>/v2.0"
clientId: "<client-id>"
clientSecret: "<from-secret>"
scope: "openid,profile,email"
groupsClaim: "groups"
adminGroup: "<admin-group-id>"
autoOnboard: true
故障排除
错误参考
| 错误代码 | 描述 | 解决方案 |
|---|---|---|
| AADSTS50011 | 回复 URL 不匹配 | 核对重定向 URI 是否完全一致,包括末尾斜杠 |
| AADSTS50105 | 用户未分配 | 将用户/组添加到企业应用分配中 |
| AADSTS700016 | 找不到应用 | 检查客户端 ID 和租户 ID |
| AADSTS7000218 | 密钥已过期 | 在 Key Vault 中轮换密钥,然后重启 Pod |
| AADSTS90102 | redirect_uri 无效 | 对于反向代理,检查 DD_SECURE_PROXY_SSL_HEADER=True |
| AADSTS65001 | 尚未授予同意 | 运行 az ad app permission admin-consent |
常见问题
redirect_uri 格式错误(代理后的 Django 应用)
症状: redirect_uri=https,%20https://...
根本原因: DD_SECURE_PROXY_SSL_HEADER 设置错误
修复方法:
- name: DD_SECURE_PROXY_SSL_HEADER
value: "True" # NOT "HTTP_X_FORWARDED_PROTO,https"
组未同步
# Verify group claims enabled
az ad app show --id <app-id> --query groupMembershipClaims
# Check API permissions
az ad app permission list --id <app-id>
# Verify group exists and user is member
az ad group member check --group "<group-name>" --member-id "<user-object-id>"
未从 Key Vault 同步密钥
# Check SecretProviderClass
kubectl describe secretproviderclass <name> -n <namespace>
# Check CSI driver pods
kubectl get pods -n kube-system | grep secrets-store
# Check managed identity access
az keyvault show --name <vault> --query properties.accessPolicies
诊断命令
# Test OAuth redirect
curl -sS -k -D - -o /dev/null "https://<app>/login/<provider>/" 2>&1 | grep -i location
# Check environment variables in pod
kubectl exec -n <ns> deploy/<app> -c <container> -- env | grep -i azure
# Decode JWT token (after login, from browser dev tools)
# Use https://jwt.io to decode and verify claims
安全最佳实践
- 切勿将密钥硬编码 - 始终使用 Key Vault + CSI Driver
- 使用托管身份 - 避免使用服务主体凭据
- 按组限制访问 - 启用
appRoleAssignmentRequired=true - 轮换密钥 - 在密钥过期前设置日历提醒
- 仅使用 HTTPS - 所有重定向 URI 都必须使用 HTTPS
- 单租户 - 内部应用切勿使用多租户
- 审计日志记录 - 启用 Azure AD 登录日志
环境参考
| 环境 | Key Vault | 托管身份 | 租户 ID |
|---|---|---|---|
| example-app-dev | kv-example-dev-hlg | <WORKLOAD_IDENTITY_CLIENT_ID> | <TENANT_ID> |
| example-app-hub | kv-example-default | <WORKLOAD_IDENTITY_CLIENT_ID> | <TENANT_ID> |
| example-app-prd | kv-example-prd | <WORKLOAD_IDENTITY_CLIENT_ID> | <TENANT_ID> |
详细参考
完整实施示例请参阅:
- references/azure-ad-sso-guide.md - 包含清单的完整指南
- references/app-configs.md - 应用专属配置
- references/troubleshooting.md - 扩展故障排除指南
注意事项
- 组声明默认使用 objectId,而非名称——启用
optionalClaims: groups: SecurityGroup并在清单中将令牌版本设为 2,否则按名称匹配的应用将看不到任何内容。 - 令牌范围:
https://graph.microsoft.com/.default范围仅授予管理员已同意的权限;委托令牌中的User.Read.All在首次使用时仍需管理员同意。 - OIDC 重定向 URI 必须完全匹配——末尾斜杠不匹配会返回 AADSTS50011,并附带一个模糊的错误,看起来像是网络问题。
- 组声明在 200 个组时会超额:属于超过 200 个组的用户不会获得任何组声明(超额)。改用 Graph 查询来查找成员资格。
- 应用令牌生命周期策略按应用设置——默认值为 1 小时。运行超过 1 小时的批处理任务会静默返回 401,除非为每次调用都封装 401 后刷新令牌的逻辑。
评论
支持 Markdown