Data & Storage Security Baselines
Data services hold your crown jewels. SQL Database, Storage Accounts, Cosmos DB, and Key Vault each store different types of sensitive data — relational records, unstructured blobs, NoSQL documents, and cryptographic secrets. Each requires specific protection patterns.
Azure SQL Database
Managed relational database with built-in intelligence, automatic tuning, and advanced security features. SQL Database has one of the richest security feature sets in Azure — leverage TDE, Always Encrypted, Dynamic Data Masking, and Defender for SQL for comprehensive protection.
| Property | Value |
|---|---|
| Defender Plan | Defender for SQL (VA + Advanced Threat Protection) |
| Azure Policy Built-in | Yes — SQL initiative (auditing, TDE, AAD-only auth, PE) |
| Diagnostic Logs | SQLSecurityAuditEvents, AutomaticTuning, QueryStoreRuntimeStatistics |
| Key Feature | Entra-only auth eliminates SQL authentication completely |
Baseline Controls
| Domain | Feature | Supported | Default | Responsibility |
|---|---|---|---|---|
| NS | Private Endpoint | ✓ | Manual | Customer |
| IM | Entra-only Auth | ✓ | Manual | Customer |
| DP | TDE (Encryption at Rest) | ✓ | Default | Microsoft |
| DP | TDE with CMK | ✓ | Manual | Customer |
| DP | Always Encrypted | ✓ | Manual | Customer |
| LT | Defender for SQL | ✓ | Manual | Customer |
| LT | Auditing | ✓ | Manual | Customer |
| BR | Automated Backups | ✓ | Default | Microsoft |
Private Endpoint
Entra-only Auth
TDE (Encryption at Rest)
TDE with CMK
Always Encrypted
Defender for SQL
Auditing
Automated Backups
Terraform: Hardened SQL Database
resource "azurerm_mssql_server" "main" {
name = "sql-prod-001"
resource_group_name = azurerm_resource_group.data.name
location = azurerm_resource_group.data.location
version = "12.0"
minimum_tls_version = "1.2"
# IM-1: Entra-only auth — disable SQL auth completely
azuread_administrator {
login_username = "sql-admin-group"
object_id = azurerm_user_assigned_identity.sql_admin.principal_id
azuread_authentication_only = true # No SQL logins
}
# NS-2: Deny public network access
public_network_access_enabled = false
identity {
type = "SystemAssigned"
}
}
resource "azurerm_mssql_database" "main" {
name = "sqldb-app-prod"
server_id = azurerm_mssql_server.main.id
sku_name = "S1"
# DP-5: TDE with Customer-Managed Key
transparent_data_encryption_key_vault_key_id = azurerm_key_vault_key.sql_cmk.id
# BR-1: Long-term retention
long_term_retention_policy {
weekly_retention = "P4W"
monthly_retention = "P12M"
yearly_retention = "P5Y"
week_of_year = 1
}
short_term_retention_policy {
retention_days = 35
}
}
# LT-1: Defender for SQL
resource "azurerm_mssql_server_security_alert_policy" "main" {
resource_group_name = azurerm_resource_group.data.name
server_name = azurerm_mssql_server.main.name
state = "Enabled"
}
# LT-3: Auditing to Log Analytics
resource "azurerm_mssql_server_extended_auditing_policy" "main" {
server_id = azurerm_mssql_server.main.id
log_monitoring_enabled = true
}
# NS-2: Private Endpoint
resource "azurerm_private_endpoint" "sql" {
name = "pe-sql-prod"
location = azurerm_resource_group.data.location
resource_group_name = azurerm_resource_group.data.name
subnet_id = azurerm_subnet.data.id
private_service_connection {
name = "psc-sql-prod"
private_connection_resource_id = azurerm_mssql_server.main.id
subresource_names = ["sqlServer"]
is_manual_connection = false
}
}resource "azurerm_mssql_server" "main" {
name = "sql-prod-001"
resource_group_name = azurerm_resource_group.data.name
location = azurerm_resource_group.data.location
version = "12.0"
minimum_tls_version = "1.2"
# IM-1: Entra-only auth — disable SQL auth completely
azuread_administrator {
login_username = "sql-admin-group"
object_id = azurerm_user_assigned_identity.sql_admin.principal_id
azuread_authentication_only = true # No SQL logins
}
# NS-2: Deny public network access
public_network_access_enabled = false
identity {
type = "SystemAssigned"
}
}
resource "azurerm_mssql_database" "main" {
name = "sqldb-app-prod"
server_id = azurerm_mssql_server.main.id
sku_name = "S1"
# DP-5: TDE with Customer-Managed Key
transparent_data_encryption_key_vault_key_id = azurerm_key_vault_key.sql_cmk.id
# BR-1: Long-term retention
long_term_retention_policy {
weekly_retention = "P4W"
monthly_retention = "P12M"
yearly_retention = "P5Y"
week_of_year = 1
}
short_term_retention_policy {
retention_days = 35
}
}
# LT-1: Defender for SQL
resource "azurerm_mssql_server_security_alert_policy" "main" {
resource_group_name = azurerm_resource_group.data.name
server_name = azurerm_mssql_server.main.name
state = "Enabled"
}
# LT-3: Auditing to Log Analytics
resource "azurerm_mssql_server_extended_auditing_policy" "main" {
server_id = azurerm_mssql_server.main.id
log_monitoring_enabled = true
}
# NS-2: Private Endpoint
resource "azurerm_private_endpoint" "sql" {
name = "pe-sql-prod"
location = azurerm_resource_group.data.location
resource_group_name = azurerm_resource_group.data.name
subnet_id = azurerm_subnet.data.id
private_service_connection {
name = "psc-sql-prod"
private_connection_resource_id = azurerm_mssql_server.main.id
subresource_names = ["sqlServer"]
is_manual_connection = false
}
}Azure Portal Instructions
Step-by-step instructions for configuring each SQL Database control through the Azure Portal.
NS-2 — Private Endpoints
- Navigate to your SQL Server resource in the Azure Portal
- In the left menu under Security, select Networking
- Select the Private access tab
- Click + Create a private endpoint
- Select the VNet, subnet, and configure the private DNS zone (
privatelink.database.windows.net) - After creation, go back to the Public access tab
- Set "Public network access" to Disable and click Save
IM-1 — Entra ID Authentication
- Navigate to your SQL Server resource in the Azure Portal
- In the left menu under Settings, select Microsoft Entra ID
- Click Set admin and select an Entra ID user or group
- Click Save to configure the Entra ID administrator
- To enforce Entra-only authentication, check "Support only Microsoft Entra authentication for this server"
- Click Save — this disables SQL authentication (password-based logins)
IM-3 — Managed Identities
- On the application side (e.g., App Service), enable Managed Identity under Settings > Identity
- Navigate to your SQL Database resource in the Azure Portal
- Connect to the database using SQL Server Management Studio or Azure Data Studio
- Create a contained user for the managed identity:
CREATE USER [app-name] FROM EXTERNAL PROVIDER - Grant appropriate roles:
ALTER ROLE db_datareader ADD MEMBER [app-name] - Update the application connection string to use
Authentication=Active Directory Managed Identity
DP-1 — Data Discovery & Classification
- Navigate to your SQL Database resource in the Azure Portal
- In the left menu under Security, select Data Discovery & Classification
- Click the "We have found X columns with classification recommendations" banner
- Review the suggested classifications for each column
- Select the columns you want to classify and click Accept selected recommendations
- Click Save to apply the classifications
- You can also manually add classifications by clicking + Add classification
DP-2 — Advanced Threat Protection
- Navigate to your SQL Server resource in the Azure Portal
- In the left menu under Security, select Microsoft Defender for SQL
- Set Microsoft Defender for SQL to On
- Configure the storage account for vulnerability assessment scan results
- Set email addresses for alert notifications
- Enable "Send scan reports to" and "Also send email notification to admins and subscription owners"
- Click Save
DP-3 — Data in Transit Encryption
This feature is enabled by default — TLS 1.2+ is enforced for all connections. To verify, navigate to your SQL Server > Networking and check that Minimum TLS version is set to 1.2.
DP-4 — Transparent Data Encryption
This feature is managed by Microsoft and enabled by default. To verify, navigate to your SQL Database > Security > Transparent data encryption and confirm Data encryption is On.
DP-5 — TDE with CMK
- Navigate to your SQL Server resource in the Azure Portal
- In the left menu under Security, select Transparent data encryption
- Change the key source from Service-managed key to Customer-managed key
- Select the Key Vault and encryption key (or enter the Key URI)
- Enable auto-rotation if prompted
- Click Save to apply the CMK configuration
- Ensure the SQL Server managed identity has Get, Wrap Key, and Unwrap Key permissions on the Key Vault
LT-1 — Microsoft Defender for SQL
- Navigate to your SQL Server resource in the Azure Portal
- In the left menu under Security, select Microsoft Defender for SQL
- Set Microsoft Defender for SQL to On
- Configure vulnerability assessment storage and notification settings
- Click Save
- Alerts and vulnerability findings will appear in Microsoft Defender for Cloud
LT-3 — Auditing
- Navigate to your SQL Server resource in the Azure Portal
- In the left menu under Security, select Auditing
- Toggle Auditing to On
- Select the audit log destination: Log Analytics workspace (recommended), Storage account, or Event Hub
- If using Log Analytics, select your workspace
- Click Save to enable auditing
- Audit logs will capture failed logins, schema changes, and data access events
Azure Storage Account
Blob, File, Queue, and Table storage under one umbrella. Storage Accounts are among the most commonly misconfigured Azure services — public blob access, shared keys, and missing network restrictions are frequent findings in security assessments.
Baseline Controls
| Domain | Feature | Supported | Default | Responsibility |
|---|---|---|---|---|
| NS | Private Endpoint | ✓ | Manual | Customer |
| NS | Disable Public Blob Access | ✓ | Default | Microsoft |
| IM | Disable Shared Key | ✓ | Manual | Customer |
| DP | SSE (Platform-Managed) | ✓ | Default | Microsoft |
| DP | Infrastructure Encryption | ✓ | Manual | Customer |
| LT | Defender for Storage | ✓ | Manual | Customer |
| BR | Soft Delete | ✓ | Default | Microsoft |
| BR | Immutable Storage | ✓ | Manual | Customer |
Private Endpoint
Disable Public Blob Access
Disable Shared Key
SSE (Platform-Managed)
Infrastructure Encryption
Defender for Storage
Soft Delete
Immutable Storage
Terraform: Hardened Storage Account
resource "azurerm_storage_account" "main" {
name = "stprod001"
resource_group_name = azurerm_resource_group.data.name
location = azurerm_resource_group.data.location
account_tier = "Standard"
account_replication_type = "GRS"
# NS-8: TLS 1.2 minimum
min_tls_version = "TLS1_2"
# NS: HTTPS only
https_traffic_only_enabled = true
# NS: Disable public blob access
allow_nested_items_to_be_public = false
# IM-1: Disable shared key — Entra ID only
shared_access_key_enabled = false
# NS-2: Default deny network rules
network_rules {
default_action = "Deny"
bypass = ["AzureServices"]
}
# DP: Infrastructure encryption (double encryption)
infrastructure_encryption_enabled = true
# DP-5: Customer-Managed Key
identity {
type = "SystemAssigned"
}
customer_managed_key {
key_vault_key_id = azurerm_key_vault_key.storage_cmk.id
user_assigned_identity_id = azurerm_user_assigned_identity.storage.id
}
# BR-2: Blob soft delete and versioning
blob_properties {
delete_retention_policy {
days = 90
}
container_delete_retention_policy {
days = 90
}
versioning_enabled = true
}
}resource "azurerm_storage_account" "main" {
name = "stprod001"
resource_group_name = azurerm_resource_group.data.name
location = azurerm_resource_group.data.location
account_tier = "Standard"
account_replication_type = "GRS"
# NS-8: TLS 1.2 minimum
min_tls_version = "TLS1_2"
# NS: HTTPS only
https_traffic_only_enabled = true
# NS: Disable public blob access
allow_nested_items_to_be_public = false
# IM-1: Disable shared key — Entra ID only
shared_access_key_enabled = false
# NS-2: Default deny network rules
network_rules {
default_action = "Deny"
bypass = ["AzureServices"]
}
# DP: Infrastructure encryption (double encryption)
infrastructure_encryption_enabled = true
# DP-5: Customer-Managed Key
identity {
type = "SystemAssigned"
}
customer_managed_key {
key_vault_key_id = azurerm_key_vault_key.storage_cmk.id
user_assigned_identity_id = azurerm_user_assigned_identity.storage.id
}
# BR-2: Blob soft delete and versioning
blob_properties {
delete_retention_policy {
days = 90
}
container_delete_retention_policy {
days = 90
}
versioning_enabled = true
}
}Azure Portal Instructions
Step-by-step instructions for configuring each Storage Account control through the Azure Portal.
NS-2 — Private Endpoints
- Navigate to your Storage Account resource in the Azure Portal
- In the left menu under Security + networking, select Networking
- Select the Private endpoint connections tab
- Click + Private endpoint to create a new private endpoint
- Select the target sub-resource (blob, file, table, or queue)
- Select the VNet, subnet, and configure the private DNS zone
- Repeat for each sub-resource that needs private access
NS-2 — Disable Public Network Access
- Navigate to your Storage Account resource in the Azure Portal
- In the left menu under Security + networking, select Networking
- On the Firewalls and virtual networks tab, set Public network access to "Enabled from selected virtual networks and IP addresses" or "Disabled"
- If using selected networks, add allowed VNets and IP addresses below
- Check "Allow Azure services on the trusted services list to access this storage account" if needed
- Click Save
IM-1 — Entra ID Authorization
- Navigate to your Storage Account resource in the Azure Portal
- In the left menu under Settings, select Configuration
- Set "Allow storage account key access" to Disabled (this forces Entra ID auth)
- Click Save
- Navigate to Access control (IAM) on the storage account
- Click + Add role assignment and assign roles like "Storage Blob Data Reader" or "Storage Blob Data Contributor" to users/groups/service principals
DP-3 — Secure Transfer Required
- Navigate to your Storage Account resource in the Azure Portal
- In the left menu under Settings, select Configuration
- Verify that "Secure transfer required" is set to Enabled (this is the default)
- Set the Minimum TLS version to Version 1.2
- Click Save
DP-4 — Data at Rest Encryption
This feature is managed by Microsoft and enabled by default. To verify, navigate to your Storage Account > Security + networking > Encryption and confirm Encryption type shows Microsoft-managed keys.
DP-5 — Encryption with CMK
- Navigate to your Storage Account resource in the Azure Portal
- In the left menu under Security + networking, select Encryption
- Under Encryption type, select Customer-managed keys
- Select the Key Vault and encryption key (or enter the Key URI)
- Choose the managed identity to use for accessing Key Vault
- To enable infrastructure encryption (double encryption), this must be set at account creation time
- Click Save
LT-1 — Defender for Storage
- Navigate to Microsoft Defender for Cloud in the Azure Portal
- In the left menu, select Environment settings
- Select your subscription
- Under Cloud Workload Protection (CWP), find the Storage row
- Toggle the plan to On
- Click Settings to configure malware scanning and sensitive data threat detection options
- Click Save
BR-2 — Immutable Storage
- Navigate to your Storage Account resource in the Azure Portal
- In the left menu under Data management, select Data protection
- Enable "Enable versioning for blobs" and "Enable soft delete for blobs" with desired retention
- Click Save
- To set immutability policies, navigate to a specific container
- Click the Access policy or Immutability policy option
- Add a time-based retention policy or legal hold as required
Shared Key = Backdoor
shared_access_key_enabled = false unless you have a specific, documented need.
This is one of the highest-impact hardening steps for Storage Accounts.
Azure Cosmos DB
Globally distributed multi-model database. Cosmos DB's security baseline follows similar patterns to SQL Database, but with unique considerations for its key-based auth model and global replication.
Baseline Controls
| Domain | Feature | Supported | Default | Responsibility |
|---|---|---|---|---|
| NS | Private Endpoint | ✓ | Manual | Customer |
| IM | Entra ID RBAC | ✓ | Manual | Customer |
| DP | Encryption at Rest | ✓ | Default | Microsoft |
| DP | CMK Encryption | ✓ | Manual | Customer |
| LT | Defender for Cosmos DB | ✓ | Manual | Customer |
| BR | Continuous Backup | ✓ | Manual | Customer |
Private Endpoint
Entra ID RBAC
Encryption at Rest
CMK Encryption
Defender for Cosmos DB
Continuous Backup
Terraform: Hardened Cosmos DB
resource "azurerm_cosmosdb_account" "main" {
name = "cosmos-prod-001"
resource_group_name = azurerm_resource_group.data.name
location = azurerm_resource_group.data.location
offer_type = "Standard"
kind = "GlobalDocumentDB"
# IM-1: Disable key-based auth — Entra RBAC only
local_authentication_disabled = true
# NS-2: Disable public access
public_network_access_enabled = false
# NS-8: TLS 1.2 minimum (default)
minimal_tls_version = "Tls12"
# BR: Continuous backup (PITR)
backup {
type = "Continuous"
tier = "Continuous30Days"
}
consistency_policy {
consistency_level = "Session"
}
geo_location {
location = azurerm_resource_group.data.location
failover_priority = 0
zone_redundant = true
}
# DP-5: CMK encryption
key_vault_key_id = azurerm_key_vault_key.cosmos_cmk.id
identity {
type = "SystemAssigned"
}
}resource "azurerm_cosmosdb_account" "main" {
name = "cosmos-prod-001"
resource_group_name = azurerm_resource_group.data.name
location = azurerm_resource_group.data.location
offer_type = "Standard"
kind = "GlobalDocumentDB"
# IM-1: Disable key-based auth — Entra RBAC only
local_authentication_disabled = true
# NS-2: Disable public access
public_network_access_enabled = false
# NS-8: TLS 1.2 minimum (default)
minimal_tls_version = "Tls12"
# BR: Continuous backup (PITR)
backup {
type = "Continuous"
tier = "Continuous30Days"
}
consistency_policy {
consistency_level = "Session"
}
geo_location {
location = azurerm_resource_group.data.location
failover_priority = 0
zone_redundant = true
}
# DP-5: CMK encryption
key_vault_key_id = azurerm_key_vault_key.cosmos_cmk.id
identity {
type = "SystemAssigned"
}
}Azure Portal Instructions
Step-by-step instructions for configuring each Cosmos DB control through the Azure Portal.
NS-2 — Private Endpoints
- Navigate to your Cosmos DB account in the Azure Portal
- In the left menu under Settings, select Networking
- Select the Private access tab
- Click + Add private endpoint
- Select the VNet, subnet, and configure the private DNS zone (
privatelink.documents.azure.com) - After creation, go to the Public access tab
- Uncheck "Allow access from Azure Portal" and "Allow access from my IP" if fully private
- Click Save
IM-1 — Entra ID Authentication
- Navigate to your Cosmos DB account in the Azure Portal
- In the left menu under Settings, select Keys
- Note: To fully disable local auth, you must currently use ARM/CLI (
set disableLocalAuth=true) - For RBAC setup, go to Access control (IAM) on the Cosmos DB account
- Click + Add role assignment
- Assign the "Cosmos DB Built-in Data Contributor" or "Cosmos DB Built-in Data Reader" role to users, groups, or managed identities
DP-4 — Data at Rest Encryption
This feature is managed by Microsoft and enabled by default. No portal configuration is required — all Cosmos DB data including indexes and backups is encrypted.
DP-5 — Encryption with CMK
- CMK must be configured at account creation time — it cannot be added later
- When creating a new Cosmos DB account, go to the Encryption tab
- Select Customer-managed key
- Select your Key Vault and encryption key
- Choose or create a managed identity for accessing the Key Vault
- Complete the account creation wizard
- Note: Existing accounts cannot be migrated to CMK encryption
LT-3 — Diagnostic Logging
- Navigate to your Cosmos DB account in the Azure Portal
- In the left menu under Monitoring, select Diagnostic settings
- Click + Add diagnostic setting
- Give the setting a name (e.g., "cosmos-diag-all")
- Select the log categories: DataPlaneRequests, QueryRuntimeStatistics, PartitionKeyStatistics, ControlPlaneRequests
- Select the destination: Send to Log Analytics workspace
- Select your Log Analytics workspace
- Click Save
BR-1 — Continuous Backup
- Continuous backup mode must be set at account creation or migrated for existing accounts
- For new accounts: during creation, on the Backup Policy tab, select Continuous (7-day or 30-day)
- For existing accounts: navigate to the Cosmos DB account
- In the left menu under Settings, select Backup & Restore
- If you are on periodic mode, click "Migrate to continuous backup" (if available)
- Select the retention tier (7-day or 30-day)
- To restore, go to Backup & Restore > Point in time restore and select the container and timestamp
Azure Key Vault
HSM-backed secrets, keys, and certificate management. Key Vault is the foundation of your entire encryption strategy — it holds the CMK keys that protect all other services. Securing Key Vault is non-negotiable.
Baseline Controls
| Domain | Feature | Supported | Default | Responsibility |
|---|---|---|---|---|
| NS | Private Endpoint | ✓ | Manual | Customer |
| IM | RBAC Authorization | ✓ | Manual | Customer |
| DP | Soft Delete | ✓ | Default | Microsoft |
| DP | Purge Protection | ✓ | Manual | Customer |
| DP | HSM-Backed Keys | ✓ | Manual | Customer |
| DP | Key Rotation | ✓ | Manual | Customer |
| LT | Defender for Key Vault | ✓ | Manual | Customer |
| LT | Audit Logging | ✓ | Manual | Customer |
Private Endpoint
RBAC Authorization
Soft Delete
Purge Protection
HSM-Backed Keys
Key Rotation
Defender for Key Vault
Audit Logging
Terraform: Hardened Key Vault
resource "azurerm_key_vault" "main" {
name = "kv-prod-001"
resource_group_name = azurerm_resource_group.security.name
location = azurerm_resource_group.security.location
tenant_id = data.azurerm_client_config.current.tenant_id
sku_name = "premium" # HSM-backed keys
soft_delete_retention_days = 90
purge_protection_enabled = true # Cannot be disabled once set
# IM: RBAC authorization (preferred over access policies)
enable_rbac_authorization = true
# NS-2: Network isolation
network_acls {
default_action = "Deny"
bypass = "AzureServices"
}
}
# NS-2: Private Endpoint
resource "azurerm_private_endpoint" "kv" {
name = "pe-kv-prod"
location = azurerm_resource_group.security.location
resource_group_name = azurerm_resource_group.security.name
subnet_id = azurerm_subnet.security.id
private_service_connection {
name = "psc-kv-prod"
private_connection_resource_id = azurerm_key_vault.main.id
subresource_names = ["vault"]
is_manual_connection = false
}
}
# LT-3: Diagnostic Settings — ALL Key Vault operations
resource "azurerm_monitor_diagnostic_setting" "kv" {
name = "diag-kv-prod"
target_resource_id = azurerm_key_vault.main.id
log_analytics_workspace_id = azurerm_log_analytics_workspace.main.id
enabled_log { category = "AuditEvent" }
enabled_log { category = "AzurePolicyEvaluationDetails" }
metric { category = "AllMetrics" }
}resource "azurerm_key_vault" "main" {
name = "kv-prod-001"
resource_group_name = azurerm_resource_group.security.name
location = azurerm_resource_group.security.location
tenant_id = data.azurerm_client_config.current.tenant_id
sku_name = "premium" # HSM-backed keys
soft_delete_retention_days = 90
purge_protection_enabled = true # Cannot be disabled once set
# IM: RBAC authorization (preferred over access policies)
enable_rbac_authorization = true
# NS-2: Network isolation
network_acls {
default_action = "Deny"
bypass = "AzureServices"
}
}
# NS-2: Private Endpoint
resource "azurerm_private_endpoint" "kv" {
name = "pe-kv-prod"
location = azurerm_resource_group.security.location
resource_group_name = azurerm_resource_group.security.name
subnet_id = azurerm_subnet.security.id
private_service_connection {
name = "psc-kv-prod"
private_connection_resource_id = azurerm_key_vault.main.id
subresource_names = ["vault"]
is_manual_connection = false
}
}
# LT-3: Diagnostic Settings — ALL Key Vault operations
resource "azurerm_monitor_diagnostic_setting" "kv" {
name = "diag-kv-prod"
target_resource_id = azurerm_key_vault.main.id
log_analytics_workspace_id = azurerm_log_analytics_workspace.main.id
enabled_log { category = "AuditEvent" }
enabled_log { category = "AzurePolicyEvaluationDetails" }
metric { category = "AllMetrics" }
}Azure Portal Instructions
Step-by-step instructions for configuring each Key Vault control through the Azure Portal.
NS-2 — Private Endpoints
- Navigate to your Key Vault resource in the Azure Portal
- In the left menu under Settings, select Networking
- Select the Private endpoint connections tab
- Click + Create to add a private endpoint
- Select the VNet, subnet, and configure the private DNS zone (
privatelink.vaultcore.azure.net) - Go back to the Firewalls and virtual networks tab
- Set "Allow access from" to "Disable public access"
- Optionally check "Allow trusted Microsoft services to bypass this firewall"
- Click Save
PA-7 — Azure RBAC for Data Plane
- Navigate to your Key Vault resource in the Azure Portal
- In the left menu under Settings, select Access configuration
- Under Permission model, select Azure role-based access control (recommended)
- Click Apply
- Navigate to Access control (IAM) on the Key Vault
- Click + Add role assignment
- Assign roles like "Key Vault Secrets User", "Key Vault Crypto Officer", or "Key Vault Certificates Officer" as needed
- Follow least-privilege principles — avoid assigning "Key Vault Administrator" broadly
DP-6 — Key Management
- Navigate to your Key Vault resource in the Azure Portal
- In the left menu under Objects, select Keys
- Click + Generate/Import to create a new key
- For HSM-backed keys, set the key type to RSA-HSM or EC-HSM (requires Premium SKU)
- Set an activation and expiration date
- After creation, click on the key and select Rotation policy
- Configure the auto-rotation interval and notification period before expiry
- Click Save
DP-7 — Certificate Management
- Navigate to your Key Vault resource in the Azure Portal
- In the left menu under Objects, select Certificates
- Click + Generate/Import
- Choose "Generate" for CA-integrated auto-renewal or "Import" for existing certificates
- For auto-renewal, select the issuer (DigiCert, GlobalSign, or Self-signed)
- Configure the certificate subject, validity period, and auto-renewal settings
- Set the Lifetime Action to auto-renew at a percentage of lifetime or days before expiry
- Click Create
DP-8 — Vault Protection
- Navigate to your Key Vault resource in the Azure Portal
- In the left menu under Settings, select Properties
- Verify that Soft delete is Enabled (this cannot be disabled once set)
- Set Purge protection to Enabled (this prevents permanent deletion during the retention period)
- Set the soft delete retention period (default 90 days, recommended to keep at 90)
- Click Save
- Note: Purge protection cannot be disabled once enabled
LT-3 — Diagnostic Logging
- Navigate to your Key Vault resource in the Azure Portal
- In the left menu under Monitoring, select Diagnostic settings
- Click + Add diagnostic setting
- Give the setting a name (e.g., "kv-audit-logs")
- Select the log category: AuditEvent
- Also select AllMetrics under Metrics
- Select the destination: Send to Log Analytics workspace
- Select your Log Analytics workspace
- Click Save
Key Vault Best Practice
Azure Cache for Redis
Managed in-memory data store for caching, session management, and real-time analytics. Redis is often overlooked in security reviews, but it frequently caches session tokens, API responses, and temporary credentials — all high-value targets.
| Property | Value |
|---|---|
| Defender Plan | N/A — monitor via diagnostic logs and Sentinel |
| Azure Policy Built-in | Yes — TLS version, private endpoint, disable non-SSL port |
| Recommended Tier | Premium (VNet injection, persistence, geo-replication) or Enterprise |
| Key Feature | Entra ID authentication to eliminate access key dependency |
Baseline Controls
| Domain | Feature | Supported | Default | Responsibility |
|---|---|---|---|---|
| NS | Private Endpoint | ✓ | Manual | Customer |
| IM | Entra ID Authentication | ✓ | Manual | Customer |
| IM | Managed Identity | ✓ | Manual | Customer |
| DP | TLS 1.2 Enforcement | ✓ | Default | Customer |
| DP | Encryption at Rest | ✓ | Default | Microsoft |
| BR | Data Persistence | ✓ | Manual | Customer |
Private Endpoint
Entra ID Authentication
Managed Identity
TLS 1.2 Enforcement
Encryption at Rest
Data Persistence
Terraform: Hardened Redis Premium Cache
resource "azurerm_redis_cache" "main" {
name = "redis-prod"
resource_group_name = azurerm_resource_group.data.name
location = azurerm_resource_group.data.location
capacity = 1
family = "P"
sku_name = "Premium"
# DP-3: TLS 1.2 enforcement
minimum_tls_version = "1.2"
enable_non_ssl_port = false
# NS-2: Private endpoint access
public_network_access_enabled = false
redis_configuration {
# BR: RDB persistence for data durability
rdb_backup_enabled = true
rdb_backup_frequency = 60
rdb_storage_connection_string = azurerm_storage_account.redis_backup.primary_blob_connection_string
}
}
# NS-2: Private Endpoint
resource "azurerm_private_endpoint" "redis" {
name = "pe-redis-prod"
location = azurerm_resource_group.data.location
resource_group_name = azurerm_resource_group.data.name
subnet_id = azurerm_subnet.data.id
private_service_connection {
name = "psc-redis-prod"
private_connection_resource_id = azurerm_redis_cache.main.id
subresource_names = ["redisCache"]
is_manual_connection = false
}
}resource "azurerm_redis_cache" "main" {
name = "redis-prod"
resource_group_name = azurerm_resource_group.data.name
location = azurerm_resource_group.data.location
capacity = 1
family = "P"
sku_name = "Premium"
# DP-3: TLS 1.2 enforcement
minimum_tls_version = "1.2"
enable_non_ssl_port = false
# NS-2: Private endpoint access
public_network_access_enabled = false
redis_configuration {
# BR: RDB persistence for data durability
rdb_backup_enabled = true
rdb_backup_frequency = 60
rdb_storage_connection_string = azurerm_storage_account.redis_backup.primary_blob_connection_string
}
}
# NS-2: Private Endpoint
resource "azurerm_private_endpoint" "redis" {
name = "pe-redis-prod"
location = azurerm_resource_group.data.location
resource_group_name = azurerm_resource_group.data.name
subnet_id = azurerm_subnet.data.id
private_service_connection {
name = "psc-redis-prod"
private_connection_resource_id = azurerm_redis_cache.main.id
subresource_names = ["redisCache"]
is_manual_connection = false
}
}Azure Database for PostgreSQL
Managed PostgreSQL with built-in high availability, automated backups, and intelligent performance tuning. Flexible Server is the recommended deployment model — it supports VNet integration for true network isolation without needing private endpoints.
| Property | Value |
|---|---|
| Defender Plan | Defender for open-source relational databases |
| Azure Policy Built-in | Yes — SSL enforcement, private access, CMK, Defender |
| Recommended Model | Flexible Server (VNet integration, Entra ID auth, zone-redundant HA) |
| Key Feature | VNet-injected deployment with Entra ID-only authentication |
Baseline Controls
| Domain | Feature | Supported | Default | Responsibility |
|---|---|---|---|---|
| NS | Private Access (VNet) | ✓ | Manual | Customer |
| IM | Entra ID Authentication | ✓ | Manual | Customer |
| IM | Managed Identity | ✓ | Manual | Customer |
| DP | Data in Transit (TLS 1.2) | ✓ | Default | Customer |
| DP | Encryption at Rest | ✓ | Default | Microsoft |
| DP | CMK Encryption | ✓ | Manual | Customer |
| LT | Defender for PostgreSQL | ✓ | Manual | Customer |
| BR | Automated Backups | ✓ | Default | Microsoft |
Private Access (VNet)
Entra ID Authentication
Managed Identity
Data in Transit (TLS 1.2)
Encryption at Rest
CMK Encryption
Defender for PostgreSQL
Automated Backups
Terraform: Hardened PostgreSQL Flexible Server
resource "azurerm_postgresql_flexible_server" "main" {
name = "psql-prod"
resource_group_name = azurerm_resource_group.data.name
location = azurerm_resource_group.data.location
version = "16"
sku_name = "GP_Standard_D4s_v3"
# NS-1: VNet integration (private access)
delegated_subnet_id = azurerm_subnet.postgresql.id
private_dns_zone_id = azurerm_private_dns_zone.postgresql.id
# DP-3: TLS enforcement
zone = "1"
storage_mb = 65536
backup_retention_days = 35 # BR-1: Max retention
# DP-5: CMK encryption
customer_managed_key {
key_vault_key_id = azurerm_key_vault_key.postgresql.id
primary_user_assigned_identity_id = azurerm_user_assigned_identity.postgresql.id
}
identity {
type = "UserAssigned"
identity_ids = [azurerm_user_assigned_identity.postgresql.id]
}
# IM-1: Entra ID authentication
authentication {
active_directory_auth_enabled = true
password_auth_enabled = false # Entra ID only
tenant_id = data.azurerm_client_config.current.tenant_id
}
high_availability {
mode = "ZoneRedundant"
}
}resource "azurerm_postgresql_flexible_server" "main" {
name = "psql-prod"
resource_group_name = azurerm_resource_group.data.name
location = azurerm_resource_group.data.location
version = "16"
sku_name = "GP_Standard_D4s_v3"
# NS-1: VNet integration (private access)
delegated_subnet_id = azurerm_subnet.postgresql.id
private_dns_zone_id = azurerm_private_dns_zone.postgresql.id
# DP-3: TLS enforcement
zone = "1"
storage_mb = 65536
backup_retention_days = 35 # BR-1: Max retention
# DP-5: CMK encryption
customer_managed_key {
key_vault_key_id = azurerm_key_vault_key.postgresql.id
primary_user_assigned_identity_id = azurerm_user_assigned_identity.postgresql.id
}
identity {
type = "UserAssigned"
identity_ids = [azurerm_user_assigned_identity.postgresql.id]
}
# IM-1: Entra ID authentication
authentication {
active_directory_auth_enabled = true
password_auth_enabled = false # Entra ID only
tenant_id = data.azurerm_client_config.current.tenant_id
}
high_availability {
mode = "ZoneRedundant"
}
}Azure Data Factory
Cloud-native ETL/ELT service for data integration and transformation at scale. Data Factory pipelines often have broad access to production data stores — SQL, Storage, Cosmos DB, and on-premises sources — making pipeline security critical for data protection.
| Property | Value |
|---|---|
| Defender Plan | N/A — monitor via diagnostic logs and Sentinel |
| Azure Policy Built-in | Yes — CMK, private endpoint, managed VNet IR, Git |
| Network Model | Managed VNet IR with managed private endpoints to data stores |
| Key Feature | Managed Identity auto-created for all linked service authentication |
Baseline Controls
| Domain | Feature | Supported | Default | Responsibility |
|---|---|---|---|---|
| NS | Managed VNet | ✓ | Manual | Customer |
| NS | Private Endpoint | ✓ | Manual | Customer |
| IM | Managed Identity | ✓ | Default | Microsoft |
| DP | Encryption at Rest | ✓ | Default | Microsoft |
| DP | CMK Encryption | ✓ | Manual | Customer |
| DS | Git Integration | ✓ | Manual | Customer |
| LT | Diagnostic Logging | ✓ | Manual | Customer |
Managed VNet
Private Endpoint
Managed Identity
Encryption at Rest
CMK Encryption
Git Integration
Diagnostic Logging
Terraform: Secure Data Factory with Managed VNet
resource "azurerm_data_factory" "main" {
name = "adf-prod"
resource_group_name = azurerm_resource_group.data.name
location = azurerm_resource_group.data.location
# IM-3: System-assigned managed identity (auto-created)
identity {
type = "SystemAssigned"
}
# NS-2: Disable public access
public_network_enabled = false
# DS-6: Git integration for source control
github_configuration {
account_name = "my-org"
branch_name = "main"
git_url = "https://github.com"
repository_name = "adf-pipelines"
root_folder = "/"
}
managed_virtual_network_enabled = true # NS-1: Managed VNet IR
}
# NS-1: Managed VNet Integration Runtime
resource "azurerm_data_factory_integration_runtime_azure" "managed" {
name = "ir-managed-vnet"
data_factory_id = azurerm_data_factory.main.id
location = azurerm_resource_group.data.location
virtual_network_enabled = true # Uses managed VNet
}
# NS-2: Private Endpoint for Data Factory
resource "azurerm_private_endpoint" "adf" {
name = "pe-adf-prod"
location = azurerm_resource_group.data.location
resource_group_name = azurerm_resource_group.data.name
subnet_id = azurerm_subnet.data.id
private_service_connection {
name = "psc-adf-prod"
private_connection_resource_id = azurerm_data_factory.main.id
subresource_names = ["dataFactory"]
is_manual_connection = false
}
}resource "azurerm_data_factory" "main" {
name = "adf-prod"
resource_group_name = azurerm_resource_group.data.name
location = azurerm_resource_group.data.location
# IM-3: System-assigned managed identity (auto-created)
identity {
type = "SystemAssigned"
}
# NS-2: Disable public access
public_network_enabled = false
# DS-6: Git integration for source control
github_configuration {
account_name = "my-org"
branch_name = "main"
git_url = "https://github.com"
repository_name = "adf-pipelines"
root_folder = "/"
}
managed_virtual_network_enabled = true # NS-1: Managed VNet IR
}
# NS-1: Managed VNet Integration Runtime
resource "azurerm_data_factory_integration_runtime_azure" "managed" {
name = "ir-managed-vnet"
data_factory_id = azurerm_data_factory.main.id
location = azurerm_resource_group.data.location
virtual_network_enabled = true # Uses managed VNet
}
# NS-2: Private Endpoint for Data Factory
resource "azurerm_private_endpoint" "adf" {
name = "pe-adf-prod"
location = azurerm_resource_group.data.location
resource_group_name = azurerm_resource_group.data.name
subnet_id = azurerm_subnet.data.id
private_service_connection {
name = "psc-adf-prod"
private_connection_resource_id = azurerm_data_factory.main.id
subresource_names = ["dataFactory"]
is_manual_connection = false
}
}Data Factory Pipeline Permissions