diff --git a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/Readme.md b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/Readme.md index 78492af71..5df01875b 100644 --- a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/Readme.md +++ b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/Readme.md @@ -65,15 +65,15 @@ Use **GitHub Codespaces with the Azure / Infra / Sovereign Cloud devcontainer** ![Codespace creation options showing the Sovereign Cloud devcontainer and a 2-core machine](./img/codespaces-create.png) -4. In **Terminal > New Terminal**, sign in to Azure using the tenant and subscription shown in the Hackathon Console: +4. In **Terminal > New Terminal**, sign in to Azure with your workshop account and select the subscription shown in the Hackathon Console. You do not need to enter a tenant ID; the workshop account belongs to a single tenant. ```bash - az login --use-device-code --tenant "" + az login --use-device-code az account set --subscription "" az account show --query "{Account:user.name,Subscription:name,Tenant:tenantId}" --output table ``` - Open the device-login URL printed by Azure CLI and enter its **device code**. Sign in with your **hacker account**, using the **Temporary Access Pass (TAP)** from the Console when prompted. The TAP and device code are different; never put either in files or commands. Use a private browser window if necessary to avoid signing in with your normal work account. For bring-your-own-subscription labs, use your own Azure identity instead. + Open the device-login URL printed by Azure CLI and enter its **device code**. Sign in with your **hacker account**, using the **Temporary Access Pass (TAP)** from the Console when prompted. The TAP and device code are different; never put either in files or commands. Use a private browser window if necessary to avoid signing in with your normal work account. Confirm that the displayed account and subscription match your workshop details before continuing. For bring-your-own-subscription labs, use your own Azure identity instead. 5. Use this same Codespace for all challenges. The Explorer opens at the Sovereign Cloud folder, with the repository already cloned. Use **Bash** for Challenges 1-3 and 7; for Challenges 4-5, open a terminal and run `pwsh` to enter **PowerShell 7**. Challenge 6 is primarily portal-based. ### Terminals, breaks and saved work diff --git a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/challenges/challenge-06.md b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/challenges/challenge-06.md index f9b33db59..d7b924615 100644 --- a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/challenges/challenge-06.md +++ b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/challenges/challenge-06.md @@ -2,7 +2,7 @@ ## Goal -The goal of this challenge is to operate a sovereign hybrid cloud environment by combining Microsoft Sovereign Public Cloud and Sovereign Private Cloud components. You will work with Azure Local, simulated via Azure Arc Jumpstart LocalBox, and provision your own VM. You will use Azure Arc to manage that VM through Azure, review its security posture with Microsoft Defender for Cloud, and assess its OS updates with Azure Update Manager. You will also deploy a container application in your team's namespace on the shared AKS cluster and access it privately from your Sovereign Cloud Codespace. +Explore how Azure Arc manages VMs and Kubernetes workloads on Azure Local, simulated by LocalBox. Create and manage a VM, then review the AKS load balancer, deploy a sample application, and connect to it using Arc Proxy. ## Scenario @@ -10,26 +10,25 @@ Your organization must run workloads in a sovereign cloud while still leveraging ## Actions -Before deploying, confirm that your Challenge 1 exercise policies are back in -**DoNotEnforce** and ask the facilitator to confirm that the shared LocalBox custom -location's Azure region is permitted in your assigned resource group. This region -can differ from the resource group's location. If a policy blocks creation, follow -the [location-policy troubleshooting steps](../walkthrough/challenge-06/solution-06.md#if-validation-or-deployment-is-blocked-by-a-location-policy); -do not disable organizer-managed policies or change allowlists yourself. +Before starting, return your Challenge 1 exercise policies to **DoNotEnforce**. If a policy blocks deployment, use the [troubleshooting steps](https://github.com/microsoft/MicroHack/blob/main/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-06/solution-06.md#if-validation-or-deployment-is-blocked-by-a-location-policy) or ask the facilitator. + +### Explore and manage a VM * Explore the LocalBox hybrid infrastructure in the Azure Portal * Deploy your own VM on Azure Local using Azure Arc VM management and verify that guest management is connected * Verify Microsoft Defender for Cloud coverage and review security recommendations for the VM you provisioned * Use Azure Update Manager to assess OS updates on the VM you provisioned -* Verify your Console-provided Microsoft Entra administrator-group membership and the existing Azure Arc Enabled Kubernetes Cluster User Role with the facilitator; portal workload visibility alone does not prove group membership -* Inspect the organizer-prepared `arcnetworking` extension (`microsoft.arcnetworking`) on `localbox-aks` in `rg-localbox-shared`: confirm **Succeeded**, healthy MetalLB workloads, and the existing **`aks-pool`** with **ARP** advertisement and organizer-reserved service VIPs. Do not install or configure shared networking -* Follow [Task 5 of the walkthrough](../walkthrough/challenge-06/solution-06.md#task-5-deploy-a-container-to-the-aks-cluster-deployed-on-azure-local) in **Bash**: run `az connectedk8s proxy` with Microsoft Entra authentication and an isolated kubeconfig, derive a unique namespace from your assigned resource group, and deploy `aks-local-sample-app.yaml` only in that namespace -* Verify successful rollout and the Service's assigned IP, then keep the proxy and namespace-scoped `kubectl port-forward service/aks-container-1 8080:80` running in separate terminals. Open port **8080** through the **Private** Codespaces Ports view (or `localhost:8080` when running directly on a local workstation). Never expose the API proxy port or use service-account/admin tokens; no RDP or static routes are needed -* Clean up only your team's application resources and, when no longer needed by teammates, your exercise namespace. Leave shared infrastructure unchanged -The organizer prepares MetalLB with `resources/prepare-localbox.ps1`; the Console's LocalBox deployment hook alone is not sufficient. If extension health, the pool, or access prerequisites are missing, contact the facilitator rather than installing extensions, creating pools, or granting additional permissions. +### Review the AKS load balancer and deploy an app + +* Review the preinstalled **MetalLB load balancer**: check that it is healthy and understand how it assigns IP addresses to applications +* Deploy the sample application to your team's namespace on the shared AKS cluster and inspect its assigned service IP +* Connect using **Azure Arc Proxy** and port forwarding, then open the application privately in your browser +* Clean up your team's application resources, leaving the shared cluster and load balancer unchanged + +Follow [Task 5 of the walkthrough](https://github.com/microsoft/MicroHack/blob/main/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-06/solution-06.md#task-5-deploy-a-container-to-the-aks-cluster-deployed-on-azure-local) for the commands and checks. The infrastructure is already prepared; you do not need to install MetalLB or access the LocalBox Client VM. Ask the facilitator if access or health checks fail. -The default service VIP range is **`10.10.0.10-10.10.0.100`**, excluding nodes **`10.10.0.101-10.10.0.199`**, control-plane IP **`10.10.0.5`**, and gateway **`10.10.0.1`**. Confirm any organizer customization. Inspect the pool's **IPAddressPool** and **L2Advertisement** in `kube-system` read-only; participants do not create them. +In a real deployment, clients would reach the app through its MetalLB IP address. In this lab, Arc Proxy and port forwarding provide private access without requiring a direct route to the cluster. ## Success criteria @@ -37,11 +36,10 @@ The default service VIP range is **`10.10.0.10-10.10.0.100`**, excluding nodes * * You have deployed your own VM on Azure Local via the Azure Portal and verified that guest management is Enabled (Connected) * You have verified Defender for Servers coverage for your VM and reviewed its available recommendations, or identified that its assessment is still pending * You have completed an Azure Update Manager assessment for your VM and reviewed the results, including when no updates are pending -* You have verified the shared MetalLB extension, workloads, and reserved ARP IP pool without changing shared infrastructure -* You have deployed the sample application to your team's unique AKS namespace, verified rollout, and recorded a Service IP from the reserved pool -* You have opened the application through a Microsoft Entra Arc proxy and a private application port-forward, with the API proxy never exposed -* You understand that production clients use the MetalLB VIP through configured network routing; proxy plus port-forward access does **not** validate that load-balancer network path -* You have cleaned up only your team's application resources +* You can explain MetalLB's role and have checked that the preinstalled load balancer is healthy +* Your sample application runs in your team's namespace and has a load-balancer IP address +* You can open the application privately using Arc Proxy and port forwarding, and explain how this differs from direct load-balancer access +* You have cleaned up your team's application resources without changing shared infrastructure * You understand how Azure Arc provides a unified control plane for sovereign hybrid scenarios ## Learning resources diff --git a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/localbox/readme.md b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/localbox/readme.md index 249bde74f..342e3219a 100644 --- a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/localbox/readme.md +++ b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/localbox/readme.md @@ -104,6 +104,14 @@ Reference: [MetalLB on AKS on Azure Local](https://learn.microsoft.com/azure/aks ## Preparation execution details +If an older script stops at MetalLB discovery with `Invalid ARM collection response`, +the response may be valid: PowerShell background jobs deserialize JSON arrays as +`ArrayList`, which the earlier array-only check rejected. The corrected script +accepts both list representations while still rejecting malformed responses. +Use the corrected script and rerun with the same parameters after inspecting +resource status; do not delete the AKS cluster, storage, or networks to fix this +parsing error. Matching resources are reused. + Azure Local may append generated suffixes to its `UserStorage1` and `UserStorage2` resource names. The script resolves the actual storage-container ID and verifies its custom location; ambiguous names fail rather than selecting the first match. On Windows, the script invokes Azure CLI through its bundled Python executable rather than `az.cmd`. This preserves arguments such as `ConvergedSwitch(compute_management)` that the batch wrapper otherwise interprets as command syntax. Keep the standard Azure CLI installation layout intact. diff --git a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/prepare-localbox.ps1 b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/prepare-localbox.ps1 index 49023b5e2..15b25cd35 100644 --- a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/prepare-localbox.ps1 +++ b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/prepare-localbox.ps1 @@ -298,7 +298,9 @@ function Get-LocalBoxArmCollection { param([Parameter(Mandatory)][string]$Url) do { $page = Invoke-LocalBoxAz @('rest', '--method', 'get', '--url', $Url) - if ($null -eq $page -or -not $page.Contains('value') -or $page.value -isnot [array]) { + # Background-job deserialization turns nested JSON arrays into ArrayList. + if ($page -isnot [System.Collections.IDictionary] -or -not $page.Contains('value') -or + $page.value -isnot [System.Collections.IList]) { throw "Invalid ARM collection response for $Url; existing resources cannot be determined." } $page.value diff --git a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/tests/prepare-localbox.tests.ps1 b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/tests/prepare-localbox.tests.ps1 index c0eb85de1..e19272d60 100644 --- a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/tests/prepare-localbox.tests.ps1 +++ b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/tests/prepare-localbox.tests.ps1 @@ -1459,6 +1459,47 @@ Describe 'CLI output streams' { @{ Executable = $script:cliTestExecutable; Prefix = @('-NoProfile', '-NonInteractive', '-Command', ($script:cliTestCommand + "`n#")) } } } + It 'reads an ARM collection through the real job transport with entries' -ForEach @( + @{ Json = '{"value":[],"nextLink":null}'; Count = 0 } + @{ Json = '{"value":[{"id":"first"}]}'; Count = 1 } + @{ Json = '{"value":[{"id":"first"},{"id":"second"}]}'; Count = 2 } + ) { + $script:cliTestCommand = "[Console]::Out.WriteLine('$Json'); exit 0" + $result = @(Get-LocalBoxArmCollection 'https://management.azure.com/test') + $result.Count | Should -Be $Count + if ($Count -ge 1) { $result[0].id | Should -Be 'first' } + if ($Count -eq 2) { $result[1].id | Should -Be 'second' } + } + It 'rejects malformed ARM collections through the real job transport: ' -ForEach @( + @{ Json = '{}' } + @{ Json = '{"value":null}' } + @{ Json = '{"value":"not-a-list"}' } + @{ Json = '{"value":{"id":"not-a-list"}}' } + @{ Json = '"not-an-object"' } + ) { + $script:cliTestCommand = "[Console]::Out.WriteLine('$Json'); exit 0" + { Get-LocalBoxArmCollection 'https://management.azure.com/test' } | Should -Throw '*Invalid ARM collection response*' + } + It 'follows ARM pagination through the real job transport without losing entries' { + $script:cliTestCommand = @' +if ($args -contains 'https://management.azure.com/next') { + [Console]::Out.WriteLine('{"value":[{"id":"second"}],"nextLink":null}') +} +else { + [Console]::Out.WriteLine('{"value":[{"id":"first"}],"nextLink":"https://management.azure.com/next"}') +} +exit 0 +'@ + Mock Get-LocalBoxAzInvocation { + @{ Executable = $script:cliTestExecutable; Prefix = @('-NoProfile', '-NonInteractive', '-File', $script:collectionCliPath) } + } + $script:collectionCliPath = Join-Path $TestDrive 'collection-cli.ps1' + $script:cliTestCommand | Set-Content $script:collectionCliPath + $result = @(Get-LocalBoxArmCollection 'https://management.azure.com/first') + $result.Count | Should -Be 2 + $result[0].id | Should -Be 'first' + $result[1].id | Should -Be 'second' + } It 'parses JSON stdout when a successful native command also writes progress to stderr' { $script:cliTestCommand = { [Console]::Error.WriteLine('Progress: completing operation') diff --git a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/tests/readme.md b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/tests/readme.md index d226a5c14..e8e5cbe48 100644 --- a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/tests/readme.md +++ b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/tests/readme.md @@ -19,10 +19,13 @@ A fresh Codespace smoke test is still required to validate the devcontainer buil ## MetalLB preparation checks MetalLB preparation tests are included in `prepare-localbox.tests.ps1`. Run -`Invoke-Pester ./prepare-localbox.tests.ps1 -FullName 'LocalBox MetalLB preparation*','MetalLB health evidence*' -Output Detailed` +`Invoke-Pester ./prepare-localbox.tests.ps1 -FullName 'LocalBox MetalLB preparation*','MetalLB health evidence*','CLI output streams*' -Output Detailed` for the focused offline checks. They cover reserved VIP validation, idempotent extension/pool installation, WhatIf, discovery failures, conflicting configuration -and failure propagation without deleting shared resources. Mocked health checks +and failure propagation without deleting shared resources. CLI tests use real +background-job serialization to cover empty, single-entry, multi-entry, paginated, +and malformed ARM collection responses, including deserialized `ArrayList` values. +Mocked health checks also reject missing or mismatched address pools and L2 advertisements. The live LocalBox suite now requires the `MetalLb` manifest fields; rerun preparation for older manifests. Full checks also inspect the Kubernetes IPAddressPool and L2Advertisement. diff --git a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-01/solution-01.md b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-01/solution-01.md index b991d42cf..55b8e8d49 100644 --- a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-01/solution-01.md +++ b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-01/solution-01.md @@ -76,6 +76,14 @@ Set up the variables that will be used throughout this challenge: > [!IMPORTANT] > The Azure CLI commands in this walkthrough use **Bash** syntax, not PowerShell. Bash is the default terminal in the Sovereign Cloud Codespace; no installation is needed there. +If you have not already signed in to Azure CLI with your workshop account, run: + +```bash +az login --use-device-code +``` + +Open the displayed sign-in URL, enter the device code, and sign in with your **hacker account**, using the **Temporary Access Pass (TAP)** from the Console when prompted. No tenant ID is required for the workshop account. Skip this step if already signed in with the correct account, including in Azure Cloud Shell. + ```bash # Set common variables # Customize RESOURCE_GROUP for each participant diff --git a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-02/solution-02.md b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-02/solution-02.md index ab3433a82..bbd44b018 100644 --- a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-02/solution-02.md +++ b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-02/solution-02.md @@ -49,6 +49,14 @@ Please ensure that you successfully verified the [General prerequisites](https:/ > [!IMPORTANT] > Use a **Bash terminal in your [Sovereign Cloud Codespace](https://github.com/microsoft/MicroHack/blob/main/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/Readme.md#recommended-environment-github-codespaces)**. These commands use Bash syntax, not PowerShell. Azure Cloud Shell (Bash) or a local Bash terminal with Azure CLI is an alternative for this challenge. +If you have not already signed in to Azure CLI with your workshop account, run: + +```bash +az login --use-device-code +``` + +Open the displayed sign-in URL, enter the device code, and sign in with your **hacker account**, using the **Temporary Access Pass (TAP)** from the Console when prompted. No tenant ID is required for the workshop account. Skip this step if already signed in with the correct account, including in Azure Cloud Shell. + Set up the common variables that will be used throughout this challenge: ```bash diff --git a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-04/resources/visual-attestation-demo-v2/README.md b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-04/resources/visual-attestation-demo-v2/README.md index 9a258a562..2f69099d4 100644 --- a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-04/resources/visual-attestation-demo-v2/README.md +++ b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-04/resources/visual-attestation-demo-v2/README.md @@ -1,9 +1,8 @@ # Visual Attestation Demo v2 on Azure Container Instances A self-contained ACI port of the AKS confidential-node attestation web UI from -`aks-samples/azure-voting-app/attestation/`. This is the **v2** of the original -[`visual-attestation-demo`](../visual-attestation-demo/) - same goal, simpler -footprint, and adds a one-shot `-Compare` mode that deploys both Confidential +`aks-samples/azure-voting-app/attestation/`. This **v2** sample has a simpler +footprint than the original demo and adds a one-shot `-Compare` mode that deploys both Confidential and Standard SKUs side-by-side. It demonstrates **runtime guest attestation** of an AMD SEV-SNP TEE via diff --git a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-04/solution-04.md b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-04/solution-04.md index ba95bb8a5..22ac7f51c 100644 --- a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-04/solution-04.md +++ b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-04/solution-04.md @@ -291,9 +291,14 @@ Unblock-File ./Deploy-VisualAttestationV2.ps1 Use the same variable names as the other challenges. If these variables are already present in your PowerShell session, do not generate a new suffix. +Use the **exact resource-group name assigned in the Console**, including the +`rg-` prefix (for example, `rg-labuser-0024`). If `RESOURCE_GROUP` is already +set, check that it matches; correct it before continuing. Only the attendee ID +omits the `rg-` prefix. + ```powershell -if (-not $env:RESOURCE_GROUP) { $env:RESOURCE_GROUP = "labuser-xx" } # Your assigned group -if (-not $env:ATTENDEE_ID) { $env:ATTENDEE_ID = $env:RESOURCE_GROUP } +if (-not $env:RESOURCE_GROUP) { $env:RESOURCE_GROUP = "rg-labuser-0024" } # Replace with your exact assigned resource-group name +if (-not $env:ATTENDEE_ID) { $env:ATTENDEE_ID = $env:RESOURCE_GROUP -replace '^rg-', '' } $env:LOCATION = "northeurope" if (-not $env:HASH_SUFFIX) { diff --git a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-05/resources/azure-voting-app/README.md b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-05/resources/azure-voting-app/README.md index f7dc2c4af..c8644f853 100644 --- a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-05/resources/azure-voting-app/README.md +++ b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-05/resources/azure-voting-app/README.md @@ -5,8 +5,7 @@ SEV-SNP confidential computing node pool** (2 nodes, `Standard_DC2as_v5`) and de multi-container [Azure Voting App](https://github.com/Azure-Samples/azure-voting-app-redis) sample to it, exposed via a public LoadBalancer. -The script follows the same conventions as [`vm-samples/BuildRandomCVM.ps1`](../../vm-samples/BuildRandomCVM.ps1): -random 5-letter suffix on the basename, full resource-group tagging (owner, BuiltBy, GitRepo, +The script uses a random 5-letter suffix on the basename, full resource-group tagging (owner, BuiltBy, GitRepo, description, smoketest), CC SKU + AMD CVM vCPU quota preflight, and an optional `-smoketest` flag that auto-deletes everything once the front-end is verified. diff --git a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-05/solution-05.md b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-05/solution-05.md index ab4c67cf5..4d3d10991 100644 --- a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-05/solution-05.md +++ b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-05/solution-05.md @@ -84,9 +84,11 @@ Otherwise, navigate to `03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthr Set the **Sovereign Lab AKS Cluster** name from Console's Credentials tab. For manual delivery, use the shared lab template's `aksClusterName` output. +Use your **exact assigned resource-group name**, including the `rg-` prefix +(for example, `rg-labuser-0024`), not just your attendee ID. ```powershell -$env:RESOURCE_GROUP = "labuser-xx" +$env:RESOURCE_GROUP = "rg-labuser-0024" # Replace with your exact assigned resource-group name $env:AKS_CLUSTER = "" az aks show --resource-group $env:RESOURCE_GROUP --name $env:AKS_CLUSTER --query '{name:name,location:location,state:provisioningState}' --output table ``` diff --git a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-06/images/aks-local-app-browser.png b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-06/images/aks-local-app-browser.png new file mode 100644 index 000000000..b49a29a61 Binary files /dev/null and b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-06/images/aks-local-app-browser.png differ diff --git a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-06/images/aks-local-app-http-check.png b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-06/images/aks-local-app-http-check.png new file mode 100644 index 000000000..5bee4a629 Binary files /dev/null and b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-06/images/aks-local-app-http-check.png differ diff --git a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-06/images/aks-local-app-port-forward.png b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-06/images/aks-local-app-port-forward.png new file mode 100644 index 000000000..7055c8e98 Binary files /dev/null and b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-06/images/aks-local-app-port-forward.png differ diff --git a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-06/images/aks-local-app-service.png b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-06/images/aks-local-app-service.png new file mode 100644 index 000000000..ae43f2caf Binary files /dev/null and b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-06/images/aks-local-app-service.png differ diff --git a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-06/images/aks-local-app-workload.png b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-06/images/aks-local-app-workload.png new file mode 100644 index 000000000..fccb3c98f Binary files /dev/null and b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-06/images/aks-local-app-workload.png differ diff --git a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-06/images/aks-local-cluster-user-access.png b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-06/images/aks-local-cluster-user-access.png new file mode 100644 index 000000000..39fdc8ef4 Binary files /dev/null and b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-06/images/aks-local-cluster-user-access.png differ diff --git a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-06/images/localbox-lab-architecture.png b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-06/images/localbox-lab-architecture.png new file mode 100644 index 000000000..ded744290 Binary files /dev/null and b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-06/images/localbox-lab-architecture.png differ diff --git a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-06/solution-06.md b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-06/solution-06.md index c55b2cc66..2a82706f1 100644 --- a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-06/solution-06.md +++ b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-06/solution-06.md @@ -23,10 +23,10 @@ Use your [Sovereign Cloud Codespace](https://github.com/microsoft/MicroHack/blob - Defender for Servers enabled by the organizer on the lab subscription, using an approved plan and the required Defender for Endpoint integration - A guest network with a valid IP address, working DNS, and outbound access to the required Azure Arc, Defender, and configured Windows update-source endpoints - For Task 5, the shared `localbox-aks` cluster in `rg-localbox-shared`, with Microsoft Entra authentication, Kubernetes RBAC, and organizer-prepared MetalLB networking -- Membership in the cluster's configured Microsoft Entra administrator group, supplied through the workshop Console, and the **Azure Arc Enabled Kubernetes Cluster User Role** already assigned by the organizer's preparer for Cluster Connect. These are separate prerequisites; ask the facilitator to verify either if access fails +- Membership in the cluster's configured Microsoft Entra administrator group, supplied through the workshop Console, and the **Azure Arc Enabled Kubernetes Cluster User Role** already assigned by the organizer for Cluster Connect. These are separate prerequisites; ask the facilitator to verify either if access fails > [!IMPORTANT] -> Complete Challenge 1's [Preparing for Next Challenges](../challenge-01/solution-01.md#preparing-for-next-challenges): your exercise policy assignments, including **Allowed locations** and any bonus initiative, must be in **DoNotEnforce**. This does not override inherited or organizer-managed policies. The organizer must also confirm that the shared LocalBox **custom location's Azure region** is permitted for resources created in your participant resource group. +> Complete Challenge 1's [Preparing for Next Challenges](https://github.com/microsoft/MicroHack/blob/main/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-01/solution-01.md#preparing-for-next-challenges): your exercise policy assignments, including **Allowed locations** and any bonus initiative, must be in **DoNotEnforce**. This does not override inherited or organizer-managed policies. The organizer must also confirm that the shared LocalBox **custom location's Azure region** is permitted for resources created in your participant resource group. > [!NOTE] > LocalBox is typically deployed by the workshop facilitator due to resource requirements and deployment time. See the [LocalBox deployment and readiness guide](https://github.com/microsoft/MicroHack/blob/main/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/demo-vm-creator/README.md). For a personal subscription, an authorized owner must also enable Defender for Servers before this challenge and review the plan's charges. Students in hosted labs should not change subscription-level Defender plans. @@ -35,23 +35,9 @@ Use your [Sovereign Cloud Codespace](https://github.com/microsoft/MicroHack/blob ## Lab Environment Architecture -```text -Azure management plane - Azure Portal / Azure Resource Manager - | Defender for Cloud - | VM lifecycle management Azure Update Manager - | | - v | Guest management -LocalBox (simulated on-premises environment) | - Azure Local cluster | - Arc Resource Bridge / Custom Location | - Gallery images / Storage / Network | - | | - +--> Your VM: labuserXX-vm-01 <------------+ - Windows Server 2025 - Azure Connected Machine agent - Azure resource in your assigned resource group -``` +Azure Portal and Resource Manager manage the LocalBox Azure Local environment through Arc Resource Bridge. A participant Windows Server 2025 VM uses the Azure Connected Machine agent for guest management, Defender for Cloud, and Azure Update Manager. + +*VM management overview for Tasks 1–4. Click the diagram to view it full-size.* LocalBox runs as a nested lab environment hosted in Azure. In a production sovereign private cloud, Azure Local and the workload VMs run on-premises; Azure provides the connected management plane. Arc Resource Bridge manages VM lifecycle operations, while guest management enables services inside your VM's operating system. @@ -123,7 +109,7 @@ The creation screenshots use `labuser24-vm-01`, while the validation and managem 1. **Username**: `localadmin` 2. **Password**: Create a strong password and make a note of it -![Azure Local](./images/localbox_04.jpg) +Azure Local VM proxy configuration, administrator account, and domain join options Do not opt-in for domain join at this time, and select **Next** @@ -165,7 +151,7 @@ Do not change the resource group, select another team's custom location, or disa 1. Expand **Error details** on **Review + create**. If the deployment was submitted, open your resource group's **Deployments**, select the failed deployment, and open the failed operation's details. Also check **Activity log** if needed. 2. Record the innermost error code, rejected resource name/type and requested location, **policy assignment ID**, **policy definition ID**, and correlation ID. For an initiative, also record the policy definition reference ID if present. Share only the relevant error details with the facilitator, not deployment parameters, passwords, TAPs or tokens. 3. For `RequestDisallowedByPolicy`, open **Policy > Assignments** and locate the assignment identified by the error. Inspect its **Scope**, **Policy enforcement** and **Parameters**. Check the actual ID, not just the friendly name: an initiative or inherited assignment may enforce a second location restriction. - - **Your own Challenge 1 exercise assignment:** restore **Do not enforce** using the [Challenge 1 instructions](../challenge-01/solution-01.md#preparing-for-next-challenges), including your bonus initiative if applicable. + - **Your own Challenge 1 exercise assignment:** restore **Do not enforce** using the [Challenge 1 instructions](https://github.com/microsoft/MicroHack/blob/main/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-01/solution-01.md#preparing-for-next-challenges), including your bonus initiative if applicable. - **Organizer-managed or inherited assignment:** stop and ask the facilitator to review the required metadata region and the approved policy configuration. A resource-group assignment cannot relax a deny inherited from subscription or management-group scope. - **Different error code or no policy identifiers:** retain the exact error for the facilitator. A message mentioning a region is not by itself proof that the Challenge 1 policy caused the failure. 4. After the authorized correction has propagated, retry validation in your assigned resource group. Check for partially created resources before retrying a submitted deployment; do not delete shared LocalBox resources. @@ -325,24 +311,26 @@ This exercise stops at assessment. Leave periodic assessment unchanged; the **En ## Task 5: Deploy a container to the AKS cluster deployed on Azure Local -**Deploy only your team's application; inspect shared infrastructure without changing it.** The organizer runs `resources/prepare-localbox.ps1` to prepare the AKS cluster and MetalLB after LocalBox deployment. The Console's LocalBox deployment hook alone does not prepare MetalLB. Attendees must not install extensions, create address pools, change routes, or change shared RBAC. +The shared AKS cluster and MetalLB load balancer are already prepared for you. In this task, you will check the load balancer, deploy your team's sample application, and access it privately using Arc Proxy and port forwarding. -### Task 5.1: Verify Cluster Access and Authentication +**Inspect shared infrastructure without changing it; deploy only in your team's namespace.** If access or health checks fail, ask the facilitator rather than changing the shared configuration. + +### Task 5.1: Verify Your Cluster Access 1. In the Azure portal, open **`localbox-aks`** in **`rg-localbox-shared`**. -2. Confirm that its authentication configuration is **Microsoft Entra authentication with Kubernetes RBAC**. Have the facilitator confirm your Console-provided membership in the configured administrator group; the historical screenshot's group name is only an example. -3. Open **Kubernetes resources > Workloads** and inspect the system workloads. +2. Open **Access control (IAM) > Check access > View my access**. +3. Under **Current role assignments**, confirm that **Azure Arc Enabled Kubernetes Cluster User Role** applies to your workshop identity on this cluster. It can be assigned directly, through a group, or inherited from a parent scope. If it is missing, ask the facilitator; do not add role assignments yourself. + +View my access on localbox-aks showing Azure Arc Enabled Kubernetes Cluster User Role for a workshop participant -The following older screenshot illustrates the authentication setting at the bottom. It is **not** an instruction to create or reconfigure the shared cluster. Its cluster name, region, and group can differ from your workshop. Click any screenshot to view it full-size. +This role allows the Arc proxy connection; permissions inside Kubernetes are provided separately through your workshop's configured Microsoft Entra group. There is no need to find or change authentication settings on the cluster's Configuration page. -Historical cluster form illustrating Microsoft Entra authentication with Kubernetes RBAC +4. Open **Kubernetes resources > Workloads** to view the system workloads. Click any screenshot to view it full-size. Kubernetes workloads option in the Azure portal Example system workload readiness in the Azure portal -Successful portal workload access proves only that the current identity can perform that operation. It does **not** independently prove administrator-group membership or that CLI access is ready. - > [!IMPORTANT] > Use your workshop Microsoft Entra identity throughout. If the portal requests a bearer token or the CLI reports `Unauthorized`/`Forbidden`, stop and ask the facilitator to verify the configured group, your membership, the existing **Azure Arc Enabled Kubernetes Cluster User Role**, and Cluster Connect readiness. After membership changes, sign in again to refresh your session. Do not create a service account token, retrieve admin credentials, or grant yourself additional roles. @@ -354,9 +342,9 @@ MetalLB assigns service virtual IP addresses (VIPs) to Kubernetes Services of ty 2. Under **Kubernetes resources > Workloads**, inspect the MetalLB controller and speaker workloads. Check that controller replicas and speaker DaemonSet pods are ready, with no persistent pending or crashing pods. Extension provisioning success alone is not proof of healthy workloads. 3. Open **Settings > Networking** and inspect the existing **`aks-pool`**. Confirm successful provisioning, **ARP** advertisement, and the IP range reserved by the organizer for service VIPs. Record that range; it must exclude the AKS node allocation pool, control-plane IP, gateway, and any other allocated addresses. -The default LocalBox service VIP reservation is **`10.10.0.10-10.10.0.100`**, separate from the node allocation pool **`10.10.0.101-10.10.0.199`**, control-plane IP **`10.10.0.5`**, and gateway **`10.10.0.1`**. The preparer uses the deployment configuration's exact reservation, so confirm the actual range if the organizer customized it. The older screenshots' `10.10.0.150` address overlaps the default node range and must not be reused as a service VIP. +The default LocalBox service VIP reservation is **`10.10.0.10-10.10.0.100`**, separate from the node allocation pool **`10.10.0.101-10.10.0.199`**, control-plane IP **`10.10.0.5`**, and gateway **`10.10.0.1`**. Confirm the actual range with the facilitator if your workshop uses a different configuration. -The organizer-created ARP pool is represented in Kubernetes by an **IPAddressPool** and **L2Advertisement** in **`kube-system`**. After starting the proxy in Task 5.3, inspect these read-only resources as shown in Task 5.4; do not create or edit them. See the [official AKS enabled by Azure Arc load-balancer documentation](https://learn.microsoft.com/azure/aks/aksarc/deploy-load-balancer-cli) for background only, not participant setup instructions. Provider registration and managed-identity preparation belong to the organizer; participants need no Microsoft Graph permissions and must not run the linked installation commands. +The facilitator handles detailed networking and readiness checks before the workshop. The portal review above is sufficient for this exercise. For optional background, see the [official AKS enabled by Azure Arc load-balancer documentation](https://learn.microsoft.com/azure/aks/aksarc/deploy-load-balancer-cli); do not run its installation commands in the shared lab. If the extension, healthy workloads, or pool are missing, stop and contact the facilitator. Do not select **Install extension**, **Add**, **Delete**, or **Uninstall extension**. No participant-side MetalLB configuration is needed. @@ -411,41 +399,7 @@ Wait for the proxy to report that it is listening and complete any Microsoft Ent ### Task 5.4: Deploy in Your Team Namespace — Terminal 2 -Open a **second Bash terminal** in the same Codespace. Environment variables do not automatically carry between terminals, so source the nonsecret settings again. Every Kubernetes operation below explicitly selects the isolated kubeconfig and context. - -```bash -source "$HOME/.config/microhack/challenge06.env" -kubectl --kubeconfig "$AKS_KUBECONFIG" --context "$AKS_CONTEXT" \ - cluster-info -kubectl --kubeconfig "$AKS_KUBECONFIG" --context "$AKS_CONTEXT" \ - --namespace kube-system get ipaddresspools.metallb.io,l2advertisements.metallb.io -o yaml -``` - -Confirm that the `aks-pool` IPAddressPool contains the organizer-reserved range and that an L2Advertisement covers that pool (an advertisement without an `ipAddressPools` restriction can cover all pools). Missing resources are a readiness issue for the facilitator, not an instruction to install or configure them. - -Discover the actual MetalLB controller Deployment and speaker DaemonSet names from their running configuration rather than assuming names or labels: - -```bash -source "$HOME/.config/microhack/challenge06.env" -kubectl --kubeconfig "$AKS_KUBECONFIG" --context "$AKS_CONTEXT" \ - --namespace kube-system get deployments,daemonsets \ - -o 'custom-columns=KIND:.kind,NAME:.metadata.name,IMAGES:.spec.template.spec.containers[*].image' -read -r -p "MetalLB controller Deployment name from the list: " METALLB_DEPLOYMENT -read -r -p "MetalLB speaker DaemonSet name from the list: " METALLB_DAEMONSET -kubectl --kubeconfig "$AKS_KUBECONFIG" --context "$AKS_CONTEXT" \ - --namespace kube-system wait --for=condition=Available \ - "deployment/${METALLB_DEPLOYMENT:?Enter the listed MetalLB Deployment name}" --timeout=180s -kubectl --kubeconfig "$AKS_KUBECONFIG" --context "$AKS_CONTEXT" \ - --namespace kube-system rollout status \ - "deployment/$METALLB_DEPLOYMENT" --timeout=180s -kubectl --kubeconfig "$AKS_KUBECONFIG" --context "$AKS_CONTEXT" \ - --namespace kube-system rollout status \ - "daemonset/${METALLB_DAEMONSET:?Enter the listed MetalLB DaemonSet name}" --timeout=180s -``` - -Identify the MetalLB entries using their names and container images; ask the facilitator if uncertain. These read-only checks wait for controller availability, a completed Deployment rollout, and an available, updated speaker DaemonSet. They do not restart or modify workloads. If a workload is missing or a wait times out, stop and report the output rather than repairing shared configuration. Continue only after these checks and the pool inspection pass. - -Now create your team's namespace: +With the proxy still running in Terminal 1, open a **second Bash terminal** in the same Codespace. Load the saved settings and create your team's namespace: ```bash source "$HOME/.config/microhack/challenge06.env" @@ -453,9 +407,9 @@ kubectl --kubeconfig "$AKS_KUBECONFIG" --context "$AKS_CONTEXT" \ create namespace "$TEAM_NAMESPACE" ``` -If namespace creation reports `AlreadyExists`, proceed **only** if it is your team's namespace from an earlier attempt. Stop on authentication or authorization errors rather than switching identities or contexts. +If namespace creation reports `AlreadyExists`, proceed **only** if it is your team's namespace from an earlier attempt. If it fails for any other reason, stop and ask the facilitator; do not switch identities or contexts. -The supplied [`aks-local-sample-app.yaml`](./manifests/aks-local-sample-app.yaml) creates a ConfigMap, Deployment, and `LoadBalancer` Service named `aks-container-1` (the ConfigMap is `aks-container-1-content`). The manifest has no hard-coded namespace; `--namespace` below keeps these resources out of `default` and other teams' namespaces. +The supplied [`aks-local-sample-app.yaml`](https://github.com/microsoft/MicroHack/blob/main/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-06/manifests/aks-local-sample-app.yaml) creates a ConfigMap, Deployment, and `LoadBalancer` Service named `aks-container-1` (the ConfigMap is `aks-container-1-content`). The manifest has no hard-coded namespace; `--namespace` below keeps these resources out of `default` and other teams' namespaces. ```bash source "$HOME/.config/microhack/challenge06.env" @@ -471,11 +425,13 @@ kubectl --kubeconfig "$AKS_KUBECONFIG" --context "$AKS_CONTEXT" \ Wait for successful rollout, ready application pods, and an **EXTERNAL-IP** from the organizer-confirmed `aks-pool` range. Record the assigned IP; do not assume the first address in the pool. Press **Ctrl+C in Terminal 2** to stop only the Service watch once the address appears. If it remains `` or is outside the reserved range, report it to the facilitator; do not create or edit pools. -You can also inspect **Workloads** and **Services and ingresses** in the portal, filtering to **your team namespace**. These older screenshots show where to inspect a workload and its Service; their `default` namespace and `10.10.0.150` address are historical examples, **not values to reuse**. The workload screenshot shows a ready pod while its Deployment summary is still updating; rely on your own successful rollout and current readiness. +You can also inspect **Workloads** and **Services and ingresses** in the portal, filtering to **your team namespace**. Open the `aks-container-1` Deployment to check that its replica is available and its pod is **Running** and **1/1** ready. The screenshots show one team's example; your namespace and assigned IP may differ. -Historical application workload view; inspect your team namespace and current rollout +Sample application Deployment with one available replica and a running, ready pod in a team namespace -Historical Services view illustrating the External IP column, not the current pool address +In **Services and ingresses**, locate your team's `aks-container-1` Service. Its type should be **LoadBalancer** with an **External IP** from the reserved pool, such as `10.10.0.10` in this example. Leave the other services unchanged. + +Services view showing the team's aks-container-1 LoadBalancer Service assigned external IP 10.10.0.10 ### Task 5.5: Forward and Open the Application — Terminals 2 and 3 @@ -488,18 +444,30 @@ kubectl --kubeconfig "$AKS_KUBECONFIG" --context "$AKS_CONTEXT" \ service/aks-container-1 8080:80 --address 127.0.0.1 ``` -Wait for `Forwarding from 127.0.0.1:8080` and **leave Terminal 2 running too**. Use a **third Bash terminal** for the HTTP check, not either occupied terminal: +Wait for `Forwarding from 127.0.0.1:8080` and **leave Terminal 2 running too**. + +Terminal showing the running application, assigned service IP, and active localhost port forward with a Codespaces port 8080 notification + +Use a **third Bash terminal** for the HTTP check, not either occupied terminal: ```bash curl --fail --show-error http://127.0.0.1:8080/ ``` -In Codespaces, open the **Ports** view, forward **8080** if it was not detected, and confirm its visibility is **Private** before selecting **Open in Browser**. Sign in to GitHub if prompted. Open that private application URL, not port 47011 and not the MetalLB VIP. +The response should contain the sample application's HTML, including **Welcome to AKS container 1 on Azure Local**. + +Successful curl request to localhost port 8080 returning the sample application's HTML + +In Codespaces, open the **Ports** view, forward **8080** if it was not detected, and confirm its visibility is **Private** before selecting **Open in Browser**. Do not select **Make Public** in the notification shown above. Sign in to GitHub if prompted. Open that private application URL, not port 47011 and not the MetalLB VIP. + +You should see the sample application's welcome page: + +Sample application welcome page opened through a Codespaces forwarded port 8080 URL If running this same workflow directly on a local workstation with Bash, Azure CLI, and `kubectl`, instead open **http://localhost:8080/** on that workstation. A Codespace's `localhost` is not your workstation's `localhost`; use its private Ports URL when working in Codespaces. > [!IMPORTANT] -> Production clients reach a MetalLB VIP through configured network routing and the advertised service network. This lab's Arc proxy plus `kubectl port-forward` tunnels to an application pod through the Kubernetes API. A working page verifies application access through that tunnel; it **does not validate the load-balancer network path**, ARP reachability, or routing to the VIP. No RDP session or static-route change is needed. +> Production clients reach a MetalLB VIP through configured network routing and the advertised service network. This lab's Arc proxy plus `kubectl port-forward` tunnels to an application pod through the Kubernetes API. Although the sample page mentions the cluster load balancer, a working page here verifies application access through that tunnel; it **does not validate the load-balancer network path**, ARP reachability, or routing to the VIP. No RDP session or static-route change is needed. If the forward stops after a pod restart, confirm the rollout again and restart it in Terminal 2. If either terminal is closed, restart the proxy first and then the port-forward, sourcing `challenge06.env` in each replacement terminal.