Skip to content

Commit ccef2ea

Browse files
emmanuelknafoCopilot
andcommitted
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>
1 parent cc016c7 commit ccef2ea

33 files changed

Lines changed: 1275 additions & 626 deletions

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
.copilot-tracking/
88
assets-hidden/
99

10+
# --- Vendor packages (downloaded by scripts/fetch-ontario-design-system.ps1) ---
11+
sample-app/spa/public/vendor/
12+
1013
# --- Node / Angular ---
1114
node_modules/
1215
dist/

README.md

Lines changed: 36 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,25 @@ ms.date: 2026-04-21
99
The Justice Evidence Portal uses a three-tier architecture with Microsoft Entra ID providing identity and access control across all layers.
1010

1111
```text
12-
┌──────────────┐ ┌──────────────────┐ ┌──────────────────┐
13-
│ Angular SPA │────►│ Spring Boot API │────►│ Azure Blob │
14-
│ (MSAL Auth) │ │ (JWT Validation) │ │ Storage │
15-
│ │ │ │ │ (Evidence Files) │
16-
└──────┬───────┘ └────────┬──────────┘ └──────────────────┘
17-
│ │ ▲
18-
│ Auth Code + PKCE │ Managed Identity │
19-
▼ ▼ │
20-
┌─────────────────────────────────────────────────────────────────┐
21-
│ Microsoft Entra ID │
22-
│ App Registrations · Roles · Scopes │
23-
└─────────────────────────────────────────────────────────────────┘
12+
┌──────────────────────────────┐
13+
│ Virtual Network 10.20.0.0/16 │
14+
│ │
15+
┌──────────────┐ ┌──────────────────┐ │ ┌──────────────────────┐ │
16+
│ Angular SPA │────►│ Spring Boot API │──── MI OAuth ──►│ │ Private Endpoint │ │
17+
│ (MSAL.js, │ │ (JWT v2, │ (Storage │ │ snet-pe / dfs │ │
18+
│ Auth Code │ │ Resource │ Blob Data │ │ privatelink.dfs.<…> │ │
19+
│ + PKCE) │ │ Server) │ Contributor)│ └──────────┬───────────┘ │
20+
└──────┬───────┘ └────────┬─────────┘ └─────────────┼────────────────┘
21+
│ │ │
22+
│ Auth Code + PKCE │ Bearer JWT v2 ▼
23+
▼ ▼ ┌────────────────────────┐
24+
┌─────────────────────────────────────────────────┐ │ ADLS Gen2 (HNS) │
25+
│ Microsoft Entra ID │ │ shared keys: disabled │
26+
│ App Registrations · Roles · Scopes │ │ public access: denied │
27+
└─────────────────────────────────────────────────┘ └────────────────────────┘
2428
```
2529

26-
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.
2731

2832
## Scenario
2933

@@ -34,7 +38,7 @@ The workshop centers on a Justice Evidence Portal: a secure application for mana
3438
### Prerequisites
3539

3640
| Tool | Version | Purpose |
37-
|---|---|---|
41+
| --- | --- | --- |
3842
| Node.js | 20 LTS or later | Angular SPA build and development |
3943
| Java JDK | 17 or later | Spring Boot API compilation and runtime |
4044
| Maven | 3.9 or later (auto-installed by start script) | Java dependency management and build |
@@ -138,15 +142,17 @@ az account set --subscription <subscriptionIdOrName>
138142
What `deploy.ps1` does end-to-end:
139143

140144
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`.
142146
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).
150156

151157
When it finishes you'll see something like:
152158

@@ -165,7 +171,7 @@ Open the SPA URL, sign in with the same account you ran the script as, and you s
165171
Common flags:
166172

167173
| Flag | Default | Purpose |
168-
|---|---|---|
174+
| --- | --- | --- |
169175
| `-ResourceGroup` | `rg-evidence-workshop` | Target resource group (created if missing). |
170176
| `-Location` | `canadacentral` | Azure region. |
171177
| `-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
185191
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.
186192

187193
| Exercise | Duration | Description |
188-
|---|---|---|
194+
| --- | --- | --- |
189195
| [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`.) |
190196
| [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. |
191197
| [Exercise 3: Add Role-Protected Endpoint](workshop/guides/exercise-3-add-endpoint.md) | 20 min | Experience the RBAC cycle: 403 Forbidden, assign CaseAdmin role, re-authenticate, 201 Created. |
@@ -231,26 +237,26 @@ msal-java/
231237
## Technology Stack
232238

233239
| Layer | Technology | Version | Purpose |
234-
|---|---|---|---|
240+
| --- | --- | --- | --- |
235241
| Frontend | Angular | 19.2 | Single Page Application framework |
236242
| Frontend Auth | MSAL Angular | 5.2 | Entra ID authentication (Auth Code + PKCE) |
237243
| Backend | Spring Boot | 3.4.4 | REST API framework |
238244
| Backend Auth | Spring Security OAuth2 Resource Server | 6.2 | JWT validation with scope and role enforcement |
239-
| Storage | Azure Blob Storage | 12.33.3 SDK | Evidence file storage via Managed Identity |
245+
| Storage | Azure Data Lake Storage Gen2 | `azure-storage-file-datalake` 12.23.0 | Evidence file storage via Managed Identity over Private Endpoint |
240246
| Identity | Azure Identity | 1.18.2 SDK | DefaultAzureCredential for Managed Identity |
241247
| Monitoring | Application Insights | 3.7.8 Agent | Telemetry for SPA (JS SDK) and API (runtime-attach) |
242248
| Infrastructure | Bicep | Latest | Azure resource provisioning (App Service, Storage, monitoring) |
243249

244250
## Key Design Decisions
245251

246252
- **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.
250256

251257
## Production Hardening
252258

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).
254260

255261
## License
256262

0 commit comments

Comments
 (0)