Skip to content

Enterprise Tier Blueprint

The Enterprise tier adds governance, policy enforcement, and full observability on top of the Foundation tier. It is designed for organizations that need cost tracking, compliance posture, and audit-ready logging.

What This Tier Adds

Addition Description
Policy baseline Enforces allowed locations, private endpoint requirements, and public PaaS denial
Cost-center tag First-class cost_center variable for chargeback/showback
Full diagnostics All resource IDs in diagnostics targets
Future hooks Prepared for alerts, RBAC, and Defender modules

What Is Not Included

  • compute/aks_private — disabled at this tier (see Regulated)
  • Customer-managed keys — enable_cmk = false
  • Extended retention — log_retention_days stays at default
enable_policy_baseline    = true
require_private_endpoints = true
deny_public_paas          = true
cost_center               = "CC-AI-0042"
log_retention_days        = 90
enable_cmk                = false
enable_aks                = false

Reference Composition

# =============================================================================
# TenantZero AI -- Enterprise Tier Blueprint
# =============================================================================
#
# WHAT THIS IS
# ------------
# A reference composition for organizations that need governance, policy
# enforcement, and full observability on top of the Foundation tier.
# Copy this file into envs/<your-env>/ as a starting point.
#
# 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 (unchanged, copied here for completeness)
# -----------------------------------------------------------------------
#   - 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 THIS TIER ADDS
# -------------------
#   + governance/policy_baseline  (enabled -- enforces location & PE posture)
#   + Full diagnostics wiring     (all resource IDs in diagnostics_targets)
#   + cost_center tag             (explicitly set for chargeback / showback)
#   + Prepared hooks for future modules (alerts, RBAC, Defender for Cloud)
#
# WHAT IS NOT INCLUDED (see regulated.tf)
# ----------------------------------------
#   - compute/aks_private         (disabled at this tier)
#   - Customer-managed keys       (enable_cmk = false)
#   - Extended retention          (log_retention_days stays at 90)
#
# 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
  }

  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"]
  )

  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"])

  # ---------------------------------------------------------------------------
  # ENTERPRISE CHANGE: diagnostics_targets includes ALL deployed resources.
  # Foundation tier covers openai, key_vault, ai_search, and one data store.
  # Enterprise tier is identical today but the list is explicitly documented
  # so that future modules (AKS, APIM, etc.) can be appended here.
  # ---------------------------------------------------------------------------
  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]
    # Future: append module.aks_private[0].cluster_id when compute is enabled
  ))
}

# =============================================================================
# 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. "staging"
  unique_suffix = var.unique_suffix # e.g. "x7q"
}

# ---------------------------------------------------------------------------
# ENTERPRISE CHANGE: cost_center is explicitly set for chargeback tracking.
# Foundation tier leaves this empty; Enterprise tier requires it.
# ---------------------------------------------------------------------------
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 at this tier
  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
# =============================================================================

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
  tags                = module.tags.common_tags
}

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

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
}

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
}

# -- CMK key (disabled in Enterprise; enabled in Regulated tier) ---------------
resource "azurerm_key_vault_key" "cmk" {
  count = var.enable_cmk ? 1 : 0   # Enterprise: false | 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
# =============================================================================
# Enterprise tier uses a longer retention (e.g. 90 days) compared to
# Foundation's 30-day default, and wires diagnostics to ALL deployed resources.

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   # Enterprise recommended: 90
  tags                = module.tags.common_tags
}

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

  # ---------------------------------------------------------------------------
  # ENTERPRISE CHANGE: Full diagnostics wiring.
  # Every resource that supports diagnostic settings is included here.
  # As new modules are added (AKS, APIM, etc.), append their resource_id
  # to this list to maintain observability coverage.
  # ---------------------------------------------------------------------------
  target_resource_ids = local.diagnostics_targets
  workspace_id        = module.log_analytics.workspace_id
  name_prefix         = "diag-${module.naming.prefix}"
}

# =============================================================================
# 7. GOVERNANCE -- policy baseline (ENTERPRISE TIER ADDITION)
# =============================================================================
# The policy baseline enforces organizational standards at the Azure resource
# level. It is the primary addition in the Enterprise tier over Foundation.
#
# Policies deployed:
#   - Allowed locations      : restricts where resources can be created
#   - Require private endpoints: audits resources missing PE connections
#   - Deny public PaaS       : blocks public network access on key services
#
# The scope defaults to the core resource group but can be elevated to
# subscription or management group level via var.policy_scope.

module "policy_baseline" {
  count  = var.enable_policy_baseline ? 1 : 0   # Enterprise: true
  source = "../../modules/governance/policy_baseline"

  scope                     = var.policy_scope != null ? var.policy_scope : module.resource_groups.ids["core"]
  allowed_locations         = local.allowed_locations
  require_private_endpoints = var.require_private_endpoints   # default: true
  deny_public_paas          = var.deny_public_paas            # default: true
}

# =============================================================================
# MODULES NOT USED IN ENTERPRISE TIER
# =============================================================================
#
# The following modules exist but are disabled at the Enterprise tier:
#
#   compute/aks_private  --> enable_aks = false (see regulated.tf)
#   CMK key creation     --> enable_cmk = false (see regulated.tf)
#
# =============================================================================
# FUTURE ENTERPRISE-TIER MODULES (not yet implemented)
# =============================================================================
#
# The following capabilities are planned for future Enterprise tier releases:
#
#   alerts/
#     Azure Monitor alert rules for AI service health, latency, and error
#     rates. Will consume log_analytics.workspace_id and wire action groups
#     to email/webhook/PagerDuty targets.
#
#   identity/rbac
#     Managed identity creation + scoped role assignments. Will assign
#     roles like "Cognitive Services OpenAI User" and "Key Vault Secrets
#     Officer" to workload identities, eliminating shared-key usage.
#
#   security/defender
#     Microsoft Defender for Cloud plans enablement. Will activate
#     Defender for Key Vault, Defender for Storage, and Defender for
#     Resource Manager at the subscription level.
#
# Each of these will follow the same composition pattern: a self-contained
# module under modules/<namespace>/<module_name> with inputs wired from
# this root stack.