You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat: Enhance Azure deployment scripts with VNet integration and ADLS Gen2 support
- Updated deploy.ps1 to include VNet with private endpoints for ADLS Gen2, ensuring secure access without shared keys.
- Added logic to detect deployer's public IP and Entra principal objectId for temporary access during deployment.
- Modified setup-entra-apps.ps1 to set API access token version to v2.0, resolving JWT validation issues.
- Introduced fetch-ontario-design-system.ps1 to manage Ontario Design System assets in the SPA.
- Created Bicep modules for VNet and private endpoint configurations, improving infrastructure as code practices.
- Revised documentation to reflect changes in architecture and deployment steps, emphasizing security and cost implications.
Co-authored-by: Copilot <copilot@github.com>
The Angular SPA authenticates users via MSAL with Auth Code + PKCE flow. The Spring Boot API validates JWT tokens and enforces role-based access. Evidence files in Azure Blob Storage are accessed through the API using Managed Identity, eliminating storage account keys entirely.
30
+
The Angular SPA authenticates users via MSAL with Auth Code + PKCE flow. The Spring Boot API validates JWT v2 tokens and enforces role-based access. Evidence files in Azure Data Lake Storage Gen2 are accessed through the API using Managed Identity over a Private Endpoint, eliminating storage account keys entirely — shared keys are disabled at the storage account.
27
31
28
32
## Scenario
29
33
@@ -34,7 +38,7 @@ The workshop centers on a Justice Evidence Portal: a secure application for mana
34
38
### Prerequisites
35
39
36
40
| Tool | Version | Purpose |
37
-
|---|---|---|
41
+
|---|---|---|
38
42
| Node.js | 20 LTS or later | Angular SPA build and development |
39
43
| Java JDK | 17 or later | Spring Boot API compilation and runtime |
40
44
| Maven | 3.9 or later (auto-installed by start script) | Java dependency management and build |
@@ -138,15 +142,17 @@ az account set --subscription <subscriptionIdOrName>
138
142
What `deploy.ps1` does end-to-end:
139
143
140
144
1. Verifies `az`, `node`, `mvn` (auto-installs Maven into `%LOCALAPPDATA%\Maven` if missing).
141
-
2. Calls `setup-entra-apps.ps1` to create/reuse both app registrations, expose the scope and roles, grant admin consent, and assign your user to `CaseReader` + `CaseAdmin`.
145
+
2. Calls `setup-entra-apps.ps1` to create/reuse both app registrations, expose the scope and roles, force the API token version to v2, grant admin consent, and assign your user to `CaseReader` + `CaseAdmin`.
142
146
3. Creates the resource group `rg-evidence-workshop` in `canadacentral` and a deterministic globally-unique storage account name.
143
-
4. Deploys the Bicep stack (App Service Plan, two App Services with system-assigned Managed Identity, Storage Account, Application Insights, role assignments).
144
-
5. Patches `environment.prod.ts` with the deployed SPA/API URLs and App Insights connection string.
145
-
6. Re-runs `setup-entra-apps.ps1` to add the production SPA URL as a SPA-platform redirect URI on the SPA app registration.
146
-
7. Builds the Angular SPA in production mode and the Spring Boot API as an executable JAR.
147
-
8. Deploys the SPA zip and the API JAR with `az webapp deploy`.
148
-
9. Grants your user `Storage Blob Data Contributor` on the storage account, creates the `evidence` container, and uploads the five sample PDFs (with retries to absorb RBAC propagation).
149
-
10. Smoke-tests the result: SPA URL must return `200`, API `/api/cases` must return `401` (proving JWT validation is enforced).
147
+
4. Detects your public IP and Entra principal objectId so the seed step can run over OAuth without ever using a shared key.
148
+
5. Deploys the Bicep stack: VNet (`snet-app` delegated to `Microsoft.Web/serverFarms`, `snet-pe` for endpoints), App Service Plan (S1 Linux — minimum SKU for VNet integration), two App Services with system-assigned Managed Identity and Regional VNet integration, hardened ADLS Gen2 storage (`isHnsEnabled=true`, `allowSharedKeyAccess=false`, `publicNetworkAccess=Disabled`, `networkAcls.defaultAction=Deny`), Private Endpoint on the storage `dfs` sub-resource, Private DNS Zone `privatelink.dfs.<storage suffix>`, Application Insights, and `Storage Blob Data Contributor` role assignment for the API Managed Identity.
149
+
6. Patches `environment.prod.ts` with the deployed SPA/API URLs and App Insights connection string.
150
+
7. Re-runs `setup-entra-apps.ps1` to add the production SPA URL as a SPA-platform redirect URI on the SPA app registration.
151
+
8. Builds the Angular SPA in production mode (with the Ontario Design System assets fetched into `public/vendor/`) and the Spring Boot API as an executable JAR.
152
+
9. Deploys the SPA zip and the API JAR with `az webapp deploy`.
153
+
10. Uploads the five sample PDFs over OAuth (`--auth-mode login`, no shared keys) using the temporary deployer-IP allow-list and `Storage Blob Data Contributor` RBAC.
154
+
11. Re-deploys storage with `deployerIp=''` so `publicNetworkAccess` flips back to `Disabled`. App Services keep working via the Private Endpoint.
155
+
12. Smoke-tests the result: SPA URL must return `200`, API `/api/cases` must return `401` (proving JWT validation is enforced).
150
156
151
157
When it finishes you'll see something like:
152
158
@@ -165,7 +171,7 @@ Open the SPA URL, sign in with the same account you ran the script as, and you s
165
171
Common flags:
166
172
167
173
| Flag | Default | Purpose |
168
-
|---|---|---|
174
+
|---|---|---|
169
175
|`-ResourceGroup`|`rg-evidence-workshop`| Target resource group (created if missing). |
170
176
|`-Location`|`canadacentral`| Azure region. |
171
177
|`-Environment`|`workshop`| Suffix used for App Service names (`app-evidence-spa-<env>`, `app-evidence-api-<env>`). |
@@ -185,7 +191,7 @@ az group delete --name rg-evidence-workshop --yes --no-wait
185
191
Follow these exercises in order for the full 3-hour workshop experience. Already saw the Fast-Track land everything in Azure? You can still use these guides as a tear-down of what `deploy.ps1` automated.
186
192
187
193
| Exercise | Duration | Description |
188
-
|---|---|---|
194
+
|---|---|---|
189
195
|[Exercise 1: Configure App Registrations](workshop/guides/exercise-1-app-registrations.md)| 30 min | Create Entra ID app registrations for the SPA and API, configure scopes, roles, and update the SPA environment. (Automated end-to-end by `setup-entra-apps.ps1`.) |
190
196
|[Exercise 2: Run SPA + API Locally](workshop/guides/exercise-2-run-locally.md)| 30 min | Sign in through the SPA, browse cases, download evidence, and inspect JWT tokens. |
-**Dev profile is open**: No authentication required to explore the API locally. JWT validation and `@PreAuthorize` enforcement activate in non-dev profiles.
247
-
-**Dual-mode storage**: `LocalStorageService` serves embedded PDFs in dev; `AzureBlobStorageService` uses Managed Identity in prod.
248
-
-**No storage keys or SAS tokens**: All Azure Blob access uses Managed Identity with Storage Blob Data Reader role.
249
-
-**Simplified workshop infrastructure**: B1 Basic App Service Plan (~$14/month) without Private Endpoints. Production hardening is documented separately.
253
+
-**Dual-mode storage**: `LocalStorageService` serves embedded PDFs in dev; `AzureBlobStorageService`(the production bean) uses the ADLS Gen2 DataLake SDK + Managed Identity over the Private Endpoint in prod.
254
+
-**No storage keys, no SAS tokens, no anonymous access**: Shared keys are disabled at the storage account; all data-plane access is Entra ID OAuth + RBAC (`Storage Blob Data Contributor` on the API Managed Identity). The seed step uploads sample PDFs the same way (`az storage blob upload-batch --auth-mode login`) under a temporary deployer-IP allow-list that is removed at the end of the deployment.
255
+
-**Hardened-by-default network**: `publicNetworkAccess=Disabled`, `networkAcls.defaultAction=Deny`, App Services run with `WEBSITE_VNET_ROUTE_ALL=1` so storage traffic resolves through the `privatelink.dfs.<storage-suffix>` zone to the Private Endpoint NIC IP.
250
256
251
257
## Production Hardening
252
258
253
-
The workshop deployment uses simplified infrastructure to keep costs low and focus on authentication concepts. For production deployments requiring network isolation with Private Endpoints, VNet integration, and Private DNS Zones, see the [Production Hardening Guide](docs/production-hardening.md).
259
+
The workshop already ships with a hardened-by-default network and identity posture: ADLS Gen2 with shared keys disabled and `publicNetworkAccess=Disabled`, App Service Regional VNet integration, a Private Endpoint on the storage `dfs` sub-resource, and Managed Identity + RBAC end-to-end. For the optional next-step controls (Front Door + WAF, App Service Private Endpoints, customer-managed keys, multi-region failover), see the [Production Hardening Guide](docs/production-hardening.md).
0 commit comments