## Bicep Template

```bicep
// evidence_category.bicep - Category-Wide Evidence Collection Architecture
// Enterprise architecture for one KSI category (e.g., all IAM, MLA, or AFR KSIs)
// Suitable for: 5-15 KSIs in same category, centralized management, cost optimization
//
// FedRAMP 20x KSI Category Support:
// - IAM (7 KSIs): Identity and Access Management
// - MLA (5 KSIs): Monitoring, Logging, and Auditing
// - AFR (11 KSIs): Audit and Financial Reporting
// - CNA (8 KSIs): Change Notification and Approval
// - SVC (9 KSIs): Service and Vulnerability Management
// - PIY (8 KSIs): Privacy and Investment
// - CMT (4 KSIs): Continuous Monitoring and Testing
// - INR (3 KSIs): Incident Response
// - TPR (2 KSIs): Third-Party Risk
// - RPL (4 KSIs): Recovery and Resilience
// - CED (4 KSIs): Continuous Evidence Delivery
//
// Architecture Components:
// 1. Centralized Log Analytics with category-specific tables
// 2. Shared storage with per-KSI containers and lifecycle policies
// 3. Function App with multiple functions (one per KSI)
// 4. Azure Automation for scheduled evidence collection
// 5. Shared Key Vault and monitoring infrastructure
// 6. Cost optimization through resource sharing

targetScope = 'resourceGroup'

@description('Location for all resources')
param location string = resourceGroup().location

@description('Unique suffix for resource naming')
param nameSuffix string = uniqueString(resourceGroup().id)

@description('KSI category (e.g., IAM, MLA, AFR, CNA, SVC, PIY, CMT, INR, TPR, RPL, CED)')
@allowed(['IAM', 'MLA', 'AFR', 'CNA', 'SVC', 'PIY', 'CMT', 'INR', 'TPR', 'RPL', 'CED'])
param ksiCategory string

@description('List of KSI IDs in this category to collect evidence for')
param ksiList array

@description('Log retention in days (365-730)')
@minValue(365)
@maxValue(730)
param logRetentionDays int = 365

@description('Evidence retention in days (2555-2555, 7 years)')
param evidenceRetentionDays int = 2555

@description('Enable Microsoft Sentinel')
param enableSentinel bool = true

@description('Email for operational alerts')
param alertEmail string

@description('Enable Azure Automation for scheduled collection')
param enableAutomation bool = true

// ============================================================================
// Log Analytics Workspace - Category-Wide Logging
// ============================================================================

// Supports: KSI-MLA-01 (log aggregation), KSI-MLA-02 (retention policies)
resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2022-10-01' = {
  name: 'law-${toLower(ksiCategory)}-${nameSuffix}'
  location: location
  properties: {
    sku: {
      name: 'PerGB2018'
    }
    retentionInDays: logRetentionDays
    features: {
      enableLogAccessUsingOnlyResourcePermissions: true
      immediatePurgeDataOn30Days: false
    }
    workspaceCapping: {
      dailyQuotaGb: 10 // Cost control: 10GB/day limit
    }
    publicNetworkAccessForIngestion: 'Enabled'
    publicNetworkAccessForQuery: 'Enabled'
  }
}

// Category-specific custom tables
resource customTable 'Microsoft.OperationalInsights/workspaces/tables@2022-10-01' = {
  parent: logAnalytics
  name: '${ksiCategory}_Evidence_CL'
  properties: {
    schema: {
      name: '${ksiCategory}_Evidence_CL'
      columns: [
        {
          name: 'TimeGenerated'
          type: 'datetime'
        }
        {
          name: 'KSI_ID'
          type: 'string'
        }
        {
          name: 'EvidenceType'
          type: 'string'
        }
        {
          name: 'ComplianceStatus'
          type: 'string'
        }
        {
          name: 'EvidenceData'
          type: 'dynamic'
        }
      ]
    }
    retentionInDays: logRetentionDays
  }
}

// Microsoft Sentinel
resource sentinel 'Microsoft.OperationsManagement/solutions@2015-11-01-preview' = if (enableSentinel) {
  name: 'SecurityInsights(${logAnalytics.name})'
  location: location
  plan: {
    name: 'SecurityInsights(${logAnalytics.name})'
    product: 'OMSGallery/SecurityInsights'
    publisher: 'Microsoft'
    promotionCode: ''
  }
  properties: {
    workspaceResourceId: logAnalytics.id
  }
}

// ============================================================================
// Storage Account - Shared Evidence Storage
// ============================================================================

resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
  name: 'st${toLower(ksiCategory)}${nameSuffix}'
  location: location
  sku: {
    name: 'Standard_GRS'
  }
  kind: 'StorageV2'
  properties: {
    accessTier: 'Cool'
    supportsHttpsTrafficOnly: true
    minimumTlsVersion: 'TLS1_2'
    allowBlobPublicAccess: false
    encryption: {
      services: {
        blob: {
          enabled: true
          keyType: 'Account'
        }
      }
      keySource: 'Microsoft.Storage'
      requireInfrastructureEncryption: true
    }
    networkAcls: {
      defaultAction: 'Allow'
      bypass: 'AzureServices'
    }
  }
}

resource blobService 'Microsoft.Storage/storageAccounts/blobServices@2023-01-01' = {
  parent: storageAccount
  name: 'default'
  properties: {
    deleteRetentionPolicy: {
      enabled: true
      days: 90
    }
    containerDeleteRetentionPolicy: {
      enabled: true
      days: 90
    }
    changeFeed: {
      enabled: true
      retentionInDays: 365
    }
    isVersioningEnabled: true
  }
}

// Create container for each KSI in the category
resource evidenceContainers 'Microsoft.Storage/storageAccounts/blobServices/containers@2023-01-01' = [for ksi in ksiList: {
  parent: blobService
  name: 'evidence-${toLower(ksi)}'
  properties: {
    publicAccess: 'None'
    metadata: {
      category: ksiCategory
      ksi: ksi
      retention: '${evidenceRetentionDays} days'
    }
  }
}]

// Shared dead-letter container
resource deadLetterContainer 'Microsoft.Storage/storageAccounts/blobServices/containers@2023-01-01' = {
  parent: blobService
  name: 'deadletter-${toLower(ksiCategory)}'
  properties: {
    publicAccess: 'None'
  }
}

// Lifecycle management for all category containers
resource lifecyclePolicy 'Microsoft.Storage/storageAccounts/managementPolicies@2023-01-01' = {
  parent: storageAccount
  name: 'default'
  properties: {
    policy: {
      rules: [
        {
          enabled: true
          name: 'ArchiveOldEvidence'
          type: 'Lifecycle'
          definition: {
            actions: {
              baseBlob: {
                tierToArchive: {
                  daysAfterModificationGreaterThan: 365
                }
                delete: {
                  daysAfterModificationGreaterThan: evidenceRetentionDays
                }
              }
            }
            filters: {
              blobTypes: ['blockBlob']
              prefixMatch: ['evidence-']
            }
          }
        }
      ]
    }
  }
}

// ============================================================================
// Key Vault - Shared Secrets Management
// ============================================================================

resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' = {
  name: 'kv-${toLower(ksiCategory)}-${nameSuffix}'
  location: location
  properties: {
    sku: {
      family: 'A'
      name: 'standard'
    }
    tenantId: subscription().tenantId
    enableRbacAuthorization: true
    enableSoftDelete: true
    softDeleteRetentionInDays: 90
    enablePurgeProtection: true
  }
}

// ============================================================================
// Managed Identity - Category-Wide Identity
// ============================================================================

resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = {
  name: 'id-${toLower(ksiCategory)}-collector-${nameSuffix}'
  location: location
}

// Role assignments
resource storageRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
  name: guid(storageAccount.id, managedIdentity.id, 'StorageBlobDataContributor')
  scope: storageAccount
  properties: {
    roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')
    principalId: managedIdentity.properties.principalId
    principalType: 'ServicePrincipal'
  }
}

resource logAnalyticsContributor 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
  name: guid(logAnalytics.id, managedIdentity.id, 'LogAnalyticsContributor')
  scope: logAnalytics
  properties: {
    roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '92aaf0da-9dab-42b6-94a3-d43ce8d16293')
    principalId: managedIdentity.properties.principalId
    principalType: 'ServicePrincipal'
  }
}

resource keyVaultRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
  name: guid(keyVault.id, managedIdentity.id, 'KeyVaultSecretsUser')
  scope: keyVault
  properties: {
    roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4633458b-17de-408a-b874-0445c86b69e6')
    principalId: managedIdentity.properties.principalId
    principalType: 'ServicePrincipal'
  }
}

// ============================================================================
// Application Insights - Category Monitoring
// ============================================================================

resource appInsights 'Microsoft.Insights/components@2020-02-02' = {
  name: 'appi-${toLower(ksiCategory)}-${nameSuffix}'
  location: location
  kind: 'web'
  properties: {
    Application_Type: 'web'
    WorkspaceResourceId: logAnalytics.id
    RetentionInDays: logRetentionDays
  }
}

// ============================================================================
// Azure Function - Category Evidence Collectors
// ============================================================================

resource appServicePlan 'Microsoft.Web/serverfarms@2022-09-01' = {
  name: 'asp-${toLower(ksiCategory)}-${nameSuffix}'
  location: location
  sku: {
    name: 'EP2' // Larger plan for multiple KSI collectors
    tier: 'ElasticPremium'
  }
  properties: {
    reserved: true
    maximumElasticWorkerCount: 30
  }
}

resource functionApp 'Microsoft.Web/sites@2022-09-01' = {
  name: 'func-${toLower(ksiCategory)}-${nameSuffix}'
  location: location
  kind: 'functionapp,linux'
  identity: {
    type: 'UserAssigned'
    userAssignedIdentities: {
      '${managedIdentity.id}': {}
    }
  }
  properties: {
    serverFarmId: appServicePlan.id
    httpsOnly: true
    siteConfig: {
      linuxFxVersion: 'PYTHON|3.11'
      alwaysOn: true
      appSettings: [
        {
          name: 'AzureWebJobsStorage'
          value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};EndpointSuffix=${environment().suffixes.storage};AccountKey=${storageAccount.listKeys().keys[0].value}'
        }
        {
          name: 'FUNCTIONS_EXTENSION_VERSION'
          value: '~4'
        }
        {
          name: 'FUNCTIONS_WORKER_RUNTIME'
          value: 'python'
        }
        {
          name: 'APPINSIGHTS_INSTRUMENTATIONKEY'
          value: appInsights.properties.InstrumentationKey
        }
        {
          name: 'LOG_ANALYTICS_WORKSPACE_ID'
          value: logAnalytics.properties.customerId
        }
        {
          name: 'STORAGE_ACCOUNT_NAME'
          value: storageAccount.name
        }
        {
          name: 'KEY_VAULT_NAME'
          value: keyVault.name
        }
        {
          name: 'MANAGED_IDENTITY_CLIENT_ID'
          value: managedIdentity.properties.clientId
        }
        {
          name: 'KSI_CATEGORY'
          value: ksiCategory
        }
        {
          name: 'KSI_LIST'
          value: join(ksiList, ',')
        }
      ]
      ftpsState: 'Disabled'
      minTlsVersion: '1.2'
    }
  }
}

// ============================================================================
// Azure Automation Account - Scheduled Evidence Collection
// ============================================================================

// Supports: KSI-CED-01 (automated scheduled evidence collection)
resource automationAccount 'Microsoft.Automation/automationAccounts@2023-11-01' = if (enableAutomation) {
  name: 'aa-${toLower(ksiCategory)}-${nameSuffix}'
  location: location
  identity: {
    type: 'UserAssigned'
    userAssignedIdentities: {
      '${managedIdentity.id}': {}
    }
  }
  properties: {
    sku: {
      name: 'Basic'
    }
    publicNetworkAccess: true
  }
}

// Link to Log Analytics
resource automationLink 'Microsoft.OperationalInsights/workspaces/linkedServices@2020-08-01' = if (enableAutomation) {
  parent: logAnalytics
  name: 'Automation'
  properties: {
    resourceId: automationAccount.id
  }
}

// ============================================================================
// Event Grid - Category-Wide Event Processing
// ============================================================================

resource eventGridTopic 'Microsoft.EventGrid/systemTopics@2023-12-15-preview' = {
  name: 'evgt-${toLower(ksiCategory)}-${nameSuffix}'
  location: location
  properties: {
    source: storageAccount.id
    topicType: 'Microsoft.Storage.StorageAccounts'
  }
}

resource eventSubscription 'Microsoft.EventGrid/systemTopics/eventSubscriptions@2023-12-15-preview' = {
  parent: eventGridTopic
  name: 'category-evidence-uploaded'
  properties: {
    destination: {
      endpointType: 'WebHook'
      properties: {
        endpointUrl: 'https://${functionApp.properties.defaultHostName}/api/evidence-notification'
        maxEventsPerBatch: 50
        preferredBatchSizeInKilobytes: 256
      }
    }
    filter: {
      includedEventTypes: [
        'Microsoft.Storage.BlobCreated'
      ]
      subjectBeginsWith: '/blobServices/default/containers/evidence-'
    }
    deadLetterDestination: {
      endpointType: 'StorageBlob'
      properties: {
        resourceId: storageAccount.id
        blobContainerName: deadLetterContainer.name
      }
    }
    retryPolicy: {
      maxDeliveryAttempts: 30
      eventTimeToLiveInMinutes: 1440
    }
  }
}

// ============================================================================
// Alert Rules - Category-Wide Monitoring
// ============================================================================

resource actionGroup 'Microsoft.Insights/actionGroups@2023-01-01' = {
  name: 'ag-${toLower(ksiCategory)}-${nameSuffix}'
  location: 'global'
  properties: {
    groupShortName: substring(ksiCategory, 0, min(length(ksiCategory), 12))
    enabled: true
    emailReceivers: [
      {
        name: 'EmailAdmin'
        emailAddress: alertEmail
        useCommonAlertSchema: true
      }
    ]
  }
}

// Category-wide function failure alert
resource functionFailureAlert 'Microsoft.Insights/metricAlerts@2018-03-01' = {
  name: 'alert-${toLower(ksiCategory)}-function-failures'
  location: 'global'
  properties: {
    description: 'Alert when category evidence collection fails'
    severity: 2
    enabled: true
    scopes: [
      functionApp.id
    ]
    evaluationFrequency: 'PT5M'
    windowSize: 'PT15M'
    criteria: {
      'odata.type': 'Microsoft.Azure.Monitor.MultipleResourceMultipleMetricCriteria'
      allOf: [
        {
          name: 'FunctionErrors'
          metricName: 'FunctionExecutionCount'
          dimensions: [
            {
              name: 'Status'
              operator: 'Include'
              values: ['Failed']
            }
          ]
          operator: 'GreaterThan'
          threshold: 5
          timeAggregation: 'Total'
        }
      ]
    }
    actions: [
      {
        actionGroupId: actionGroup.id
      }
    ]
  }
}

// Storage capacity alert
resource storageCapacityAlert 'Microsoft.Insights/metricAlerts@2018-03-01' = {
  name: 'alert-${toLower(ksiCategory)}-storage-capacity'
  location: 'global'
  properties: {
    description: 'Alert when category evidence storage reaches 80% capacity'
    severity: 2
    enabled: true
    scopes: [
      storageAccount.id
    ]
    evaluationFrequency: 'PT1H'
    windowSize: 'PT6H'
    criteria: {
      'odata.type': 'Microsoft.Azure.Monitor.MultipleResourceMultipleMetricCriteria'
      allOf: [
        {
          name: 'UsedCapacity'
          metricName: 'UsedCapacity'
          operator: 'GreaterThan'
          threshold: 858993459200 // 800GB (assuming 1TB total)
          timeAggregation: 'Average'
        }
      ]
    }
    actions: [
      {
        actionGroupId: actionGroup.id
      }
    ]
  }
}

// ============================================================================
// Diagnostic Settings
// ============================================================================

resource storageDiagnostics 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = {
  scope: storageAccount
  name: 'storage-diagnostics'
  properties: {
    workspaceId: logAnalytics.id
    logs: [
      {
        category: 'StorageRead'
        enabled: true
      }
      {
        category: 'StorageWrite'
        enabled: true
      }
      {
        category: 'StorageDelete'
        enabled: true
      }
    ]
    metrics: [
      {
        category: 'Transaction'
        enabled: true
      }
    ]
  }
}

resource functionDiagnostics 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = {
  scope: functionApp
  name: 'function-diagnostics'
  properties: {
    workspaceId: logAnalytics.id
    logs: [
      {
        category: 'FunctionAppLogs'
        enabled: true
      }
    ]
    metrics: [
      {
        category: 'AllMetrics'
        enabled: true
      }
    ]
  }
}

// ============================================================================
// Outputs
// ============================================================================

output logAnalyticsWorkspaceId string = logAnalytics.id
output storageAccountName string = storageAccount.name
output keyVaultName string = keyVault.name
output functionAppName string = functionApp.name
output automationAccountName string = enableAutomation ? automationAccount.name : ''
output managedIdentityId string = managedIdentity.id
output evidenceContainers array = [for i in range(0, length(ksiList)): evidenceContainers[i].name]
```

## Deployment Instructions

```bash
# Example: Deploy IAM category with 7 KSIs
az group create --name rg-evidence-iam --location eastus

az deployment group create \
  --resource-group rg-evidence-iam \
  --template-file evidence_category.bicep \
  --parameters \
    ksiCategory='IAM' \
    ksiList='["KSI-IAM-01","KSI-IAM-02","KSI-IAM-03","KSI-IAM-04","KSI-IAM-05","KSI-IAM-06","KSI-IAM-07"]' \
    logRetentionDays=365 \
    evidenceRetentionDays=2555 \
    enableSentinel=true \
    enableAutomation=true \
    alertEmail='security@example.com'
```

## Usage Notes

**Purpose:** Enterprise architecture for all KSIs in one category (e.g., all 7 IAM KSIs).

**Scope:** 5-15 KSIs in same category, resource optimization through shared infrastructure.

**Benefits:** ~50-70% resource efficiency compared to individual KSI architectures.

**Supported Categories:**
- IAM (7 KSIs), MLA (5 KSIs), AFR (11 KSIs), CNA (8 KSIs), SVC (9 KSIs)
- PIY (8 KSIs), CMT (4 KSIs), INR (3 KSIs), TPR (2 KSIs), RPL (4 KSIs), CED (4 KSIs)
