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 ServiceHTTPS OnlyPublic AccessAuth MethodPrivate Endpoint
bom-explorer-webYesDisabledApp-layer authbom-explorer-web-pe
psi-portalYesDisabledApp-layer authpsi-portal-pe
erp-migration-api (DMT)YesDisabledApp-layer auth (EasyAuth disabled intentionally)ERPMigrationAPIEndpoint
ps-project-explorerYesDisabledApp-layer authps-project-explorer-pe
ps-progressive-viewYesDisabledEasyAuth + app-layerps-progressive-view-pe
redbook-webYesDisabledApp-layer authredbook-web-pe
ps-redbook-dashboardYesDisabledEasyAuth + app-layerps-redbook-dashboard-pe
prgjsmes-prodYesDisabledApp-layer authprgjsmes-prod-pe
psredbookphotosYesEnabledNone (intentional)None
ps-shipphotosYesEnabledNone (intentional)None
psi-zebra-trackingYesEnabled ⚠️App-layer authNone — app has no PE (only the SQL has psi-zebra-tracking-sql-pe)
psi-datasyncYesEnabled ⚠️App-layer authpsi-datasync-pe (10.160.0.19) — PE exists but public access was never disabled
csm-boardYesDisabledApp-layer auth (MSAL PKCE) + tenant CA “Require MFA”None — public-exception (ADR-0028)
ps-winget-sourceYesDisabledApp-layer authps-winget-source-pe
ps-argo-analyticsYesDisabledApp-layer authps-argo-analytics-pe
procisely-redirectYesDisabledApp-layer auth (redirect only)None
ps-dispatchYesDisabledApp-layer authps-dispatch-pe (SCM: 10.160.140.22)
psi-serviceYesDisabledApp-layer auth (MSAL redirect)psi-service-pe (10.160.140.24)
psi-notify-listenerYesDisabledApp-layer auth (function app)TBD
ps-sop 🚧YesDisabledApp-layer auth (MSAL redirect) — admin consent not yet grantedps-sop-pe (10.160.140.31)
ps-buildvsbuy 🚧No 🚧Enabled 🚧None yet — in active developmentps-buildvsbuy-pe (10.160.140.5)
ps-bom-coverageYesDisabledEasyAuth (Entra-only) — verified end-to-endps-bom-coverage-pe (10.160.140.33)

ps-bom-coverage is 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-coverage A → 10.160.140.33, asuid.bom-coverage TXT), 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.scm and bom-coverage.progressivesurface.com all answer 10.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 on Failed 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) holds secrets: get on ps-certificates-kv via access policy. It could not be self-served because PSI-WebApp-Deployers carries a data-plane policy on that vault but no RBAC on PS-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-kv is defaultAction: Deny, and Microsoft requires Linux apps reaching a network-restricted vault to route all outbound through the VNet. The app had vnetRouteAllEnabled: false, so the reference never resolved. Setting it true fixed it — confirmed in the vault audit log with an OK from 10.160.150.250 and no ipRule in place. Every PSI web app is Linux, so this applies fleet-wide; it is now a checkbox in the deploy-webapp skill. 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 single excludedPaths entry 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:

  1. Never write Graph permission GUIDs from memory. profile and email were first declared with plausible-but-wrong IDs (correct prefix, invented tail). Entra does not reject them — it renders them as bare GUIDs with no name or description in the portal’s API-permissions blade, and admin consent quietly grants only the valid ones. A permission row showing a GUID instead of a name is the symptom. Read the real values off the Graph SP (az ad sp show --id 00000003-0000-0000-c000-000000000000 --query "oauth2PermissionScopes[?value=='profile'].id").
  2. EasyAuth requires enableIdTokenIssuance: true. It signs in with response_type=code+id_token; without ID-token issuance the sign-in fails AADSTS700054 regardless of how complete the consent is. az ad app create does not set it, and nothing warns you.

psi-bc-part-checker’s verify.yml catches both by hitting the Entra authorize endpoint anonymously and failing on any AADSTS code — 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/appsettings without any vault permission — that is the signal to check when you cannot read the vault control plane, instead of inferring a missing grant from az keyvault show returning “not found within subscription”. It is also what shows the vnetRouteAllEnabled failure above as an unresolved reference rather than a permissions error. (2) .32 looked free in a live NIC scan from the deployer’s subscription but is psi-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 on ps-certificates-kv and failed every secret read continuously. Granted secrets get/list + certificates get on 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-sop is provisioned to the baseline but not yet serving (created 2026-08-05). Unlike ps-buildvsbuy below 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 the PSI SOP Web app 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-buildvsbuy is 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: enable httpsOnly, 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

PropertyValue
Serverprocserv-proddata
Public AccessEnabled (firewall rules for PSI office IPs)
Private EndpointPS-ProdData-SQL-Private (10.160.140.4)
TLS1.2 minimum
Entra AdminADevereaux@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-view
  • ps-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, shared asp-erp-migration-tool B3 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 is https://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 by CSM Board — phishing-resistant MFA (CA policy 78bc38a1-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).

    • Owner: adevereaux@progressivesurface.com.

    • 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). publicNetworkAccess stays Enabled, but the origin no longer answers the internet. board.progressivesurface.com is a CNAME to board-efejceb3d6acf7ay.a03.azurefd.net (Front Door Standard profile psi-frontdoor), and the App Service carries a default-deny access restriction admitting only the AzureFrontDoor.Backend service tag with x-azure-fdid: 9ad6dca2-bac8-4679-900b-6a0d6c5052d5. WAF psiWafPolicy runs 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.net returns 403 directly — including with a forged x-azure-fdid header, since the service-tag source check also applies — while board.progressivesurface.com serves 200 through the edge. Anonymous /openapi.json, /docs and /redoc were 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-toolthe same B3 Basic plan csm-board runs on — already use them: erp-migration-api (PE ERPMigrationAPIEndpoint) and psi-portal, both publicNetworkAccess=Disabled. ~19 web-app private endpoints exist across PS-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 for csm-board are remote reachability and the inbound Teams bot, both recorded above. The same error appears in csm-board/docs/ADR-0028 and 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:

NamePriorityDirectionAccessSourceDestinationPort
AllowVnetInBound65000InboundAllowVirtualNetworkVirtualNetwork*
AllowAzureLoadBalancerInBound65001InboundAllowAzureLoadBalancer**
DenyAllInBound65500InboundDeny***
AllowVnetOutBound65000OutboundAllowVirtualNetworkVirtualNetwork*
AllowInternetOutBound65001OutboundAllow*Internet*
DenyAllOutBound65500OutboundDeny***

NSGs with Default Rules Only (No Custom Rules)

These NSGs have no additional custom rules beyond the Azure defaults:

NSGAssociated VM
basicNsgPS-AZ-APPS-0-nic2PS-AZ-APPS-0
basicNsgps-az-optix204PS-AZ-OPTIX (NIC)
PS-AZ-DC01-nsgPS-AZ-DC01 (Domain Controller)
PS-AZ-LS3-nsgPS-AZ-LS3 (App Proxy Connector)
PS-AZ-OPTIX-nsgPS-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

RulePriorityDirectionAccessProtocolSourceDest Port
RDP300InboundAllowTCP65.23.94.210/323389
AllowAnyCustom1822Inbound301InboundAllowTCP*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 ResourceResource GroupConnected To
ps-intunedatacollection-fa-aiPS-RG-01Intune data collection function
psredbookphotosPS-RG-01(not connected to webapp)
psredbookphotos202411071353PsRedbookPhotospsredbookphotos webapp
ps-shippingphotosPS-WEBAPPS(not connected to ps-shipphotos webapp)
mlworkspace19587593495PS-RG-01ML workspace

Webapp Monitoring Status

WebappApplication InsightsStatus
psi-explorer-webpsi-webapps-insightsTelemetry active
psi-portalpsi-webapps-insightsTelemetry active
erp-migration-apipsi-webapps-insightsTelemetry active
ps-project-explorerpsi-webapps-insightsTelemetry active
ps-progressive-viewpsi-webapps-insightsTelemetry active
redbook-webpsi-webapps-insightsTelemetry active
ps-redbook-dashboardpsi-webapps-insightsTelemetry active
prgjsmes-prodpsi-webapps-insightsTelemetry active
psredbookphotospsredbookphotos202411071353Telemetry active
ps-shipphotosps-shippingphotosTelemetry active
psi-datasyncNot configured — pending
ps-argo-analyticsNot configured — no KV refs; MI not required
csm-boardNot configured — pending
ps-winget-sourceNot configured — pending
psi-tap-bridgeNot 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

AlertScopeSeverityFrequency
Available Memory BytesPS-AZ-SFTP135 min
Percentage CPUPS-AZ-SFTP135 min
OS Disk IOPS ConsumedPS-AZ-SFTP135 min
Network In TotalPS-AZ-SFTP135 min
Data Disk IOPS ConsumedPS-AZ-SFTP135 min
Network Out TotalPS-AZ-SFTP135 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 VaultResource GroupLocationPurposeStatus
ps-certificates-kvPS-RG-01North Central USPrimary — wildcard SSL cert + app secrets (access policies)Active
psi-finance-kvPS-RG-01North Central USFinance/banking credentials (RBAC, per-secret scoping)Active (created 2026-08-05)
migratekv1295344273PS-RG-01North Central USAzure MigrateDeleted 2026-02-23
GRtoAzure6174kvPS-RG-01West US 2Azure Migrate (GR migration)Deleted 2026-02-23
ps-intunekvPS-RG-01North Central USIntune integration (RBAC-locked)Needs review
akv-34-4v72iquhmxtiu3PS-RG-01North Central USAuto-generated (access policy locked)Needs review
akv-19-y7jefihe7cin65SERVER-NCUS-CSPNorth Central USCSP-managed (server infrastructure)Needs review
mlworkspace10747543858PS-RG-01North Central USML workspace (access policy locked)Needs review

ps-certificates-kv is the primary vault for app deployments. psi-finance-kv exists 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 ship AuditEvent to 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

DateActionDetails
2026-06-25Wiki doc-currency audit — live-vs-doc reconciliationReconciled 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-24Full compliance audit + new resource cataloging + TLS AutoFixAudit: 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-02ps-progressive-view MI + stray artifact cleanup; ps-winget-source stale connection string fixps-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-02KV DC DNS zone + full privatelink DNS sync auditCreated 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-01ps-winget-source KV PE + Access Policy; full incident remediationCreated 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-01Secrets migration, SQL PE, and full SQL size/cost assessmentMigrated 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-01Full subscription extended audit + 10 auto-remediationsExtended 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-27Extended resource enumeration — 57 additional resources catalogedFull 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-27Disabled SCM/FTP basic publishing auth on 4 newly discovered appspsi-zebra-tracking, psi-tap-bridge, psi-notify-listener, ps-intunedatacollection — all had basic auth enabled; disabled via automated compliance scan.
2026-05-27Fixed psi-notify-listener HTTPS-only = falseFunction app was missing httpsOnly=true; corrected via az functionapp update --set httpsOnly=true.
2026-05-27Re-migrated ps-project-explorer secrets to Key VaultDB_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-27Full subscription compliance scan — new apps onboardedFirst 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-02Migrated sensitive app settings to Key Vault referencesApplied across target production apps; standardized on ps-certificates-kv naming pattern app--setting.
2026-04-02Disabled SCM/FTP basic publishing auth on production app targetsSet basicPublishingCredentialsPolicies/scm=false and /ftp=false for hardened apps to reduce credential attack surface.
2026-04-02Real deploy validation runs executedRan one-by-one workflow_dispatch deploy validations and endpoint probes for production app set; all target runs successful after workflow fixes.
2026-04-02Fixed ERP deploy health gate for EasyAuth-aware checksUpdated ERP deploy workflow to treat 200/401/403 as healthy/reachable instead of requiring only 200.
2026-04-02Resolved DMT HTTP 401 incidentCorrected erp-migration-api authsettingsV2 (requireAuthentication=false, AllowAnonymous, identity providers disabled) restoring dmt.progressivesurface.com to HTTP 200 on / and /api/health.
2026-04-02Cross-site auth regression scanVerified no DMT-like regression on other custom domains; quality.progressivesurface.com remains intentionally auth-enforced (401) under EasyAuth.
2026-02-23Enabled HTTPS-only on ps-progressive-viewWas the only webapp allowing HTTP
2026-02-23Disabled unused social providersRemoved Apple, Facebook, GitHub, Google, Twitter, legacyMicrosoftAccount from ps-progressive-view and ps-redbook-dashboard EasyAuth
2026-02-23Decommissioned PS-AZ-OPTIX2Deleted VM, OS disk, NIC, and NSG (had RDP open to internet)
2026-02-23Deleted orphaned MeshCentral NSGNSG had SSH/HTTP/HTTPS open to internet, was not attached to anything
2026-02-23Deleted Azure Migrate Key VaultsRemoved migratekv1295344273 and GRtoAzure6174kv (migration complete)
2026-02-23Enabled Application Insights on all webappsCreated 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-23Fixed api.progressivesurface.com DNSReplaced 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-23Initial security auditFull 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 telemetry
  • Wire ps-shipphotos to its existing Application Insights resource — Done
  • Delete 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 bypass
  • Container Registry admin user enabled — Disabled 2026-05-27
  • Service 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-datasync and psi-zebra-tracking web apps have publicNetworkAccess=Enabled despite the private-endpoint policy — psi-datasync’s PE already exists (10.160.0.19) but public access was never flipped; psi-zebra-tracking has no app PE at all. Disable public access once DC + Azure privatelink DNS records are confirmed (see dns-standards). Also remove the duplicate prgjsmes-prod-pe (10.160.140.13 in PS-WEBAPPS, redundant with 10.160.140.11 in PS-RG-01). ps-buildvsbuy is new and still in development — harden before launch, not a violation yet.

  1. Add alert rules for webapp HTTP 5xx errors and response time thresholds
  2. Convert auth exceptions (app-layer-only apps) to approved profile matrix with owner + expiry
  3. 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 to ps-certificates-kv. Exception: psi-notify-listener DEPLOYMENT_STORAGE_CONNECTION_STRING is a Flex Consumption platform setting resolved before KV auth — kept plain text (see CLAUDE.md exceptions)
  4. Enable managed identity on apps missing it: ps-argo-analytics, ps-progressive-view, ps-shipphotos. ps-winget-source MI enabled 2026-06-01. ps-progressive-view MI enabled 2026-06-02 (KV ref now resolving). ps-argo-analytics and ps-shipphotos have no KV refs — MI not required.
  5. Enable App Insights on: ps-argo-analytics, csm-board, ps-winget-source, psi-tap-bridge, psi-datasync
  6. psi-zebra-tracking-sql: Disable public access; provision private endpoint — Done 2026-06-01: PE at 10.160.140.21, public access disabled
  7. SQL database LTR policies: 9/10 databases have no LTR — PSI_Analytics (8.4 GB) and DataSync (12.2 GB) are highest priority
  8. DataSync on 10 DTU with 12.2 GB data — severe underprovisioning risk; scale to Standard S2 (50 DTU) or S3 (100 DTU)
  9. PRGJSMES autopause disabled — billing ~$760/month continuously; re-enable autopause if workload allows (currently set to -1/never)
  10. Investigate ps-project-explorer secret regressionDB_PASSWORD and GRAPH_CLIENT_SECRET reverted 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)


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)