Skip to content

Regulated Tier Blueprint

The Regulated tier is for organizations operating under regulatory requirements (HIPAA, FedRAMP, SOC2, financial-services mandates) that need maximum security posture: customer-managed keys, private compute, strict policy enforcement, and extended audit retention.

What This Tier Adds

Addition Description
Private AKS Private API server, UDR egress, workload identity enabled
CMK encryption Customer-managed key created in Key Vault for downstream services
365-day retention Full year of audit logs for compliance
Explicit policy flags All governance flags set explicitly — no reliance on defaults
AKS diagnostics Cluster logs included in diagnostics fan-out
Future hooks Prepared for firewall, APIM, egress control, region restriction
enable_cmk                = true
enable_aks                = true
enable_policy_baseline    = true
log_retention_days        = 365
cost_center               = "CC-AI-0042"
allowed_locations         = ["eastus2"]
require_private_endpoints = true
deny_public_paas          = true

Future Regulated-Tier Modules

The following capabilities are planned for future releases:

Module Purpose
networking/firewall Azure Firewall with UDR egress and FQDN application rules
networking/apim API Management (internal mode) for governed AI API gateway
networking/egress_control NSG flow logs, Network Watcher, traffic analytics
governance/region_restriction Data replication restriction policies for data residency
security/defender Defender for Cloud plans for Key Vault, Storage, Resource Manager, AKS
identity/rbac Workload identities, CMK encryption roles, least-privilege operator roles

Reference Composition

# =============================================================================
# TenantZero AI -- Regulated Tier Blueprint
# =============================================================================
#
# WHAT THIS IS
# ------------
# A reference composition for organizations operating under regulatory
# requirements (HIPAA, FedRAMP, SOC2, financial-services mandates) that need
# maximum security posture: customer-managed keys, private compute, strict
# policy enforcement, and extended audit retention.
#
# THIS FILE IS NOT EXECUTABLE. It is documentation that mirrors the real
# module composition pattern used by every TenantZero environment stack.
#
# WHAT FOUNDATION TIER INCLUDES
# ------------------------------
#   - Foundation   : naming, tags, resource_group
#   - Networking   : vnet, private_dns
#   - Security     : key_vault + private endpoint
#   - AI           : openai + PE, ai_search + PE
#   - Data         : cosmos OR postgres + PE
#   - Observability: log_analytics, diagnostics
#
# WHAT ENTERPRISE TIER ADDS
# --------------------------
#   + governance/policy_baseline  (enabled)
#   + cost_center tag             (explicitly set)
#   + Full diagnostics wiring
#
# WHAT THIS TIER ADDS
# -------------------
#   + compute/aks_private         (enabled -- private API server, UDR egress)
#   + enable_cmk = true           (CMK key created in Key Vault)
#   + log_retention_days = 365    (1-year audit trail for compliance)
#   + All policy flags explicit   (no reliance on defaults)
#   + AKS added to diagnostics   (cluster logs sent to Log Analytics)
#   + Prepared hooks for future   (firewall, APIM, egress, region restriction)
#
# MODULE PATH CONVENTION
# ----------------------
# All source paths are relative to envs/<env>/ -- i.e. ../../modules/<ns>/<mod>
# =============================================================================

# -- Data source: current Azure context (tenant ID, subscription, etc.) -------
data "azurerm_client_config" "current" {}

# =============================================================================
# Local helpers
# =============================================================================

locals {
  group_names = {
    core = module.naming.resource_groups.core
    net  = module.naming.resource_groups.net
    sec  = module.naming.resource_groups.sec
    obs  = module.naming.resource_groups.obs
  }

  # ---------------------------------------------------------------------------
  # REGULATED ADDITION: AKS requires its own private DNS zone for the
  # private API server endpoint. Add it to the zone list when AKS is enabled.
  # ---------------------------------------------------------------------------
  private_dns_zones = concat(
    [
      "privatelink.openai.azure.com",
      "privatelink.vaultcore.azure.net",
      "privatelink.search.windows.net",
    ],
    var.data_profile == "cosmos"
      ? ["privatelink.documents.azure.com"]
      : ["privatelink.postgres.database.azure.com"],
    var.enable_aks
      ? ["privatelink.${var.location}.azmk8s.io"]
      : []
  )

  allowed_locations = length(var.allowed_locations) > 0 ? var.allowed_locations : [var.location]

  cosmos_zone_id   = try(module.private_dns.zone_ids["privatelink.documents.azure.com"], null)
  postgres_zone_id = try(module.private_dns.zone_ids["privatelink.postgres.database.azure.com"], null)
  postgres_subnet  = try(module.networking.subnet_ids["snet-data"], module.networking.subnet_ids["snet-compute"])
  aks_dns_zone_id  = try(module.private_dns.zone_ids["privatelink.${var.location}.azmk8s.io"], null)

  # ---------------------------------------------------------------------------
  # REGULATED CHANGE: diagnostics_targets includes ALL resources, including
  # AKS cluster and PostgreSQL when present. Every resource that supports
  # diagnostic settings MUST be wired for compliance audit trails.
  # ---------------------------------------------------------------------------
  diagnostics_targets = compact(concat(
    [
      module.openai.resource_id,
      module.key_vault.resource_id,
      module.ai_search.resource_id,
    ],
    var.data_profile == "cosmos"
      ? [module.cosmos[0].resource_id]
      : [module.postgres[0].resource_id],
    var.enable_aks
      ? [module.aks_private[0].cluster_id]
      : []
  ))
}

# =============================================================================
# 1. FOUNDATION -- naming, tags, resource groups
# =============================================================================

module "naming" {
  source = "../../modules/foundation/naming"

  client_name   = var.client_name   # e.g. "contoso"
  env           = var.env           # e.g. "prod"
  unique_suffix = var.unique_suffix # e.g. "x7q"
}

# ---------------------------------------------------------------------------
# ENTERPRISE+REGULATED: cost_center is mandatory for chargeback tracking.
# ---------------------------------------------------------------------------
module "tags" {
  source = "../../modules/foundation/tags"

  client_name = var.client_name
  env         = var.env
  cost_center = var.cost_center   # e.g. "CC-AI-0042" -- required
  extra_tags  = var.tags
}

module "resource_groups" {
  source = "../../modules/foundation/resource_group"

  groups = {
    core = { name = local.group_names.core, location = var.location, tags = module.tags.common_tags }
    net  = { name = local.group_names.net,  location = var.location, tags = module.tags.common_tags }
    sec  = { name = local.group_names.sec,  location = var.location, tags = module.tags.common_tags }
    obs  = { name = local.group_names.obs,  location = var.location, tags = module.tags.common_tags }
  }
}

# =============================================================================
# 2. NETWORKING -- VNet, subnets, NSGs, private DNS zones
# =============================================================================
# Regulated tier typically has a more segmented subnet layout:
#   snet-pe       -- private endpoints for PaaS services
#   snet-compute  -- AKS node pools
#   snet-data     -- PostgreSQL delegated subnet (if postgres profile)
# Ensure the VNet CIDR and subnet definitions in your tfvars accommodate
# the additional compute subnet capacity for AKS nodes.

module "networking" {
  source = "../../modules/networking/vnet"

  name                = module.naming.vnet_name
  resource_group_name = module.resource_groups.names["net"]
  location            = var.location
  vnet_cidr           = var.vnet_cidr
  subnets             = var.subnets
  nsg_rules           = var.nsg_rules
  tags                = module.tags.common_tags
}

module "private_dns" {
  source = "../../modules/networking/private_dns"

  resource_group_name = module.resource_groups.names["net"]
  vnet_id             = module.networking.vnet_id
  zones               = local.private_dns_zones   # includes AKS zone when enabled
  tags                = module.tags.common_tags
}

# =============================================================================
# 3. SECURITY -- Key Vault + private endpoint + CMK key
# =============================================================================

module "key_vault" {
  source = "../../modules/security/key_vault"

  name                = module.naming.key_vault_name
  resource_group_name = module.resource_groups.names["sec"]
  location            = var.location
  tenant_id           = data.azurerm_client_config.current.tenant_id
  tags                = module.tags.common_tags
  # All security defaults are already private-first:
  #   public_network_access_enabled = false
  #   enable_rbac_authorization     = true
  #   purge_protection_enabled      = true
  #   soft_delete_retention_days    = 90
}

module "pe_key_vault" {
  source = "../../modules/networking/private_endpoint"

  name                           = "pe-${module.naming.key_vault_name}"
  location                       = var.location
  resource_group_name            = module.resource_groups.names["sec"]
  subnet_id                      = module.networking.subnet_ids["snet-pe"]
  private_connection_resource_id = module.key_vault.resource_id
  subresource_names              = ["vault"]
  private_dns_zone_ids           = [module.private_dns.zone_ids["privatelink.vaultcore.azure.net"]]
  tags                           = module.tags.common_tags
}

# ---------------------------------------------------------------------------
# REGULATED CHANGE: CMK is ENABLED. The customer-managed key is created in
# Key Vault after the private endpoint is established. Services that support
# CMK encryption (Cosmos DB, OpenAI, AI Search, PostgreSQL, AKS etcd) can
# reference azurerm_key_vault_key.cmk[0].id for their encryption config.
#
# NOTE: Some services require additional RBAC (Key Vault Crypto Service
# Encryption User) for their managed identities. Those role assignments
# will be added when the identity/rbac module is implemented.
# ---------------------------------------------------------------------------
resource "azurerm_key_vault_key" "cmk" {
  count = var.enable_cmk ? 1 : 0   # Regulated: true

  name         = "cmk-default"
  key_vault_id = module.key_vault.resource_id
  key_type     = "RSA"
  key_size     = 2048
  key_opts     = ["decrypt", "encrypt", "sign", "unwrapKey", "verify", "wrapKey"]

  depends_on = [module.pe_key_vault]
}

# =============================================================================
# 4. AI SERVICES -- Azure OpenAI + AI Search, each with private endpoints
# =============================================================================

module "openai" {
  source = "../../modules/ai/openai"

  name                          = module.naming.openai_name
  resource_group_name           = module.resource_groups.names["core"]
  location                      = var.location
  sku                           = var.openai_sku
  public_network_access_enabled = false
  deployments                   = var.models
  tags                          = module.tags.common_tags
}

module "pe_openai" {
  source = "../../modules/networking/private_endpoint"

  name                           = "pe-${module.naming.openai_name}"
  location                       = var.location
  resource_group_name            = module.resource_groups.names["core"]
  subnet_id                      = module.networking.subnet_ids["snet-pe"]
  private_connection_resource_id = module.openai.resource_id
  subresource_names              = ["account"]
  private_dns_zone_ids           = [module.private_dns.zone_ids["privatelink.openai.azure.com"]]
  tags                           = module.tags.common_tags
}

module "ai_search" {
  source = "../../modules/ai/ai_search"

  name                          = module.naming.ai_search_name
  resource_group_name           = module.resource_groups.names["core"]
  location                      = var.location
  sku                           = var.search_sku
  replicas                      = var.search_replicas
  partitions                    = var.search_partitions
  public_network_access_enabled = false
  tags                          = module.tags.common_tags
}

module "pe_ai_search" {
  source = "../../modules/networking/private_endpoint"

  name                           = "pe-${module.naming.ai_search_name}"
  location                       = var.location
  resource_group_name            = module.resource_groups.names["core"]
  subnet_id                      = module.networking.subnet_ids["snet-pe"]
  private_connection_resource_id = module.ai_search.resource_id
  subresource_names              = ["searchService"]
  private_dns_zone_ids           = [module.private_dns.zone_ids["privatelink.search.windows.net"]]
  tags                           = module.tags.common_tags
}

# =============================================================================
# 5. DATA -- Cosmos DB -or- PostgreSQL
# =============================================================================

module "cosmos" {
  count  = var.data_profile == "cosmos" ? 1 : 0
  source = "../../modules/data/cosmos"

  name                = module.naming.cosmos_name
  resource_group_name = module.resource_groups.names["core"]
  location            = var.location
  tags                = module.tags.common_tags
}

module "pe_cosmos" {
  count  = var.data_profile == "cosmos" ? 1 : 0
  source = "../../modules/networking/private_endpoint"

  name                           = "pe-${module.naming.cosmos_name}"
  location                       = var.location
  resource_group_name            = module.resource_groups.names["core"]
  subnet_id                      = module.networking.subnet_ids["snet-pe"]
  private_connection_resource_id = module.cosmos[0].resource_id
  subresource_names              = ["Sql"]
  private_dns_zone_ids           = [local.cosmos_zone_id]
  tags                           = module.tags.common_tags
}

module "postgres" {
  count  = var.data_profile == "postgres" ? 1 : 0
  source = "../../modules/data/postgres"

  name                   = module.naming.postgres_name
  resource_group_name    = module.resource_groups.names["core"]
  location               = var.location
  delegated_subnet_id    = local.postgres_subnet
  private_dns_zone_id    = local.postgres_zone_id
  tenant_id              = data.azurerm_client_config.current.tenant_id
  administrator_password = var.postgres_administrator_password
  tags                   = module.tags.common_tags
}

# =============================================================================
# 6. OBSERVABILITY -- Log Analytics workspace + diagnostic settings
# =============================================================================
# ---------------------------------------------------------------------------
# REGULATED CHANGE: log_retention_days = 365 for 1-year compliance retention.
# All deployed resources (including AKS and data stores) are wired into the
# diagnostics fan-out to ensure complete audit trail coverage.
# ---------------------------------------------------------------------------

module "log_analytics" {
  source = "../../modules/observability/log_analytics"

  name                = module.naming.log_analytics_name
  resource_group_name = module.resource_groups.names["obs"]
  location            = var.location
  log_retention_days  = var.log_retention_days   # Regulated: 365
  tags                = module.tags.common_tags
}

module "diagnostics" {
  source = "../../modules/observability/diagnostics"

  # ---------------------------------------------------------------------------
  # REGULATED CHANGE: diagnostics_targets includes AKS cluster_id and ALL
  # data resources. This ensures every platform resource has log and metric
  # forwarding for compliance audits and incident investigation.
  # ---------------------------------------------------------------------------
  target_resource_ids = local.diagnostics_targets
  workspace_id        = module.log_analytics.workspace_id
  name_prefix         = "diag-${module.naming.prefix}"
}

# =============================================================================
# 7. GOVERNANCE -- policy baseline (ALL FLAGS EXPLICIT)
# =============================================================================
# ---------------------------------------------------------------------------
# REGULATED CHANGE: All policy flags are set explicitly -- no reliance on
# module defaults. This makes the compliance posture auditable directly
# from the Terraform configuration without needing to inspect module code.
# ---------------------------------------------------------------------------

module "policy_baseline" {
  count  = var.enable_policy_baseline ? 1 : 0   # Regulated: true (always)
  source = "../../modules/governance/policy_baseline"

  scope                     = var.policy_scope != null ? var.policy_scope : module.resource_groups.ids["core"]
  allowed_locations         = local.allowed_locations   # e.g. ["eastus2"] -- single region for data residency
  require_private_endpoints = true                      # Explicit: audit resources without PEs
  deny_public_paas          = true                      # Explicit: block public network access on PaaS
}

# =============================================================================
# 8. COMPUTE -- Private AKS cluster (REGULATED TIER ADDITION)
# =============================================================================
# The private AKS cluster provides a secure compute plane for containerized
# AI workloads. The API server is accessible only through a private endpoint
# in the VNet. Egress uses user-defined routing (UDR) so that all outbound
# traffic can be inspected by a future firewall module.
#
# Subnet requirements:
#   - snet-compute must have enough address space for AKS nodes (e.g. /22)
#   - Max pods per node defaults to 30; plan IP space accordingly
#
# The cluster's system-assigned managed identity will need RBAC roles
# for pulling images (AcrPull) and managing network resources. These will
# be added when the identity/rbac module is implemented.

module "aks_private" {
  count  = var.enable_aks ? 1 : 0   # Regulated: true
  source = "../../modules/compute/aks_private"

  name                = module.naming.aks_name
  resource_group_name = module.resource_groups.names["core"]
  location            = var.location
  subnet_id           = module.networking.subnet_ids["snet-compute"]
  private_dns_zone_id = local.aks_dns_zone_id   # Private API server DNS
  outbound_type       = "userDefinedRouting"     # Required for firewall egress
  tags                = module.tags.common_tags
}

# =============================================================================
# REGULATED TIER -- RECOMMENDED VARIABLE VALUES
# =============================================================================
#
# When configuring a Regulated tier environment, use these values in your
# .tfvars file:
#
#   enable_cmk             = true     # Customer-managed encryption keys
#   enable_aks             = true     # Private AKS compute cluster
#   enable_policy_baseline = true     # Full governance policy set
#   log_retention_days     = 365      # 1-year compliance retention
#   cost_center            = "CC-..."  # Mandatory cost allocation
#   allowed_locations      = ["eastus2"]  # Single-region data residency
#   require_private_endpoints = true   # Audit PE coverage
#   deny_public_paas       = true     # Block public PaaS access
#
# =============================================================================
# FUTURE REGULATED-TIER MODULES (not yet implemented)
# =============================================================================
#
# The following capabilities are planned for future Regulated tier releases:
#
#   networking/firewall
#     Azure Firewall deployed in a dedicated snet-fw subnet. All AKS egress
#     and inter-VNet traffic routes through the firewall via UDR. Application
#     rules restrict outbound to approved FQDNs only (e.g. MCR, Azure APIs).
#     This is why aks_private.outbound_type is set to "userDefinedRouting".
#
#   networking/apim
#     Azure API Management (internal mode) deployed behind the VNet. Provides
#     a governed gateway for AI API consumers with rate limiting, JWT
#     validation, and usage analytics. Connects to OpenAI and AI Search
#     through their private endpoints.
#
#   networking/egress_control
#     NSG flow logs, Network Watcher, and traffic analytics. Provides
#     visibility into all network flows for compliance reporting and
#     anomaly detection. Integrates with Log Analytics workspace.
#
#   governance/region_restriction
#     Enhanced policy assignments that restrict not just resource deployment
#     location but also data replication targets. Ensures data residency
#     requirements are met for GDPR, data sovereignty, or sector-specific
#     regulations.
#
#   security/defender
#     Microsoft Defender for Cloud with enhanced tier plans enabled for
#     Key Vault, Storage, Resource Manager, DNS, and Kubernetes (AKS).
#     Provides threat detection, vulnerability assessment, and security
#     posture scoring.
#
#   identity/rbac
#     Comprehensive role assignments: workload identities for AKS pods
#     (via workload identity federation), CMK encryption roles for service
#     managed identities, and least-privilege operator roles. Eliminates
#     all shared-key authentication paths.
#
# Each module will follow the established composition pattern and integrate
# with the existing naming, tagging, networking, and observability layers.