How to Secure Cost Management Data in Azure Storage and Synapse

Implement security best practices for protecting sensitive billing data

18 min readUpdated January 2025

How to Secure Cost Management Data in Azure Storage and Synapse

Azure Cost Management exports contain highly sensitive financial and organizational data—detailed resource usage, cost allocations, tag information, and subscription hierarchies. A security breach could expose pricing negotiations, project budgets, or confidential business structures. This guide implements defense-in-depth security for cost management data across Azure Storage and Synapse Analytics, ensuring compliance with data protection regulations while maintaining analytical capabilities.

Overview

Securing cost management data requires a multi-layered approach:

  • Encryption at rest and in transit: Protect data from unauthorized access
  • Network isolation: Use private endpoints to prevent public internet exposure
  • Identity-based access control: Implement least-privilege RBAC policies
  • Data classification: Apply sensitivity labels and compliance tagging
  • Audit logging: Track all access and modifications to billing data
  • Key management: Secure encryption keys with Azure Key Vault
  • Compliance alignment: Meet GDPR, SOC 2, and industry regulations

This guide covers four security layers:

  1. Storage Account Security: Encryption, firewall rules, private endpoints
  2. Synapse Analytics Security: Workspace isolation, SQL security, data masking
  3. Access Control: RBAC, managed identities, conditional access
  4. Monitoring & Compliance: Auditing, alerts, compliance posture

Prerequisites

Before you begin, ensure you have:

  • Azure subscription with Owner or Security Administrator role
  • Existing cost exports configured to storage account
  • Azure Storage account with cost export data
  • Azure Synapse workspace (if using Synapse for analytics)
  • Azure CLI (2.40.0+) OR Azure PowerShell module (7.0.0+)
  • Azure Key Vault for customer-managed keys (optional but recommended)
  • Understanding of Azure RBAC and networking concepts

Security Layer 1: Storage Account Hardening

Step 1: Enable Infrastructure Encryption

# Create storage account with double encryption
az storage account create \
  --name securecoststorage \
  --resource-group rg-cost-management \
  --location eastus \
  --sku Standard_GRS \
  --kind StorageV2 \
  --require-infrastructure-encryption \
  --encryption-services blob file \
  --min-tls-version TLS1_2 \
  --allow-blob-public-access false \
  --enable-hierarchical-namespace false

# Verify encryption
az storage account show \
  --name securecoststorage \
  --resource-group rg-cost-management \
  --query "{InfraEncryption:encryption.requireInfrastructureEncryption, Services:encryption.services}"

Step 2: Configure Customer-Managed Encryption Keys

# Create Key Vault for encryption keys
az keyvault create \
  --name kv-cost-encryption \
  --resource-group rg-cost-management \
  --location eastus \
  --enable-soft-delete true \
  --enable-purge-protection true \
  --retention-days 90

# Create encryption key
az keyvault key create \
  --vault-name kv-cost-encryption \
  --name cost-data-encryption-key \
  --protection software \
  --size 2048 \
  --kty RSA

# Grant storage account access to Key Vault
STORAGE_PRINCIPAL_ID=$(az storage account show \
  --name securecoststorage \
  --resource-group rg-cost-management \
  --query identity.principalId -o tsv)

az keyvault set-policy \
  --name kv-cost-encryption \
  --object-id $STORAGE_PRINCIPAL_ID \
  --key-permissions get unwrapKey wrapKey

# Enable customer-managed keys
KEY_VAULT_URI=$(az keyvault show \
  --name kv-cost-encryption \
  --query properties.vaultUri -o tsv)

az storage account update \
  --name securecoststorage \
  --resource-group rg-cost-management \
  --encryption-key-name cost-data-encryption-key \
  --encryption-key-source Microsoft.Keyvault \
  --encryption-key-vault $KEY_VAULT_URI

Step 3: Implement Network Security

# Disable public network access
az storage account update \
  --name securecoststorage \
  --resource-group rg-cost-management \
  --public-network-access Disabled

# Create virtual network
az network vnet create \
  --name vnet-cost-management \
  --resource-group rg-cost-management \
  --location eastus \
  --address-prefix 10.0.0.0/16

# Create subnet for private endpoints
az network vnet subnet create \
  --name snet-private-endpoints \
  --resource-group rg-cost-management \
  --vnet-name vnet-cost-management \
  --address-prefix 10.0.1.0/24 \
  --disable-private-endpoint-network-policies true

# Create private endpoint for blob storage
az network private-endpoint create \
  --name pe-cost-storage-blob \
  --resource-group rg-cost-management \
  --vnet-name vnet-cost-management \
  --subnet snet-private-endpoints \
  --private-connection-resource-id $(az storage account show \
    --name securecoststorage \
    --resource-group rg-cost-management \
    --query id -o tsv) \
  --group-id blob \
  --connection-name cost-storage-connection

# Create private DNS zone
az network private-dns zone create \
  --name privatelink.blob.core.windows.net \
  --resource-group rg-cost-management

# Link DNS zone to VNet
az network private-dns link vnet create \
  --name cost-dns-link \
  --resource-group rg-cost-management \
  --zone-name privatelink.blob.core.windows.net \
  --virtual-network vnet-cost-management \
  --registration-enabled false

# Create DNS zone group
az network private-endpoint dns-zone-group create \
  --name cost-dns-zone-group \
  --resource-group rg-cost-management \
  --endpoint-name pe-cost-storage-blob \
  --private-dns-zone privatelink.blob.core.windows.net \
  --zone-name cost-blob-zone

Step 4: Enable Advanced Threat Protection

# Enable Microsoft Defender for Storage
az security atp storage update \
  --resource-group rg-cost-management \
  --storage-account securecoststorage \
  --is-enabled true

# Configure threat protection settings
az storage account update \
  --name securecoststorage \
  --resource-group rg-cost-management \
  --enable-alw true

Step 5: Configure Immutable Storage Policies

# Enable versioning for accidental deletion protection
az storage account blob-service-properties update \
  --account-name securecoststorage \
  --resource-group rg-cost-management \
  --enable-versioning true \
  --enable-change-feed true

# Create container with immutability policy
az storage container create \
  --name cost-exports-protected \
  --account-name securecoststorage \
  --public-access off \
  --auth-mode login

# Set time-based retention policy (7 years for financial records)
az storage container immutability-policy create \
  --account-name securecoststorage \
  --container-name cost-exports-protected \
  --period 2555 \
  --resource-group rg-cost-management

# Lock the policy (irreversible)
az storage container immutability-policy lock \
  --account-name securecoststorage \
  --container-name cost-exports-protected \
  --if-match "*" \
  --resource-group rg-cost-management

Security Layer 2: Synapse Analytics Security

Step 1: Create Secure Synapse Workspace

# Create managed virtual network for Synapse
az synapse workspace create \
  --name synapse-secure-cost-analytics \
  --resource-group rg-cost-management \
  --storage-account securecoststorage \
  --file-system cost-data \
  --sql-admin-login-user sqladmin \
  --sql-admin-login-password "$(openssl rand -base64 32)" \
  --location eastus \
  --enable-managed-virtual-network true \
  --prevent-data-exfiltration true \
  --allowed-tenant-ids "your-tenant-id"

# Disable public network access
az synapse workspace update \
  --name synapse-secure-cost-analytics \
  --resource-group rg-cost-management \
  --enable-public-network-access false

Step 2: Configure Managed Private Endpoints

# Create managed private endpoint to storage
az synapse managed-private-endpoints create \
  --workspace-name synapse-secure-cost-analytics \
  --pe-name mpe-cost-storage \
  --file-path ./managed-pe-config.json \
  --resource-group rg-cost-management

managed-pe-config.json:

{
  "name": "mpe-cost-storage",
  "properties": {
    "privateLinkResourceId": "/subscriptions/{subscription-id}/resourceGroups/rg-cost-management/providers/Microsoft.Storage/storageAccounts/securecoststorage",
    "groupId": "blob"
  }
}

Step 3: Implement SQL Security (Serverless or Dedicated Pool)

-- Connect to Synapse serverless SQL pool

-- Create database with encryption
CREATE DATABASE CostAnalytics;
GO

USE CostAnalytics;
GO

-- Enable Transparent Data Encryption
ALTER DATABASE CostAnalytics SET ENCRYPTION ON;
GO

-- Create master key
CREATE MASTER KEY ENCRYPTION BY PASSWORD = 'Strong_P@ssw0rd_Here';
GO

-- Create database scoped credential using managed identity
CREATE DATABASE SCOPED CREDENTIAL StorageCredential
WITH IDENTITY = 'Managed Identity';
GO

-- Create external data source with managed identity auth
CREATE EXTERNAL DATA SOURCE SecureCostData
WITH (
    LOCATION = 'https://securecoststorage.blob.core.windows.net/cost-exports',
    CREDENTIAL = StorageCredential
);
GO

-- Create external file format
CREATE EXTERNAL FILE FORMAT SecureCsvFormat
WITH (
    FORMAT_TYPE = DELIMITEDTEXT,
    FORMAT_OPTIONS (
        FIELD_TERMINATOR = ',',
        STRING_DELIMITER = '"',
        FIRST_ROW = 2,
        USE_TYPE_DEFAULT = FALSE
    )
);
GO

-- Create external table with row-level security in mind
CREATE EXTERNAL TABLE CostData
(
    Date DATE NOT NULL,
    SubscriptionId VARCHAR(36) NOT NULL,
    SubscriptionName VARCHAR(100),
    ResourceGroup VARCHAR(90),
    ResourceId VARCHAR(255),
    MeterCategory VARCHAR(50),
    Cost DECIMAL(18,4),
    Currency VARCHAR(3),
    Tags VARCHAR(MAX)
)
WITH (
    LOCATION = '/costs/*.csv',
    DATA_SOURCE = SecureCostData,
    FILE_FORMAT = SecureCsvFormat
);
GO

Step 4: Implement Row-Level Security

-- Create security policy to restrict access by subscription
CREATE SCHEMA Security;
GO

-- Create predicate function
CREATE FUNCTION Security.fn_CostSecurityPredicate
(
    @SubscriptionId VARCHAR(36)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
SELECT 1 AS result
WHERE
    -- Allow admins to see all data
    IS_MEMBER('CostAdministrators') = 1
    OR
    -- Restrict users to their subscriptions
    @SubscriptionId IN (
        SELECT SubscriptionId
        FROM dbo.UserSubscriptionAccess
        WHERE UserPrincipal = USER_NAME()
    );
GO

-- Create security policy
CREATE SECURITY POLICY CostDataSecurityPolicy
ADD FILTER PREDICATE Security.fn_CostSecurityPredicate(SubscriptionId)
ON dbo.CostData
WITH (STATE = ON);
GO

Step 5: Implement Dynamic Data Masking

-- Mask sensitive cost information for non-finance users
ALTER TABLE CostData
ALTER COLUMN Cost ADD MASKED WITH (FUNCTION = 'default()');

ALTER TABLE CostData
ALTER COLUMN Tags ADD MASKED WITH (FUNCTION = 'default()');

-- Grant unmasking to finance team
GRANT UNMASK TO [FinanceTeam];

Security Layer 3: Access Control

Step 1: Implement Least-Privilege RBAC

# Cost management RBAC script
param(
    [Parameter(Mandatory=$true)]
    [string]$ResourceGroupName,

    [Parameter(Mandatory=$true)]
    [string]$StorageAccountName
)

# Define role groups
$roles = @{
    # Read-only access for analysts
    "CostAnalysts" = @{
        Users = @("[email protected]", "[email protected]")
        Role = "Storage Blob Data Reader"
        Scope = "Container"
        Containers = @("cost-exports")
    }

    # Write access for automation
    "CostExportManagers" = @{
        ManagedIdentities = @("cost-export-identity")
        Role = "Storage Blob Data Contributor"
        Scope = "Container"
        Containers = @("cost-exports")
    }

    # Full management for admins
    "CostAdministrators" = @{
        Users = @("[email protected]")
        Role = "Storage Account Contributor"
        Scope = "Account"
    }
}

# Get storage account
$storageAccount = Get-AzStorageAccount `
    -ResourceGroupName $ResourceGroupName `
    -Name $StorageAccountName

foreach ($roleName in $roles.Keys) {
    $roleConfig = $roles[$roleName]

    Write-Host "Configuring role: $roleName" -ForegroundColor Cyan

    # Determine scope
    $scope = if ($roleConfig.Scope -eq "Account") {
        $storageAccount.Id
    } else {
        # Container scope
        foreach ($containerName in $roleConfig.Containers) {
            "$($storageAccount.Id)/blobServices/default/containers/$containerName"
        }
    }

    # Assign to users
    if ($roleConfig.Users) {
        foreach ($user in $roleConfig.Users) {
            New-AzRoleAssignment `
                -SignInName $user `
                -RoleDefinitionName $roleConfig.Role `
                -Scope $scope `
                -ErrorAction SilentlyContinue
            Write-Host "  Assigned $($roleConfig.Role) to $user"
        }
    }

    # Assign to managed identities
    if ($roleConfig.ManagedIdentities) {
        foreach ($identityName in $roleConfig.ManagedIdentities) {
            $identity = Get-AzUserAssignedIdentity `
                -Name $identityName `
                -ResourceGroupName $ResourceGroupName

            New-AzRoleAssignment `
                -ObjectId $identity.PrincipalId `
                -RoleDefinitionName $roleConfig.Role `
                -Scope $scope `
                -ErrorAction SilentlyContinue
            Write-Host "  Assigned $($roleConfig.Role) to identity $identityName"
        }
    }
}

Step 2: Enable Conditional Access Policies

# Require MFA for cost data access
# This requires Azure AD Premium

Connect-MgGraph -Scopes "Policy.ReadWrite.ConditionalAccess"

# Create conditional access policy
$conditions = @{
    Applications = @{
        IncludeApplications = @("All")
    }
    Users = @{
        IncludeGroups = @("CostAnalysts", "CostAdministrators")
    }
    Locations = @{
        IncludeLocations = @("All")
        ExcludeLocations = @("AllTrusted")
    }
}

$grantControls = @{
    Operator = "AND"
    BuiltInControls = @("mfa", "compliantDevice")
}

$params = @{
    DisplayName = "Require MFA for Cost Data Access"
    State = "enabled"
    Conditions = $conditions
    GrantControls = $grantControls
}

New-MgIdentityConditionalAccessPolicy -BodyParameter $params

Step 3: Configure Service Principal with Certificate Auth

# Create service principal for automated access
$sp = New-AzADServicePrincipal -DisplayName "CostDataAutomation"

# Create self-signed certificate
$cert = New-SelfSignedCertificate `
    -Subject "CN=CostDataAutomation" `
    -CertStoreLocation "Cert:\CurrentUser\My" `
    -KeyExportPolicy Exportable `
    -KeySpec Signature `
    -KeyLength 2048 `
    -KeyAlgorithm RSA `
    -HashAlgorithm SHA256 `
    -NotAfter (Get-Date).AddYears(2)

# Export certificate
$certPath = "C:\Certs\cost-automation.pfx"
$certPassword = ConvertTo-SecureString -String "P@ssw0rd" -Force -AsPlainText
Export-PfxCertificate -Cert $cert -FilePath $certPath -Password $certPassword

# Upload certificate to service principal
$keyValue = [System.Convert]::ToBase64String($cert.GetRawCertData())
New-AzADSpCredential -ObjectId $sp.Id -CertValue $keyValue -EndDate $cert.NotAfter

# Grant access to storage
New-AzRoleAssignment `
    -ApplicationId $sp.AppId `
    -RoleDefinitionName "Storage Blob Data Reader" `
    -Scope $storageAccount.Id

Security Layer 4: Monitoring & Compliance

Step 1: Enable Comprehensive Auditing

# Enable diagnostic settings for storage account
az monitor diagnostic-settings create \
  --name cost-storage-audit \
  --resource $(az storage account show \
    --name securecoststorage \
    --resource-group rg-cost-management \
    --query id -o tsv) \
  --logs '[
    {
      "category": "StorageRead",
      "enabled": true,
      "retentionPolicy": {"enabled": true, "days": 365}
    },
    {
      "category": "StorageWrite",
      "enabled": true,
      "retentionPolicy": {"enabled": true, "days": 365}
    },
    {
      "category": "StorageDelete",
      "enabled": true,
      "retentionPolicy": {"enabled": true, "days": 365}
    }
  ]' \
  --metrics '[
    {
      "category": "Transaction",
      "enabled": true,
      "retentionPolicy": {"enabled": true, "days": 90}
    }
  ]' \
  --workspace $(az monitor log-analytics workspace show \
    --resource-group rg-cost-management \
    --workspace-name law-cost-audit \
    --query id -o tsv)

# Enable diagnostic settings for Synapse
az monitor diagnostic-settings create \
  --name synapse-audit \
  --resource $(az synapse workspace show \
    --name synapse-secure-cost-analytics \
    --resource-group rg-cost-management \
    --query id -o tsv) \
  --logs '[
    {
      "category": "SQLSecurityAuditEvents",
      "enabled": true,
      "retentionPolicy": {"enabled": true, "days": 365}
    },
    {
      "category": "SynapseRbacOperations",
      "enabled": true,
      "retentionPolicy": {"enabled": true, "days": 365}
    }
  ]' \
  --workspace $(az monitor log-analytics workspace show \
    --resource-group rg-cost-management \
    --workspace-name law-cost-audit \
    --query id -o tsv)

Step 2: Create Security Alerts

# Alert on unauthorized access attempts
az monitor scheduled-query create \
  --name "Unauthorized Cost Data Access" \
  --resource-group rg-cost-management \
  --scopes $(az storage account show \
    --name securecoststorage \
    --resource-group rg-cost-management \
    --query id -o tsv) \
  --condition "count > 0" \
  --condition-query "StorageBlobLogs
    | where StatusCode == 403
    | where TimeGenerated > ago(15m)" \
  --description "Alert on failed authorization attempts" \
  --evaluation-frequency "15m" \
  --window-size "15m" \
  --severity 2 \
  --action-groups "/subscriptions/{sub-id}/resourceGroups/rg-cost-management/providers/microsoft.insights/actionGroups/SecurityAlerts"

# Alert on encryption key access
az monitor scheduled-query create \
  --name "Cost Encryption Key Access" \
  --resource-group rg-cost-management \
  --scopes $(az keyvault show \
    --name kv-cost-encryption \
    --query id -o tsv) \
  --condition "count > 0" \
  --condition-query "AzureDiagnostics
    | where ResourceProvider == 'MICROSOFT.KEYVAULT'
    | where OperationName == 'VaultGet' or OperationName == 'KeyGet'
    | where TimeGenerated > ago(5m)" \
  --description "Alert on encryption key access" \
  --evaluation-frequency "5m" \
  --window-size "5m" \
  --severity 1 \
  --action-groups "/subscriptions/{sub-id}/resourceGroups/rg-cost-management/providers/microsoft.insights/actionGroups/SecurityAlerts"

Step 3: Implement Data Classification

# Apply sensitivity labels to cost data
Import-Module Az.Resources

# Define sensitivity label (requires Azure Information Protection)
$label = @{
    DisplayName = "Confidential - Finance"
    Description = "Cost management and billing data"
    Sensitivity = "Confidential"
}

# Tag storage account
$tags = @{
    "DataClassification" = "Confidential"
    "ComplianceScope" = "SOC2,GDPR"
    "DataOwner" = "Finance"
    "RetentionPeriod" = "7years"
}

Update-AzStorageAccount `
    -ResourceGroupName $ResourceGroupName `
    -Name $StorageAccountName `
    -Tag $tags

Step 4: Configure Microsoft Purview for Data Governance

# Create Purview account for data cataloging
az purview account create \
  --name purview-cost-governance \
  --resource-group rg-cost-management \
  --location eastus \
  --managed-resource-group-name rg-purview-managed

# Register storage account in Purview
az purview account add-root-collection \
  --name purview-cost-governance \
  --resource-group rg-cost-management \
  --collection-name "Cost Management Data"

# Scan storage account
az purview data-source create \
  --name cost-storage-source \
  --purview-account purview-cost-governance \
  --resource-group rg-cost-management \
  --kind "AzureStorage" \
  --properties @purview-source-config.json

Best Practices

1. Regular Security Audits

-- Query to audit storage access patterns
SELECT
    TimeGenerated,
    OperationName,
    Identity = CallerIpAddress,
    AuthenticationType,
    StatusCode,
    Uri
FROM StorageBlobLogs
WHERE TimeGenerated > ago(7d)
    AND AccountName == 'securecoststorage'
ORDER BY TimeGenerated DESC;

-- Identify suspicious access patterns
SELECT
    CallerIpAddress,
    COUNT(*) AS AccessCount,
    COUNT(DISTINCT OperationName) AS UniqueOperations,
    SUM(CASE WHEN StatusCode >= 400 THEN 1 ELSE 0 END) AS FailedAttempts
FROM StorageBlobLogs
WHERE TimeGenerated > ago(24h)
GROUP BY CallerIpAddress
HAVING SUM(CASE WHEN StatusCode >= 400 THEN 1 ELSE 0 END) > 10
ORDER BY FailedAttempts DESC;

2. Automated Key Rotation

# Rotate customer-managed encryption keys annually
$keyVaultName = "kv-cost-encryption"
$keyName = "cost-data-encryption-key"

# Create new key version
$newKey = Add-AzKeyVaultKey `
    -VaultName $keyVaultName `
    -Name $keyName `
    -Destination Software

# Storage account automatically uses latest key version
Write-Host "New key version created: $($newKey.Version)"

3. Backup Encryption Keys

# Backup encryption keys to secure location
$keyBackup = Backup-AzKeyVaultKey `
    -VaultName $keyVaultName `
    -Name $keyName `
    -OutputFile "C:\SecureBackups\cost-encryption-key-backup-$(Get-Date -Format 'yyyyMMdd').blob"

# Store backup in separate subscription/region

4. Implement Zero Trust Principles

  • Never trust, always verify every access request
  • Use managed identities instead of keys/passwords
  • Implement just-in-time (JIT) access for admins
  • Require MFA for all human access
  • Monitor and alert on all privileged operations

5. Regular Penetration Testing

# Security Testing Checklist

## Quarterly Tests
- [ ] Attempt access from non-approved IPs
- [ ] Test RBAC boundaries (privilege escalation)
- [ ] Verify MFA enforcement
- [ ] Test private endpoint connectivity
- [ ] Validate encryption at rest

## Annual Tests
- [ ] Full penetration test by third party
- [ ] Social engineering assessment
- [ ] Disaster recovery drill (key loss scenario)
- [ ] Compliance audit (SOC 2, ISO 27001)

Troubleshooting

Issue: Cannot access storage after enabling private endpoint

Symptoms: Applications can't reach storage account

Resolution:

# Verify DNS resolution
nslookup securecoststorage.blob.core.windows.net

# Should resolve to private IP (10.x.x.x)
# If resolves to public IP, DNS not configured correctly

# Check private endpoint connection
az network private-endpoint show \
  --name pe-cost-storage-blob \
  --resource-group rg-cost-management \
  --query "customDnsConfigs[].{FQDN:fqdn, IP:ipAddresses}"

Issue: Synapse can't access storage with managed identity

Symptoms: "Authorization failed" when querying external tables

Resolution:

# Verify Synapse managed identity has storage permissions
SYNAPSE_IDENTITY=$(az synapse workspace show \
  --name synapse-secure-cost-analytics \
  --resource-group rg-cost-management \
  --query identity.principalId -o tsv)

az role assignment create \
  --role "Storage Blob Data Contributor" \
  --assignee-object-id $SYNAPSE_IDENTITY \
  --scope $(az storage account show \
    --name securecoststorage \
    --resource-group rg-cost-management \
    --query id -o tsv)

Issue: Row-level security not filtering data

Symptoms: Users seeing data they shouldn't

Resolution:

-- Verify security policy is enabled
SELECT name, is_enabled
FROM sys.security_policies
WHERE name = 'CostDataSecurityPolicy';

-- Check if user has UNMASK privilege
SELECT pr.name, pe.permission_name
FROM sys.database_principals pr
INNER JOIN sys.database_permissions pe ON pr.principal_id = pe.grantee_principal_id
WHERE pe.permission_name = 'UNMASK';

-- Test predicate function
SELECT * FROM Security.fn_CostSecurityPredicate('test-subscription-id');

Issue: Key Vault access denied for encryption

Symptoms: Storage account can't access encryption keys

Resolution:

# Ensure storage account has system-assigned identity
az storage account update \
  --name securecoststorage \
  --resource-group rg-cost-management \
  --assign-identity

# Grant Key Vault permissions
STORAGE_IDENTITY=$(az storage account show \
  --name securecoststorage \
  --resource-group rg-cost-management \
  --query identity.principalId -o tsv)

az keyvault set-policy \
  --name kv-cost-encryption \
  --object-id $STORAGE_IDENTITY \
  --key-permissions get wrapKey unwrapKey

Compliance Certifications

SOC 2 Compliance

**Required Controls:**
- Encryption: Customer-managed keys ✓
- Access Control: RBAC with least privilege ✓
- Network Security: Private endpoints ✓
- Audit Logging: 365-day retention ✓
- Monitoring: Real-time alerts ✓

GDPR Compliance

**Required Controls:**
- Data Encryption: TLS 1.2+ in transit, AES-256 at rest ✓
- Access Rights: Row-level security for data isolation ✓
- Data Retention: Immutable storage policies ✓
- Breach Notification: Automated security alerts ✓
- Data Minimization: Dynamic data masking ✓

Next Steps

Once security is implemented:

  1. Document security architecture: Create diagrams and runbooks
  2. Train team members: Ensure understanding of security controls
  3. Schedule regular reviews: Quarterly security posture assessments
  4. Implement cost alerts: See "How to Set Up Cost Alerts and Budgets in Azure"
  5. Monitor export health: Use "How to Monitor Cost Export Status and Data Freshness in Azure"

Related Resources

Frequently Asked Questions

Find answers to common questions

To enable customer-managed keys for your Azure Storage account, first, create an Azure Key Vault and generate an encryption key. Use the Azure CLI to grant your storage account access to this Key Vault by setting the appropriate access policies. After that, update your storage account to link it to the Key Vault and the specific encryption key using the `az storage account update` command. It's critical to ensure that the Key Vault has soft delete and purge protection enabled for added security.

Need Professional Help?

Our team of experts can help you implement and configure these solutions for your organization.