Your Azure SQL Is Public Right Now. ISO 27017 Demands You Fix It

Your Azure SQL Is Public Right Now. ISO 27017 Demands You Fix It

Here’s a pattern I encounter far too often during security assessments: teams deploy Azure App Services, SQL databases, and storage accounts using default network configurations. Compliance audit rolls around. Suddenly everyone’s surprised that their entire infrastructure sits wide open on the public internet.

“But it’s in the cloud!” they argue. Sure it is. And that cloud has network boundaries you’re responsible for defining. Azure won’t do it for you.

The shared responsibility model is unambiguous here. Microsoft secures the physical infrastructure, the hypervisor, the host OS. Network configuration? That’s yours. Always has been. The Azure portal makes deployment easy, but “easy” and “secure” aren’t synonyms.

ISO/IEC 27017 Control CLD 13.1.4 addresses exactly this gap. It requires cloud customers to implement network isolation equivalent to traditional data centers. Virtualized networks need deliberate configuration. They don’t magically secure themselves.

Let me show you what breaks when you rely on defaults, and how to fix it.

The Fatal Default: Your Database on the Public Internet

Deploy Azure resources without touching the network settings? Here’s what you actually get:

Fatal Example: Default Network Configuration

// ❌ SQL Server: publicNetworkAccess defaults to Enabled
resource sqlServer 'Microsoft.Sql/servers@2023-05-01-preview' = {
  name: 'mysqlserver-prod'
  properties: {
    publicNetworkAccess: 'Enabled'  // ❌ Accessible from internet
  }
}

// ❌ The "Allow Azure Services" firewall rule is a lie
resource firewallRule 'Microsoft.Sql/servers/firewallRules@2023-05-01-preview' = {
  name: 'AllowAllAzureServices'
  properties: {
    startIpAddress: '0.0.0.0'  // ❌ Allows ANY Azure subscription
    endIpAddress: '0.0.0.0'    // ❌ Including attacker-controlled ones
  }
}

// ❌ Storage Account: networkAcls.defaultAction defaults to Allow
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
  name: 'mystorageprod'
  properties: {
    allowBlobPublicAccess: true   // ❌ Anonymous access possible
    networkAcls: { defaultAction: 'Allow' }  // ❌ No restrictions
  }
}

Why This Fails Every Audit

This configuration violates ISO/IEC 27017 CLD 13.1.4 in ways auditors flag immediately:

No network segmentation. Everything’s reachable from the public internet, or from any Azure service in any subscription, including ones you don’t control.

The 0.0.0.0 firewall rule is security theater. It sounds like “Allow Azure Services” but actually means “allow any Azure service from any subscription worldwide.” Including attacker-controlled ones.

Zero defense in depth. Credentials leak through phishing, code injection, or that .env file someone committed to GitHub. Without network isolation, leaked credentials mean direct database access. Game over.

No audit trail. Without network-level controls, you can’t demonstrate compliance with ISO/IEC 27001 Control A.13.1.

I’ve seen this exact configuration in production systems. The usual excuse: “We needed it to work” or “Azure handles security.” Neither reflects how shared responsibility actually operates.

The Fix: Network Isolation That Actually Works

ISO 27017 CLD 13.1.4 demands logical isolation appropriate to the cloud model. For Azure PaaS, that translates to three mechanisms: VNets for segmentation, Private Endpoints for private connectivity, and NSGs for traffic control.

Correct Example: Network-Isolated Architecture

// ✅ VNet with dedicated subnets
resource vnet 'Microsoft.Network/virtualNetworks@2023-05-01' = {
  name: 'vnet-prod'
  properties: {
    addressSpace: { addressPrefixes: ['10.0.0.0/16'] }
    subnets: [
      { name: 'snet-app', properties: { addressPrefix: '10.0.1.0/24' } }
      { name: 'snet-endpoints', properties: { addressPrefix: '10.0.2.0/24' } }
    ]
  }
}

// ✅ SQL Server: publicNetworkAccess = Disabled
resource sqlServer 'Microsoft.Sql/servers@2023-05-01-preview' = {
  name: 'mysqlserver-prod'
  properties: {
    publicNetworkAccess: 'Disabled'  // ✅ Internet can't reach it
  }
}

// ✅ Private Endpoint: SQL only accessible within VNet
resource sqlPrivateEndpoint 'Microsoft.Network/privateEndpoints@2023-05-01' = {
  name: 'pe-sql-prod'
  properties: {
    subnet: { id: vnet.properties.subnets[1].id }
    privateLinkServiceConnections: [{
      name: 'sql-connection'
      properties: { privateLinkServiceId: sqlServer.id, groupIds: ['sqlServer'] }
    }]
  }
}

// ✅ Storage Account: Deny by default, private endpoints only
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
  name: 'mystorageprod'
  properties: {
    allowBlobPublicAccess: false
    publicNetworkAccess: 'Disabled'
    networkAcls: { defaultAction: 'Deny' }
  }
}

Why This Passes Audit

This satisfies ISO/IEC 27017 CLD 13.1.4 because:

Network segmentation exists. Dedicated subnets separate application traffic from private endpoints. Each serves a specific purpose. NSGs restrict traffic between them.

Private connectivity only. SQL and Storage are accessible exclusively through Private Endpoints inside the VNet. No public IP. No public exposure. These resources literally don’t have internet-routable addresses anymore.

Defense in depth works. Credentials leak? Attackers still can’t reach the database without VNet access. That’s a meaningful security boundary: network-level isolation, not just authentication.

DNS resolves privately. Private DNS zones ensure apps hit Private Endpoint addresses, not public endpoints someone forgot to disable. Without proper DNS configuration, your app might still try reaching the public endpoint. Don’t skip this.

Prove It Works: Validation That Matters

Deploying network controls isn’t enough. You need to verify resources are genuinely unreachable from the public internet. Trust nothing.

The key insight: use inverted logic. Most health checks verify services ARE reachable. For network isolation, you want checks that fail when services are reachable from outside the VNet. Configuration drift happens. Someone adds an IP exception “temporarily.” Without automated validation, you won’t notice until the auditor does.

C# Health Check: Verify Connectivity Works Inside VNet

For production health checks, I recommend using NetEvolve.HealthChecks.SqlServer, part of the NetEvolve HealthChecks library I maintain. It provides production-ready SQL Server health checks with proper timeout handling and connection pooling.

// Program.cs - Register SQL Server health check
builder.Services.AddHealthChecks()
    .AddSqlServer(options =>
    {
        options.ConnectionString = builder.Configuration.GetConnectionString("SqlServer");
        options.Timeout = 5000; // 5 second timeout
    });

// This check will FAIL if network isolation blocks your app
// That's your signal: Private Endpoint DNS isn't resolving correctly

The health check verifies your application can reach SQL Server through the Private Endpoint. If it fails after enabling network isolation, you’ve got a DNS or VNet integration problem, not a code problem.

For the inverse check (verify public inaccessibility), run port scans from outside your VNet during CI/CD:

GitHub Actions: Block Insecure Configs

- name: Reject Public Network Access
  run: |
    # Fail if any resource allows public access
    if grep -r "publicNetworkAccess.*Enabled" infrastructure/; then
      echo "❌ FATAL: Public network access detected"; exit 1
    fi
    # Fail if dangerous 0.0.0.0 firewall rule exists  
    if grep -r "startIpAddress.*0\.0\.0\.0" infrastructure/; then
      echo "❌ FATAL: 0.0.0.0 firewall rule detected"; exit 1
    fi

Post-Deployment Verification

# Run from OUTSIDE your VNet - connection should FAIL
timeout 5 bash -c "cat < /dev/null > /dev/tcp/mysqlserver.database.windows.net/1433" \
  && echo "❌ SQL is PUBLIC" || echo "✅ SQL is isolated"

Environment Isolation: Why Dev and Prod Share Nothing

Dev, test, and prod in the same VNet? That’s how a junior developer’s DROP TABLE reaches production. I’ve watched it happen.

Environment isolation limits blast radius. A compromised dev environment shouldn’t provide a network path to production data. Separate VNets with no peering means an attacker who compromises dev has to start from scratch for prod. That’s the point.

// Separate address spaces = no lateral movement
resource vnetDev  'Microsoft.Network/virtualNetworks@2023-05-01' = { properties: { addressSpace: { addressPrefixes: ['10.1.0.0/16'] }}}
resource vnetProd 'Microsoft.Network/virtualNetworks@2023-05-01' = { properties: { addressSpace: { addressPrefixes: ['10.0.0.0/16'] }}}
// No peering between them. Ever.

If you absolutely need connectivity between environments (centralized logging, shared services), document the justification and implement it through a hub-spoke topology with explicit firewall rules. Default to isolation.

What Actually Matters

ISO/IEC 27017 Control CLD 13.1.4 isn’t bureaucratic box-ticking. It codifies what experienced architects already know: cloud services are virtual networks requiring deliberate security architecture.

“Cloud” doesn’t mean “public.” Azure resources default to public access. VNets, Private Endpoints, and NSGs are opt-in. You have to configure them.

Deny by default, allow by exception. That’s least-privilege networking. Permit only the protocols, ports, and sources your application actually needs.

Private Endpoints eliminate attack surface. No public IP means credential leaks become less catastrophic. The attacker has credentials but nowhere to use them.

Test that isolation works. Health checks that fail when services ARE accessible catch configuration drift before auditors do.

Separate environments completely. Different VNets for dev, test, and production. No peering. No exceptions without documented justification.

The pattern repeats: teams deploy with defaults, then panic when audits reveal their “cloud-native” architecture is actually a compliance liability. Network isolation isn’t remediation. It’s a foundational decision that belongs in your architecture from day one.

If you’re running .NET applications in Azure, these controls aren’t optional. They’re the baseline for proving you understand where Microsoft’s responsibility ends and yours begins.

Comments

VG Wort