Containers

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.

2
Services
14
Controls
13
Customer-Owned
13
Manual Controls
2/2
Private Endpoint
2/2
Defender Coverage
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.

PropertyValue
Defender PlanDefender for Containers (runtime + VA + admission)
Azure Policy Built-inYes — AKS initiative (50+ policies via Gatekeeper)
Diagnostic Logskube-apiserver, kube-audit, kube-controller-manager, guard
Recommended ConfigPrivate cluster, Entra ID RBAC, Azure CNI Overlay, OIDC issuer

Baseline Controls

NS✓ Supported

Private Cluster

○ ManualCustomer
NS✓ Supported

Network Policies

○ ManualCustomer
IM✓ Supported

Entra ID RBAC for K8s

○ ManualCustomer
IM✓ Supported

Workload Identity

○ ManualCustomer
DP✓ Supported

etcd Encryption

● DefaultMicrosoft
DP✓ Supported

Key Vault CSI Driver

○ ManualCustomer
PV✓ Supported

Azure Policy (Gatekeeper)

○ ManualCustomer
PV✓ Supported

Node Auto-Upgrade

○ ManualCustomer
LT✓ Supported

Defender for Containers

○ ManualCustomer
ES✓ Supported

MDE on Nodes

○ ManualCustomer

Terraform: Hardened AKS Cluster

hcl
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
  1. When creating a new AKS cluster, on the Networking tab, check "Enable private cluster"
  2. For existing clusters, navigate to your AKS resource in the Azure Portal
  3. In the left menu under Settings, select Networking
  4. Under API server access, enable "Private cluster" (note: this may cause a brief cluster restart)
  5. Under Network policy, select Azure or Calico to enable pod-to-pod segmentation
  6. Under Authorized IP ranges, add specific IPs allowed to reach the API server (if not fully private)
NS-2 — Private Link (API Server)
  1. Navigate to your AKS resource in the Azure Portal
  2. In the left menu under Settings, select Networking
  3. Under API server access, verify that "Private cluster" is enabled
  4. The API server is automatically placed behind Private Link when private cluster mode is on
  5. Access kubectl from a VM in the same or peered VNet, or via Azure Bastion
  6. Configure private DNS zone for API server name resolution from on-premises networks
IM-3 — Workload Identity
  1. Navigate to your AKS resource in the Azure Portal
  2. In the left menu under Settings, select Cluster configuration
  3. Under Security, enable "OIDC Issuer"
  4. Enable "Workload Identity" (this requires OIDC Issuer to be enabled)
  5. Click Apply
  6. After enabling, create a Kubernetes ServiceAccount annotated with the client ID of your Entra ID managed identity
  7. Configure federated credential on the managed identity in Entra ID
DP-3 — Data in Transit Encryption
  1. Navigate to your AKS resource in the Azure Portal
  2. In the left menu under Settings, select Service mesh (if available)
  3. Enable Istio-based service mesh for automatic mTLS between pods
  4. Alternatively, for ingress TLS, deploy NGINX Ingress Controller or use Application Gateway
  5. Configure TLS termination on the ingress controller with certificates from Key Vault
  6. Note: Service mesh mTLS provides encryption for all pod-to-pod communication within the cluster
ES-1 — Defender for Containers
  1. Navigate to Microsoft Defender for Cloud in the Azure Portal
  2. In the left menu, select Environment settings
  3. Select your subscription
  4. Under Cloud Workload Protection (CWP), find the Containers row
  5. Toggle the plan to On
  6. Click Settings and ensure "Runtime protection" and "Agentless scanning" are enabled
  7. Click Save — the Defender agent will be auto-deployed to AKS clusters
PV-5 — Image Vulnerability Scanning
  1. Enable Defender for Containers (see ES-1 steps above) — this automatically enables image vulnerability scanning in ACR
  2. Navigate to your Container Registry resource in the Azure Portal
  3. In the left menu, select Microsoft Defender for Cloud to view vulnerability findings
  4. To enforce policies, navigate to your AKS resource > Policies
  5. Assign the "Kubernetes cluster containers should only use allowed images" policy
  6. Configure the allowed image registries regex pattern
  7. Apply the policy — images from untrusted registries will be blocked by admission control
DS-6 — Supply Chain Security
  1. Navigate to your AKS resource in the Azure Portal
  2. In the left menu under Settings, select Policies
  3. Assign the "Kubernetes cluster containers should only use allowed images" policy
  4. Configure allowed registry patterns to restrict to your trusted ACR instances
  5. Enforce trusted registries with Azure Policy and require signed-image, SBOM, and vulnerability checks in CI/CD before promotion
  6. 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

NS✓ Supported

Private Endpoint

○ ManualCustomer
NS✓ Supported

Disable Public Access

○ ManualCustomer
PV✓ Supported

Image Vulnerability Scanning

○ ManualCustomer
DS✓ Supported

Signed Image Workflow

○ ManualCustomer

Azure Portal Instructions

ACR Baseline Configuration Workflow
  1. Navigate to your Azure Container Registry resource in the Azure Portal.
  2. Use Premium SKU if you need private endpoints, geo-replication, or advanced security controls.
  3. Under Networking, disable public network access and create a private endpoint for the registry.
  4. Integrate the registry with Defender for Containers so vulnerability findings are surfaced centrally.
  5. Use CI/CD policy gates for signed images, SBOM review, and vulnerability severity thresholds before promotion.
  6. Validate that AKS or other clients pull from the private registry path rather than a public endpoint.
hcl
# 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

Use Premium SKU for private endpoints and geo-replication. Enable Defender for Containers for image vulnerability findings and enforce signed-image, SBOM, and severity gates before deployment.

AKS Diagnostic Settings

hcl
# 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

PolicyEffectPurpose
No privileged containersDenyPrevent container breakout via privileged mode
Containers must not run as rootDenyEnforce non-root UID in containers
Read-only root filesystemAuditDetect containers with writable root FS
Allowed container imagesDenyOnly pull from approved registries (ACR)
No host namespacesDenyBlock hostPID, hostIPC, hostNetwork
Resource limits requiredDenyPrevent resource exhaustion / noisy neighbor
Drop all capabilitiesDenyEnforce minimal Linux capabilities
No LoadBalancer servicesAuditForce traffic through ingress controller

Image Supply Chain

Never pull images directly from Docker Hub or public registries in production. Import approved images into your private ACR, scan them with Defender for Containers, require signed-image provenance, and use CI/CD or admission-control gates to block unapproved images from being deployed.