Containers & Kubernetes Security Baselines
AKS is a complex surface — it combines infrastructure (nodes), orchestration (Kubernetes API), and application (container images) security. The baseline must cover all three layers: harden the cluster control plane, secure the node pools, enforce admission policies, and scan every container image.
AKS Security Layers diagram
Azure Kubernetes Service (AKS)
Managed Kubernetes with integrated CI/CD, monitoring, and security. AKS has the most complex security baseline of any Azure service because it spans infrastructure, platform, and application layers.
| Property | Value |
|---|---|
| Defender Plan | Defender for Containers (runtime + VA + admission) |
| Azure Policy Built-in | Yes — AKS initiative (50+ policies via Gatekeeper) |
| Diagnostic Logs | kube-apiserver, kube-audit, kube-controller-manager, guard |
| Recommended Config | Private cluster, Entra ID RBAC, Azure CNI Overlay, OIDC issuer |
Baseline Controls
| Domain | Feature | Supported | Default | Responsibility |
|---|---|---|---|---|
| NS | Private Cluster | ✓ | Manual | Customer |
| NS | Network Policies | ✓ | Manual | Customer |
| IM | Entra ID RBAC for K8s | ✓ | Manual | Customer |
| IM | Workload Identity | ✓ | Manual | Customer |
| DP | etcd Encryption | ✓ | Default | Microsoft |
| DP | Key Vault CSI Driver | ✓ | Manual | Customer |
| PV | Azure Policy (Gatekeeper) | ✓ | Manual | Customer |
| PV | Node Auto-Upgrade | ✓ | Manual | Customer |
| LT | Defender for Containers | ✓ | Manual | Customer |
| ES | MDE on Nodes | ✓ | Manual | Customer |
Private Cluster
Network Policies
Entra ID RBAC for K8s
Workload Identity
etcd Encryption
Key Vault CSI Driver
Azure Policy (Gatekeeper)
Node Auto-Upgrade
Defender for Containers
MDE on Nodes
Terraform: Hardened AKS Cluster
resource "azurerm_kubernetes_cluster" "main" {
name = "aks-prod-001"
resource_group_name = azurerm_resource_group.aks.name
location = azurerm_resource_group.aks.location
dns_prefix = "aks-prod"
kubernetes_version = "1.29"
# NS: Private cluster — API server not exposed to internet
private_cluster_enabled = true
# IM-1: Entra ID RBAC (disable local accounts)
local_account_disabled = true
azure_active_directory_role_based_access_control {
azure_rbac_enabled = true
managed = true
admin_group_object_ids = [azuread_group.aks_admins.object_id]
}
# IM-3: Workload Identity for pod-level Entra auth
oidc_issuer_enabled = true
workload_identity_enabled = true
# NS: Azure CNI Overlay for network policies
network_profile {
network_plugin = "azure"
network_plugin_mode = "overlay"
network_policy = "calico" # Kubernetes network policies
service_cidr = "172.16.0.0/16"
dns_service_ip = "172.16.0.10"
}
# System node pool
default_node_pool {
name = "system"
vm_size = "Standard_D4s_v5"
node_count = 3
vnet_subnet_id = azurerm_subnet.aks_nodes.id
zones = ["1", "2", "3"]
os_sku = "AzureLinux" # Minimal attack surface
temporary_name_for_rotation = "systemtemp"
upgrade_settings {
max_surge = "33%"
}
}
# PV: Auto-upgrade channel
automatic_channel_upgrade = "stable"
node_os_channel_upgrade = "NodeImage"
# DP: Secrets Store CSI Driver for Key Vault
key_vault_secrets_provider {
secret_rotation_enabled = true
secret_rotation_interval = "2m"
}
# PV: Azure Policy add-on (Gatekeeper)
azure_policy_enabled = true
# LT: Defender profile
microsoft_defender {
log_analytics_workspace_id = azurerm_log_analytics_workspace.sentinel.id
}
# LT: OMS Agent for Container Insights
oms_agent {
log_analytics_workspace_id = azurerm_log_analytics_workspace.sentinel.id
}
identity {
type = "SystemAssigned"
}
maintenance_window_auto_upgrade {
frequency = "Weekly"
interval = 1
day_of_week = "Sunday"
start_time = "02:00"
duration = 4
}
}
# ACR — Azure Container Registry
resource "azurerm_kubernetes_cluster" "main" {
name = "aks-prod-001"
resource_group_name = azurerm_resource_group.aks.name
location = azurerm_resource_group.aks.location
dns_prefix = "aks-prod"
kubernetes_version = "1.29"
# NS: Private cluster — API server not exposed to internet
private_cluster_enabled = true
# IM-1: Entra ID RBAC (disable local accounts)
local_account_disabled = true
azure_active_directory_role_based_access_control {
azure_rbac_enabled = true
managed = true
admin_group_object_ids = [azuread_group.aks_admins.object_id]
}
# IM-3: Workload Identity for pod-level Entra auth
oidc_issuer_enabled = true
workload_identity_enabled = true
# NS: Azure CNI Overlay for network policies
network_profile {
network_plugin = "azure"
network_plugin_mode = "overlay"
network_policy = "calico" # Kubernetes network policies
service_cidr = "172.16.0.0/16"
dns_service_ip = "172.16.0.10"
}
# System node pool
default_node_pool {
name = "system"
vm_size = "Standard_D4s_v5"
node_count = 3
vnet_subnet_id = azurerm_subnet.aks_nodes.id
zones = ["1", "2", "3"]
os_sku = "AzureLinux" # Minimal attack surface
temporary_name_for_rotation = "systemtemp"
upgrade_settings {
max_surge = "33%"
}
}
# PV: Auto-upgrade channel
automatic_channel_upgrade = "stable"
node_os_channel_upgrade = "NodeImage"
# DP: Secrets Store CSI Driver for Key Vault
key_vault_secrets_provider {
secret_rotation_enabled = true
secret_rotation_interval = "2m"
}
# PV: Azure Policy add-on (Gatekeeper)
azure_policy_enabled = true
# LT: Defender profile
microsoft_defender {
log_analytics_workspace_id = azurerm_log_analytics_workspace.sentinel.id
}
# LT: OMS Agent for Container Insights
oms_agent {
log_analytics_workspace_id = azurerm_log_analytics_workspace.sentinel.id
}
identity {
type = "SystemAssigned"
}
maintenance_window_auto_upgrade {
frequency = "Weekly"
interval = 1
day_of_week = "Sunday"
start_time = "02:00"
duration = 4
}
}
# ACR — Azure Container Registry
Azure Portal Instructions
Step-by-step instructions for configuring each AKS control through the Azure Portal.
NS-1 — Private Cluster
- When creating a new AKS cluster, on the Networking tab, check "Enable private cluster"
- For existing clusters, navigate to your AKS resource in the Azure Portal
- In the left menu under Settings, select Networking
- Under API server access, enable "Private cluster" (note: this may cause a brief cluster restart)
- Under Network policy, select Azure or Calico to enable pod-to-pod segmentation
- Under Authorized IP ranges, add specific IPs allowed to reach the API server (if not fully private)
NS-2 — Private Link (API Server)
- Navigate to your AKS resource in the Azure Portal
- In the left menu under Settings, select Networking
- Under API server access, verify that "Private cluster" is enabled
- The API server is automatically placed behind Private Link when private cluster mode is on
- Access kubectl from a VM in the same or peered VNet, or via Azure Bastion
- Configure private DNS zone for API server name resolution from on-premises networks
IM-3 — Workload Identity
- Navigate to your AKS resource in the Azure Portal
- In the left menu under Settings, select Cluster configuration
- Under Security, enable "OIDC Issuer"
- Enable "Workload Identity" (this requires OIDC Issuer to be enabled)
- Click Apply
- After enabling, create a Kubernetes ServiceAccount annotated with the client ID of your Entra ID managed identity
- Configure federated credential on the managed identity in Entra ID
DP-3 — Data in Transit Encryption
- Navigate to your AKS resource in the Azure Portal
- In the left menu under Settings, select Service mesh (if available)
- Enable Istio-based service mesh for automatic mTLS between pods
- Alternatively, for ingress TLS, deploy NGINX Ingress Controller or use Application Gateway
- Configure TLS termination on the ingress controller with certificates from Key Vault
- Note: Service mesh mTLS provides encryption for all pod-to-pod communication within the cluster
ES-1 — Defender for Containers
- Navigate to Microsoft Defender for Cloud in the Azure Portal
- In the left menu, select Environment settings
- Select your subscription
- Under Cloud Workload Protection (CWP), find the Containers row
- Toggle the plan to On
- Click Settings and ensure "Runtime protection" and "Agentless scanning" are enabled
- Click Save — the Defender agent will be auto-deployed to AKS clusters
PV-5 — Image Vulnerability Scanning
- Enable Defender for Containers (see ES-1 steps above) — this automatically enables image vulnerability scanning in ACR
- Navigate to your Container Registry resource in the Azure Portal
- In the left menu, select Microsoft Defender for Cloud to view vulnerability findings
- To enforce policies, navigate to your AKS resource > Policies
- Assign the "Kubernetes cluster containers should only use allowed images" policy
- Configure the allowed image registries regex pattern
- Apply the policy — images from untrusted registries will be blocked by admission control
DS-6 — Supply Chain Security
- Navigate to your AKS resource in the Azure Portal
- In the left menu under Settings, select Policies
- Assign the "Kubernetes cluster containers should only use allowed images" policy
- Configure allowed registry patterns to restrict to your trusted ACR instances
- Enforce trusted registries with Azure Policy and require signed-image, SBOM, and vulnerability checks in CI/CD before promotion
- Use admission controls or deployment gates to block images that fail provenance or vulnerability policy
Azure Container Registry (ACR)
ACR stores container images used by AKS. Premium SKU is required for private endpoints, geo-replication, and customer-managed key scenarios. Pair it with Defender for Containers and CI/CD gates for image scanning, provenance, and promotion control.
Baseline Controls
| Domain | Feature | Supported | Default | Responsibility |
|---|---|---|---|---|
| NS | Private Endpoint | ✓ | Manual | Customer |
| NS | Disable Public Access | ✓ | Manual | Customer |
| PV | Image Vulnerability Scanning | ✓ | Manual | Customer |
| DS | Signed Image Workflow | ✓ | Manual | Customer |
Private Endpoint
Disable Public Access
Image Vulnerability Scanning
Signed Image Workflow
Azure Portal Instructions
ACR Baseline Configuration Workflow
- Navigate to your Azure Container Registry resource in the Azure Portal.
- Use Premium SKU if you need private endpoints, geo-replication, or advanced security controls.
- Under Networking, disable public network access and create a private endpoint for the registry.
- Integrate the registry with Defender for Containers so vulnerability findings are surfaced centrally.
- Use CI/CD policy gates for signed images, SBOM review, and vulnerability severity thresholds before promotion.
- Validate that AKS or other clients pull from the private registry path rather than a public endpoint.
# ACR with private endpoint and image scanning
resource "azurerm_container_registry" "main" {
name = "acrprod001"
resource_group_name = azurerm_resource_group.aks.name
location = azurerm_resource_group.aks.location
sku = "Premium" # Required for PE and geo-replication
# NS: Disable public access
public_network_access_enabled = false
# PV/DS: Pair ACR with Defender for Containers and CI/CD deployment gates
# for image vulnerability scanning, SBOM review, and signed-image policy.
}
# NS: Private endpoint for ACR
resource "azurerm_private_endpoint" "acr" {
name = "pe-acr-prod"
location = azurerm_resource_group.aks.location
resource_group_name = azurerm_resource_group.aks.name
subnet_id = azurerm_subnet.private_endpoints.id
private_service_connection {
name = "psc-acr-prod"
private_connection_resource_id = azurerm_container_registry.main.id
subresource_names = ["registry"]
is_manual_connection = false
}
}# ACR with private endpoint and image scanning
resource "azurerm_container_registry" "main" {
name = "acrprod001"
resource_group_name = azurerm_resource_group.aks.name
location = azurerm_resource_group.aks.location
sku = "Premium" # Required for PE and geo-replication
# NS: Disable public access
public_network_access_enabled = false
# PV/DS: Pair ACR with Defender for Containers and CI/CD deployment gates
# for image vulnerability scanning, SBOM review, and signed-image policy.
}
# NS: Private endpoint for ACR
resource "azurerm_private_endpoint" "acr" {
name = "pe-acr-prod"
location = azurerm_resource_group.aks.location
resource_group_name = azurerm_resource_group.aks.name
subnet_id = azurerm_subnet.private_endpoints.id
private_service_connection {
name = "psc-acr-prod"
private_connection_resource_id = azurerm_container_registry.main.id
subresource_names = ["registry"]
is_manual_connection = false
}
}ACR Security Essentials
AKS Diagnostic Settings
# LT-3: AKS Diagnostic Settings
resource "azurerm_monitor_diagnostic_setting" "aks" {
name = "diag-aks-prod"
target_resource_id = azurerm_kubernetes_cluster.main.id
log_analytics_workspace_id = azurerm_log_analytics_workspace.sentinel.id
enabled_log { category = "kube-apiserver" }
enabled_log { category = "kube-audit-admin" }
enabled_log { category = "kube-controller-manager" }
enabled_log { category = "guard" }
metric { category = "AllMetrics" }
}# LT-3: AKS Diagnostic Settings
resource "azurerm_monitor_diagnostic_setting" "aks" {
name = "diag-aks-prod"
target_resource_id = azurerm_kubernetes_cluster.main.id
log_analytics_workspace_id = azurerm_log_analytics_workspace.sentinel.id
enabled_log { category = "kube-apiserver" }
enabled_log { category = "kube-audit-admin" }
enabled_log { category = "kube-controller-manager" }
enabled_log { category = "guard" }
metric { category = "AllMetrics" }
}Essential Azure Policy Assignments for AKS
| Policy | Effect | Purpose |
|---|---|---|
| No privileged containers | Deny | Prevent container breakout via privileged mode |
| Containers must not run as root | Deny | Enforce non-root UID in containers |
| Read-only root filesystem | Audit | Detect containers with writable root FS |
| Allowed container images | Deny | Only pull from approved registries (ACR) |
| No host namespaces | Deny | Block hostPID, hostIPC, hostNetwork |
| Resource limits required | Deny | Prevent resource exhaustion / noisy neighbor |
| Drop all capabilities | Deny | Enforce minimal Linux capabilities |
| No LoadBalancer services | Audit | Force traffic through ingress controller |
Image Supply Chain