Skip to main content
Pulumi logo Pulumi logo
  1. Docs
  2. Reference
  3. Pre-built Policy Packs
  4. CIS Kubernetes
  5. Azure (AKS)

CIS Kubernetes - Azure (AKS)

This Pulumi Cloud feature is available in the Business Critical edition.

    This page lists all 30 policies in the CIS Kubernetes pack for Azure (AKS), as published in cis-kubernetes-azure version 1.0.1.

    Policies by control

    2.1 Audit Logging — Enable audit logs for AKS clusters to track all API server requests and administrative actions.

    4.1 RBAC and Authentication — Restrict administrator privileges to dedicated administrator accounts on enterprise assets. Implement proper RBAC controls.

    4.2 Pod Security — Minimize the admission of privileged containers and containers with dangerous capabilities.

    4.4 Network Policies — Ensure that all Namespaces have Network Policies defined.

    4.5-4.6 Secret Management and Namespaces — Prefer using secrets as files over secrets as environment variables. Ensure default namespace is not used for workloads. Apply security context to pods and containers.

    5.1 Container Registry Security — Ensure Image Vulnerability Scanning using Microsoft Defender for Cloud (MDC). Minimize user access to Azure Container Registry (ACR).

    5.2 Service Accounts — Prefer using dedicated AKS Service Accounts.

    5.3 Encryption — Ensure Kubernetes Secrets are encrypted.

    5.4 Network Security — Restrict Access to the Control Plane Endpoint. Ensure clusters are created with Private Endpoints and Private Nodes. Enable Network Policy. Encrypt traffic to HTTPS load balancers.

    5.5 Azure AD Integration — Manage Kubernetes RBAC users with Azure AD. Use Azure RBAC for Kubernetes Authorization.

    Policy details

    acr-user-access-minimized

    Severity: medium · Enforcement: advisory

    Ensure Azure Container Registry has minimized user access with proper authentication and network restrictions.

    • 5.1 Container Registry Security — Ensure Image Vulnerability Scanning using Microsoft Defender for Cloud (MDC). Minimize user access to Azure Container Registry (ACR).
    Remediation
    Fix: Minimize User Access to Azure Container Registry
    Step 1: Disable Admin User and Use Service Principals
    import * as azuread from "@pulumi/azuread";
    
    // Create service principal for ACR access
    const acrServicePrincipal = new azuread.ServicePrincipal("acr-sp", {
        applicationId: acrApp.applicationId,
    });
    
    const registry = new azure.containerregistry.Registry("registry", {
        resourceGroupName: resourceGroup.name,
        registryName: "myregistry",
        sku: {
            name: "Premium",  // Premium SKU for advanced features
        },
        adminUserEnabled: false,  // Disable admin user
        publicNetworkAccess: "Disabled",  // Disable public access
        anonymousPullEnabled: false,  // Disable anonymous pull
        // ... other config
    });
    
    // Grant AcrPull role to service principal
    const acrPullRole = new azure.authorization.RoleAssignment("acr-pull", {
        principalId: acrServicePrincipal.objectId,
        principalType: "ServicePrincipal",
        roleDefinitionId: "/providers/Microsoft.Authorization/roleDefinitions/7f951dda-4ed3-4680-a7ca-43fe172d538d", // AcrPull
        scope: registry.id,
    });
    
    Step 2: Configure Private Endpoints
    const privateEndpoint = new azure.network.PrivateEndpoint("acr-pe", {
        resourceGroupName: resourceGroup.name,
        location: resourceGroup.location,
        subnet: {
            id: privateSubnet.id,
        },
        privateLinkServiceConnections: [{
            name: "acr-connection",
            privateLinkServiceId: registry.id,
            groupIds: ["registry"],
        }],
    });
    
    const privateDnsZone = new azure.network.PrivateZone("acr-dns", {
        resourceGroupName: resourceGroup.name,
        privateZoneName: "privatelink.azurecr.io",
    });
    
    Step 3: Configure Network Rules for Limited Public Access (if required)
    const registry = new azure.containerregistry.Registry("registry", {
        resourceGroupName: resourceGroup.name,
        registryName: "myregistry",
        sku: {
            name: "Premium",
        },
        adminUserEnabled: false,
        publicNetworkAccess: "Enabled",  // Enable with restrictions
        networkRuleSet: {
            defaultAction: "Deny",  // Deny by default
            ipRules: [
                {
                    action: "Allow",
                    ipAddressOrRange: "203.0.113.0/24",  // Office network
                },
                {
                    action: "Allow",
                    ipAddressOrRange: "198.51.100.0/24",  // VPN range
                },
            ],
            virtualNetworkRules: [
                {
                    action: "Allow",
                    virtualNetworkResourceId: subnet.id,
                },
            ],
        },
        // ... other config
    });
    
    Step 4: Use Managed Identity for AKS Access
    const cluster = new azure.containerservice.ManagedCluster("cluster", {
        resourceGroupName: resourceGroup.name,
        identity: {
            type: "SystemAssigned",
        },
        // ... other config
    });
    
    // Grant AcrPull to AKS managed identity
    const acrPullForAKS = new azure.authorization.RoleAssignment("aks-acr-pull", {
        principalId: cluster.identity.apply(i => i!.principalId),
        principalType: "ServicePrincipal",
        roleDefinitionId: "/providers/Microsoft.Authorization/roleDefinitions/7f951dda-4ed3-4680-a7ca-43fe172d538d", // AcrPull
        scope: registry.id,
    });
    

    Azure Container Registry Roles:

    • AcrPull: Pull images only
    • AcrPush: Pull and push images
    • AcrDelete: Pull, push, and delete images
    • AcrImageSigner: Sign images
    • Owner: Full control over registry

    aks-acr-readonly-access

    Severity: medium · Enforcement: advisory

    Ensure AKS clusters have read-only access to Azure Container Registry, preventing unauthorized image pushes.

    • 5.1 Container Registry Security — Ensure Image Vulnerability Scanning using Microsoft Defender for Cloud (MDC). Minimize user access to Azure Container Registry (ACR).
    Remediation
    Fix: Configure Read-Only ACR Access for AKS
    Step 1: Remove Excessive Permissions
    ##### Remove existing role assignments with push permissions
    az role assignment delete \
        --assignee <aks-managed-identity-object-id> \
        --role AcrPush \
        --scope <acr-resource-id>
    
    az role assignment delete \
        --assignee <aks-managed-identity-object-id> \
        --role Contributor \
        --scope <acr-resource-id>
    
    Step 2: Grant AcrPull (Read-Only) Access to AKS
    const cluster = new azure.containerservice.ManagedCluster("cluster", {
        resourceGroupName: resourceGroup.name,
        identity: {
            type: "SystemAssigned",
        },
        // ... other config
    });
    
    const registry = new azure.containerregistry.Registry("registry", {
        resourceGroupName: resourceGroup.name,
        registryName: "myregistry",
        sku: {
            name: "Premium",
        },
        // ... other config
    });
    
    // Grant read-only access (AcrPull) to AKS cluster
    const acrPullRole = new azure.authorization.RoleAssignment("aks-acr-pull", {
        principalId: cluster.identity.apply(i => i!.principalId!),
        principalType: "ServicePrincipal",
        roleDefinitionId: "/providers/Microsoft.Authorization/roleDefinitions/7f951dda-4ed3-4680-a7ca-43fe172d538d", // AcrPull
        scope: registry.id,
    });
    
    Step 3: Use Separate Service Principal for CI/CD Push
    import * as azuread from "@pulumi/azuread";
    
    // Create service principal for CI/CD pipelines (push access)
    const cicdApp = new azuread.Application("cicd-app", {
        displayName: "ACR Push CI/CD",
    });
    
    const cicdSp = new azuread.ServicePrincipal("cicd-sp", {
        applicationId: cicdApp.applicationId,
    });
    
    const cicdPassword = new azuread.ServicePrincipalPassword("cicd-password", {
        servicePrincipalId: cicdSp.id,
    });
    
    // Grant push access only to CI/CD service principal
    const acrPushRole = new azure.authorization.RoleAssignment("cicd-acr-push", {
        principalId: cicdSp.objectId,
        principalType: "ServicePrincipal",
        roleDefinitionId: "/providers/Microsoft.Authorization/roleDefinitions/8311e382-0749-4cb8-b61a-304f252e45ec", // AcrPush
        scope: registry.id,
    });
    
    Step 4: Use Attachment for ACR Integration
    // Alternatively, use attachACR for simplified setup
    const acrAttachment = new azure.containerservice.ManagedClusterAcrAttachment("acr-attachment", {
        resourceGroupName: resourceGroup.name,
        resourceName: cluster.name,
        acrName: registry.name,
    });
    

    Azure Container Registry Roles:

    • AcrPull (7f951dda-4ed3-4680-a7ca-43fe172d538d): Pull images only - recommended for AKS
    • AcrPush (8311e382-0749-4cb8-b61a-304f252e45ec): Pull and push images - use only for CI/CD
    • AcrDelete (c2f4ef07-c644-48eb-af81-4b1b4947fb11): Pull, push, and delete - use sparingly
    • Contributor: Full control - avoid for container operations

    Best Practices:

    • AKS clusters should only have AcrPull (read-only) access
    • Use separate service principals/managed identities for CI/CD push operations
    • Audit role assignments regularly
    • Use Azure Policy to enforce ACR access controls

    aks-approved-registries-only

    Severity: high · Enforcement: advisory

    Ensure Kubernetes workloads only pull container images from approved registries to prevent supply chain attacks.

    • 5.1 Container Registry Security — Ensure Image Vulnerability Scanning using Microsoft Defender for Cloud (MDC). Minimize user access to Azure Container Registry (ACR).
    Remediation
    Fix: Restrict Container Images to Approved Registries
    Step 1: Define Approved Registry List
    // Configure policy with approved registries
    const policyConfig = {
        "aks-approved-registries-only": {
            approvedRegistries: [
                "mycompany.azurecr.io",           // Corporate ACR
                "production.azurecr.io",          // Production ACR
                "gcr.io/my-project",              // GCP registry if needed
                "mycompany.io",                   // Custom domain
            ],
            allowSameSubscriptionACR: true,       // Allow *.azurecr.io
            requireFullyQualifiedImages: true,    // Prevent :latest tags
            allowDevNamespaces: ["dev", "test"],  // Dev exemptions
        },
    };
    
    Step 2: Deploy Applications Using Approved Registries
    import * as k8s from "@pulumi/kubernetes";
    
    const deployment = new k8s.apps.v1.Deployment("my-app", {
        metadata: {
            name: "my-app",
        },
        spec: {
            replicas: 3,
            selector: {
                matchLabels: { app: "my-app" },
            },
            template: {
                metadata: {
                    labels: { app: "my-app" },
                },
                spec: {
                    containers: [{
                        name: "app",
                        // GOOD: Using approved ACR registry with specific tag
                        image: "mycompany.azurecr.io/my-app:v1.2.3",
                        ports: [{ containerPort: 8080 }],
                    }],
                },
            },
        },
    });
    
    // BAD examples (will fail policy):
    // image: "docker.io/untrusted/image:latest"      // Unapproved registry
    // image: "mycompany.azurecr.io/my-app:latest"    // Using :latest tag
    // image: "mycompany.azurecr.io/my-app"           // Implicit :latest
    
    Step 3: Use Image Pull Secrets for Private Registries
    import * as k8s from "@pulumi/kubernetes";
    import * as pulumi from "@pulumi/pulumi";
    
    // Create Docker config secret for ACR authentication
    const dockerConfig = pulumi.secret({
        auths: {
            "mycompany.azurecr.io": {
                username: "<service-principal-id>",
                password: "<service-principal-password>",
                email: "notused@example.com",
                auth: "<base64-encoded-username:password>",
            },
        },
    });
    
    const acrSecret = new k8s.core.v1.Secret("acr-secret", {
        metadata: {
            name: "acr-secret",
            namespace: "default",
        },
        type: "kubernetes.io/dockerconfigjson",
        stringData: {
            ".dockerconfigjson": dockerConfig.apply(c => JSON.stringify(c)),
        },
    });
    
    // Deployment using the image pull secret
    const deployment = new k8s.apps.v1.Deployment("my-app", {
        metadata: { name: "my-app" },
        spec: {
            replicas: 3,
            selector: { matchLabels: { app: "my-app" } },
            template: {
                metadata: { labels: { app: "my-app" } },
                spec: {
                    imagePullSecrets: [{ name: acrSecret.metadata.name }],
                    containers: [{
                        name: "app",
                        image: "mycompany.azurecr.io/my-app:v1.2.3",
                    }],
                },
            },
        },
    });
    
    import * as azure from "@pulumi/azure-native";
    
    const cluster = new azure.containerservice.ManagedCluster("cluster", {
        resourceGroupName: resourceGroup.name,
        identity: {
            type: "SystemAssigned",
        },
        // ... other config
    });
    
    const registry = new azure.containerregistry.Registry("registry", {
        resourceGroupName: resourceGroup.name,
        registryName: "mycompany",
        sku: { name: "Premium" },
    });
    
    // Grant AKS pull access to ACR
    const acrPullRole = new azure.authorization.RoleAssignment("aks-acr-pull", {
        principalId: cluster.identity.apply(i => i!.principalId!),
        principalType: "ServicePrincipal",
        roleDefinitionId: "/providers/Microsoft.Authorization/roleDefinitions/7f951dda-4ed3-4680-a7ca-43fe172d538d", // AcrPull
        scope: registry.id,
    });
    

    Best Practices:

    • Maintain a corporate list of approved registries
    • Use ACR with Azure AD integration for authentication
    • Scan images in approved registries with Defender for Containers
    • Use image signing and verification (e.g., Notary, Cosign)
    • Avoid :latest tags; use specific version tags
    • Implement admission controllers (Gatekeeper, Kyverno) for enforcement
    • Regularly audit images in use across clusters

    aks-azure-ad-integration-enabled

    Severity: high · Enforcement: advisory

    Ensure AKS clusters are integrated with Azure AD for centralized user authentication and management.

    • 5.5 Azure AD Integration — Manage Kubernetes RBAC users with Azure AD. Use Azure RBAC for Kubernetes Authorization.
    Remediation
    Fix: Enable Azure AD Integration for AKS Cluster
    Step 1: Create Azure AD Groups for Cluster Administration
    import * as azuread from "@pulumi/azuread";
    
    const clusterAdminsGroup = new azuread.Group("aks-cluster-admins", {
        displayName: "AKS Cluster Admins",
        securityEnabled: true,
        description: "Administrators for AKS clusters",
    });
    
    const clusterUsersGroup = new azuread.Group("aks-cluster-users", {
        displayName: "AKS Cluster Users",
        securityEnabled: true,
        description: "Users for AKS clusters",
    });
    
    Step 2: Configure AKS with Azure AD Integration
    const cluster = new azure.containerservice.ManagedCluster("cluster", {
        resourceGroupName: resourceGroup.name,
        aadProfile: {
            managed: true,  // Enable managed Azure AD integration
            enableAzureRBAC: false,  // Use Kubernetes RBAC (or true for Azure RBAC)
            adminGroupObjectIDs: [
                clusterAdminsGroup.objectId,  // Azure AD group for cluster admins
            ],
            tenantID: tenantId,  // Optional: specify tenant ID
        },
        disableLocalAccounts: true,  // Disable local accounts for security
        // ... other config
    });
    
    Step 3: Configure Azure AD with Azure RBAC (Optional - Enhanced Security)
    const cluster = new azure.containerservice.ManagedCluster("cluster", {
        resourceGroupName: resourceGroup.name,
        aadProfile: {
            managed: true,
            enableAzureRBAC: true,  // Use Azure RBAC for Kubernetes authorization
            adminGroupObjectIDs: [
                clusterAdminsGroup.objectId,
            ],
        },
        disableLocalAccounts: true,
        // ... other config
    });
    
    // Assign Azure roles for Kubernetes access
    const adminRoleAssignment = new azure.authorization.RoleAssignment("aks-admin-role", {
        principalId: clusterAdminsGroup.objectId,
        principalType: "Group",
        roleDefinitionId: "/providers/Microsoft.Authorization/roleDefinitions/0ab0b1a8-8aac-4efd-b8c2-3ee1fb270be8", // Azure Kubernetes Service Cluster Admin Role
        scope: cluster.id,
    });
    

    Important Notes:

    • Managed Azure AD is the recommended approach (legacy AAD is deprecated)
    • Disabling local accounts ensures all access goes through Azure AD
    • Consider using Azure RBAC for Kubernetes for centralized access management
    • Users need appropriate Azure AD group membership to access the cluster

    aks-azure-rbac-enabled

    Severity: high · Enforcement: advisory

    Ensure AKS clusters use Azure RBAC for Kubernetes authorization, providing centralized access control.

    • 5.5 Azure AD Integration — Manage Kubernetes RBAC users with Azure AD. Use Azure RBAC for Kubernetes Authorization.
    Remediation
    Fix: Enable Azure RBAC for Kubernetes Authorization

    Note: Azure RBAC requires managed Azure AD integration. Ensure the aks-azure-ad-integration-enabled policy is satisfied first.

    Step 1: Enable Azure RBAC on AKS Cluster
    const cluster = new azure.containerservice.ManagedCluster("cluster", {
        resourceGroupName: resourceGroup.name,
        aadProfile: {
            managed: true,  // Managed Azure AD integration is required
            enableAzureRBAC: true,  // Enable Azure RBAC for Kubernetes
            adminGroupObjectIDs: [
                clusterAdminsGroup.objectId,
            ],
            tenantID: tenantId,
        },
        disableLocalAccounts: true,  // Recommended with Azure RBAC
        // ... other config
    });
    
    Step 2: Assign Azure Built-in Roles for Kubernetes Access
    // Cluster Admin Role
    const clusterAdminRole = new azure.authorization.RoleAssignment("cluster-admin", {
        principalId: adminGroup.objectId,
        principalType: "Group",
        roleDefinitionId: "/providers/Microsoft.Authorization/roleDefinitions/0ab0b1a8-8aac-4efd-b8c2-3ee1fb270be8", // Azure Kubernetes Service Cluster Admin Role
        scope: cluster.id,
    });
    
    // Cluster User Role
    const clusterUserRole = new azure.authorization.RoleAssignment("cluster-user", {
        principalId: userGroup.objectId,
        principalType: "Group",
        roleDefinitionId: "/providers/Microsoft.Authorization/roleDefinitions/4abbcc35-e782-43d8-92c5-2d3f1bd2253f", // Azure Kubernetes Service Cluster User Role
        scope: cluster.id,
    });
    
    // RBAC Admin (manage roles and bindings)
    const rbacAdminRole = new azure.authorization.RoleAssignment("rbac-admin", {
        principalId: rbacAdminGroup.objectId,
        principalType: "Group",
        roleDefinitionId: "/providers/Microsoft.Authorization/roleDefinitions/3498e952-d568-435e-9b2c-8d77e338d7f7", // Azure Kubernetes Service RBAC Admin
        scope: cluster.id,
    });
    
    // RBAC Reader (view resources)
    const rbacReaderRole = new azure.authorization.RoleAssignment("rbac-reader", {
        principalId: readerGroup.objectId,
        principalType: "Group",
        roleDefinitionId: "/providers/Microsoft.Authorization/roleDefinitions/7f6c6a51-bcf8-42ba-9220-52d62157d7db", // Azure Kubernetes Service RBAC Reader
        scope: cluster.id,
    });
    
    // RBAC Writer (modify resources)
    const rbacWriterRole = new azure.authorization.RoleAssignment("rbac-writer", {
        principalId: writerGroup.objectId,
        principalType: "Group",
        roleDefinitionId: "/providers/Microsoft.Authorization/roleDefinitions/a7ffa36f-339b-4b5c-8bfd-d2d2f9b8018d", // Azure Kubernetes Service RBAC Writer
        scope: cluster.id,
    });
    
    Step 3: Create Custom Role for Specific Permissions (Optional)
    const customK8sRole = new azure.authorization.RoleDefinition("custom-k8s-role", {
        roleName: "AKS Custom Developer",
        description: "Custom role for developers with limited Kubernetes permissions",
        assignableScopes: [subscription.id],
        permissions: [{
            actions: [
                "Microsoft.ContainerService/managedClusters/listClusterUserCredential/action",
                "Microsoft.ContainerService/managedClusters/read",
            ],
            dataActions: [
                "Microsoft.ContainerService/managedClusters/apps/deployments/write",
                "Microsoft.ContainerService/managedClusters/apps/deployments/read",
                "Microsoft.ContainerService/managedClusters/core/pods/read",
                "Microsoft.ContainerService/managedClusters/core/pods/logs/read",
            ],
        }],
    });
    
    const customRoleAssignment = new azure.authorization.RoleAssignment("custom-role-assignment", {
        principalId: devGroup.objectId,
        principalType: "Group",
        roleDefinitionId: customK8sRole.id,
        scope: cluster.id,
    });
    

    Azure Built-in Kubernetes Roles:

    • Azure Kubernetes Service Cluster Admin: Full access to all resources
    • Azure Kubernetes Service Cluster User: List cluster credentials, basic access
    • Azure Kubernetes Service RBAC Admin: Manage RBAC permissions
    • Azure Kubernetes Service RBAC Writer: Create/update most resources
    • Azure Kubernetes Service RBAC Reader: View most resources
    • Azure Kubernetes Service RBAC Cluster Admin: Full admin within namespaces

    aks-cluster-audit-logging-enabled

    Severity: high · Enforcement: advisory

    Ensure AKS clusters have audit logging enabled via diagnostic settings for security monitoring and compliance.

    • 2.1 Audit Logging — Enable audit logs for AKS clusters to track all API server requests and administrative actions.
    Remediation
    Fix: Enable Audit Logging for AKS Cluster
    const cluster = new azure.containerservice.ManagedCluster("cluster", {
        resourceGroupName: resourceGroup.name,
        addonProfiles: {
            omsagent: {
                enabled: true,
                config: {
                    logAnalyticsWorkspaceResourceID: workspace.id,
                },
            },
        },
        // ... other config
    });
    
    // Also create diagnostic settings for detailed logs
    const diagnosticSetting = new azure.insights.DiagnosticSetting("clusterDiagnostics", {
        resourceUri: cluster.id,
        workspaceId: workspace.id,
        logs: [
            { category: "kube-apiserver", enabled: true },
            { category: "kube-audit", enabled: true },
            { category: "kube-audit-admin", enabled: true },
            { category: "kube-controller-manager", enabled: true },
            { category: "kube-scheduler", enabled: true },
            { category: "cluster-autoscaler", enabled: true },
            { category: "guard", enabled: true },
        ],
    });
    

    aks-cluster-endpoint-restrict-public-access

    Severity: high · Enforcement: advisory

    Ensure AKS cluster API server has restricted public access through private endpoints or authorized IP ranges.

    • 5.4 Network Security — Restrict Access to the Control Plane Endpoint. Ensure clusters are created with Private Endpoints and Private Nodes. Enable Network Policy. Encrypt traffic to HTTPS load balancers.
    Remediation
    Fix: Restrict API Server Access for AKS Cluster
    const cluster = new azure.containerservice.ManagedCluster("cluster", {
        resourceGroupName: resourceGroup.name,
        apiServerAccessProfile: {
            enablePrivateCluster: true,
            enablePrivateClusterPublicFQDN: false, // Disable public FQDN
            privateDNSZone: "system", // Or specify custom private DNS zone ID
        },
        // ... other config
    });
    
    Option 2: Restrict to Specific IP Ranges
    const cluster = new azure.containerservice.ManagedCluster("cluster", {
        resourceGroupName: resourceGroup.name,
        apiServerAccessProfile: {
            enablePrivateCluster: false,
            authorizedIPRanges: [
                "203.0.113.0/24",    // Office network
                "198.51.100.0/24",   // VPN range
                "192.0.2.1/32",      // Specific management IP
            ],
        },
        // ... other config
    });
    
    Option 3: Private Cluster with Limited Public Access
    const cluster = new azure.containerservice.ManagedCluster("cluster", {
        resourceGroupName: resourceGroup.name,
        apiServerAccessProfile: {
            enablePrivateCluster: true,
            enablePrivateClusterPublicFQDN: true, // Enable public FQDN for specific scenarios
            privateDNSZone: privateZone.id,
            authorizedIPRanges: [
                "203.0.113.0/24", // Backup access from specific network
            ],
        },
        // ... other config
    });
    

    Best Practices:

    • Use private clusters for production environments
    • If public access is required, always restrict to specific IP ranges
    • Regularly review and update authorized IP ranges
    • Use Azure Firewall or Network Security Groups for additional protection

    aks-dedicated-service-accounts

    Severity: medium · Enforcement: advisory

    Ensure Kubernetes workloads use dedicated service accounts instead of the default service account for better access control and auditability.

    • 5.2 Service Accounts — Prefer using dedicated AKS Service Accounts.
    Remediation
    Fix: Use Dedicated Service Accounts for Workloads
    import * as azuread from "@pulumi/azuread";
    import * as azure from "@pulumi/azure-native";
    
    // Create Azure AD app and federated credential
    const app = new azuread.Application("app", {
        displayName: "my-app-identity",
    });
    
    const sp = new azuread.ServicePrincipal("app-sp", {
        applicationId: app.applicationId,
    });
    
    const federatedIdentity = new azuread.ApplicationFederatedIdentityCredential("federated-id", {
        applicationObjectId: app.objectId,
        displayName: "my-app-k8s-federation",
        audiences: ["api://AzureADTokenExchange"],
        issuer: cluster.oidcIssuerProfile.apply(p => p!.issuerUrl!),
        subject: "system:serviceaccount:production:my-app-sa",
    });
    
    // Create service account with workload identity annotation
    const appServiceAccount = new k8s.core.v1.ServiceAccount("app-sa", {
        metadata: {
            name: "my-app-sa",
            namespace: "production",
            annotations: {
                "azure.workload.identity/client-id": app.applicationId,
            },
            labels: {
                "azure.workload.identity/use": "true",
            },
        },
    });
    
    // Grant Azure permissions
    const roleAssignment = new azure.authorization.RoleAssignment("app-role", {
        principalId: sp.objectId,
        principalType: "ServicePrincipal",
        roleDefinitionId: "/providers/Microsoft.Authorization/roleDefinitions/2a2b9908-6ea1-4ae2-8e65-a410df84e7d1", // Storage Blob Data Reader
        scope: storageAccount.id,
    });
    
    Alternative: Basic Service Account (for K8s API access only)
    import * as k8s from "@pulumi/kubernetes";
    
    // Create dedicated service account
    const appServiceAccount = new k8s.core.v1.ServiceAccount("app-sa", {
        metadata: {
            name: "my-app-sa",
            namespace: "production",
        },
        automountServiceAccountToken: false,  // Only mount if needed
    });
    
    // Optional: Add RBAC permissions
    const appRole = new k8s.rbac.v1.Role("app-role", {
        metadata: {
            name: "my-app-role",
            namespace: "production",
        },
        rules: [{
            apiGroups: [""],
            resources: ["configmaps"],
            verbs: ["get", "list"],
        }],
    });
    
    const appRoleBinding = new k8s.rbac.v1.RoleBinding("app-binding", {
        metadata: {
            name: "my-app-binding",
            namespace: "production",
        },
        subjects: [{
            kind: "ServiceAccount",
            name: appServiceAccount.metadata.name,
            namespace: "production",
        }],
        roleRef: {
            kind: "Role",
            name: appRole.metadata.name,
            apiGroup: "rbac.authorization.k8s.io",
        },
    });
    
    // Deploy application
    const deployment = new k8s.apps.v1.Deployment("app", {
        metadata: {
            name: "my-app",
            namespace: "production",
        },
        spec: {
            replicas: 3,
            selector: { matchLabels: { app: "my-app" } },
            template: {
                metadata: { labels: { app: "my-app" } },
                spec: {
                    serviceAccountName: appServiceAccount.metadata.name,
                    automountServiceAccountToken: true,  // Only if app needs K8s API access
                    containers: [{
                        name: "app",
                        image: "myapp:v1.0.0",
                    }],
                },
            },
        },
    });
    

    Best Practices:

    • Create a dedicated service account for each application or microservice
    • Use descriptive names (e.g., app-name-sa)
    • Set automountServiceAccountToken: false by default; only enable when needed
    • Grant minimal RBAC permissions using Roles/RoleBindings
    • Consider Azure Workload Identity for accessing Azure resources
    • Never use the default service account for production workloads
    • Document the purpose of each service account in annotations
    • Regularly audit service account usage and permissions

    aks-defender-container-scanning-enabled

    Severity: medium · Enforcement: advisory

    Ensure container registries have vulnerability scanning enabled through Microsoft Defender for Cloud or third-party solutions.

    • 5.1 Container Registry Security — Ensure Image Vulnerability Scanning using Microsoft Defender for Cloud (MDC). Minimize user access to Azure Container Registry (ACR).
    Remediation
    Fix: Enable Vulnerability Scanning for Container Registry
    Step 1: Upgrade Registry SKU (if using Basic)
    const registry = new azure.containerregistry.Registry("registry", {
        resourceGroupName: resourceGroup.name,
        registryName: "myregistry",
        sku: {
            name: "Standard",  // Or "Premium" for advanced features
        },
        dataEndpointEnabled: true,  // Enable for better performance
        // ... other config
    });
    
    Step 2: Enable Microsoft Defender for Container Registries
    import * as azure from "@pulumi/azure-native";
    
    // Enable Defender for Container Registries at the subscription level
    const defenderPlan = new azure.security.Pricing("containerRegistry", {
        pricingName: "ContainerRegistry",
        pricingTier: "Standard",
    });
    
    // The registry will automatically integrate with Defender for vulnerability scanning
    // Ensure the registry SKU is Standard or Premium (Basic does not support scanning)
    
    Step 3: Enable Quarantine Policy (Premium SKU)
    const registry = new azure.containerregistry.Registry("registry", {
        resourceGroupName: resourceGroup.name,
        registryName: "myregistry",
        sku: {
            name: "Premium",
        },
        policies: {
            quarantinePolicy: {
                status: "enabled",  // Quarantine unscanned images
            },
            trustPolicy: {
                status: "enabled",  // Require signed images (optional)
                type: "Notary",
            },
            retentionPolicy: {
                status: "enabled",
                days: 30,  // Retain images for 30 days
            },
        },
        dataEndpointEnabled: true,
    });
    
    Step 4: Integrate with Third-Party Scanner (Optional)
    // Example: Configure webhook for third-party scanning
    const webhook = new azure.containerregistry.Webhook("scanner-webhook", {
        resourceGroupName: resourceGroup.name,
        registryName: registry.name,
        webhookName: "scanner",
        location: resourceGroup.location,
        serviceUri: "https://scanner.example.com/webhook",
        actions: ["push"],
        status: "enabled",
        scope: "myapp/*:*",  // Scan all tags in myapp repositories
    });
    
    Step 5: Set Up Continuous Scanning
    // Task to scan all images in the registry
    const scanTask = new azure.containerregistry.Task("scan-task", {
        resourceGroupName: resourceGroup.name,
        registryName: registry.name,
        taskName: "scan-images",
        location: resourceGroup.location,
        platform: {
            os: "Linux",
        },
        step: {
            type: "Docker",
            dockerFilePath: "Dockerfile",
            contextPath: "https://github.com/Azure/acr-cli.git",
            imageNames: ["scan:{{.Run.ID}}"],
        },
        trigger: {
            timerTriggers: [{
                name: "daily-scan",
                schedule: "0 2 * * *",  // Run daily at 2 AM
            }],
        },
    });
    

    Best Practices:

    • Enable Microsoft Defender for all production registries
    • Use Premium SKU for production workloads requiring quarantine and trust policies
    • Implement CI/CD pipeline integration for automatic scanning on push
    • Set up notifications for critical vulnerabilities
    • Regularly review and act on vulnerability scan results
    • Consider content trust (image signing) for production images

    aks-load-balancer-tls-encryption

    Severity: high · Enforcement: advisory

    Ensure load balancers use TLS encryption for HTTPS traffic with proper certificate configuration.

    • 5.4 Network Security — Restrict Access to the Control Plane Endpoint. Ensure clusters are created with Private Endpoints and Private Nodes. Enable Network Policy. Encrypt traffic to HTTPS load balancers.
    Remediation
    Fix: Enable TLS Encryption for LoadBalancer Services
    import * as k8s from "@pulumi/kubernetes";
    
    const service = new k8s.core.v1.Service("my-service", {
        metadata: {
            name: "my-service",
            annotations: {
                // Azure Load Balancer with SSL certificate
                "service.beta.kubernetes.io/azure-load-balancer-ssl-cert": "my-ssl-cert",
                "service.beta.kubernetes.io/azure-load-balancer-ssl-ports": "443",
                // Optional: Use internal load balancer
                "service.beta.kubernetes.io/azure-load-balancer-internal": "true",
            },
        },
        spec: {
            type: "LoadBalancer",
            ports: [{
                name: "https",
                port: 443,
                targetPort: 8080,
                protocol: "TCP",
            }],
            selector: {
                app: "my-app",
            },
        },
    });
    

    aks-network-policy-enabled

    Severity: medium · Enforcement: advisory

    Ensure AKS clusters have network policy enabled to control traffic flow between pods, namespaces, and external networks.

    • 5.4 Network Security — Restrict Access to the Control Plane Endpoint. Ensure clusters are created with Private Endpoints and Private Nodes. Enable Network Policy. Encrypt traffic to HTTPS load balancers.
    Remediation
    Fix: Enable Network Policy for AKS Cluster
    Option 1: Azure CNI with Azure Network Policy
    const cluster = new azure.containerservice.ManagedCluster("cluster", {
        resourceGroupName: resourceGroup.name,
        networkProfile: {
            networkPlugin: "azure",
            networkPolicy: "azure",  // Azure network policy
            serviceCidr: "10.0.0.0/16",
            dnsServiceIP: "10.0.0.10",
        },
        // ... other config
    });
    
    Option 2: Azure CNI with Calico Network Policy
    const cluster = new azure.containerservice.ManagedCluster("cluster", {
        resourceGroupName: resourceGroup.name,
        networkProfile: {
            networkPlugin: "azure",
            networkPolicy: "calico",  // Calico network policy
            serviceCidr: "10.0.0.0/16",
            dnsServiceIP: "10.0.0.10",
        },
        // ... other config
    });
    
    Option 3: Kubenet with Calico Network Policy
    const cluster = new azure.containerservice.ManagedCluster("cluster", {
        resourceGroupName: resourceGroup.name,
        networkProfile: {
            networkPlugin: "kubenet",
            networkPolicy: "calico",  // Only Calico works with kubenet
            podCidr: "10.244.0.0/16",
            serviceCidr: "10.0.0.0/16",
            dnsServiceIP: "10.0.0.10",
        },
        // ... other config
    });
    

    Note: Azure network policy requires Azure CNI network plugin. Kubenet only supports Calico network policy.

    aks-private-endpoint-enabled

    Severity: high · Enforcement: advisory

    Ensure AKS clusters are created with private endpoint enabled and public access disabled.

    • 5.4 Network Security — Restrict Access to the Control Plane Endpoint. Ensure clusters are created with Private Endpoints and Private Nodes. Enable Network Policy. Encrypt traffic to HTTPS load balancers.
    Remediation
    Fix: Enable Private Endpoint for AKS Cluster

    Note: This policy mandates that clusters must use private endpoints. For detailed private cluster configuration validation, see the aks-cluster-endpoint-restrict-public-access policy.

    Option 1: Private Cluster with System-Managed DNS
    const cluster = new azure.containerservice.ManagedCluster("cluster", {
        resourceGroupName: resourceGroup.name,
        apiServerAccessProfile: {
            enablePrivateCluster: true,
            enablePrivateClusterPublicFQDN: false,
            privateDNSZone: "system", // Azure manages the private DNS zone
        },
        // ... other config
    });
    
    Option 2: Private Cluster with Custom Private DNS Zone
    const privateDnsZone = new azure.network.PrivateDnsZone("aksPrivateDns", {
        resourceGroupName: resourceGroup.name,
        location: "global",
        privateZoneName: "privatelink.eastus.azmk8s.io", // Region-specific
    });
    
    const cluster = new azure.containerservice.ManagedCluster("cluster", {
        resourceGroupName: resourceGroup.name,
        apiServerAccessProfile: {
            enablePrivateCluster: true,
            enablePrivateClusterPublicFQDN: false,
            privateDNSZone: privateDnsZone.id, // Custom private DNS zone
        },
        // ... other config
    });
    
    Option 3: Private Cluster with No Private DNS Zone (BYO DNS)
    const cluster = new azure.containerservice.ManagedCluster("cluster", {
        resourceGroupName: resourceGroup.name,
        apiServerAccessProfile: {
            enablePrivateCluster: true,
            enablePrivateClusterPublicFQDN: false,
            privateDNSZone: "none", // Manage DNS resolution yourself
        },
        // ... other config
    });
    

    Important Notes:

    • Private clusters require network connectivity from where kubectl is run
    • Configure VPN or ExpressRoute for accessing private clusters
    • Private DNS zones must be linked to the virtual network
    • Consider using Azure Bastion or jump boxes for cluster management

    aks-private-nodes-enabled

    Severity: high · Enforcement: advisory

    Ensure AKS cluster nodes are private without public IP addresses assigned.

    • 5.4 Network Security — Restrict Access to the Control Plane Endpoint. Ensure clusters are created with Private Endpoints and Private Nodes. Enable Network Policy. Encrypt traffic to HTTPS load balancers.
    Remediation
    Fix: Disable Public IPs for AKS Cluster Nodes
    Option 1: Private Nodes with Load Balancer Outbound (Default)
    const cluster = new azure.containerservice.ManagedCluster("cluster", {
        resourceGroupName: resourceGroup.name,
        networkProfile: {
            networkPlugin: "azure",
            outboundType: "loadBalancer", // NAT through Azure Load Balancer
            loadBalancerProfile: {
                managedOutboundIPs: {
                    count: 1, // Number of managed public IPs for outbound
                },
            },
        },
        agentPoolProfiles: [{
            name: "agentpool",
            count: 3,
            vmSize: "Standard_DS2_v2",
            enableNodePublicIP: false, // No public IPs on nodes
            vnetSubnetID: subnet.id,
            mode: "System",
        }],
        // ... other config
    });
    
    Option 2: Private Nodes with User-Defined Routing (Most Secure)
    const cluster = new azure.containerservice.ManagedCluster("cluster", {
        resourceGroupName: resourceGroup.name,
        networkProfile: {
            networkPlugin: "azure",
            outboundType: "userDefinedRouting", // Route through firewall/NVA
            serviceCidr: "10.0.0.0/16",
            dnsServiceIP: "10.0.0.10",
        },
        agentPoolProfiles: [{
            name: "agentpool",
            count: 3,
            vmSize: "Standard_DS2_v2",
            enableNodePublicIP: false, // No public IPs on nodes
            vnetSubnetID: subnet.id, // Subnet must have route table configured
            mode: "System",
        }],
        // ... other config
    });
    
    Option 3: Private Nodes with NAT Gateway
    const natGateway = new azure.network.NatGateway("natGateway", {
        resourceGroupName: resourceGroup.name,
        location: resourceGroup.location,
        sku: {
            name: "Standard",
        },
    });
    
    const cluster = new azure.containerservice.ManagedCluster("cluster", {
        resourceGroupName: resourceGroup.name,
        networkProfile: {
            networkPlugin: "azure",
            outboundType: "managedNATGateway", // Or "userAssignedNATGateway"
            natGatewayProfile: {
                managedOutboundIPProfile: {
                    count: 1,
                },
            },
        },
        agentPoolProfiles: [{
            name: "agentpool",
            count: 3,
            vmSize: "Standard_DS2_v2",
            enableNodePublicIP: false, // No public IPs on nodes
            vnetSubnetID: subnet.id,
            mode: "System",
        }],
        // ... other config
    });
    

    Important Notes:

    • User-defined routing requires proper route table configuration on the subnet
    • Ensure egress firewall rules are configured when using user-defined routing
    • NAT Gateway provides better SNAT port allocation for high-scale scenarios

    aks-secrets-encryption-enabled

    Severity: critical · Enforcement: advisory

    Ensure AKS clusters have encryption at rest enabled for Kubernetes secrets stored in etcd.

    • 5.3 Encryption — Ensure Kubernetes Secrets are encrypted.
    Remediation
    Fix: Enable Secrets Encryption for AKS Cluster
    Option 1: Enable Encryption at Host (Platform-Managed Keys)
    const cluster = new azure.containerservice.ManagedCluster("cluster", {
        resourceGroupName: resourceGroup.name,
        agentPoolProfiles: [{
            name: "agentpool",
            count: 3,
            vmSize: "Standard_DS2_v2",
            enableEncryptionAtHost: true,  // Enable encryption at host
            osDiskSizeGB: 128,
            mode: "System",
        }],
        // ... other config
    });
    
    Option 2: Use Customer-Managed Keys with Azure Key Vault
    const keyVault = new azure.keyvault.Vault("vault", {
        resourceGroupName: resourceGroup.name,
        location: resourceGroup.location,
        tenantId: tenantId,
        sku: {
            family: "A",
            name: "standard",
        },
        enabledForDiskEncryption: true,
    });
    
    const key = new azure.keyvault.Key("key", {
        keyVaultId: keyVault.id,
        keyType: "RSA",
        keySize: 2048,
        keyOpts: ["encrypt", "decrypt", "wrapKey", "unwrapKey"],
    });
    
    const diskEncryptionSet = new azure.compute.DiskEncryptionSet("des", {
        resourceGroupName: resourceGroup.name,
        location: resourceGroup.location,
        identity: {
            type: "SystemAssigned",
        },
        encryptionType: "EncryptionAtRestWithCustomerKey",
        activeKey: {
            keyUrl: key.keyUriWithVersion,
            sourceVault: {
                id: keyVault.id,
            },
        },
    });
    
    const cluster = new azure.containerservice.ManagedCluster("cluster", {
        resourceGroupName: resourceGroup.name,
        diskEncryptionSetID: diskEncryptionSet.id,  // Use customer-managed keys
        agentPoolProfiles: [{
            name: "agentpool",
            count: 3,
            vmSize: "Standard_DS2_v2",
            enableEncryptionAtHost: true,
            osDiskSizeGB: 128,
            mode: "System",
        }],
        // ... other config
    });
    

    Note: Encryption at host must be enabled on your Azure subscription before use.

    k8s-cluster-admin-role-binding-minimized

    Severity: high · Enforcement: advisory

    Ensure cluster-admin ClusterRole is bound to a minimal number of users and service accounts.

    • 4.1 RBAC and Authentication — Restrict administrator privileges to dedicated administrator accounts on enterprise assets. Implement proper RBAC controls.
    Remediation
    Fix: Minimize cluster-admin Role Bindings

    Use more specific roles instead of cluster-admin:

    // Instead of binding cluster-admin, create specific roles
    const role = new k8s.rbac.v1.Role("specificRole", {
        metadata: { namespace: "default" },
        rules: [{
            apiGroups: [""],
            resources: ["pods"],
            verbs: ["get", "list", "watch"],
        }],
    });
    
    const roleBinding = new k8s.rbac.v1.RoleBinding("specificBinding", {
        metadata: { namespace: "default" },
        roleRef: {
            apiGroup: "rbac.authorization.k8s.io",
            kind: "Role",
            name: role.metadata.name,
        },
        subjects: [{
            kind: "User",
            name: "specific-user",
        }],
    });
    

    k8s-default-namespace-not-used

    Severity: medium · Enforcement: advisory

    Ensure workloads are not deployed to the default namespace.

    • 4.5-4.6 Secret Management and Namespaces — Prefer using secrets as files over secrets as environment variables. Ensure default namespace is not used for workloads. Apply security context to pods and containers.
    Remediation
    Fix: Use Dedicated Namespaces

    Create and use dedicated namespaces:

    const namespace = new k8s.core.v1.Namespace("app-namespace", {
        metadata: {
            name: "my-app",
        },
    });
    
    const deployment = new k8s.apps.v1.Deployment("app", {
        metadata: {
            namespace: namespace.metadata.name,
        },
        // ... rest of deployment spec
    });
    

    k8s-default-service-accounts-not-used

    Severity: medium · Enforcement: advisory

    Ensure workloads do not use the default service account.

    • 4.1 RBAC and Authentication — Restrict administrator privileges to dedicated administrator accounts on enterprise assets. Implement proper RBAC controls.
    Remediation
    Fix: Use Dedicated Service Accounts

    Create and use dedicated service accounts for workloads:

    const serviceAccount = new k8s.core.v1.ServiceAccount("appServiceAccount", {
        metadata: {
            namespace: "default",
            name: "my-app-sa",
        },
    });
    
    const deployment = new k8s.apps.v1.Deployment("app", {
        spec: {
            template: {
                spec: {
                    serviceAccountName: serviceAccount.metadata.name,
                    // ... rest of pod spec
                },
            },
        },
    });
    

    k8s-namespaces-network-policies-defined

    Severity: medium · Enforcement: advisory

    Ensure all namespaces have network policies defined to control pod-to-pod communication and implement network segmentation.

    • 4.4 Network Policies — Ensure that all Namespaces have Network Policies defined.
    Remediation
    Fix: Define Network Policies for All Namespaces
    Step 1: Create Default Deny Network Policies
    import * as k8s from "@pulumi/kubernetes";
    
    // Create namespace with label
    const namespace = new k8s.core.v1.Namespace("app-namespace", {
        metadata: {
            name: "production",
            labels: {
                name: "production",
                environment: "prod",
            },
        },
    });
    
    // Default deny all ingress
    const denyAllIngress = new k8s.networking.v1.NetworkPolicy("deny-all-ingress", {
        metadata: {
            name: "default-deny-ingress",
            namespace: namespace.metadata.name,
        },
        spec: {
            podSelector: {},  // Applies to all pods
            policyTypes: ["Ingress"],
        },
    });
    
    // Default deny all egress
    const denyAllEgress = new k8s.networking.v1.NetworkPolicy("deny-all-egress", {
        metadata: {
            name: "default-deny-egress",
            namespace: namespace.metadata.name,
        },
        spec: {
            podSelector: {},  // Applies to all pods
            policyTypes: ["Egress"],
        },
    });
    
    // Allow specific ingress traffic
    const allowIngress = new k8s.networking.v1.NetworkPolicy("allow-ingress", {
        metadata: {
            name: "allow-from-frontend",
            namespace: namespace.metadata.name,
        },
        spec: {
            podSelector: {
                matchLabels: { app: "api" },
            },
            policyTypes: ["Ingress"],
            ingress: [{
                from: [
                    {
                        namespaceSelector: {
                            matchLabels: { name: "frontend" },
                        },
                    },
                    {
                        podSelector: {
                            matchLabels: { app: "web" },
                        },
                    },
                ],
                ports: [{
                    protocol: "TCP",
                    port: 8080,
                }],
            }],
        },
    });
    
    // Allow DNS and specific egress
    const allowEgress = new k8s.networking.v1.NetworkPolicy("allow-egress", {
        metadata: {
            name: "allow-to-services",
            namespace: namespace.metadata.name,
        },
        spec: {
            podSelector: {
                matchLabels: { app: "api" },
            },
            policyTypes: ["Egress"],
            egress: [
                // Allow database access
                {
                    to: [{
                        namespaceSelector: {
                            matchLabels: { name: "database" },
                        },
                        podSelector: {
                            matchLabels: { app: "postgres" },
                        },
                    }],
                    ports: [{
                        protocol: "TCP",
                        port: 5432,
                    }],
                },
                // Allow DNS
                {
                    to: [{
                        namespaceSelector: {
                            matchLabels: { name: "kube-system" },
                        },
                        podSelector: {
                            matchLabels: { "k8s-app": "kube-dns" },
                        },
                    }],
                    ports: [{
                        protocol: "UDP",
                        port: 53,
                    }],
                },
            ],
        },
    });
    
    Step 2: Additional Network Policy Patterns
    // Allow traffic within same namespace only
    const allowSameNamespace = new k8s.networking.v1.NetworkPolicy("allow-same-namespace", {
        metadata: {
            name: "allow-same-namespace",
            namespace: namespace.metadata.name,
        },
        spec: {
            podSelector: {},  // Applies to all pods
            policyTypes: ["Ingress"],
            ingress: [{
                from: [{
                    podSelector: {},  // All pods in same namespace
                }],
            }],
        },
    });
    
    // Allow traffic from ingress controller
    const allowFromIngress = new k8s.networking.v1.NetworkPolicy("allow-from-ingress", {
        metadata: {
            name: "allow-from-ingress",
            namespace: namespace.metadata.name,
        },
        spec: {
            podSelector: {
                matchLabels: { expose: "true" },
            },
            policyTypes: ["Ingress"],
            ingress: [{
                from: [
                    {
                        namespaceSelector: {
                            matchLabels: { name: "ingress-nginx" },
                        },
                    },
                    {
                        podSelector: {
                            matchLabels: { "app.kubernetes.io/name": "ingress-nginx" },
                        },
                    },
                ],
            }],
        },
    });
    

    Best Practices:

    • Start with default-deny policies (deny all ingress/egress)
    • Explicitly allow only required traffic flows
    • Use namespace and pod selectors for fine-grained control
    • Always allow DNS (UDP 53 to kube-dns)
    • Document the purpose of each network policy
    • Test network policies in non-production first
    • Use tools like kubectl auth can-i to verify connectivity
    • Consider using Calico or Cilium for enhanced network policy features

    k8s-pod-security-allow-privilege-escalation-minimized

    Severity: high · Enforcement: advisory

    Ensure containers do not allow privilege escalation.

    • 4.2 Pod Security — Minimize the admission of privileged containers and containers with dangerous capabilities.
    Remediation
    Fix: Disable Privilege Escalation

    Set allowPrivilegeEscalation to false in container security context:

    const pod = new k8s.core.v1.Pod("pod", {
        spec: {
            containers: [{
                name: "app",
                image: "nginx",
                securityContext: {
                    allowPrivilegeEscalation: false,
                    runAsNonRoot: true,
                    capabilities: {
                        drop: ["ALL"],
                    },
                },
            }],
        },
    });
    

    k8s-pod-security-context-applied

    Severity: medium · Enforcement: advisory

    Ensure security context is applied to pods and containers with appropriate security settings for defense in depth.

    • 4.5-4.6 Secret Management and Namespaces — Prefer using secrets as files over secrets as environment variables. Ensure default namespace is not used for workloads. Apply security context to pods and containers.
    Remediation
    Fix: Apply Security Context to Pods and Containers
    Step 1: Create Deployment with Basic Security Context
    import * as k8s from "@pulumi/kubernetes";
    
    const deployment = new k8s.apps.v1.Deployment("secure-app", {
        metadata: {
            name: "secure-app",
            namespace: "production",
        },
        spec: {
            replicas: 3,
            selector: {
                matchLabels: { app: "secure-app" },
            },
            template: {
                metadata: {
                    labels: { app: "secure-app" },
                },
                spec: {
                    // Pod-level security context
                    securityContext: {
                        runAsNonRoot: true,
                        runAsUser: 1000,
                        runAsGroup: 3000,
                        fsGroup: 2000,
                        seccompProfile: {
                            type: "RuntimeDefault",
                        },
                    },
                    containers: [{
                        name: "app",
                        image: "myapp:v1.0.0",
                        // Container-level security context
                        securityContext: {
                            allowPrivilegeEscalation: false,
                            readOnlyRootFilesystem: true,
                            runAsNonRoot: true,
                            runAsUser: 1000,
                            capabilities: {
                                drop: ["ALL"],
                            },
                            seccompProfile: {
                                type: "RuntimeDefault",
                            },
                        },
                        volumeMounts: [
                            {
                                name: "tmp",
                                mountPath: "/tmp",
                            },
                            {
                                name: "cache",
                                mountPath: "/app/cache",
                            },
                        ],
                        resources: {
                            requests: {
                                cpu: "100m",
                                memory: "128Mi",
                            },
                            limits: {
                                cpu: "500m",
                                memory: "512Mi",
                            },
                        },
                    }],
                    volumes: [
                        {
                            name: "tmp",
                            emptyDir: { sizeLimit: "100Mi" },
                        },
                        {
                            name: "cache",
                            emptyDir: { sizeLimit: "1Gi" },
                        },
                    ],
                },
            },
        },
    });
    
    Step 2: Hardened Deployment with Read-Only Root Filesystem
    const hardenedDeployment = new k8s.apps.v1.Deployment("hardened-app", {
        metadata: {
            name: "hardened-app",
            namespace: "production",
        },
        spec: {
            replicas: 3,
            selector: {
                matchLabels: { app: "hardened-app" },
            },
            template: {
                metadata: {
                    labels: { app: "hardened-app" },
                },
                spec: {
                    securityContext: {
                        runAsNonRoot: true,
                        runAsUser: 10001,  // High UID (non-system user)
                        fsGroup: 10001,
                        seccompProfile: {
                            type: "RuntimeDefault",
                        },
                    },
                    containers: [{
                        name: "app",
                        image: "myapp:v1.0.0",
                        securityContext: {
                            allowPrivilegeEscalation: false,
                            privileged: false,
                            readOnlyRootFilesystem: true,
                            runAsNonRoot: true,
                            capabilities: {
                                drop: ["ALL"],
                                // Only add if absolutely necessary
                                // add: ["NET_BIND_SERVICE"],  // For binding to ports < 1024
                            },
                            seccompProfile: {
                                type: "RuntimeDefault",
                            },
                        },
                        volumeMounts: [
                            {
                                name: "tmp",
                                mountPath: "/tmp",
                            },
                            {
                                name: "app-data",
                                mountPath: "/app/data",
                            },
                        ],
                    }],
                    volumes: [
                        {
                            name: "tmp",
                            emptyDir: { sizeLimit: "100Mi" },
                        },
                        {
                            name: "app-data",
                            persistentVolumeClaim: {
                                claimName: "app-data-pvc",
                            },
                        },
                    ],
                },
            },
        },
    });
    
    Step 3: Enforce Pod Security Standards at Namespace Level
    // Create namespace with Pod Security Standards labels
    const productionNamespace = new k8s.core.v1.Namespace("production", {
        metadata: {
            name: "production",
            labels: {
                // Restricted is the most secure PSS level
                "pod-security.kubernetes.io/enforce": "restricted",
                "pod-security.kubernetes.io/enforce-version": "latest",
                "pod-security.kubernetes.io/audit": "restricted",
                "pod-security.kubernetes.io/warn": "restricted",
            },
        },
    });
    

    Security Context Fields Reference:

    Pod-Level Security Context:

    • runAsUser: UID to run pod processes (avoid 0/root)
    • runAsGroup: GID to run pod processes
    • runAsNonRoot: Enforce non-root user
    • fsGroup: Group for volume ownership
    • supplementalGroups: Additional groups
    • seccompProfile: Seccomp profile (RuntimeDefault recommended)
    • seLinuxOptions: SELinux context
    • fsGroupChangePolicy: How fsGroup is applied

    Container-Level Security Context:

    • runAsUser: UID for container (overrides pod)
    • runAsGroup: GID for container (overrides pod)
    • runAsNonRoot: Enforce non-root (overrides pod)
    • readOnlyRootFilesystem: Immutable root FS
    • allowPrivilegeEscalation: Prevent privilege escalation
    • privileged: Run as privileged (never use)
    • capabilities: Add/drop Linux capabilities
    • seccompProfile: Seccomp profile
    • seLinuxOptions: SELinux context

    Best Practices:

    • Always set runAsNonRoot: true
    • Use high UIDs (>= 1000) to avoid conflicts
    • Drop all capabilities, add only required ones
    • Use readOnlyRootFilesystem: true with volume mounts for writable dirs
    • Set allowPrivilegeEscalation: false
    • Use seccompProfile: RuntimeDefault for syscall filtering
    • Apply Pod Security Standards at namespace level
    • Test security context settings in non-prod first

    k8s-pod-security-host-ipc-minimized

    Severity: high · Enforcement: advisory

    Ensure containers do not share the host IPC namespace.

    • 4.2 Pod Security — Minimize the admission of privileged containers and containers with dangerous capabilities.
    Remediation
    Fix: Disable Host IPC Sharing

    Remove hostIPC from pod spec:

    const pod = new k8s.core.v1.Pod("pod", {
        spec: {
            hostIPC: false,  // Explicitly set to false or remove
            containers: [{
                name: "app",
                image: "nginx",
            }],
        },
    });
    

    k8s-pod-security-host-network-minimized

    Severity: high · Enforcement: advisory

    Ensure containers do not share the host network namespace.

    • 4.2 Pod Security — Minimize the admission of privileged containers and containers with dangerous capabilities.
    Remediation
    Fix: Disable Host Network Sharing

    Remove hostNetwork from pod spec:

    const pod = new k8s.core.v1.Pod("pod", {
        spec: {
            hostNetwork: false,  // Explicitly set to false or remove
            containers: [{
                name: "app",
                image: "nginx",
            }],
        },
    });
    

    k8s-pod-security-host-pid-minimized

    Severity: high · Enforcement: advisory

    Ensure containers do not share the host process ID namespace.

    • 4.2 Pod Security — Minimize the admission of privileged containers and containers with dangerous capabilities.
    Remediation
    Fix: Disable Host PID Sharing

    Remove hostPID from pod spec:

    const pod = new k8s.core.v1.Pod("pod", {
        spec: {
            hostPID: false,  // Explicitly set to false or remove
            containers: [{
                name: "app",
                image: "nginx",
            }],
        },
    });
    

    k8s-pod-security-privileged-containers-minimized

    Severity: critical · Enforcement: advisory

    Ensure containers do not run in privileged mode.

    • 4.2 Pod Security — Minimize the admission of privileged containers and containers with dangerous capabilities.
    Remediation
    Fix: Disable Privileged Mode

    Remove privileged flag from container security context:

    const pod = new k8s.core.v1.Pod("pod", {
        spec: {
            containers: [{
                name: "app",
                image: "nginx",
                securityContext: {
                    privileged: false,  // Explicitly set to false or remove
                    runAsNonRoot: true,
                    capabilities: {
                        drop: ["ALL"],
                    },
                },
            }],
        },
    });
    

    k8s-rbac-create-pods-minimized

    Severity: medium · Enforcement: advisory

    Ensure Roles and ClusterRoles do not grant excessive permissions to create pods or resources that can create pods (deployments, jobs, etc.).

    • 4.1 RBAC and Authentication — Restrict administrator privileges to dedicated administrator accounts on enterprise assets. Implement proper RBAC controls.
    Remediation
    Fix: Minimize Pod Creation Permissions

    Avoid granting create permissions on pods and pod-creating resources:

    // Good: Read-only access to pods
    const podReader = new k8s.rbac.v1.Role("podReader", {
        metadata: { namespace: "default" },
        rules: [{
            apiGroups: [""],
            resources: ["pods"],
            verbs: ["get", "list", "watch"],  // Read-only
        }],
    });
    
    // Bad: Avoid granting pod creation abilities
    const podCreator = new k8s.rbac.v1.Role("podCreator", {
        rules: [{
            apiGroups: [""],
            resources: ["pods", "pods/exec", "pods/attach"],
            verbs: ["create", "*"],  // DON'T DO THIS unless absolutely necessary
        }],
    });
    

    Resources that can create pods and should be restricted:

    • pods, pods/exec, pods/attach, pods/portforward
    • deployments, replicasets, statefulsets, daemonsets, jobs, cronjobs
    • replicationcontrollers

    k8s-rbac-secret-access-minimized

    Severity: critical · Enforcement: advisory

    Ensure Roles and ClusterRoles do not grant excessive permissions to secrets (create, update, patch, delete, or wildcard verbs).

    • 4.1 RBAC and Authentication — Restrict administrator privileges to dedicated administrator accounts on enterprise assets. Implement proper RBAC controls.
    Remediation
    Fix: Minimize Secret Access Permissions

    Limit secret access to read-only (get, list, watch) unless write access is explicitly required:

    // Good: Read-only secret access
    const readOnlyRole = new k8s.rbac.v1.Role("secretReader", {
        metadata: { namespace: "default" },
        rules: [{
            apiGroups: [""],
            resources: ["secrets"],
            verbs: ["get", "list", "watch"],  // Read-only access
        }],
    });
    
    // Bad: Avoid granting write access to secrets
    const writeRole = new k8s.rbac.v1.Role("secretWriter", {
        rules: [{
            apiGroups: [""],
            resources: ["secrets"],
            verbs: ["*"],  // DON'T DO THIS
        }],
    });
    

    Only grant create/update/delete/patch permissions when absolutely necessary and document the justification.

    k8s-rbac-wildcard-use-minimized

    Severity: high · Enforcement: advisory

    Ensure Roles and ClusterRoles do not use wildcards for resources, verbs, or apiGroups.

    • 4.1 RBAC and Authentication — Restrict administrator privileges to dedicated administrator accounts on enterprise assets. Implement proper RBAC controls.
    Remediation
    Fix: Remove Wildcards from RBAC Rules

    Be specific about resources, verbs, and API groups:

    const role = new k8s.rbac.v1.Role("specificRole", {
        metadata: { namespace: "default" },
        rules: [{
            apiGroups: [""],  // Specific API group, not "*"
            resources: ["pods", "services"],  // Specific resources, not "*"
            verbs: ["get", "list", "watch"],  // Specific verbs, not "*"
        }],
    });
    

    k8s-resource-namespace-boundaries

    Severity: low · Enforcement: advisory

    Ensure administrative boundaries are maintained between resources using namespaces for proper multi-tenancy and resource isolation.

    • 4.5-4.6 Secret Management and Namespaces — Prefer using secrets as files over secrets as environment variables. Ensure default namespace is not used for workloads. Apply security context to pods and containers.
    Remediation
    Fix: Establish Proper Namespace Boundaries
    Step 1: Create Namespaces with Resource Quotas and Limits
    import * as k8s from "@pulumi/kubernetes";
    
    // Create production namespace
    const prodNamespace = new k8s.core.v1.Namespace("production", {
        metadata: {
            name: "production",
            labels: {
                environment: "production",
                team: "platform",
                "cost-center": "engineering",
            },
            annotations: {
                description: "Production environment workloads",
            },
        },
    });
    
    // Create resource quota
    const prodQuota = new k8s.core.v1.ResourceQuota("prod-quota", {
        metadata: {
            name: "compute-quota",
            namespace: prodNamespace.metadata.name,
        },
        spec: {
            hard: {
                "requests.cpu": "100",
                "requests.memory": "200Gi",
                "limits.cpu": "200",
                "limits.memory": "400Gi",
                "persistentvolumeclaims": "10",
                "pods": "50",
            },
        },
    });
    
    // Create limit range
    const prodLimits = new k8s.core.v1.LimitRange("prod-limits", {
        metadata: {
            name: "resource-limits",
            namespace: prodNamespace.metadata.name,
        },
        spec: {
            limits: [
                {
                    type: "Container",
                    default: {
                        cpu: "500m",
                        memory: "512Mi",
                    },
                    defaultRequest: {
                        cpu: "100m",
                        memory: "128Mi",
                    },
                    max: {
                        cpu: "2",
                        memory: "4Gi",
                    },
                    min: {
                        cpu: "50m",
                        memory: "64Mi",
                    },
                },
                {
                    type: "Pod",
                    max: {
                        cpu: "4",
                        memory: "8Gi",
                    },
                },
            ],
        },
    });
    
    // Deploy application in proper namespace
    const deployment = new k8s.apps.v1.Deployment("app", {
        metadata: {
            name: "my-app",
            namespace: prodNamespace.metadata.name,  // Use dedicated namespace
            labels: {
                app: "my-app",
                environment: "production",
            },
        },
        spec: {
            replicas: 3,
            selector: {
                matchLabels: { app: "my-app" },
            },
            template: {
                metadata: {
                    labels: { app: "my-app" },
                },
                spec: {
                    containers: [{
                        name: "app",
                        image: "myapp:v1.0.0",
                        resources: {
                            requests: {
                                cpu: "100m",
                                memory: "128Mi",
                            },
                            limits: {
                                cpu: "500m",
                                memory: "512Mi",
                            },
                        },
                    }],
                },
            },
        },
    });
    
    Step 2: Configure RBAC for Namespace Isolation
    // Create development namespace
    const devNamespace = new k8s.core.v1.Namespace("development", {
        metadata: {
            name: "development",
            labels: {
                environment: "development",
                team: "platform",
            },
        },
    });
    
    // Create role for namespace-scoped access
    const developerRole = new k8s.rbac.v1.Role("developer", {
        metadata: {
            name: "developer",
            namespace: devNamespace.metadata.name,
        },
        rules: [{
            apiGroups: ["", "apps", "batch"],
            resources: ["pods", "deployments", "jobs", "configmaps", "secrets"],
            verbs: ["get", "list", "watch", "create", "update", "patch", "delete"],
        }],
    });
    
    // Bind role to team
    const developersBinding = new k8s.rbac.v1.RoleBinding("developers", {
        metadata: {
            name: "developers",
            namespace: devNamespace.metadata.name,
        },
        subjects: [{
            kind: "Group",
            name: "developers",
            apiGroup: "rbac.authorization.k8s.io",
        }],
        roleRef: {
            kind: "Role",
            name: developerRole.metadata.name,
            apiGroup: "rbac.authorization.k8s.io",
        },
    });
    

    Best Practices:

    • Create separate namespaces for environments (prod, staging, dev)
    • Use namespaces for team or application boundaries
    • Set resource quotas to prevent resource exhaustion
    • Define limit ranges for default resource constraints
    • Use RBAC to restrict access to specific namespaces
    • Label namespaces consistently (environment, team, cost-center)
    • Never deploy production workloads in default namespace
    • Consider using namespace-as-a-service tools (Hierarchical Namespace Controller)
    • Monitor namespace resource usage and quota limits

    k8s-secrets-as-files-not-env-vars

    Severity: medium · Enforcement: advisory

    Ensure secrets are mounted as files via volumes rather than exposed as environment variables.

    • 4.5-4.6 Secret Management and Namespaces — Prefer using secrets as files over secrets as environment variables. Ensure default namespace is not used for workloads. Apply security context to pods and containers.
    Remediation
    Fix: Mount Secrets as Files

    Use volume mounts instead of environment variables for secrets:

    // Bad: Using secrets as environment variables
    const badPod = new k8s.core.v1.Pod("bad-pod", {
        spec: {
            containers: [{
                name: "app",
                image: "myapp:latest",
                env: [{
                    name: "DB_PASSWORD",
                    valueFrom: {
                        secretKeyRef: {
                            name: "db-secret",
                            key: "password",
                        },
                    },
                }],
            }],
        },
    });
    
    // Good: Mount secrets as files
    const goodPod = new k8s.core.v1.Pod("good-pod", {
        spec: {
            containers: [{
                name: "app",
                image: "myapp:latest",
                volumeMounts: [{
                    name: "secret-volume",
                    mountPath: "/etc/secrets",
                    readOnly: true,
                }],
            }],
            volumes: [{
                name: "secret-volume",
                secret: {
                    secretName: "db-secret",
                },
            }],
        },
    });
    // Access secret via: cat /etc/secrets/password
    

    Benefits of file-based secrets:

    • Not visible in process listings
    • Not logged by default
    • Can be rotated without container restart (with proper file watching)

    k8s-service-account-token-mounted-minimized

    Severity: medium · Enforcement: advisory

    Ensure service account tokens are not automatically mounted in pods unless explicitly required.

    • 4.1 RBAC and Authentication — Restrict administrator privileges to dedicated administrator accounts on enterprise assets. Implement proper RBAC controls.
    Remediation
    Fix: Disable Service Account Token Auto-Mounting

    Set automountServiceAccountToken: false unless the pod needs Kubernetes API access:

    // Good: Disable auto-mounting when not needed
    const pod = new k8s.core.v1.Pod("myPod", {
        metadata: { name: "my-pod" },
        spec: {
            automountServiceAccountToken: false,  // Explicitly disable
            containers: [{
                name: "app",
                image: "nginx:latest",
            }],
        },
    });
    
    // Also disable at ServiceAccount level
    const sa = new k8s.core.v1.ServiceAccount("myServiceAccount", {
        metadata: { name: "my-sa" },
        automountServiceAccountToken: false,  // Disable by default
    });
    

    Only enable when the pod explicitly requires Kubernetes API access.

      The infrastructure as code platform for any cloud.