Skip to main content

Azure Command

Structured guidance for Azure identity, resource governance, AKS platform patterns, and production-readiness review.

Activation​

/platform-skills:azure identity # Workload Identity, OIDC federation, managed identities, Entra ID
/platform-skills:azure tagging # common_tags pattern, Azure Policy enforce/remediate, AKS MC_ group
/platform-skills:azure aks # AKS provisioning, add-ons, Flux/Argo bootstrap, node pools
/platform-skills:azure rbac # role assignment scoping, custom roles, audit over-permissioned identities
/platform-skills:azure review # production-readiness checklist for an Azure environment

Interactive Wizard (fires when no mode is provided)​

When invoked with no arguments, ask before proceeding:

Q1 — Mode?

What do you need?
1. identity — Workload Identity, OIDC federation for GitHub Actions, managed identities, Entra ID
2. tagging — common_tags baseline, Azure Policy enforcement, MC_ resource group, cost analysis
3. aks — AKS cluster provisioning, node pools, workload identity, Flux/Argo bootstrap
4. rbac — role assignment scoping, custom roles, audit over-permissioned identities
5. review — production-readiness checklist (tagging, RBAC, OIDC, protected environments)

Enter 1–5 or mode name:

Q2 — Context (after mode selected):

  • identity: What needs Azure access — a GitHub Actions workflow, an in-cluster workload, or a human team?
  • tagging: Paste your Terraform module or describe the resource types you need to tag.
  • aks: New cluster or modifying existing? What add-ons are needed (Flux, Argo CD, ESO, Linkerd)?
  • rbac: Describe the identity (user, group, managed identity) and what it needs to do.
  • review: Describe the environment — how many subscriptions, which workloads, any compliance requirements?

Mode: identity​

Triggers: Workload Identity, OIDC, managed identity, service principal, federated credential, GitHub Actions Azure login, Entra ID

Read references/azure.md before responding.

GitHub Actions OIDC federation (no long-lived secrets)​

# Create a user-assigned managed identity
az identity create \
--name github-actions-deploy \
--resource-group platform-rg \
--location northeurope

# Get the identity's client ID
CLIENT_ID=$(az identity show \
--name github-actions-deploy \
--resource-group platform-rg \
--query clientId -o tsv)

# Add a federated credential — scoped to a specific repo and branch
az identity federated-credential create \
--name github-main-branch \
--identity-name github-actions-deploy \
--resource-group platform-rg \
--issuer https://token.actions.githubusercontent.com \
--subject repo:org/repo:ref:refs/heads/main \
--audience api://AzureADTokenExchange

GitHub Actions workflow:

permissions:
id-token: write
contents: read

steps:
- uses: azure/login@a65d910e8af852a8061c627c456678983e180302 # v2.2.0
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

AKS Workload Identity (in-cluster pods)​

# Terraform — user-assigned managed identity for a workload
resource "azurerm_user_assigned_identity" "app" {
name = "app-workload-identity"
resource_group_name = azurerm_resource_group.this.name
location = var.location
}

# Federated credential — links the Kubernetes service account to the managed identity
# Requires azurerm >= 5.0.0. On 4.x and earlier this resource took parent_id plus
# resource_group_name; 5.0.0 replaced both with user_assigned_identity_id.
resource "azurerm_federated_identity_credential" "app" {
name = "app-k8s-sa"
user_assigned_identity_id = azurerm_user_assigned_identity.app.id
audience = ["api://AzureADTokenExchange"]
issuer = azurerm_kubernetes_cluster.this.oidc_issuer_url
subject = "system:serviceaccount:app-team:app-sa"
}

Kubernetes service account with workload identity annotation:

apiVersion: v1
kind: ServiceAccount
metadata:
name: app-sa
namespace: app-team
annotations:
azure.workload.identity/client-id: "<managed-identity-client-id>"

Pod label to opt in:

spec:
template:
metadata:
labels:
azure.workload.identity/use: "true"

Common identity mistakes​

MistakeFix
Using service principal secrets for GitHub ActionsReplace with OIDC federated credential
Forgetting azure.workload.identity/use: "true" label on podsAdd the label to the pod template spec
subject in federated credential does not match the SA namespace/namesystem:serviceaccount:<namespace>:<sa-name> — must match exactly
Identity scoped at subscription level for app workloadsScope role assignment to the resource group or specific resource

Mode: tagging​

Triggers: tags, tagging, common_tags, Azure Policy, MC_, cost allocation, chargeback, compliance tags

Read references/azure.md → Tagging resources section before responding.

Shared locals pattern (Terraform)​

# variables.tf
variable "common_tags" {
description = "Baseline tags merged into every resource. Keys are defined by the organization."
type = map(string)
}

# locals.tf
locals {
common_tags = var.common_tags
}

# Every resource uses merge — resource-level tags extend the baseline
resource "azurerm_kubernetes_cluster" "this" {
name = var.cluster_name
location = var.location
resource_group_name = azurerm_resource_group.this.name

tags = merge(local.common_tags, {
component = "aks-control-plane"
})
}

AKS managed resource group tagging​

resource "azurerm_kubernetes_cluster" "this" {
node_resource_group = "mc-${var.cluster_name}-nodes"

tags = merge(local.common_tags, {
component = "aks-control-plane"
})
}

# Tag the MC_ group explicitly — AKS does not inherit tags automatically
resource "azurerm_resource_group_tag" "mc_tags" {
for_each = local.common_tags
resource_group_name = azurerm_kubernetes_cluster.this.node_resource_group
tag_key = each.key
tag_value = each.value

depends_on = [azurerm_kubernetes_cluster.this]
}

Azure Policy — enforce required tags​

# Start with audit during rollout, then switch to deny once estate is clean
az policy assignment create \
--name "require-team-tag" \
--display-name "Require team tag on all resources" \
--policy "/providers/Microsoft.Authorization/policyDefinitions/<built-in-id>" \
--scope "/subscriptions/<subscription-id>" \
--enforcement-mode DoNotEnforce # change to Default when ready to enforce

# Remediate existing non-compliant resources
az policy remediation create \
--name "tag-remediation-$(date +%Y%m%d)" \
--policy-assignment <assignment-id> \
--resource-discovery-mode ExistingNonCompliant

Validation:

# Check compliance state
az policy state summarize --subscription <subscription-id> \
--filter "policyDefinitionName eq '<policy-id>'"

Mode: aks​

Triggers: AKS, cluster, node pool, provision, bootstrap, Flux on AKS, Argo on AKS, add-ons

Read references/azure.md before responding.

Terraform AKS baseline​

resource "azurerm_kubernetes_cluster" "this" {
name = var.cluster_name
location = var.location
resource_group_name = azurerm_resource_group.this.name
dns_prefix = var.cluster_name
kubernetes_version = var.kubernetes_version
node_resource_group = "mc-${var.cluster_name}-nodes"

# System node pool
default_node_pool {
name = "system"
vm_size = "Standard_D4s_v5"
node_count = 3
os_disk_size_gb = 128
type = "VirtualMachineScaleSets"
zones = ["1", "2", "3"]
only_critical_addons_enabled = true # system pool: control-plane add-ons only
}

# Workload identity + OIDC issuer (required for managed identity federation)
workload_identity_enabled = true
oidc_issuer_enabled = true

# Managed identity for the cluster itself
identity {
type = "SystemAssigned"
}

# Azure CNI Overlay — scales further than kubenet, simpler than full CNI
network_profile {
network_plugin = "azure"
network_plugin_mode = "overlay"
network_policy = "calico"
service_cidr = "10.96.0.0/16"
dns_service_ip = "10.96.0.10"
}

tags = merge(local.common_tags, {
component = "aks-control-plane"
})
}

# User node pool for workloads
resource "azurerm_kubernetes_cluster_node_pool" "workload" {
name = "workload"
kubernetes_cluster_id = azurerm_kubernetes_cluster.this.id
vm_size = "Standard_D8s_v5"
node_count = 3
zones = ["1", "2", "3"]
mode = "User"

tags = merge(local.common_tags, {
component = "aks-workload-nodes"
})
}

Post-provision bootstrap sequence​

# Get credentials
az aks get-credentials \
--resource-group <rg> \
--name <cluster> \
--overwrite-existing

# Verify nodes are ready
kubectl get nodes

# Bootstrap Flux (if using GitOps)
flux bootstrap github \
--owner=<org> \
--repository=<gitops-repo> \
--branch=main \
--path=clusters/<cluster-name>

For Flux patterns → /platform-skills:gitops For Argo CD → /platform-skills:gitops

Node pool strategy​

PoolPurposeNode type
systemkube-system, CoreDNS, metrics-serverSmall, stable, zonal
workloadApplication podsRight-sized for workload type
spotBatch jobs, non-criticalSpot VMs for cost reduction

Taint system pool to prevent workload scheduling:

node_taints = ["CriticalAddonsOnly=true:NoSchedule"]

Mode: rbac​

Triggers: role assignment, contributor, owner, custom role, over-permissioned, least privilege, audit RBAC

Read references/azure.md → RBAC and identity patterns before responding.

Role assignment scoping rules​

Identity typeAssignment scope
Platform teamManagement group or subscription
Workload teamResource group
Automation / managed identitySpecific resource or resource group
NeverOwner or Contributor to a service principal
# Assign a built-in role scoped to a resource group
az role assignment create \
--assignee <managed-identity-client-id-or-user-upn> \
--role "Contributor" \
--scope "/subscriptions/<sub>/resourceGroups/<rg>"

# Scoped to a specific resource
az role assignment create \
--assignee <managed-identity-client-id> \
--role "Key Vault Secrets User" \
--scope "/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.KeyVault/vaults/<vault-name>"

Audit over-permissioned identities​

# Find all Owner and Contributor assignments on a subscription
az role assignment list \
--subscription <subscription-id> \
--query "[?roleDefinitionName=='Owner' || roleDefinitionName=='Contributor']" \
-o table

# Find service principals with Owner
az role assignment list \
--subscription <subscription-id> \
--query "[?principalType=='ServicePrincipal' && roleDefinitionName=='Owner']" \
-o table

Custom role — when built-ins are too permissive​

{
"Name": "AKS Metrics Reader",
"Description": "Can read AKS metrics and node pools. Cannot modify.",
"Actions": [
"Microsoft.ContainerService/managedClusters/read",
"Microsoft.ContainerService/managedClusters/agentPools/read",
"Microsoft.Insights/metrics/read"
],
"NotActions": [],
"AssignableScopes": ["/subscriptions/<sub>"]
}
az role definition create --role-definition custom-role.json

Mode: review​

Triggers: review, production ready, production checklist, audit my Azure config

Structured production-readiness review. Ask for:

  1. Subscription and resource group structure
  2. AKS cluster config (or Terraform)
  3. Identity model (managed identities, OIDC, service principals in use)
  4. Tag baseline enforcement approach

Then evaluate against:

CRITICAL (must fix before production)

❌ Long-lived service principal secrets for automation — replace with managed identity + OIDC
❌ Owner or Contributor assigned to a service principal — violates least privilege
❌ No resource tagging — blocks cost allocation, ownership tracking, and policy compliance
❌ AKS without workload_identity_enabled = true — forces static credentials in pods
❌ No protected environments in GitHub Actions for production subscription applies

WARNING (should fix)

⚠️ Tags missing from MC_ resource group — AKS does not inherit automatically
⚠️ Single subscription for all environments — mix prod and non-prod increases blast radius
⚠️ No Azure Policy enforce tagging — relies on developer discipline
⚠️ ClusterRoleBinding to workload identity — scope to RoleBinding unless cross-namespace access is required
⚠️ System and workload nodes in same pool — system pool should have CriticalAddonsOnly taint

INFORMATIONAL

ℹ️ Cost Management tags not validated in CI — add OPA/Conftest gate on terraform plan JSON
ℹ️ No Management Group hierarchy — direct subscription assignment makes policy rollout harder
ℹ️ AKS node pool not zone-redundant — add zones: ["1","2","3"] for HA

Handoffs:

  • Terraform generation → /platform-skills:terraform
  • In-cluster secrets strategy → /platform-skills:secrets
  • GitOps bootstrap on AKS → /platform-skills:gitops

Common mistakes​

  • Tags not in every resource — azurerm provider has no default_tags; use merge(local.common_tags, {...}) in every resource and module. The merge pattern is mandatory.
  • Tags on resource group do not propagate — Azure does not inherit tags from resource groups. Each resource must be tagged independently.
  • AKS MC_ group untagged — azurerm_resource_group_tag or node_resource_group_tags must be added explicitly.
  • Service principal secrets for GitHub Actions — OIDC federated credentials eliminate the need; rotate secrets before migrating.
  • azurerm version below 3.87 for node_resource_group_tags — check provider version in versions.tf.

Reference​

Full guidance: references/azure.md

For Terraform module generation: /platform-skills:terraform

For in-cluster secrets (Azure Key Vault via ESO): /platform-skills:secrets

Examples:

  • examples/azure/ — AKS cluster, workload identity, resource group baseline