Skip to main content

Source maintenance reviewed September 4, 2026: this updated source passed its offline and CI checks. Historical tenant evidence below retains its original dates; no fresh tenant deployment or detection result is asserted here.

Overview

This lab deploys a complete AKS runtime security stack using Microsoft Defender for Cloud:

  1. Gated Deployment (GA) โ€” Evaluates images at admission; configured rules can audit or deny matching images
  2. Binary Drift Detection and Blocking (GA within documented support constraints) โ€” Detects external executables and can alert on or block them after the policy and prerequisites are configured
  3. Container Anti-Malware (GA) โ€” Detects and blocks malware inside running containers
  4. Sentinel Analytics Rules โ€” 3 owner-marked rules for binary drift, malware, and kubectl exec, deployed disabled until explicitly enabled
  5. Sentinel Workbook โ€” Container Runtime Security Dashboard

Prerequisites

  • Azure subscription with Owner or Contributor + User Access Administrator
  • Azure CLI v2.60+
  • kubectl compatible with AKS Kubernetes 1.35
  • Helm v3.12+
  • PowerShell 7.4 or later, enforced before either helper runs
  • Your actual public VPN/NAT egress IPv4 CIDRs for API server access
  • Permission to read the lab Log Analytics workspace ID and shared key for Helm sensor telemetry

The lab uses a Helm-managed sensor, so the operator owns chart upgrades. The portal can label a Helm-installed subscription not supported for Gated deployment; if the rule flow offers to auto-install components, choose Skip to avoid conflicting with the existing Helm deployment. Gated deployment also requires Kubernetes 1.31 or later, OIDC on AKS, Defender sensor with Security Gating, Registry access with Security findings, and vulnerability findings artifacts for evaluated images. Verify these current service-side prerequisites before attributing an allow or denial to the policy.

Quick Start

git clone https://github.com/j-dahl7/aks-runtime-security-lab.git
cd aks-runtime-security-lab
git checkout 47ea45cae2372a2333b4a105e8daee1d3a073e63
# Replace this placeholder with your actual public operator egress.
$operatorEgress = @('<your-public-egress-ip>/32')

# Preview only: no Azure, kubeconfig, Kubernetes, Helm, or secret-file mutations
./scripts/Deploy-Lab.ps1 -Location "eastus" -ProjectName "aks-runtime-lab" -ApiServerAuthorizedIpRanges $operatorEgress -WhatIf

# If the required paid subscription settings are not already enabled, review
# the active subscription and pricing before explicitly authorizing the change.
$env:CONFIRM_SUBSCRIPTION_SCOPE = "ENABLE-DEFENDER-FOR-CONTAINERS"

# Deploy infrastructure, sensor, disabled Sentinel rules, and workbook
./scripts/Deploy-Lab.ps1 -Location "eastus" -ProjectName "aks-runtime-lab" -ApiServerAuthorizedIpRanges $operatorEgress

First deployment requires 1โ€“20 explicit public IPv4 CIDRs, each /24 through /32. The helper rejects broad, private, reserved, documentation, benchmark, malformed, and duplicate ranges; it does not discover your egress or allow all addresses by default. Confirm the actual VPN/NAT egress with your network owner. Owned reruns reuse the exact recorded ranges when omitted and reject changed inputs or live drift. The API endpoint remains public behind its IP allowlist and retains local Kubernetes authentication; this is not a private or Entra-only deployment.

Before testing, configure both portal-owned controls. Change the default binary drift action from Ignore drift detection to the GA Drift detection alert action. Use the GA Drift detection blocking action only after confirming the current cluster, cloud, sensor, policy, and network prerequisites. Then create a gated-deployment vulnerability rule under Environment Settings > Security Rules; start with Audit, validate its decisions in Gated deployment > Admission Monitoring, and move to Deny only after confirming scope and thresholds. The deployment script does not create either policy.

# After reviewing the queries, explicitly enable the three rules
./scripts/Deploy-Lab.ps1 -Location "eastus" -ProjectName "aks-runtime-lab" -EnableSentinelRules

# Run test scenarios
./scripts/Test-RuntimeSecurity.ps1 -ProjectName "aks-runtime-lab" -Namespace "runtime-security-tests"

What Gets Deployed

ResourceTypePurpose
aks-runtime-labAKS ClusterKubernetes 1.35, single-node cluster
defender-k8sHelm releasePinned Defender chart 0.11.4 with anti-malware collector
aks-runtime-lab-lawLog AnalyticsContainer Insights + Sentinel
Defender for ContainersSecurity PlanSubscription-level enablement
3 Analytics RulesSentinelOwner-marked and disabled by default; binary drift, malware, kubectl exec
1 WorkbookSentinelContainer Runtime Security Dashboard

Test Scenarios

Test 1: Binary Drift

./scripts/Test-RuntimeSecurity.ps1 -SkipMalware -SkipGated

Validation boundary: the activity should be evaluated when binary drift is enabled, but alert/block behavior depends on the selected policy. Generation and ingestion are asynchronous; this lab does not promise a fixed latency.

Test 2: Anti-Malware (EICAR)

./scripts/Test-RuntimeSecurity.ps1 -SkipDrift -SkipGated

Validation boundary: runtime anti-malware evaluates execution, not the file write alone. EICAR is harmless, and a nonzero container command does not itself prove Defender blocked it. Inspect the policy action and Defender security alerts; policy changes can take up to 30 minutes to reach sensors, and alert latency varies.

Test 3: Gated Deployment

./scripts/Test-RuntimeSecurity.ps1 -SkipDrift -SkipMalware

Validation boundary: this is Microsoft’s documented gated-deployment test image. A matching Deny rule returns an admission-webhook denial; Audit allows the request. Review the decision, rule, image digest, violations, and exemptions in Gated deployment > Admission Monitoring. An allow can also mean a scope mismatch, missing findings artifact, or incomplete prerequisite.

Deployment Safety

  • -WhatIf exits after a preview-only summary and performs zero mutations.
  • A per-project, owner-only manifest is written before cloud mutation. Existing resource groups are rejected unless the exact manifest token matches their ownership tag; cleanup uses that same proof.
  • Runtime testing verifies the tenant/subscription, ARM resourceUID, server/CA fingerprint, kube-system namespace UID, ownership, and live API allowlist before mutation. A matching context alias alone is insufficient. Older manifests require an owned deployment rerun with explicit API ranges.
  • Foreign namespaces and pods are refused. The helper creates unique owner-labeled pods, checks UID before exec, and never pre-deletes existing pods. Review its printed run selector before manual pod cleanup.
  • Azure calls bind the captured subscription; Kubernetes and Helm calls bind the verified context. Keep the local manifest trusted and avoid concurrent kubeconfig definition or pod administration; exec has no atomic UID precondition. Recorded identity changes, including certificate rotation, require independent operator review.
  • The script checks for conflicting auto-provision policy assignments, other Defender Helm releases, and exact known stale cluster-scoped resources before changing protection.
  • Before any deployment mutation, the script reads the subscription-wide Defender for Containers object. It preserves unrelated extensions and supported properties, refuses unknown properties rather than dropping them, and exits unless settings already match or CONFIRM_SUBSCRIPTION_SCOPE exactly authorizes the paid shared-setting change.
  • Log Analytics credentials go through an owner-only temporary values file, never Helm process arguments, and are deleted on success or failure.
  • Helm uses --atomic. If installation fails after the managed profile or exclusion tag changes, the script restores the captured Defender workspace/profile and exact prior tag state.
  • Sensor deployment succeeds only when matching pods reach both Running and Ready state.
  • All Sentinel objects are preflighted before the first Sentinel write. Rules and the workbook use deterministic manifest IDs and provenance; foreign collisions and any failed write stop the deployment.
  • Run pwsh ./tests/Test-DeployLabSafety.ps1 for offline deployment, cleanup, context-drift, and failure mocks, and pwsh ./tests/Test-RuntimeSafety.ps1 for offline foreign-target refusal and all three owned runtime scenarios. These tests submit no Azure or Kubernetes activity.

Estimated Cost

ResourceCost driver
AKSOne Standard_D4s_v3 node plus regional control-plane/network charges
Defender for ContainersCurrent subscription plan and protected vCores
Log AnalyticsIngestion and retention in the selected region

Pricing and included allowances change. Confirm the active subscription, region, and current Azure pricing before deployment; destroy the lab when it is not in use.

Cleanup

./scripts/Deploy-Lab.ps1 -ProjectName "aks-runtime-lab" -Destroy -WhatIf
./scripts/Deploy-Lab.ps1 -ProjectName "aks-runtime-lab" -Destroy

Cleanup validates the exact manifest, resource-group owner token, and shared pricing before deletion. It deletes the owned group and verifies its absence before restoring captured pre-lab Defender pricing/extensions. Failed or incomplete deletion keeps protection enabled and retains the manifest. Restore rechecks shared-setting drift and verifies the result before removing the manifest. A retry accepts an exact already-restored before-state; other shared changes are never overwritten.

Companion Blog Post

AKS Runtime Security: Binary Drift, Anti-Malware & Gated Deployment with Defender for Cloud

Repository

github.com/j-dahl7/aks-runtime-security-lab