Skip to content

Phase 2 Platform Security Evidence Checklist

Created: 2026-04-25
Owner: Project maintainers
Purpose: Repeatable validation commands for Azure platform security controls.

This checklist covers NSG, RBAC, Storage, Redis, PostgreSQL, and Key Vault security for low-side and high-side environments.


Prerequisites

export RG_LOW="<low-side-resource-group>"
export RG_HIGH="<high-side-resource-group>"
export SUBSCRIPTION="<subscription-id>"
export NAME_PREFIX="<deployment-name-prefix>"

Low-Side Security Controls

1. NSG Rules

Control: NSG allows only documented inbound paths from allowed CIDRs. Validation:

az network nsg rule list --resource-group "${RG_LOW}" --nsg-name "${NAME_PREFIX}-pulp-nsg" --output table
Expected: SSH:22, HTTPS:443, PulpAPI:18080, PulpContent:18081 with sourceAddressPrefix = VirtualNetwork or operator CIDR

2. Key Vault Security Configuration

Control: Purge protection, RBAC authorization, public access controlled. Validation:

KV_NAME=$(az keyvault list --resource-group "${RG_LOW}" --query "[0].name" -o tsv)
az keyvault show --name "${KV_NAME}" --query "{purgeProtection:properties.enablePurgeProtection, rbacAuth:properties.enableRbacAuthorization, publicAccess:properties.publicNetworkAccess, networkDefaultAction:properties.networkAcls.defaultAction}" -o json
Expected: purgeProtection=true, rbacAuth=true, publicAccess=Enabled (M1) or Disabled (M2), networkDefaultAction=Allow (M1) or Deny (M2)

3. Key Vault RBAC

Control: VM MI read-only Secrets User; operators Secrets Officer. Validation:

az role assignment list --scope "/subscriptions/${SUBSCRIPTION}/resourceGroups/${RG_LOW}/providers/Microsoft.KeyVault/vaults/${KV_NAME}" --output table
Expected: VM MI = Key Vault Secrets User (4633458b...), Operators = Key Vault Secrets Officer (b86a8fe4...)

4. Storage Security

Control: Blob public access disabled, TLS 1.2, public network controlled. Validation:

STORAGE_NAME=$(az storage account list --resource-group "${RG_LOW}" --query "[0].name" -o tsv)
az storage account show --name "${STORAGE_NAME}" --query "{allowBlobPublicAccess:allowBlobPublicAccess, minimumTlsVersion:minimumTlsVersion, publicNetworkAccess:publicNetworkAccess, httpsOnly:supportsHttpsTrafficOnly}" -o json
Expected: allowBlobPublicAccess=false, minimumTlsVersion=TLS1_2, httpsOnly=true

5. Storage Network ACLs

Control: Storage allows access from app subnet or Azure Services. Validation:

az storage account show --name "${STORAGE_NAME}" --query "{defaultAction:networkRuleSet.defaultAction, bypass:networkRuleSet.bypass, virtualNetworkRules:networkRuleSet.virtualNetworkRules[].id}" -o json
Expected: defaultAction=Allow (no subnet ACLs) or Deny (with subnet ACLs), bypass=AzureServices

6. Redis Cache Security

Control: Non-SSL port disabled, TLS 1.2, public access controlled. Validation:

REDIS_NAME=$(az redis list --resource-group "${RG_LOW}" --query "[0].name" -o tsv)
az redis show --name "${REDIS_NAME}" --resource-group "${RG_LOW}" --query "{enableNonSslPort:enableNonSslPort, minimumTlsVersion:minimumTlsVersion, publicNetworkAccess:publicNetworkAccess}" -o json
Expected: enableNonSslPort=false, minimumTlsVersion=1.2, publicNetworkAccess=Enabled (M1) or Disabled (M2)

7. PostgreSQL Flexible Server Security

Control: Private-only via delegated subnet, SSL enforced. Validation:

PG_NAME=$(az postgres flexible-server list --resource-group "${RG_LOW}" --query "[0].name" -o tsv)
az postgres flexible-server show --name "${PG_NAME}" --resource-group "${RG_LOW}" --query "{delegatedSubnet:network.delegatedSubnetResourceId, privateDnsZone:network.privateDnsZoneArmResourceId, publicAccess:network.publicNetworkAccess}" -o json
Expected: delegatedSubnet set, privateDnsZone set, publicAccess=null (private-only)


High-Side Security Controls

8. High-Side Public Network Access Audit

Control: High-side services disable public access unless explicit exception. Services: ACR, Service Bus, Redis, Storage, Key Vault Validation: See docs/evidence/collect-high-side-public-access-evidence.sh

9. High-Side VM Managed Identity RBAC

Control: VM identity has least-privilege access (AcrPull, Storage Blob Contributor, KV Secrets User read-only). Validation:

VM_NAME=$(az vm list --resource-group "${RG_HIGH}" --query "[0].name" -o tsv)
VM_IDENTITY_ID=$(az vm show --name "${VM_NAME}" --resource-group "${RG_HIGH}" --query "identity.userAssignedIdentities" -o json | jq -r 'keys[0]')
PRINCIPAL_ID=$(az identity show --ids "${VM_IDENTITY_ID}" --query "principalId" -o tsv)
az role assignment list --assignee "${PRINCIPAL_ID}" --output table
Expected: AcrPull (7f951dda...), Storage Blob Data Contributor (ba92f5b4...), Key Vault Secrets User (4633458b...)


Evidence Collection

Store generated JSON outputs in the gitignored manual-validation evidence directory, artifacts/evidence/manual-validation/<YYYYMMDD>/:

EVIDENCE_DIR="artifacts/evidence/manual-validation/$(date +%Y%m%d)"
mkdir -p "${EVIDENCE_DIR}"
az network nsg rule list --resource-group "${RG_LOW}" --nsg-name "${NAME_PREFIX}-pulp-nsg" -o json > "${EVIDENCE_DIR}/nsg-rules-low-side.json"
az keyvault show --name "${KV_NAME}" -o json > "${EVIDENCE_DIR}/keyvault-config-low-side.json"
# ... (continue for all controls)


Compliance Mapping

NIST SP 800-53 Rev 5: AC-3, AC-4, SC-7, SC-8, SC-28
Azure Security Benchmark v3: NS-1, NS-2, DP-3, DP-4, PA-7
DoD IL5+: Network isolation, TLS enforcement, RBAC least privilege


Validation Frequency

  • Initial deployment: Full checklist
  • Post-change: Affected sections
  • Periodic audit: Monthly
  • Pre-release: Full checklist before milestone gates