Azure Security & Monitoring
Security configuration, network security group rules, monitoring setup, and audit findings for PSI Azure infrastructure.
Security Audit Summary
Last audited: 2026-06-24 (full-scope automated compliance scan — all resource types)
App Service Security
| App Service | HTTPS Only | Public Access | Auth Method | Private Endpoint |
|---|---|---|---|---|
| bom-explorer-web | Yes | Disabled | App-layer auth | bom-explorer-web-pe |
| psi-portal | Yes | Disabled | App-layer auth | psi-portal-pe |
| erp-migration-api (DMT) | Yes | Disabled | App-layer auth (EasyAuth disabled intentionally) | ERPMigrationAPIEndpoint |
| ps-project-explorer | Yes | Disabled | App-layer auth | ps-project-explorer-pe |
| ps-progressive-view | Yes | Disabled | EasyAuth + app-layer | ps-progressive-view-pe |
| redbook-web | Yes | Disabled | App-layer auth | redbook-web-pe |
| ps-redbook-dashboard | Yes | Disabled | EasyAuth + app-layer | ps-redbook-dashboard-pe |
| prgjsmes-prod | Yes | Disabled | App-layer auth | prgjsmes-prod-pe |
| psredbookphotos | Yes | Enabled | None (intentional) | None |
| ps-shipphotos | Yes | Enabled | None (intentional) | None |
| psi-zebra-tracking | Yes | Enabled ⚠️ | App-layer auth | None — app has no PE (only the SQL has psi-zebra-tracking-sql-pe) |
| psi-datasync | Yes | Enabled ⚠️ | App-layer auth | psi-datasync-pe (10.160.0.19) — PE exists but public access was never disabled |
| csm-board | Yes | Disabled | App-layer auth (MSAL PKCE) + tenant CA “Require MFA” | None — public-exception (ADR-0028) |
| ps-winget-source | Yes | Disabled | App-layer auth | ps-winget-source-pe |
| ps-argo-analytics | Yes | Disabled | App-layer auth | ps-argo-analytics-pe |
| procisely-redirect | Yes | Disabled | App-layer auth (redirect only) | None |
| ps-dispatch | Yes | Disabled | App-layer auth | ps-dispatch-pe (SCM: 10.160.140.22) |
| psi-service | Yes | Disabled | App-layer auth (MSAL redirect) | psi-service-pe (10.160.140.24) |
| psi-notify-listener | Yes | Disabled | App-layer auth (function app) | TBD |
| ps-sop 🚧 | Yes | Disabled | App-layer auth (MSAL redirect) — admin consent not yet granted | ps-sop-pe (10.160.140.31) |
| ps-buildvsbuy 🚧 | No 🚧 | Enabled 🚧 | None yet — in active development | ps-buildvsbuy-pe (10.160.140.5) |
| ps-bom-coverage | Yes | Disabled | EasyAuth (Entra-only) — verified end-to-end | ps-bom-coverage-pe (10.160.140.33) |
ps-bom-coverageis live and authenticated (created 2026-08-10, first deploy 2026-08-12, auth verified end-to-end 2026-08-13). The full network baseline is done and verified:httpsOnly,publicNetworkAccess=Disabled, private endpoint.33, SCM/FTP basic creds disabled, system-assigned MI, App Insights, DNS (#7), custom domain + wildcard cert (SNI), and a resolving Key Vault reference (#8, granted as an access policy). Deploy is identity-based on the self-hosted runner.Two central hand-offs gated first light. Both are now fulfilled (2026-08-12):
DNS (psi-azure-admin#7) — complete. Public zone (
bom-coverageA →10.160.140.33,asuid.bom-coverageTXT), Azure Private DNS, and the DC AD-integrated records are all in. Verified from the DC (10.160.0.5):ps-bom-coverage,ps-bom-coverage.scmandbom-coverage.progressivesurface.comall answer10.160.140.33. Fulfilment arrived in two parts — the public record landed first and the privatelink half several days later, during which four deploys failed onFailed to resolve …scm.azurewebsites.net. Worth knowing the request can be partially fulfilled: check the privatelink names specifically, not just that the custom domain resolves.Key Vault grant (psi-azure-admin#8) — resolved. The app MI (
3957aa4e-595b-485c-b0d2-21da71c6b587) holdssecrets: getonps-certificates-kvvia access policy. It could not be self-served becausePSI-WebApp-Deployerscarries a data-plane policy on that vault but no RBAC onPS-RG-01, so the vault is invisible to the control plane (not found within subscription) even though writing secrets works.The grant alone was not sufficient.
ps-certificates-kvisdefaultAction: Deny, and Microsoft requires Linux apps reaching a network-restricted vault to route all outbound through the VNet. The app hadvnetRouteAllEnabled: false, so the reference never resolved. Setting ittruefixed it — confirmed in the vault audit log with anOKfrom10.160.150.250and noipRulein place. Every PSI web app is Linux, so this applies fleet-wide; it is now a checkbox in thedeploy-webappskill. Full detail: key-vaults.It is the first EasyAuth-only app in the fleet — Entra-only with every other provider explicitly disabled,
requireAuthentication=true, and a singleexcludedPathsentry for/api/health. That exclusion exists so the deploy gate can poll the served commit SHA; on the first deploy the log shows attempts 1–10 returning<none>and attempt 11 returning the expected SHA — it caught the hostname binding taking effect rather than passing on a stale 200 from the old container. Worth copying: a plain 200 check would have gone green before the app was actually serving.Admin consent (#11) — granted and verified:
User.Read openid profile email offline_access(AllPrincipals), matching the reference shape above. Getting there exposed two app-registration traps worth checking on every new EasyAuth app, both of which fail silently until someone tries to sign in:
- Never write Graph permission GUIDs from memory.
profileandaz ad sp show --id 00000003-0000-0000-c000-000000000000 --query "oauth2PermissionScopes[?value=='profile'].id").- EasyAuth requires
enableIdTokenIssuance: true. It signs in withresponse_type=code+id_token; without ID-token issuance the sign-in failsAADSTS700054regardless of how complete the consent is.az ad app createdoes not set it, and nothing warns you.
psi-bc-part-checker’sverify.ymlcatches both by hitting the Entra authorize endpoint anonymously and failing on anyAADSTScode — Entra returns config errors as a page without credentials, so it needs no browser and costs one runner minute. Recommend copying it for the next app. Details: bom-coverage.Two diagnostics worth reusing. (1) A Key Vault reference’s resolution status is readable at
config/configreferences/appsettingswithout any vault permission — that is the signal to check when you cannot read the vault control plane, instead of inferring a missing grant fromaz keyvault showreturning “not found within subscription”. It is also what shows thevnetRouteAllEnabledfailure above as an unresolved reference rather than a permissions error. (2).32looked free in a live NIC scan from the deployer’s subscription but ispsi-finance-kv; the allocation table in azure-resources, not the scan, is what prevented a collision.
Slot managed identities are separate principals
prgjsmes-prod/slots/staging(oid=bb2cc61f-587c-4589-a8c9-9c02c973095e) had no access policy onps-certificates-kvand failed every secret read continuously. Grantedsecrets get/list+certificates geton 2026-08-12, mirroring the production slot. A slot’s MI is never covered by the production slot’s grant — the same trap as the slot-MI SQL grant.🚧
ps-sopis provisioned to the baseline but not yet serving (created 2026-08-05). Unlikeps-buildvsbuybelow it needs no hardening —httpsOnly,publicNetworkAccess=Disabled, private endpoint, SCM/FTP basic creds disabled, MI + Key Vault, and App Insights are all already in place, and it is listed here so the posture is on record rather than because anything is outstanding on the network side. Two non-network items remain before first light: the DNS request (psi-azure-admin#4) which also gates the custom domain and therefore the cert binding, and tenant admin consent for thePSI SOP Webapp registration (4b462967-e117-42c0-86c5-34478c4c051d) — without it every sign-in dead-ends in the admin-approval flow, because PSI has user consent disabled tenant-wide. Details: psi-sop.
🚧
ps-buildvsbuyis a brand-new app under active development (created June 2026, after the 2026-06-24 audit). It is not a compliance violation — it just hasn’t been hardened yet. Pre-launch checklist before it goes live: enablehttpsOnly, attach an approved auth profile, and disable public network access (its PE already exists at 10.160.140.5 — add DC + Azure privatelink DNS records first, see dns-standards). Listed here so the hardening isn’t forgotten at launch.
Note: psredbookphotos and ps-shipphotos are intentionally public — they serve photo assets that need broad accessibility.
SQL Server Security
| Property | Value |
|---|---|
| Server | procserv-proddata |
| Public Access | Enabled (firewall rules for PSI office IPs) |
| Private Endpoint | PS-ProdData-SQL-Private (10.160.140.4) |
| TLS | 1.2 minimum |
| Entra Admin | ADevereaux@progressivesurface.com |
Note: Public access is enabled with IP-restricted firewall rules for management access from PSI office IPs. Internal app traffic routes through the private endpoint.
EasyAuth Configuration
Apps currently using Azure App Service EasyAuth:
ps-progressive-viewps-redbook-dashboard
Baseline:
- Provider: Azure Active Directory only
- Unused social providers: Disabled (Apple, Facebook, GitHub, Google, Twitter — removed 2026-02-23)
- Session: 8-hour fixed cookie expiration
- Nonce validation: Enabled (5-minute expiration)
- HTTPS required: Yes
Exception noted:
-
erp-migration-api(DMT) runs with platform auth disabled and app-layer auth, because anonymous root/health access is required for current operation and remote MCP health checks. -
csm-board— public-exposure exception (ADR-0028, csm-board#28).-
Rule excepted: “Internal web apps must use private endpoints and have public access disabled.”
-
App / env:
csm-board(board.progressivesurface.com), prod,PS-WEBAPPS, sharedasp-erp-migration-toolB3 Basic plan. -
Business reason: the board must be reachable externally by remote developers and their federated
csm agent, which authenticates headlessly via MSAL device-code over an outbound WebSocket — a path that cannot satisfy edge pre-auth (App Proxy / Easy Auth) and is broken by a private-only posture. It must also receive inbound Bot Framework activities:csm-board-bot’s messaging endpoint ishttps://board.progressivesurface.com/api/teams/messages, pushed to from Microsoft’s cloud, which a private-only origin cannot receive (csm-board#41). Identity is gated at the app layer, by the tenant-wide “Require MFA” Conditional Access policy (verified 2026-06-24 to cover csm-board, agent device-code intact), and since 2026-07-28 byCSM Board — phishing-resistant MFA(CA policy78bc38a1-a0aa-4f0e-a506-d7b428bf8165, enabled,clientAppTypes: [browser], requiring WHfB / FIDO2 / x509-multifactor). Edge exposure is reduced by Front Door + WAF + origin-lock (ADR-0028 Phase 2). -
Next review: when the board is reclassified internal-only and the Teams inbound loop is retired — at that point Private Endpoint becomes the right answer and is available immediately on the current plan. Otherwise review with the next compliance sweep.
-
Posture as implemented (2026-07-28).
publicNetworkAccessstaysEnabled, but the origin no longer answers the internet.board.progressivesurface.comis a CNAME toboard-efejceb3d6acf7ay.a03.azurefd.net(Front Door Standard profilepsi-frontdoor), and the App Service carries a default-deny access restriction admitting only theAzureFrontDoor.Backendservice tag withx-azure-fdid: 9ad6dca2-bac8-4679-900b-6a0d6c5052d5. WAFpsiWafPolicyruns in Prevention mode with a 1000 req/min-per-IP rate limit. SCM is separately default-deny, allowing the CI runner NAT egress (52.162.239.139) plus PSI office ranges. Verified:csm-board.azurewebsites.netreturns 403 directly — including with a forgedx-azure-fdidheader, since the service-tag source check also applies — whileboard.progressivesurface.comserves 200 through the edge. Anonymous/openapi.json,/docsand/redocwere also closed the same day (csm-board#75); they had been publishing 40 paths / 51 operations plus every route docstring. Details in azure-resources § Azure Front Door.
Correction (2026-07-28) — do not reuse the old tier reasoning "Private Endpoints are also unavailable on the B3 Basic shared plan." That is factually wrong. Private Endpoints work on Basic-tier plans, and two apps on
asp-erp-migration-tool— the same B3 Basic plancsm-boardruns on — already use them:erp-migration-api(PEERPMigrationAPIEndpoint) andpsi-portal, bothpublicNetworkAccess=Disabled. ~19 web-app private endpoints exist acrossPS-WEBAPPS. No plan upgrade is required to lock an app on this plan down, and there is no blast radius on the shared plan. The real blockers forcsm-boardare remote reachability and the inbound Teams bot, both recorded above. The same error appears incsm-board/docs/ADR-0028and is corrected there (csm-board#77).This exception previously read
-
Network Security Groups
All NSGs are in the PS-RG-01 resource group. Most use only Azure default rules — custom rules are called out below.
Default Rules (All NSGs)
Every NSG includes these 6 Azure default rules:
| Name | Priority | Direction | Access | Source | Destination | Port |
|---|---|---|---|---|---|---|
| AllowVnetInBound | 65000 | Inbound | Allow | VirtualNetwork | VirtualNetwork | * |
| AllowAzureLoadBalancerInBound | 65001 | Inbound | Allow | AzureLoadBalancer | * | * |
| DenyAllInBound | 65500 | Inbound | Deny | * | * | * |
| AllowVnetOutBound | 65000 | Outbound | Allow | VirtualNetwork | VirtualNetwork | * |
| AllowInternetOutBound | 65001 | Outbound | Allow | * | Internet | * |
| DenyAllOutBound | 65500 | Outbound | Deny | * | * | * |
NSGs with Default Rules Only (No Custom Rules)
These NSGs have no additional custom rules beyond the Azure defaults:
| NSG | Associated VM |
|---|---|
| basicNsgPS-AZ-APPS-0-nic2 | PS-AZ-APPS-0 |
| basicNsgps-az-optix204 | PS-AZ-OPTIX (NIC) |
| PS-AZ-DC01-nsg | PS-AZ-DC01 (Domain Controller) |
| PS-AZ-LS3-nsg | PS-AZ-LS3 (App Proxy Connector) |
| PS-AZ-OPTIX-nsg | PS-AZ-OPTIX |
NSGs with Custom Rules
PS-AZ-MESHCENTRAL-nsg (Deleted 2026-02-23)
This NSG was orphaned (not attached to any NIC or subnet) and had SSH/HTTP/HTTPS open to any source IP. Deleted during security remediation.
PS-AZ-OPTIX2-nsg (Deleted 2026-02-23)
This NSG was associated with PS-AZ-OPTIX2 which was decommissioned. Had RDP open to any source IP. VM, disk, NIC, and NSG all deleted during security remediation.
PS-AZ-SFTP1-nsg
| Rule | Priority | Direction | Access | Protocol | Source | Dest Port |
|---|---|---|---|---|---|---|
| RDP | 300 | Inbound | Allow | TCP | 65.23.94.210/32 | 3389 |
| AllowAnyCustom1822Inbound | 301 | Inbound | Allow | TCP | * | 1822 |
SFTP1 follows better practice — RDP is locked to a specific IP. Port 1822 is the custom SFTP port.
Monitoring & Alerting
Application Insights
| App Insights Resource | Resource Group | Connected To |
|---|---|---|
| ps-intunedatacollection-fa-ai | PS-RG-01 | Intune data collection function |
| psredbookphotos | PS-RG-01 | (not connected to webapp) |
| psredbookphotos202411071353 | PsRedbookPhotos | psredbookphotos webapp |
| ps-shippingphotos | PS-WEBAPPS | (not connected to ps-shipphotos webapp) |
| mlworkspace19587593495 | PS-RG-01 | ML workspace |
Webapp Monitoring Status
| Webapp | Application Insights | Status |
|---|---|---|
| psi-explorer-web | psi-webapps-insights | Telemetry active |
| psi-portal | psi-webapps-insights | Telemetry active |
| erp-migration-api | psi-webapps-insights | Telemetry active |
| ps-project-explorer | psi-webapps-insights | Telemetry active |
| ps-progressive-view | psi-webapps-insights | Telemetry active |
| redbook-web | psi-webapps-insights | Telemetry active |
| ps-redbook-dashboard | psi-webapps-insights | Telemetry active |
| prgjsmes-prod | psi-webapps-insights | Telemetry active |
| psredbookphotos | psredbookphotos202411071353 | Telemetry active |
| ps-shipphotos | ps-shippingphotos | Telemetry active |
| psi-datasync | ❌ | Not configured — pending |
| ps-argo-analytics | ❌ | Not configured — no KV refs; MI not required |
| csm-board | ❌ | Not configured — pending |
| ps-winget-source | ❌ | Not configured — pending |
| psi-tap-bridge | ❌ | Not configured — pending |
10 of 15 tracked webapps have Application Insights configured. The shared resource psi-webapps-insights (PS-WEBAPPS) covers 8 apps; the two photo services use their own dedicated resources. 5 apps are pending connection.
Alert Rules
| Alert | Scope | Severity | Frequency |
|---|---|---|---|
| Available Memory Bytes | PS-AZ-SFTP1 | 3 | 5 min |
| Percentage CPU | PS-AZ-SFTP1 | 3 | 5 min |
| OS Disk IOPS Consumed | PS-AZ-SFTP1 | 3 | 5 min |
| Network In Total | PS-AZ-SFTP1 | 3 | 5 min |
| Data Disk IOPS Consumed | PS-AZ-SFTP1 | 3 | 5 min |
| Network Out Total | PS-AZ-SFTP1 | 3 | 5 min |
Gap: Only PS-AZ-SFTP1 has metric alerts. No alerts exist for any webapps, the SQL server, or other VMs. No activity log alerts are configured.
Key Vault Inventory
| Key Vault | Resource Group | Location | Purpose | Status |
|---|---|---|---|---|
| ps-certificates-kv | PS-RG-01 | North Central US | Primary — wildcard SSL cert + app secrets (access policies) | Active |
| psi-finance-kv | PS-RG-01 | North Central US | Finance/banking credentials (RBAC, per-secret scoping) | Active (created 2026-08-05) |
| PS-RG-01 | North Central US | Azure Migrate | Deleted 2026-02-23 | |
| PS-RG-01 | West US 2 | Azure Migrate (GR migration) | Deleted 2026-02-23 | |
| ps-intunekv | PS-RG-01 | North Central US | Intune integration (RBAC-locked) | Needs review |
| akv-34-4v72iquhmxtiu3 | PS-RG-01 | North Central US | Auto-generated (access policy locked) | Needs review |
| akv-19-y7jefihe7cin65 | SERVER-NCUS-CSP | North Central US | CSP-managed (server infrastructure) | Needs review |
| mlworkspace10747543858 | PS-RG-01 | North Central US | ML workspace (access policy locked) | Needs review |
ps-certificates-kvis the primary vault for app deployments.psi-finance-kvexists because access policies cannot be scoped below the vault — adding a reader there would grant read on every app secret, which is the wrong blast radius for banking credentials. Both vaults now shipAuditEventto Log Analytics (enabled 2026-08-07). Two Azure Migrate vaults were deleted (migration complete). The remaining 4 require elevated access to investigate. Which vault to use and how to grant access: key-vaults.
Remediation History
| Date | Action | Details |
|---|---|---|
| 2026-06-25 | Wiki doc-currency audit — live-vs-doc reconciliation | Reconciled deploy-to-azure / azure-security / azure-resources against live Azure. Found: psi-datasync + psi-zebra-tracking apps still publicNetworkAccess=Enabled (docs said Disabled); psi-datasync PE exists (10.160.0.19) but public access never flipped; duplicate prgjsmes-prod-pe (10.160.140.13 PS-WEBAPPS vs 10.160.140.11 PS-RG-01); new in-development app ps-buildvsbuy (+PE .5). Resolved all TBD PE IPs in azure-resources; created dns-standards canonical page. No live Azure changes made — remediation tracked in psi-azure-admin#1. |
| 2026-06-24 | Full compliance audit + new resource cataloging + TLS AutoFix | Audit: 0 Critical, 17 High, 53 Medium, 15 Low across 17 web apps, 4 function apps, 2 SQL servers (11 DBs), 6 KVs, 1 ACR, 1 Service Bus, 5 Cognitive Services, 20 Storage. New resources found: ps-dispatch (.NET 8 App Service, PS-WEBAPPS) and alerts SQL DB (GP_S Gen5, procserv-proddata) — both absent from June 1 audit. AutoFix: TLS 1.2 enforced on 4 remaining TLS 1.0 storage accounts (stgpsavddev06c387541, stgpsavdpool095e48998, stgpsazapps0a4b13855, stgpsazsrvc0b9175975). Manual validation confirmed 67/67 resources match raw az CLI pull — zero drift. |
| 2026-06-02 | ps-progressive-view MI + stray artifact cleanup; ps-winget-source stale connection string fix | ps-winget-source: Removed stale App Service connection string (WinGetDb/SQLAzure, psdbadmin/empty password) that was overriding the KV ref app setting — root cause of Login failed for user 'psdbadmin' crash. HTTP 200 restored. ps-progressive-view: Enabled system-assigned MI (93657eb7), granted get Access Policy on ps-certificates-kv, deleted stray value app setting (artifact from prior wrong JSON array format). MICROSOFT_PROVIDER_AUTHENTICATION_SECRET KV ref now resolving. HTTP 200 confirmed via PE. ps-certificates-kv: Removed stray IP rule 209.124.58.162/32 (admin machine, no longer needed). |
| 2026-06-02 | KV DC DNS zone + full privatelink DNS sync audit | Created privatelink.vaultcore.azure.net primary zone on PS-AZ-DC01 (Forest-replicated), A record ps-certificates-kv → 10.160.140.23. Removed WEBSITE_DNS_SERVER=168.63.129.16 workaround — KV refs resolve via DC. Full DC/Azure sync audit: added ps-progressive-view/.scm (10.160.140.15) and psi-zebra-tracking-sql (10.160.140.21) to DC; added ps-dispatch/.scm (10.160.140.22) and shippingappfunctions/.scm (10.160.0.16) to Azure privatelink.azurewebsites.net zone. All 5 privatelink zones now in sync. |
| 2026-06-01 | ps-winget-source KV PE + Access Policy; full incident remediation | Created privatelink.vaultcore.azure.net Private DNS zone (PS-RG-01), linked to PS-VNMAIN, PE ps-certificates-kv-pe at 10.160.140.23 (PS-ProdData). Added Access Policy (get secrets) for ps-winget-source MI (ebe7de0c). Set WEBSITE_DNS_SERVER=168.63.129.16 to bypass PS-AZ-DC01 for private DNS zone resolution. Both KV refs now Resolved. See audits/2026-06-01/incident-kv-winget-remediation.md. |
| 2026-06-01 | Secrets migration, SQL PE, and full SQL size/cost assessment | Migrated 5 plain-text secrets to ps-certificates-kv: ps-shipphotos (AZURE_STORAGE_ACCOUNT_KEY), ps-progressive-view (MICROSOFT_PROVIDER_AUTHENTICATION_SECRET), ps-winget-source (BlobStorage__ConnectionString, ConnectionStrings__WinGetDb), psi-notify-listener (DEPLOYMENT_STORAGE_CONNECTION_STRING). Created PE psi-zebra-tracking-sql-pe (10.160.140.21), disabled public access on psi-zebra-tracking-sql. Full SQL assessment: DataSync has 12.2 GB data on 10 DTU (underpowered), PRGJSMES autopause disabled (~$760/mo), 9/10 DBs missing LTR. Full report at audits/2026-06-01/sql-assessment.md. |
| 2026-06-01 | Full subscription extended audit + 10 auto-remediations | Extended audit script to cover SQL, KV, ACR, Service Bus, Cognitive Services, Storage. Auto-fixed: purge protection + network Deny enabled on 5 KVs (ps-intunekv, akv-34, akv-19, mlworkspace10747543858, ps-certificates-kv); TLS 1.2 enforced on 5 storage accounts (psiwingetpkgs, stgpsavddev06c387541, stgpsavdpool095e48998, stgpsazapps0a4b13855, stgpsazsrvc0b9175975). All apps smoke-tested; HTTP 200 on all custom domains confirmed post-change. |
| 2026-05-27 | Extended resource enumeration — 57 additional resources cataloged | Full subscription sweep beyond App Services: discovered 2 SQL servers, 10 databases, 5 Logic Apps, 5 Cognitive Services, Service Bus, 6 KVs, ACR, Static Web App, 12 VMs, 2 NMM Automation Accounts. Auto-fixed: Service Bus TLS 1.0→1.2; Container Registry admin user disabled; psizebratrackingkv purge protection + network Deny. All new resources documented in wiki. |
| 2026-05-27 | Disabled SCM/FTP basic publishing auth on 4 newly discovered apps | psi-zebra-tracking, psi-tap-bridge, psi-notify-listener, ps-intunedatacollection — all had basic auth enabled; disabled via automated compliance scan. |
| 2026-05-27 | Fixed psi-notify-listener HTTPS-only = false | Function app was missing httpsOnly=true; corrected via az functionapp update --set httpsOnly=true. |
| 2026-05-27 | Re-migrated ps-project-explorer secrets to Key Vault | DB_PASSWORD and GRAPH_CLIENT_SECRET had regressed to plain text since April 2026 audit; re-migrated to ps-certificates-kv references. Root cause TBD (likely a workflow deploy overwriting app settings). |
| 2026-05-27 | Full subscription compliance scan — new apps onboarded | First automated scan covering all 16 web apps + 4 function apps. New apps discovered: psi-zebra-tracking, psi-datasync, psi-notify-listener, procisely-redirect. Persistent psi-azure-admin agent project created at C:\GIT\psi-azure-admin for weekly scheduled audits. |
| ---------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 2026-04-02 | Migrated sensitive app settings to Key Vault references | Applied across target production apps; standardized on ps-certificates-kv naming pattern app--setting. |
| 2026-04-02 | Disabled SCM/FTP basic publishing auth on production app targets | Set basicPublishingCredentialsPolicies/scm=false and /ftp=false for hardened apps to reduce credential attack surface. |
| 2026-04-02 | Real deploy validation runs executed | Ran one-by-one workflow_dispatch deploy validations and endpoint probes for production app set; all target runs successful after workflow fixes. |
| 2026-04-02 | Fixed ERP deploy health gate for EasyAuth-aware checks | Updated ERP deploy workflow to treat 200/401/403 as healthy/reachable instead of requiring only 200. |
| 2026-04-02 | Resolved DMT HTTP 401 incident | Corrected erp-migration-api authsettingsV2 (requireAuthentication=false, AllowAnonymous, identity providers disabled) restoring dmt.progressivesurface.com to HTTP 200 on / and /api/health. |
| 2026-04-02 | Cross-site auth regression scan | Verified no DMT-like regression on other custom domains; quality.progressivesurface.com remains intentionally auth-enforced (401) under EasyAuth. |
| 2026-02-23 | Enabled HTTPS-only on ps-progressive-view | Was the only webapp allowing HTTP |
| 2026-02-23 | Disabled unused social providers | Removed Apple, Facebook, GitHub, Google, Twitter, legacyMicrosoftAccount from ps-progressive-view and ps-redbook-dashboard EasyAuth |
| 2026-02-23 | Decommissioned PS-AZ-OPTIX2 | Deleted VM, OS disk, NIC, and NSG (had RDP open to internet) |
| 2026-02-23 | Deleted orphaned MeshCentral NSG | NSG had SSH/HTTP/HTTPS open to internet, was not attached to anything |
| 2026-02-23 | Deleted Azure Migrate Key Vaults | Removed migratekv1295344273 and GRtoAzure6174kv (migration complete) |
| 2026-02-23 | Enabled Application Insights on all webapps | Created psi-webapps-insights (shared), wired ps-shipphotos to existing resource. All 10 apps now have telemetry. Note: removed ApplicationInsightsAgent__EXTENSION_VERSION=~3 from Linux/Node.js apps (causes startup issues — agent is .NET only). Kept on prgjsmes-prod (Windows/.NET). |
| 2026-02-23 | Fixed api.progressivesurface.com DNS | Replaced CNAME (→ ps-proxy.ad.ptihome.com) with A record (→ 192.9.201.217). CNAME was leaking to hostile wildcard on external ptihome.com domain (PSI no longer owns ptihome.com). |
| 2026-02-23 | Initial security audit | Full tenant audit of all apps, NSGs, monitoring, auth |
Recommendations
Completed
Restrict RDP/SSH access on MeshCentral and OPTIX2 NSGs— Deleted (orphaned NSG + decommissioned VM)Enable Application Insights on production webapps— All 10 webapps now have telemetryWire ps-shipphotos to its existing Application Insights resource— DoneDelete Azure Migrate Key Vaults— Done (migratekv1295344273, GRtoAzure6174kv)Review 4 remaining Key Vaults— Done 2026-06-01: all 5 remaining KVs (ps-intunekv,akv-34,akv-19,mlworkspace,ps-certificates-kv) now have purge protection enabled and network default=Deny+AzureServices bypassContainer Registry admin user enabled— Disabled 2026-05-27Service Bus TLS 1.0— Updated to 1.2 on 2026-05-27
Remaining — High Priority
NEW (2026-06-25) — public-access drift, tracked in psi-azure-admin#1.
psi-datasyncandpsi-zebra-trackingweb apps havepublicNetworkAccess=Enableddespite the private-endpoint policy —psi-datasync’s PE already exists (10.160.0.19) but public access was never flipped;psi-zebra-trackinghas no app PE at all. Disable public access once DC + Azure privatelink DNS records are confirmed (see dns-standards). Also remove the duplicateprgjsmes-prod-pe(10.160.140.13 in PS-WEBAPPS, redundant with 10.160.140.11 in PS-RG-01).ps-buildvsbuyis new and still in development — harden before launch, not a violation yet.
- Add alert rules for webapp HTTP 5xx errors and response time thresholds
- Convert auth exceptions (app-layer-only apps) to approved profile matrix with owner + expiry
Migrate plain-text secrets on remaining apps to Key Vault— Done 2026-06-01:ps-shipphotos(AZURE_STORAGE_ACCOUNT_KEY),ps-progressive-view(MICROSOFT_PROVIDER_AUTHENTICATION_SECRET),ps-winget-source(BlobStorage__ConnectionString, ConnectionStrings__WinGetDb) all migrated tops-certificates-kv. Exception:psi-notify-listenerDEPLOYMENT_STORAGE_CONNECTION_STRINGis a Flex Consumption platform setting resolved before KV auth — kept plain text (see CLAUDE.md exceptions)Enable managed identity on apps missing it:ps-argo-analytics,ps-progressive-view,ps-shipphotos.ps-winget-sourceMI enabled 2026-06-01.ps-progressive-viewMI enabled 2026-06-02 (KV ref now resolving).ps-argo-analyticsandps-shipphotoshave no KV refs — MI not required.- Enable App Insights on:
ps-argo-analytics,csm-board,ps-winget-source,psi-tap-bridge,psi-datasync psi-zebra-tracking-sql: Disable public access; provision private endpoint— Done 2026-06-01: PE at 10.160.140.21, public access disabled- SQL database LTR policies: 9/10 databases have no LTR — PSI_Analytics (8.4 GB) and DataSync (12.2 GB) are highest priority
- DataSync on 10 DTU with 12.2 GB data — severe underprovisioning risk; scale to Standard S2 (50 DTU) or S3 (100 DTU)
- PRGJSMES autopause disabled — billing ~$760/month continuously; re-enable autopause if workload allows (currently set to -1/never)
- Investigate ps-project-explorer secret regression —
DB_PASSWORDandGRAPH_CLIENT_SECRETreverted to plain text between April and May audits; re-migrated May 2026. Root cause: deploy workflow likely overwrites app settings. Add KV reference preservation check to deploy workflow.
Remaining — Medium Priority
- Enable HTTPS-only on 4 AVD storage accounts (
stgpsavddev06c387541,stgpsavdpool095e48998,stgpsazapps0a4b13855,stgpsazsrvc0b9175975) — TLS 1.2 enforced; HTTPS-only still off (AVD compatibility testing needed) - Disable local auth on production Cognitive Services (gpt5-9353-resource, psi-foundry-dcooper) — enforce managed identity auth
- Logic App connector audit — review authentication on 5 Logic App connectors (PaylocityInbound, entra-bc-employee-sync, egnyte-stp-sync, etc.)
- Add activity log alerts for security-sensitive operations (role assignments, NSG changes, KV access)
- Document Conditional Access policies in Entra ID
- Complete upstream secret rotation for integration credentials now that KV references are in place
- RBAC cleanup — reduce subscription Owner count; move Worksighted to PIM JIT access
Remaining — Low Priority
- Create disaster recovery runbook for SQL server and critical apps
- Review all public IPs (9 total) to ensure none are unnecessarily exposed
- Upgrade Container Registry from Basic to Standard (geo-replication, vulnerability scanning)
Related Pages
- PSI Web App Compliance Standard — Canonical implementation and compliance baseline
- deploy-to-azure — Deployment architecture, private endpoints, auth, CI/CD
- azure-resources — Resource inventory and app registrations
- azure-remediation-2026-04 — April 2026 hardening rollout, deploy validation, and incident notes
- azure-app-proxy-implementation — App Proxy implementation details
- index — Application portfolio
Last updated: 2026-07-28 (App Insights monitoring table corrected — 5 apps without AI added; audit script updated to discover all subscription resources via baseline diff)