AKS Platform: Security Policy¶
This page describes the guardrails that protect the cluster and the workloads on it: Azure Policy for Kubernetes, the Kyverno policies, network policies, namespace quotas, image rules and the exception process. Workload teams meet these rules every time they deploy.
Reference design, your deployment may differ
The Azure-side assignments are read from Terraform. The in-cluster policies (Kyverno, network policies, quotas) come
from the manifests that were migrated to the platform GitOps repository; the copies left in the Terraform repository are
deprecated and are used here as the reference for their content. Effects (Audit or Enforce) change per environment
and over time. Ask your platform team for the current state.
Defence in depth¶
| Stage | Control |
|---|---|
| Azure | Azure Policy assignments on the resource group, Defender for Cloud (optional), diagnostic settings, alerts |
| Cluster | Private API server, Entra ID only, managed identities, hardened node pools, upgrade strategy |
| Admission | Azure Policy add-on (Gatekeeper) for Microsoft's built-in initiatives, Kyverno for platform-defined validation and mutation |
| Runtime | Network policies, workload identity, Key Vault, TLS at the gateway |
Azure Policy and Defender¶
The Azure Policy layer assigns these built-in definitions to a resource group (see the policy scope remark in Architecture):
| Assignment | Effect in the reference configuration |
|---|---|
| Kubernetes cluster pod security baseline standards for Linux workloads | Variable azure_policy_baseline_effect, Audit in both environments. The comment says to move to Deny after a validation period. |
| Pod security restricted standards for Linux workloads (production only) | Audit |
| Kubernetes clusters should not allow container privilege escalation | Deny |
| Kubernetes clusters should be accessible only over HTTPS | Deny |
| Containers should not share host process ID or IPC namespace | Deny |
| Custom assignments | A map in the configuration, empty by default (for example "AKS must use a private API server") |
Namespaces excluded from these assignments in the reference files: kube-system, gatekeeper-system, azure-arc, kyverno,
gateway-system, monitoring.
The design says the baseline is enforced in production and audited in non-production. The configuration files set Audit in
both, so check the live effect before relying on it.
Optional switches of the layer, all off in the reference files:
| Switch | What it does |
|---|---|
deploy_defender |
Defender for Cloud plans Containers, KeyVaults and CloudPosture at subscription level, and a security contact |
deploy_diagnostic_settings |
Diagnostic settings for the listed resources to a Log Analytics workspace |
deploy_security_alerts |
An action group with email receivers and scheduled query alert rules on Log Analytics |
Defender for Containers is described as unsupported on Azure Local (AKS on Azure Local).
Kyverno policies¶
Kyverno is the recommended policy engine for admission rules: YAML instead of Rego, validation, mutation and generation in one tool, and policy reports as Kubernetes resources. It is deployed by the GitOps repository and is optional. Until it is deployed, Azure Policy gives the baseline pod security.
The policy lifecycle is draft (pull request, reviewed by platform and security), then audit in non-production, then enforce in non-production after 7 days without new violations, then enforce in production after 14 days stable. All promotions are pull requests.
Pod security and image controls¶
| Control | Non-production | Production | Engine |
|---|---|---|---|
| Block privileged containers | Enforce | Enforce | Azure Policy and Kyverno |
| Block hostPath, hostNetwork, hostPID, hostIPC | Enforce | Enforce | Azure Policy and Kyverno |
| Block privilege escalation | Enforce | Enforce | Azure Policy and Kyverno |
| Require resource requests and limits | Enforce | Enforce | Kyverno |
| Require approved registries | Enforce | Enforce | Kyverno |
Require runAsNonRoot |
Audit | Enforce | Kyverno |
| Drop all capabilities | Audit | Enforce | Kyverno |
Require seccomp RuntimeDefault |
Audit | Enforce | Kyverno |
Block the latest tag |
Audit | Enforce | Kyverno |
| Read-only root file system | Audit | Audit | Kyverno |
The table is the design. The Kyverno manifests in the repository all carry validationFailureAction: Audit; the
production and non-production difference is meant to be set by environment values in the GitOps repository.
Approved registries are the organisation's container registry (*.azurecr.io) and mcr.microsoft.com.
Kyverno can also mutate pods so teams do not have to set secure defaults: runAsNonRoot, no privilege escalation,
seccomp RuntimeDefault, drop all capabilities. The repository contains mutation policies for non-root, dropped capabilities
and seccomp.
Image rules (design)¶
| Requirement | Non-production | Production |
|---|---|---|
| Images only from approved registries | Enforce | Enforce |
| Vulnerability scan, critical and high | Warn | Block (high unless accepted) |
| Image signing and SBOM | Not required | Recommended, future |
Images are pulled by the kubelet identity with the AcrPull role, so no pull secrets are stored in the cluster.
Network policies¶
Every application namespace gets default deny for ingress and egress, and then explicit allows from the base set:
| Policy | Effect |
|---|---|
default-deny-all |
Blocks all ingress and egress |
allow-dns |
Egress to kube-dns |
allow-ingress-from-gateway |
Ingress from the gateway namespace to application pods |
allow-egress-to-private-endpoints |
Egress to the private endpoint subnet on ports 443, 5432 and 6379 |
allow-prometheus-scrape |
Ingress from the Prometheus pods in the monitoring namespace on ports 9090, 8080, 9394 |
Teams add their own policies for calls between namespaces and for specific egress. NetworkPolicy controls east-west traffic inside the cluster; a firewall (when present) controls north-south traffic and traffic between spokes.
Note
The private endpoint policy contains the private endpoint subnet CIDR of the cluster, so it differs per environment.
The load test adds its own policies for the load-testing namespace and for the target ports (Load testing).
Namespace baseline¶
Each team namespace gets a resource quota and a limit range from the GitOps repository.
| Setting | Non-production overlay | Production overlay |
|---|---|---|
| Requests CPU / memory | 8 / 16 GiB | 2 / 4 GiB |
| Limits CPU / memory | 16 / 32 GiB | 4 / 8 GiB |
| Pods | 100 | 30 |
| Base defaults | 4 CPU / 8 GiB requests, 8 CPU / 16 GiB limits, 50 pods, 10 services, 10 persistent volume claims, 20 secrets, 20 config maps |
Container defaults from the limit range: request 100m CPU and 128 MiB, limit 500m CPU and 512 MiB; minimum 50m and 64 MiB, maximum 4 CPU and 8 GiB per container, 8 CPU and 16 GiB per pod.
Check the production overlay
In the deprecated overlays the production quota is smaller than the non-production quota. This may be intended (small initial namespaces) or a swapped value. The values in the GitOps repository apply.
Namespaces are created only through GitOps; creating one with kubectl is blocked by admission policy for non-platform
subjects (design). Each new namespace is provisioned with the quota, the limit range, the default-deny network policy and
the role bindings. The design makes these labels mandatory:
| Label | Purpose |
|---|---|
<labelDomain>/team |
Team ownership |
<labelDomain>/environment |
dev, test, accp or prod |
<labelDomain>/type |
application or platform |
<labelDomain>/data-classification |
public, internal, confidential or restricted |
<labelDomain>/security-tier |
standard or high |
<labelDomain>/cost-center |
Cost tracking |
An owner contact and the way the namespace was created are mandatory annotations. The Kyverno policy in the repository checks only the team and environment labels, so the exact mandatory set depends on the version of the GitOps repository.
Secrets in the cluster¶
| Rule | Enforcement (design) |
|---|---|
| No plaintext secrets in Git | Pre-commit scanning and server-side push protection |
| Prefer Key Vault over Kubernetes Secrets | Guidance |
| No secrets in config maps | Kyverno, audit |
Only Opaque and TLS secret types |
Kyverno, audit |
Rotation is handled by Key Vault rotation policies and the application reads the latest value with the SDK.
Policy exceptions¶
An exception exempts one workload from one policy, for a limited time.
- The team opens a pull request in the platform GitOps repository that adds a
PolicyException(a list in the environment values). The description gives the justification. - The security team reviews and approves. The platform team merges and Argo CD deploys it.
- Quarterly review: renew with justification, or remove.
| Rule | Detail |
|---|---|
| Expiry | Every exception has an expiry date, 90 days at most, renewable with re-approval |
| Scope | One namespace and workload, never cluster-wide |
| Metadata | Owner, approver, reason, risk, compensating controls, ticket reference |
| Tracking | Policy reports show which workloads run under an exception |
See also Policies and exemptions for the Azure Policy exemptions of the MCP platform itself.