Skip to content

Non-Prod Network Security — Current State

This document captures the actual deployed networking and security posture for the non-production estate (rg-eshars-nonprod and related RGs). It covers what is in place, what is intentionally deferred, and what are known gaps.


Network Diagram

flowchart TB
    Internet(["🌐 Internet"])
    VPNUser(["👤 VPN User\nEntra ID auth"])
    OpsUser(["👤 Ops / Portal User"])

    subgraph vpngw_vnet["vnet-vpngw-nonprod (10.9.0.0/26)"]
        direction TB
        GWSubnet["GatewaySubnet\n10.9.0.0/27"]
        PESubnet2["sn-pe-nonprod\n10.9.0.32/27"]
        VPNGW["vpngw-eshars-nonprod\nVpnGw1 · OpenVPN · Entra ID\nClient pool: 172.16.1.0/24"]
        GWSubnet --> VPNGW
    end

    subgraph nonprod_vnet["rg-eshars-nonprod VNet (10.1.0.0/16)"]
        direction TB

        subgraph bastion_snet["AzureBastionSubnet (10.1.1.0/26) — retained, no Bastion deployed"]
        end

        subgraph agw_snet["sn-agw-eshars-np (10.1.2.0/24)"]
            AGW["agw-eshars-np\nStandard_v2\n⚠️ No WAF"]
        end

        subgraph apps_snet["sn-webapp-eshars-np (10.1.3.0/24)"]
            AppSvc["App Service VNet Integration\napp-eshars-nonprod-cus"]
        end

        subgraph pe_snet["sn-pe-sql-001 (10.1.5.0/28)"]
            PE["Private Endpoints"]
        end

        subgraph default_snet["default subnet (10.1.0.0/24)"]
            direction LR
            SSRS["vm-sql16-np\n10.1.0.4\nSSRS nonprod"]
            SSRSTrain["vm-sql16-1-np\n10.1.0.6\nSSRS training\n(rg-eshars-nonprod-centralus)"]
            JobSched["vm-jobscheduler-np-001\n10.1.0.10\nBatch/import jobs"]
            CleanupJobs["vm-cleanup-jobs\n10.1.0.9\nScheduled jobs"]
            OpsVM["vm-ops-nonprod\n10.1.0.12\nOps + build agent"]
            DbApi["vm-db-api\n10.1.0.8\nSQL jump box\n🗑️ To deprecate"]
            SuccVM["vm-successed-nonprod\n10.1.0.5\n⚠️ Purpose unknown"]
        end
    end

    VPNUser -->|"OpenVPN\nEntra ID"| VPNGW
    VPNGW -->|"172.16.1.x → 10.1.0.x\nvia peering"| default_snet
    Internet -->|"HTTPS :443\nHTTP :80"| AGW
    AGW -->|"10.1.0.4 :80/443"| SSRS
    AGW -->|"10.1.0.6 :80/443"| SSRSTrain
    AGW --> AppSvc
    vpngw_vnet <-->|"VNet Peering\nUseRemoteGateways=true"| nonprod_vnet

VNet Topology

Two VNets are peered together to form the non-prod network:

rg-eshars-nonprod (10.1.0.0/16) vnet-vpngw-nonprod (10.9.0.0/26)
southcentralus southcentralus
───────────────────────────── ──────────────────────────────
AzureBastionSubnet 10.1.1.0/26 GatewaySubnet 10.9.0.0/27
sn-agw-eshars-np 10.1.2.0/24 ◄─── sn-pe-nonprod 10.9.0.32/27
sn-webapp-eshars-np 10.1.3.0/24 peering (UseRemoteGateways=true)
sn-dnsresolver-nonprod 10.1.4.0/28
sn-pe-sql-001 10.1.5.0/28
default 10.1.0.0/24

The rg-eshars-nonprod VNet uses UseRemoteGateways=true on the peering, so all VMs in the non-prod VNet route through the VPN gateway in vnet-vpngw-nonprod.


Access Controls

VPN Gateway

PropertyValue
Namevpngw-eshars-nonprod
Resource Grouprg-eshars-nonprod
SKUVpnGw1
TypeRouteBased
ProtocolOpenVPN
AuthAzure AD (Entra ID)
Client address pool172.16.1.0/24
Active-activeNo
BGPNo

VPN clients authenticate with their Entra ID credentials. Once connected, they receive an IP in 172.16.1.0/24 and can reach VMs in the default subnet (10.1.0.0/24) via the peering.

Azure Bastion

Management Access Summary

VMs have no public IP addresses. Access is via VPN only:

  1. VPN — OpenVPN (Entra ID) → 172.16.1.0/24 client pool → reaches VMs at 10.1.0.x

Virtual Machines

All non-prod VMs sit in the default subnet (10.1.0.0/24), spread across two resource groups:

VMIPSizeRGRole
vm-sql16-np10.1.0.4Standard_D4s_v3rg-eshars-nonprodSSRS nonprod (SQL Express + SSRS 2022)
vm-sql16-1-np10.1.0.6rg-eshars-nonprod-centralusSSRS training (same VNet, different RG)
vm-cleanup-jobs10.1.0.9Standard_D2s_v5rg-eshars-nonprodScheduled/timer jobs
vm-jobscheduler-np-00110.1.0.10Standard_DS3_v2rg-eshars-nonprodImport/upload/batch jobs
vm-ops-nonprod10.1.0.12Standard_D2s_v3rg-eshars-nonprodOps machine + Azure DevOps build agent
vm-db-api10.1.0.8Standard_B2msrg-eshars-nonprodSQL Server jump box — to be deprecated (devs should use Entra ID direct access)
vm-successed-nonprod10.1.0.5Standard_B2msrg-eshars-nonprodPurpose unknown — candidate for decommission

App Gateways

agw-eshars-np — SSRS/Nonprod

PropertyValue
TierStandard_v2 (no WAF)
Locationcentralus
StateRunning

Backend pools:

PoolTargetUsed for
appGatewayBackendPool10.1.0.4Nonprod SSRS (vm-sql16-np)
bepool-ssrs-training10.1.0.6Training SSRS (vm-sql16-1-np)
app-eshars-nonprod-cusapp-eshars-nonprod-cus.azurewebsites.netNonprod web app

Listeners: nonprodreports (HTTPS, no host filter), listener-ssrs-training (HTTPS, trainingreports.myeshars.com)

**Migration path**: When SSRS migrates to the new `vnet-eshars-core` shared App Gateway (WAF_v2, Phase 3), `agw-eshars-np` will be decommissioned.

ag-cus-np — Dev/Test (to be decommissioned)

PropertyValue
TierWAF_v2
WAF Policyag-waf-policy
Backendwa-hellowrld-test-... (test app)

This gateway was used to test the dev web app with the new shared gateway pattern. To be decommissioned by end of week once dev site validation is complete.


Network Security Groups

SSRS VM NSG (vm-sql16-np)

RulePriorityDirectionAccessProtocolSrcDstPort
AllowBastionRDP1000InboundAllowTCPAzureBastionSubnet**
AllowVpnRDP1001InboundAllowTCP172.16.1.0/2410.1.0.43389
reportserverurl1509InboundAllowTCP**443
report-server-url1510InboundAllowTCP**80
**Fix**: Update both rules to set `sourceAddressPrefix = 10.1.2.0/24`.

Bastion NSG (nsg-bastion-nonprod)

Standard Microsoft-recommended Bastion NSG rules — correctly configured:

DirectionRulePortSource
InboundAllowHttpsInbound443Internet
InboundAllowGatewayManagerInbound443GatewayManager
InboundAllowAzureLoadBalancerInbound443AzureLoadBalancer
InboundAllowBastionHostCommunication*VirtualNetwork
OutboundAllowSshRdpOutbound** → VirtualNetwork
OutboundAllowAzureCloudOutbound443* → AzureCloud
OutboundAllowBastionCommunication** → VirtualNetwork
OutboundAllowHttpOutbound80* → Internet

Private Endpoints

The subnet sn-pe-sql-001 (10.1.5.0/28) exists for private endpoints. The VPN gateway VNet also has sn-pe-nonprod (10.9.0.32/27).

Private endpoints in use: to be fully inventoried.


What’s in Place — Summary

ControlStatus
All VMs have private IPs only
VPN access via Azure AD (Entra)
Azure Bastion⛔ Decommissioned 2026-04-17 — VPN is sole access path
AzureBastionSubnet retained for recreation
SSRS behind App Gateway
Per-VM NSGs✅ (all VMs have individual NSGs)

Known Gaps

GapSeverityFix
SSRS NSG (vm-sql16-np) allows 80/443 from *MediumRestrict to App GW subnet 10.1.2.0/24
agw-eshars-np has no WAFLow (intentional for non-prod)Accept until SSRS migrates to shared WAF_v2 gateway
vm-db-api — SQL jump box, devs connecting via shared VMMediumDeprecate: enable Entra ID auth on SQL Servers, have devs connect directly via VPN
vm-successed-nonprod — purpose unknownLowInvestigate, document or decommission
vm-sql16-1-np in wrong RGLowMove to rg-eshars-nonprod during RG reorg
Flat default subnet — no VM-level segmentationLowFuture: segment by workload type as estate grows
VPN gateway not in IaCMediumAdd vpngw-eshars-nonprod to eshars-ops Terraform
ag-cus-np (WAF_v2) still runningLowDecommission after dev site validation

Planned Improvements

See Shared Core Architecture for the full migration plan. Relevant phases for non-prod:

  • Phase 3 — Move SSRS VM into new vnet-eshars-core shared VNet, behind the shared WAF_v2 App Gateway. Decommission agw-eshars-np.
  • Phase 4 — Investigate and decommission unknown VMs (vm-db-api, vm-successed-nonprod).
  • RG Reorg — Consolidate rg-eshars-nonprod-centralus VMs into rg-eshars-nonprod.