to confirm and continue to the next command.
# Install system dependencies
export DEBIAN_FRONTEND=noninteractive
export NEEDRESTART_MODE=a
export APT_LISTCHANGES_FRONTEND=none
sudo -E apt-get update -y
sudo -E apt-get upgrade -y
sudo -E apt-get install -y build-essential libcurl4-openssl-dev libjsoncpp-dev libboost-all-dev nlohmann-json3-dev cmake wget git jq
# Clone the attestation repository
git clone https://github.com/Azure/confidential-computing-cvm-guest-attestation.git
# Download the attestation package
wget https://packages.microsoft.com/repos/azurecore/pool/main/a/azguestattestation1/azguestattestation1_1.1.2_amd64.deb
# Install the attestation package
sudo dpkg -i azguestattestation1_1.1.2_amd64.deb
# Build the attestation client
cd confidential-computing-cvm-guest-attestation/cvm-attestation-sample-app/
cmake .
make
| | 
 |
+| 6. In the terminal, run the following commands:
**NOTE**: if you encounter any prompts to restart services, just hit \ to confirm and continue to the next command.
# Install system dependencies
export DEBIAN_FRONTEND=noninteractive
export NEEDRESTART_MODE=a
export APT_LISTCHANGES_FRONTEND=none
sudo -E apt-get update -y
sudo -E apt-get upgrade -y
sudo -E apt-get install -y build-essential libcurl4-openssl-dev libjsoncpp-dev libboost-all-dev nlohmann-json3-dev cmake wget git jq
# Clone the attestation repository
git clone https://github.com/Azure/confidential-computing-cvm-guest-attestation.git
# Download the attestation package
wget https://packages.microsoft.com/repos/azurecore/pool/main/a/azguestattestation1/azguestattestation1_1.1.2_amd64.deb
# Install the attestation package
sudo dpkg -i azguestattestation1_1.1.2_amd64.deb
# Build the attestation client
cd confidential-computing-cvm-guest-attestation/cvm-attestation-sample-app/
cmake .
make
| | 
 |
| Step | Action | Screenshot |
|------|--------|------------|
-| 8. Get your custom attestation provider URI from the Azure Portal:
- Navigate to **All Resources**
- Search for your attestation provider (e.g., `attesta1b2c3`)
- Click on the attestation provider
- Copy the **Attest URI** from the Overview page | | 
 |
-| 9. Back in the Bastion terminal, run the attestation client with your custom provider URI:
# Replace with your actual Attestation URI
sudo ./AttestationClient -a https://attesta1b2c3.neu.attest.azure.net -o token \| jq -R 'split(".") \| .[0],.[1] \| @base64d \| fromjson'
🔑 **Why Use a Custom Attestation Provider?**:
- **Custom Policies**: Define organization-specific attestation policies
- **Audit Control**: Maintain your own attestation logs and policies
- **Compliance**: Meet regulatory requirements for attestation service ownership
- **Isolation**: Separate attestation infrastructure from shared services | |  |
+| 7. Use the **Attestation Provider** URI from your lab dashboard note field (e.g., `https://..attest.azure.net`) | | |
+| 8. Back in the Bastion terminal, run the attestation client with your custom provider URI:
# Set the Attestation URI from your dashboard note field
ATTESTATION_URI=""
sudo ./AttestationClient -a $ATTESTATION_URI -o token \| jq -R 'split(".") \| .[0],.[1] \| @base64d \| fromjson'
🔑 **Why Use a Custom Attestation Provider?**:
- **Custom Policies**: Define organization-specific attestation policies
- **Audit Control**: Maintain your own attestation logs and policies
- **Compliance**: Meet regulatory requirements for attestation service ownership
- **Isolation**: Separate attestation infrastructure from shared services | |  |
---
-## Task 9: Understanding Attestation Results and Production Use
+## Task 4: Understanding Attestation Results and Production Use
💡 **Understanding what the attestation token proves and how to use it in production workloads.**
@@ -337,57 +209,31 @@ def process_sensitive_data(customer_data):
---
-## Task 10: Clean Up Resources
-
-💡 **Delete all resources to avoid ongoing charges.**
-
-### Delete Resource Group
-
-1. Navigate to **Resource groups**
-2. Click on `rg-cc-attendee01`
-3. Click **Delete resource group**
-4. Type the resource group name to confirm: `rg-cc-attendee01`
-5. Click **Delete**
-6. Wait for deletion to complete (5-10 minutes)
-
-> **💡 Tip**: Deleting the resource group will remove all resources created in this workshop
-
-⚠️ **Warning**: This command will permanently delete all resources in the resource group including the VM, Key Vault, Bastion, and all associated resources.
-
----
-
## Troubleshooting
### Cannot Connect via Bastion
-- **Check**: Bastion deployment is complete (Status: Succeeded)
-- **Check**: VM is running (Status: Running)
-- **Check**: You have Key Vault Secrets Officer role on the Key Vault
-- **Wait**: 2-3 minutes after Bastion deployment before connecting
-
-### VM Creation Failed
-
-- **Check**: Quota availability for DC-series VMs in North Europe
-- **Try**: Different region (e.g., West Europe, UK South)
-- **Try**: Smaller VM size (Standard_DC2es_v5)
+- **Check**: VM is running (Status: Running) in the Azure Portal
+- **Check**: You are using the correct username and password from your dashboard
+- **Wait**: 2-3 minutes after the lab starts before connecting
### Attestation Client Build Fails
- **Check**: All dependencies were installed successfully
- **Try**: Re-run the apt-get commands
-- **Check**: VM has internet connectivity
+- **Check**: VM has internet connectivity (`curl -I https://packages.microsoft.com`)
-### Key Vault Access Denied
+### Attestation Client Fails to Connect to MAA
-- **Check**: RBAC role assignment completed successfully
-- **Wait**: 2-3 minutes for permissions to propagate
-- **Verify**: You're logged in with the correct account
+- **Check**: The Attestation URI is correct (copy from dashboard note field, not the provider name)
+- **Check**: The URI includes the `https://` scheme (e.g., `https://..attest.azure.net`)
+- **Verify**: The VM has outbound internet access
---
## Key Takeaways
-In this challenge, you successfully implemented and validated Azure Confidential Computing with guest attestation using the Azure Portal. Here are the key concepts and best practices:
+In this challenge, you validated Azure Confidential Computing with guest attestation on a pre-provisioned Confidential VM using the Azure Portal. Here are the key concepts and best practices:
### Confidential Computing Fundamentals
@@ -407,11 +253,11 @@ In this challenge, you successfully implemented and validated Azure Confidential
### Security Architecture
-✅ **Defense in Depth** - Combining multiple security layers (no public IPs, Azure Bastion, Key Vault, hardware encryption, attestation)
+✅ **Defense in Depth** - Combining multiple security layers (no public IPs, Azure Bastion, hardware encryption, attestation)
-✅ **Secure Key Management** - SSH keys stored exclusively in Azure Key Vault with RBAC controls, never persisted locally
+✅ **Custom Attestation Provider** - A dedicated MAA provider enables organization-specific policies, audit logs, and compliance requirements separate from shared services
-✅ **Network Isolation** - VMs without public IPs, accessed only through Azure Bastion with Azure AD authentication
+✅ **Network Isolation** - Confidential VM has no public IP; all access routes through Azure Bastion with lab-scoped credentials
### Production Best Practices
@@ -469,19 +315,12 @@ In this challenge, you successfully implemented and validated Azure Confidential
- [Microsoft Azure Attestation](https://learn.microsoft.com/azure/attestation/overview)
- [AMD SEV-SNP Technology](https://www.amd.com/en/developer/sev.html)
- [Azure Bastion Documentation](https://learn.microsoft.com/azure/bastion/bastion-overview)
-- [Azure Key Vault Best Practices](https://learn.microsoft.com/azure/key-vault/general/best-practices)
---
## Technical Notes
-1. **Resource Naming**: Ensure globally unique names for Key Vault and Attestation Provider
-2. **SSH Keys**: Generated keys are securely stored in Key Vault and never exposed locally
-3. **Security**: No public IPs are used; all access is through Azure Bastion
-4. **Pricing**: VMs use standard pricing; remember to delete resources after the workshop
-5. **Bastion**: Basic SKU is sufficient for this workshop
-6. **Regions**: North Europe has good availability for DC-series VMs
-7. **Attestation**: The attestation token provides cryptographic proof of VM integrity
-8. **Deployment Time**: Azure Bastion typically takes 5-10 minutes to deploy
-9. **Auto-Created Resources**: NSGs, NICs, and OS Disks are created automatically by the portal
-10. **RBAC Propagation**: Key Vault RBAC permissions may take 2-3 minutes to propagate
+1. **Security**: No public IPs are used; all VM access is through Azure Bastion
+2. **Regions**: The platform uses sovereign-eligible regions with Confidential Compute availability and regional fallback for the Attestation Provider
+3. **Attestation**: The attestation token provides cryptographic proof of VM integrity
+4. **Credentials**: Use username/password from your lab dashboard for Bastion login
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 457a86966..8a7819c51 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
@@ -2,19 +2,18 @@
[Previous Challenge Solution](../challenge-03/solution-03.md) - **[Home](../../Readme.md)** - [Next Challenge Solution](../challenge-05/solution-05.md)
-**Estimated Duration:** 90-120 minutes
+**Estimated Duration:** 45-60 minutes
-> 💡 **Objective:** Learn how to implement and validate guest attestation on Azure Confidential VMs to ensure business logic only executes in trusted, hardware-backed confidential computing environments. You will deploy a Confidential VM, configure secure access through Azure Bastion, build and run attestation client applications, and verify cryptographic proof of VM integrity before processing sensitive workloads.
+> 💡 **Objective:** Validate guest attestation on a pre-provisioned Azure Confidential VM to confirm that workloads execute only in trusted, hardware-backed confidential computing environments. You will inspect the VM's security configuration, connect through Azure Bastion, build and run attestation client applications, and verify cryptographic proof of VM integrity using your dedicated Microsoft Azure Attestation (MAA) provider.
## Prerequisites
Please ensure that you successfully verified the [General prerequisites](../../Readme.md#general-prerequisites) before continuing with this challenge.
-- Azure subscription with Contributor permissions on your resource group
-- Azure CLI >= 2.54 or access to Azure Portal
-- **Linux/Bash environment** — Azure Cloud Shell (Bash), WSL2 on Windows, or a native Linux/macOS terminal
-- Basic understanding of Azure Virtual Machines, networking, and SSH key authentication
-- Basic understanding of confidential computing concepts
+- Access to the Azure Portal or Azure CLI
+- Lab dashboard credentials (Resource Group Name, Confidential VM, Confidential VM Admin Username, Confidential VM Admin Password, Attestation Provider, Sovereign Lab Bastion)
+- **Linux/Bash environment** — Azure Cloud Shell (Bash), WSL2 on Windows, or a native Linux/macOS terminal (for optional CLI steps)
+- Basic understanding of Azure Virtual Machines, networking, and confidential computing concepts
## Scenario Context
@@ -80,267 +79,128 @@ The sample application and deployment patterns have been adapted for this MicroH
---
-## Task 1: Understand the Deployment Architecture
+## Task 1: Locate Pre-Provisioned Resources and Verify CVM Security Settings
-💡 **Before deploying resources, it's important to understand the security architecture and components you'll be creating.**
+💡 **The platform has pre-provisioned a private Ubuntu Confidential VM, a shared Azure Bastion, and a custom Attestation Provider in your resource group. Inspect the VM's security configuration before connecting.**
-### Resources Being Deployed
+### Pre-Provisioned Resources
-The following resources will be created in the `North Europe` Azure region:
+Locate the following values on your lab dashboard before proceeding:
-- **1 Attestation Provider** - Microsoft Azure Attestation (MAA) service for verifying TEE integrity
-- **1 Virtual Network** - Isolated network with VM and Bastion subnets
-- **1 Confidential VM** - Ubuntu 22.04 with AMD SEV-SNP hardware encryption
-- **1 Azure Bastion** - Secure remote access without public IP exposure
-- **1 Azure Key Vault** - Secure storage for SSH keys
-- **Associated Resources** - NSGs, NICs, OS Disks (auto-created)
-
-### Security Architecture
-
-This setup implements a zero-trust security model:
-
-- **No Public IPs on VMs** - VMs are not directly accessible from internet
-- **Azure Bastion (Basic SKU)** - Provides secure RDP/SSH access through Azure Portal
-- **Azure Key Vault** - Stores SSH private keys securely with RBAC controls
-- **Fresh SSH Key Pair** - Generated specifically for this deployment, never stored locally
-- **Hardware-Based Encryption** - AMD SEV-SNP encrypts VM memory at the hardware level
-- **Secure Boot** - Protects boot chain integrity from firmware through kernel
-
-🔑 **Security Best Practice**: This architecture ensures VM access is authenticated through Azure AD, encrypted with TLS, and never exposes SSH directly to the internet.
-
----
-
-## Task 2: Set Up Your Environment and Deploy Infrastructure
-
-💡 **You'll create the foundational Azure resources including Resource Group, Key Vault, SSH keys, Attestation Provider, Virtual Network, Confidential VM, and Azure Bastion.**
-
-> [!IMPORTANT]
-> **Prerequisite — Challenge 1 policy adjustment:** This challenge deploys a **public IP address** for Azure Bastion and creates resources that require tags. If you completed Challenge 1, make sure you ran the **"Preparing for Next Challenges"** section at the end of that walkthrough to switch the tag-requirement and public-IP-block policies to **DoNotEnforce** mode. Otherwise the deployments below will fail.
+| Dashboard Field | Description |
+|---|---|
+| **Resource Group Name** | Your dedicated resource group containing all lab resources |
+| **Sovereign Lab Region** | Azure region where the CVM and Attestation Provider are deployed |
+| **Confidential VM** | Name of the pre-provisioned private Ubuntu 22.04 Confidential VM |
+| **Confidential VM Admin Username** | Username for Bastion login |
+| **Confidential VM Admin Password** | Password for Bastion login |
+| **Attestation Provider** | Name of the custom MAA provider (note field contains the full Attest URI) |
+| **Sovereign Lab Bastion** | Name of the shared Standard Azure Bastion for private VNet access |
> [!IMPORTANT]
-> The Azure CLI commands in this walkthrough use **bash** syntax and will not work directly in PowerShell. Use **Azure Cloud Shell (Bash)** for the best experience. If running locally on Windows, use **WSL2** (Windows Subsystem for Linux) to run a bash shell. You can install the Azure CLI inside WSL with:
->
-> ```bash
-> curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
-> ```
+> Copy the **Attestation Provider** Attest URI from the dashboard note field — you will need it in Task 4 when running the attestation client inside the VM.
----
-
-### Step 1: Configure Environment Variables
-
-Set up the variables that will be used throughout the deployment:
+### Step 1: Verify Confidential VM Security Settings via Azure CLI
```bash
-# Set common variables
-# Customize ATTENDEE_ID for each participant
-RESOURCE_GROUP="labuser-xx" # Change this for each participant (e.g., labuser-01, labuser-02, ...)
-
-ATTENDEE_ID="${RESOURCE_GROUP}"
-# Generate a short hash from ATTENDEE_ID with random component for uniqueness
-HASH_SUFFIX=$(echo -n "${ATTENDEE_ID}-${RANDOM}-${RANDOM}" | md5sum | cut -c1-8)
-
-LOCATION="northeurope"
-ADMIN_USERNAME="azureuser"
-KEYVAULT_NAME="kv-cc-${HASH_SUFFIX}" # Must be globally unique
-SSH_KEY_NAME="cc-${ATTENDEE_ID}-key"
-ATTESTATION_NAME="attest${HASH_SUFFIX}"
-```
+# Set variables from your dashboard
+RESOURCE_GROUP=""
+VM_NAME=""
-🔑 **Best Practice**: Using hash-based suffixes ensures globally unique resource names even if multiple participants use similar attendee IDs.
-
-> [!WARNING]
-> If your Azure Cloud Shell session times out (e.g. during a break), the variables defined above will be lost and must be re-defined before continuing. We recommend saving them in a local text file on your machine so you can quickly copy and paste them back into a new session.
-
-### Step 2: Create Key Vault
-
-Create a Key Vault with RBAC-based permissions:
-
-```bash
-# Create Key Vault with Azure RBAC permission model
-az keyvault create \
- --name $KEYVAULT_NAME \
+# Verify security type, secure boot, and vTPM
+az vm show \
--resource-group $RESOURCE_GROUP \
- --location $LOCATION \
- --sku standard \
- --enable-rbac-authorization true \
- --enabled-for-deployment true \
- --enabled-for-template-deployment true
-
-# Get current user's object ID
-CURRENT_USER_ID=$(az ad signed-in-user show --query id -o tsv)
-
-# Assign Key Vault Secrets Officer role to current user
-az role assignment create \
- --role "Key Vault Secrets Officer" \
- --assignee $CURRENT_USER_ID \
- --scope $(az keyvault show --name $KEYVAULT_NAME --resource-group $RESOURCE_GROUP --query id -o tsv)
-
-# Wait for RBAC permissions to propagate
-echo "Waiting for RBAC permissions to propagate..."
-sleep 30
+ --name $VM_NAME \
+ --query "{securityType: securityProfile.securityType, secureBootEnabled: securityProfile.uefiSettings.secureBootEnabled, vTpmEnabled: securityProfile.uefiSettings.vTpmEnabled}" \
+ -o table
```
-🔑 **Best Practice**: Using Azure RBAC for Key Vault instead of access policies provides more granular control and better integration with Azure AD identity governance.
-
-### Step 3: Generate and Store SSH Keys
-
-Generate SSH key pair and store securely in Key Vault:
+Expected output:
-```bash
-# Generate SSH key pair using temporary file
-SSH_TEMP_FILE="/tmp/cc_ssh_key_${ATTENDEE_ID}_${RANDOM}"
-ssh-keygen -t rsa -b 4096 -f "$SSH_TEMP_FILE" -N "" -C "microhack-cc"
-
-# Store private key in Key Vault
-az keyvault secret set \
- --vault-name $KEYVAULT_NAME \
- --name "ssh-private-key" \
- --file "$SSH_TEMP_FILE"
-
-# Store public key in Key Vault
-az keyvault secret set \
- --vault-name $KEYVAULT_NAME \
- --name "ssh-public-key" \
- --file "${SSH_TEMP_FILE}.pub"
-
-# Read public key for VM creation
-SSH_PUBLIC_KEY=$(cat "${SSH_TEMP_FILE}.pub")
-
-# Clean up temporary files
-rm -f "$SSH_TEMP_FILE" "${SSH_TEMP_FILE}.pub"
-
-echo "SSH key pair generated and stored in Key Vault: $KEYVAULT_NAME"
-echo "Public key: $SSH_PUBLIC_KEY"
```
-
-🔑 **Security Insight**: SSH keys are never stored persistently on your local machine. They exist only in Azure Key Vault, reducing the risk of key compromise.
-
-### Step 4: Create Attestation Provider
-
-Create the Microsoft Azure Attestation provider:
-
-```bash
-# Create Attestation Provider
-az attestation create \
- --name $ATTESTATION_NAME \
- --resource-group $RESOURCE_GROUP \
- --location $LOCATION
+SecurityType SecureBootEnabled VTpmEnabled
+----------------- ------------------- -----------
+ConfidentialVM True True
```
-💡 **Understanding MAA**: Microsoft Azure Attestation is a unified solution for remotely verifying the trustworthiness of platforms and integrity of binaries running inside them.
-
-### Step 5: Create Virtual Network with Subnets
-
-Create virtual network with separate subnets for VMs and Azure Bastion:
+### Step 2: Confirm No Public IP Address
```bash
-# Create Virtual Network with VM and Bastion subnets for CVM
-az network vnet create \
+# Verify the VM has no public IP (private access via Bastion only)
+az vm list-ip-addresses \
--resource-group $RESOURCE_GROUP \
- --name "vm-ubuntu-cvm-vnet" \
- --location $LOCATION \
- --address-prefix "10.10.0.0/24" \
- --subnet-name "vm-subnet" \
- --subnet-prefix "10.10.0.0/26"
-
-# Create Bastion subnet (must be named AzureBastionSubnet)
-az network vnet subnet create \
- --resource-group $RESOURCE_GROUP \
- --vnet-name "vm-ubuntu-cvm-vnet" \
- --name "AzureBastionSubnet" \
- --address-prefix "10.10.0.64/26"
+ --name $VM_NAME \
+ --query "[].virtualMachine.network.publicIpAddresses" \
+ -o table
```
-🔑 **Networking Best Practice**: Separating VM and Bastion subnets provides network segmentation and allows for different security policies.
+The output should be empty — no public IP is assigned to the Confidential VM.
-### Step 6: Deploy Confidential VM
+> **Why these settings matter:**
+> - `ConfidentialVM` security type activates AMD SEV-SNP hardware memory encryption
+> - Secure Boot prevents unauthorized firmware or bootloaders from running
+> - vTPM is required for cryptographic attestation
+> - No public IP enforces network isolation; all access routes through Azure Bastion
-Create the Confidential VM with AMD SEV-SNP hardware encryption:
+---
-```bash
-# Create Confidential VM - No public IP
-az vm create \
- --resource-group $RESOURCE_GROUP \
- --name "vm-ubuntu-cvm" \
- --location $LOCATION \
- --size "Standard_DC2as_v6" \
- --admin-username $ADMIN_USERNAME \
- --ssh-key-value "$SSH_PUBLIC_KEY" \
- --authentication-type ssh \
- --enable-vtpm true \
- --image "Canonical:0001-com-ubuntu-confidential-vm-jammy:22_04-lts-cvm:latest" \
- --security-type "ConfidentialVM" \
- --os-disk-security-encryption-type "VMGuestStateOnly" \
- --enable-secure-boot true \
- --vnet-name "vm-ubuntu-cvm-vnet" \
- --subnet "vm-subnet" \
- --public-ip-address ""
-
-# Enable system-assigned managed identity for CVM
-az vm identity assign \
- --resource-group $RESOURCE_GROUP \
- --name "vm-ubuntu-cvm"
-```
+## Task 2: Connect to the Confidential VM via Azure Bastion
-💡 **Key Configuration Details**:
+💡 **Connect to the private Confidential VM through your pre-provisioned Azure Bastion using username/password credentials from your dashboard.**
-- `--security-type "ConfidentialVM"` - Enables hardware-based confidential computing
-- `--os-disk-security-encryption-type "VMGuestStateOnly"` - Encrypts VM guest state with platform-managed keys
-- `--enable-secure-boot true` - Protects boot integrity
-- `--enable-vtpm true` - Enables virtual Trusted Platform Module for attestation
-- `--public-ip-address ""` - No public IP for enhanced security
+> [!IMPORTANT]
+> The Azure CLI commands in this walkthrough use **bash** syntax and will not work directly in PowerShell. Use **Azure Cloud Shell (Bash)** or a local bash terminal.
-### Step 7: Deploy Azure Bastion
+### Step 1: Connect via Azure Bastion (Portal)
-Create Azure Bastion for secure remote access:
+1. Navigate to the [Azure Portal](https://portal.azure.com)
+2. Open your resource group (**Resource Group Name** from your dashboard)
+3. Click on the Confidential VM (**Confidential VM** from your dashboard)
+4. Click **Connect** > **Connect via Bastion**
+5. In the connection panel:
+ - **Authentication Type**: `Password`
+ - **Username**: value of **Confidential VM Admin Username** from your dashboard
+ - **Password**: value of **Confidential VM Admin Password** from your dashboard
+6. Click **Connect** — a new browser tab opens with the VM terminal
-```bash
-# Create Public IP for Bastion
-az network public-ip create \
- --resource-group $RESOURCE_GROUP \
- --name "bastion-ip" \
- --location $LOCATION \
- --sku "Standard" \
- --allocation-method "Static"
+
-# Create Azure Bastion (Basic SKU)
+> [!NOTE]
+> All commands in Tasks 3 and 4 run **inside this VM terminal**, not in Azure Cloud Shell or your local machine.
-echo "Bastion deployment initiated (this may take 5-10 minutes)"
+### Alternative: Connect via Azure CLI Bastion Tunnel
-az network bastion create \
+```bash
+# Set variables from your dashboard
+RESOURCE_GROUP=""
+VM_NAME=""
+BASTION_NAME=""
+ADMIN_USERNAME=""
+
+# Get the VM resource ID
+VM_RESOURCE_ID=$(az vm show --resource-group $RESOURCE_GROUP --name $VM_NAME --query id -o tsv)
+
+# Open SSH tunnel via Bastion
+az network bastion ssh \
+ --name $BASTION_NAME \
--resource-group $RESOURCE_GROUP \
- --name "bastion" \
- --location $LOCATION \
- --vnet-name "vm-ubuntu-cvm-vnet" \
- --public-ip-address "bastion-ip" \
- --sku "Basic"
+ --target-resource-id $VM_RESOURCE_ID \
+ --auth-type "password" \
+ --username $ADMIN_USERNAME
```
-⏱️ **Deployment Time**: Azure Bastion typically takes 5-10 minutes to deploy. You can proceed with reviewing the next sections while waiting.
-
---
-## Task 3: Connect to Confidential VM and Install Attestation Tools
+## Task 3: Install System Dependencies and Attestation Package
-💡 **Now you'll connect to the Confidential VM through Azure Bastion and install the necessary dependencies to build and run the attestation client.**
+💡 **Install the required build tools and attestation library on the Confidential VM.**
-### Step 1: Connect via Azure Bastion
+> **📝 Note: These commands run ON the Linux VM itself inside the Bastion terminal (not on your local machine or in Azure Cloud Shell)**
-> **📝 Note: These commands run ON the Linux VM itself after connecting via Bastion (not on your local machine or in Azure Cloud Shell)**
+> [!NOTE]
+> If you encounter prompts to restart services during installation, press `` to confirm and continue.
-1. Navigate to the Azure Portal
-2. Go to **Virtual Machines** > **vm-ubuntu-cvm** (select the one in your resource group)
-3. Click **Connect** > **Connect via Bastion**
-4. **Authentication Type**: SSH Private Key from Azure Key Vault
-5. **Username**: `azureuser`
-6. **Azure Key Vault Secret**: Select your Key Vault and choose `ssh-private-key`
-7. Click **Connect**
-
-
-
-### Step 2: Install System Dependencies and Build Tools
-
-Once connected to the VM via Bastion, run the following commands:
+### Step 1: Install System Dependencies and Build Tools
```bash
# Install system dependencies
@@ -361,14 +221,14 @@ sudo -E apt-get install -y build-essential libcurl4-openssl-dev libjsoncpp-dev l
- CMake for build orchestration
- jq for JSON parsing and token inspection
-### Step 3: Clone Attestation Repository
+### Step 2: Clone Attestation Repository
```bash
# Clone the attestation repository
git clone https://github.com/Azure/confidential-computing-cvm-guest-attestation.git
```
-### Step 4: Install Azure Guest Attestation Package
+### Step 3: Download and Install Azure Guest Attestation Package
```bash
# Download the attestation package
@@ -418,28 +278,19 @@ make
### Step 2: Run Attestation and Inspect JWT Token
-To use the dedicated Attestation Provider you created in Task 2 Step 4, you need to pass its URI using the `-a` argument.
-
-#### Retrieve Your Attestation URI via Azure CLI from your Azure CLI session in Cloud Shell or your local machine
+To use the dedicated Attestation Provider from your lab dashboard, pass its Attest URI using the `-a` argument.
-```bash
-# Retrieve your attestation provider URI
-ATTESTATION_URI=$(az attestation show \
- --name $ATTESTATION_NAME \
- --resource-group $RESOURCE_GROUP \
- --query "attestUri" \
- -o tsv)
-echo $ATTESTATION_URI
-```
+#### Retrieve Your Attestation Provider URI
-Alternatively, you may also retrieve the URL from the Azure portal (navigate to your resource group and click on the Attestation provider resource):
+Use the **Attestation Provider** URI from your lab dashboard note field:
-
+> On your dashboard, the **Attestation Provider** row shows the provider name as the value and the full Attest URI in the note field (e.g., `https://..attest.azure.net`).
#### Run attestation with your custom provider (inside the Linux VM)
```bash
-ATTESTATION_URI=
+# Set the Attestation URI from your dashboard note field
+ATTESTATION_URI=""
# Run attestation with your custom provider
sudo ./AttestationClient -a $ATTESTATION_URI -o token | jq -R 'split(".") | .[0],.[1] | @base64d | fromjson'
```
@@ -534,11 +385,11 @@ In this challenge, you successfully implemented and validated Azure Confidential
### Security Architecture
-✅ **Defense in Depth** - Combining multiple security layers (no public IPs, Azure Bastion, Key Vault, hardware encryption, attestation)
+✅ **Defense in Depth** - Combining multiple security layers (no public IPs, Azure Bastion, hardware encryption, attestation)
-✅ **Secure Key Management** - SSH keys stored exclusively in Azure Key Vault with RBAC controls, never persisted locally
+✅ **Custom Attestation Provider** - A dedicated MAA provider enables organization-specific policies, audit logs, and compliance requirements separate from shared services
-✅ **Network Isolation** - VMs without public IPs, accessed only through Azure Bastion with Azure AD authentication
+✅ **Network Isolation** - Confidential VM has no public IP; all access routes through Azure Bastion with lab-scoped credentials
### Production Best Practices
@@ -596,41 +447,12 @@ In this challenge, you successfully implemented and validated Azure Confidential
- [Microsoft Azure Attestation](https://learn.microsoft.com/azure/attestation/overview)
- [AMD SEV-SNP Technology](https://www.amd.com/en/developer/sev.html)
- [Azure Bastion Documentation](https://learn.microsoft.com/azure/bastion/bastion-overview)
-- [Azure Key Vault Best Practices](https://learn.microsoft.com/azure/key-vault/general/best-practices)
---
## Technical Notes
-1. **Attendee Identification**:
- - Set `ATTENDEE_ID` to a unique value for each participant (e.g., attendee01, attendee02, etc.)
- - A hash-based suffix is automatically generated from ATTENDEE_ID plus random numbers for globally unique resources
- - The hash output is alphanumeric (hexadecimal) and safe for all Azure resource naming requirements
- - This prevents resource name collisions even if multiple students use the same ATTENDEE_ID
-
-2. **SSH Key Generation**:
- - Fresh SSH key pair is generated and stored directly in Key Vault
- - No local SSH key files are created or retained
- - Keys are only accessible through Key Vault
-
-3. **Security Compliance**:
- - VMs have **no public IPs** - not directly accessible from internet
- - Access only through Azure Bastion (Basic SKU)
- - Private SSH key stored securely in Azure Key Vault
- - Bastion uses Key Vault integration for authentication
-
-4. **Network Range**:
- - VNet (North Europe): `10.10.0.0/24`
- - VM Subnet: `10.10.0.0/26`
- - Bastion Subnet: `10.10.0.64/26`
-
-5. **VM Pricing**: VMs use standard (pay-as-you-go) pricing for microhack reliability
-
-6. **Bastion Deployment**: Takes 5-10 minutes to complete. You can proceed with other steps while it deploys
-
-7. **Auto-Created Resources**:
- - NSGs (Network Security Groups) - Created automatically by `az vm create`
- - NICs (Network Interfaces) - Created automatically and named `VMNic`
- - OS Disks - Created automatically with the VMs
-
-8. **Key Vault Access**: Ensure your Azure account has appropriate permissions to read secrets from Key Vault when using Bastion
+1. **Security**: Confidential VM has no public IP; all access routes through Azure Bastion
+2. **Credentials**: Use the username and password from your lab dashboard for Bastion login
+3. **Attestation URI**: Copy the Attest URI from the dashboard note field — it follows the pattern `https://..attest.azure.net`
+4. **Regional Fallback**: The Attestation Provider is deployed with regional fallback; the URI region code reflects the actual deployment region
diff --git a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-05/portal-guide.md b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-05/portal-guide.md
index 6c67b1e15..262415ebb 100644
--- a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-05/portal-guide.md
+++ b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-05/portal-guide.md
@@ -1,9 +1,9 @@
# Walkthrough Challenge 5 - Encryption in use with Confidential VMs/Node Pools in Azure Kubernetes Service (AKS)
# Azure Portal Guide
-**Estimated Duration:** 90-120 minutes
+**Estimated Duration:** 45-60 minutes
-> 💡 **Objective:** Learn how to deploy and validate guest attestation on Azure Confidential VMs in AKS to ensure business logic only executes in trusted, hardware-backed confidential computing environments. You will create an AKS cluster through the Azure Portal, add a Confidential VM node pool, deploy attestation workloads, and verify cryptographic proof of node integrity before processing sensitive operations.
+> 💡 **Objective:** Learn how to validate guest attestation on Azure Confidential VMs in AKS to ensure business logic only executes in trusted, hardware-backed confidential computing environments. You will navigate to the pre-provisioned AKS cluster and Confidential VM node pool through the Azure Portal, deploy attestation workloads via Cloud Shell, and verify cryptographic proof of node integrity before processing sensitive operations.
---
@@ -12,21 +12,23 @@
Please ensure that you successfully verified the [General prerequisites](../../Readme.md#general-prerequisites) before continuing with this challenge.
- Access to Azure Portal (https://portal.azure.com)
-- Azure subscription with permissions to create AKS clusters, node pools, and register preview features
+- Azure subscription with Reader/Contributor permissions on your resource group (pre-assigned)
- Azure Cloud Shell access (for kubectl commands)
- Basic understanding of Kubernetes concepts (pods, deployments, node pools)
- Familiarity with Azure Portal navigation
- Basic understanding of confidential computing concepts
-**Configuration Variables:** Throughout this challenge, replace these placeholders with your values:
-- **ATTENDEE_ID**: `labuser-xx` (customize for each participant, e.g., labuser-01, labuser-02)
-- **Region**: North Europe
-- **Resource Group**: `${ATTENDEE_ID}`
-- **AKS Cluster Name**: `aks-cvmcluster-` (Azure will help ensure uniqueness)
-- **Confidential Node Pool Name**: `cvmnodepool`
-- **VM Size for Confidential Nodes**: `Standard_DC2as_v5`
+**Pre-Provisioned Infrastructure:** The shared lab automation has already deployed an AKS cluster with a standard system node pool and an AMD SEV-SNP Confidential VM user node pool named `cvmnodepool`. The exact v5 or v6 size is selected from subscription availability and quota. This cluster is shared with Challenge 7 — do not delete it. Retrieve the following values from your lab dashboard credentials:
-💡 **Note**: Whenever you see `${ATTENDEE_ID}` or `` in commands or configuration, replace them with your actual values.
+| Dashboard Credential | Description |
+|---|---|
+| **Resource Group Name** | Your dedicated resource group |
+| **Sovereign Lab Region** | Region selected after capacity checks |
+| **Confidential VM Size** | AMD SEV-SNP size selected during shared preparation |
+| **Sovereign Lab AKS Cluster** | Name of the pre-provisioned AKS cluster |
+| **AKS Confidential Node Pool** | Name of the Confidential VM node pool (`cvmnodepool`) |
+
+💡 **Note**: Whenever you see `${ATTENDEE_ID}` or a cluster/node pool name placeholder in these steps, replace them with the values from your dashboard credentials.
## Scenario Context
@@ -40,7 +42,7 @@ Your mandate includes:
- **Zero Trust for Kubernetes**: Validate the integrity of worker nodes before deploying sensitive workloads
- **Compliance and Auditability**: Provide cryptographic evidence that workloads execute in compliant infrastructure
-In this challenge, you'll deploy Azure Confidential Computing node pools in AKS using the Azure Portal. You'll configure a confidential VM node pool with AMD SEV-SNP technology, deploy attestation-aware workloads, and verify that containers run in hardware-protected environments before processing sensitive operations.
+In this challenge, you'll inspect a pre-provisioned Azure Confidential Computing node pool in AKS through the Azure Portal. You'll validate a confidential VM node pool configured with AMD SEV-SNP technology, deploy attestation-aware workloads, and verify that containers run in hardware-protected environments before processing sensitive operations.
### Understanding Confidential VMs in AKS
@@ -73,146 +75,54 @@ This challenge is based on the **AKS with Confidential Computing Linux Sample**
- **Main Repository**: [Azure Confidential Computing CVM Guest Attestation](https://github.com/Azure/confidential-computing-cvm-guest-attestation)
- **Source Module**: [AKS Linux Sample](https://github.com/Azure/confidential-computing-cvm-guest-attestation/tree/main/aks-linux-sample)
-The AKS deployment patterns and attestation verification workflows have been adapted for this MicroHack challenge to provide a guided learning experience with Confidential VM node pools in Azure Kubernetes Service using the Azure Portal.
-
----
-
-## Task 1: Enable Preview Features and Prepare Environment
-
-💡 **Before creating AKS clusters with Confidential VM node pools, you must register the required preview features.**
-
-### Step 1: Register the AzureLinuxCVMPreview Feature
-
-1. In the Azure Portal, search for **Subscriptions** in the top search bar
-2. Select your subscription
-3. In the left menu, under **Settings**, click **Preview features**
-
- ![Screenshot placeholder: Subscription Preview Features menu]
-
-4. In the search box, type: `AzureLinuxCVMPreview`
-5. Select **AzureLinuxCVMPreview** from the results
-6. Click **Register**
-
- ![Screenshot placeholder: Register AzureLinuxCVMPreview feature]
-
-7. Wait for the registration status to show **Registered** (this may take several minutes)
-8. You can check the status by clicking **Refresh** periodically
-
- ![Screenshot placeholder: Feature registration status showing Registered]
-
----
-
----
-
-## Task 2: Create Resource Group
-
-💡 **Create the foundational resource group to organize all AKS-related resources.**
-
-1. In the Azure Portal, click **Create a resource** or search for **Resource groups**
-2. Click **+ Create**
-3. Fill in the following details:
- - **Subscription**: Select your subscription
- - **Resource group**: `${ATTENDEE_ID}` (e.g., `labuser-01`)
- - **Region**: **North Europe**
-
- ![Screenshot placeholder: Create resource group form]
-
-4. Click **Review + create**
-5. Click **Create**
-
- ![Screenshot placeholder: Resource group created successfully]
+The AKS deployment patterns and attestation verification workflows have been adapted for this MicroHack challenge to provide a guided learning experience with Confidential VM node pools in Azure Kubernetes Service, navigated through the Azure Portal.
---
----
-
-## Task 3: Create AKS Cluster with Standard Node Pool
-
-💡 **Deploy the AKS cluster with a standard system node pool. You'll add the Confidential VM node pool in the next task.**
-
-1. In the Azure Portal, search for **Kubernetes services** in the top search bar
-2. Click **+ Create** and select **Kubernetes cluster**
-
- ![Screenshot placeholder: Create Kubernetes cluster button]
-
-### Basics Tab
-
-3. Fill in the **Basics** tab:
- - **Subscription**: Select your subscription
- - **Resource group**: `${ATTENDEE_ID}` (select the one you just created)
- - **Cluster preset configuration**: **Dev/Test**
- - **Kubernetes cluster name**: `aks-cvmcluster-` (e.g., `aks-cvmcluster-abc123`)
- - **Region**: **North Europe**
- - **Availability zones**: None (or as preferred)
- - **AKS pricing tier**: **Free**
- - **Kubernetes version**: Default (latest stable version)
- - **Automatic upgrade**: **Disabled**
- - **Node security channel type**: **None**
-
- ![Screenshot placeholder: AKS Basics tab configuration]
+## Task 1: Navigate to the Pre-Provisioned AKS Cluster
-### Node pools Tab
+💡 **Locate the AKS cluster and resource group that have already been deployed for you.**
-4. Click **Next: Node pools**
-5. Keep the default node pool settings:
- - **Node pool name**: `agentpool` (default system node pool)
- - **Node size**: Default (e.g., `Standard_DS2_v2`)
- - **Scale method**: **Manual**
- - **Node count**: **1**
+1. In the Azure Portal, search for **Resource groups** in the top search bar
+2. Select the resource group named in your **Resource Group Name** dashboard credential
- ![Screenshot placeholder: AKS Node pools tab]
+ ![Screenshot placeholder: Resource group overview]
-### Networking Tab
+3. In the resource list, select the AKS cluster resource named in your **Sovereign Lab AKS Cluster** dashboard credential
-6. Click **Next: Networking**
-7. Configure networking:
- - **Network configuration**: **Azure CNI**
- - **Network policy**: **None** (or **Calico** if preferred)
- - Leave other settings as default
+ ![Screenshot placeholder: AKS cluster resource in resource group]
- ![Screenshot placeholder: AKS Networking tab]
+4. On the cluster **Overview** page, confirm the **Region** matches your **Sovereign Lab Region** dashboard credential
-### Integrations, Advanced, and Tags Tabs
+ ![Screenshot placeholder: AKS cluster overview page]
-8. Click **Next** through **Integrations**, **Advanced**, and **Tags** tabs
-9. Keep the default settings or configure as needed
+5. In the left menu, go to **Settings** > **Node pools** and confirm you see two node pools: the default system pool and the confidential pool named in your **AKS Confidential Node Pool** dashboard credential (`cvmnodepool`)
-### Review + Create
-
-10. Click **Review + create**
-11. Wait for validation to complete
-12. Click **Create**
-
- ![Screenshot placeholder: AKS cluster deployment in progress]
-
-13. Wait for the deployment to complete (this may take 5-10 minutes)
-
- ![Screenshot placeholder: AKS deployment completed]
+ ![Screenshot placeholder: Node pools list showing system pool and cvmnodepool]
---
---
-## Task 4: Connect to AKS Cluster
+## Task 2: Connect to the Cluster via Cloud Shell
-💡 **Configure kubectl to connect to your AKS cluster using Azure Cloud Shell.**
+💡 **Configure kubectl to connect to the pre-provisioned AKS cluster using Azure Cloud Shell.**
-1. Once deployment is complete, click **Go to resource**
-2. In the AKS cluster overview page, click **Connect** in the top menu
+1. In the AKS cluster overview page, click **Connect** in the top menu
![Screenshot placeholder: AKS Connect button]
-3. Select **Azure CLI** tab
-4. Click **Open Cloud Shell**
+2. Select **Azure CLI** tab
+3. Click **Open Cloud Shell**
![Screenshot placeholder: Cloud Shell connection instructions]
-5. In Cloud Shell, run the provided `az aks get-credentials` command (replace with your values):
+4. In Cloud Shell, run the provided `az aks get-credentials` command (replace with your dashboard credential values):
```bash
- az aks get-credentials --resource-group ${ATTENDEE_ID} --name aks-cvmcluster-
+ az aks get-credentials --resource-group --name --overwrite-existing
```
-6. Verify the connection:
+5. Verify the connection:
```bash
kubectl get nodes
```
@@ -223,9 +133,9 @@ The AKS deployment patterns and attestation verification workflows have been ada
---
-## Task 5: Add Confidential VM Node Pool
+## Task 3: Inspect the Confidential VM Node Pool
-💡 **Create a dedicated node pool with Confidential VM compute resources for running sensitive workloads.**
+💡 **Validate that the pre-provisioned Confidential VM node pool is properly configured, using both the Azure Portal and Cloud Shell.**
### Using Azure Portal
@@ -233,68 +143,17 @@ The AKS deployment patterns and attestation verification workflows have been ada
![Screenshot placeholder: Node pools menu]
-2. Click **+ Add node pool**
-
- ![Screenshot placeholder: Add node pool button]
-
-3. Configure the confidential node pool:
- - **Node pool name**: `cvmnodepool`
- - **Mode**: **User**
- - **OS SKU**: **Azure Linux** (or **Ubuntu**)
- - **Node size**: Click **Choose a size**
- - In the size selector, search for: `DC2as_v5`
- - Select **Standard_DC2as_v5** (Confidential VM size)
- - Click **Select**
-
- ![Screenshot placeholder: Select VM size showing DC2as_v5]
-
-4. Configure scale settings:
- - **Scale method**: **Manual**
- - **Node count**: **1**
-
- ![Screenshot placeholder: Node pool configuration form]
-
-5. Leave other settings as default
-6. Click **Add**
+2. Click on the node pool named in your **AKS Confidential Node Pool** dashboard credential (`cvmnodepool`)
- ![Screenshot placeholder: Node pool being added]
-
-7. Wait for the node pool to be created (this may take 5-10 minutes)
-8. The status will change from **Creating** to **Succeeded**
-
- ![Screenshot placeholder: Node pool list showing cvmnodepool]
-
-### Alternative: Using Cloud Shell
-
-If you prefer, you can add the node pool using Cloud Shell:
-
-```bash
-az aks nodepool add \
- --resource-group ${ATTENDEE_ID} \
- --cluster-name aks-cvmcluster- \
- --name cvmnodepool \
- --node-count 1 \
- --node-vm-size Standard_DC2as_v5
-```
-
----
-
----
-
-## Task 6: Verify Confidential Node Pool Configuration
-
-💡 **Validate that the Confidential VM node pool is properly configured and running.**
-
-### Using Azure Portal
+ ![Screenshot placeholder: Node pool details page]
-1. In your AKS cluster, go to **Settings** > **Node pools**
-2. Click on **cvmnodepool**
3. Verify the following details:
- - **Node size**: `Standard_DC2as_v5`
- - **Node count**: 1
- - **Status**: Running
+ - **Node size**: value shown in the **Confidential VM Size** dashboard credential
+ - **Mode**: `User`
+ - **OS SKU**: `Azure Linux`
+ - **Status**: `Running`
- ![Screenshot placeholder: Node pool details page]
+ ![Screenshot placeholder: Node pool details showing the selected confidential VM size]
### Using Cloud Shell
@@ -304,15 +163,15 @@ az aks nodepool add \
```bash
# Verify the VM size
az aks nodepool show \
- --resource-group ${ATTENDEE_ID} \
- --cluster-name aks-cvmcluster- \
+ --resource-group \
+ --cluster-name \
--name cvmnodepool \
--query 'vmSize'
# Verify the node image version
az aks nodepool list \
- --resource-group ${ATTENDEE_ID} \
- --cluster-name aks-cvmcluster- \
+ --resource-group \
+ --cluster-name \
--query "[?name=='cvmnodepool'].nodeImageVersion" -o tsv
```
@@ -321,21 +180,33 @@ az aks nodepool list \
kubectl get nodes -o wide
```
-You should see two nodes: one from the default node pool and one confidential VM node.
+You should see three nodes: two from the default system node pool and one confidential VM node.
![Screenshot placeholder: kubectl get nodes showing cvmnodepool node]
+4. Inspect node labels and the OS SKU that confirms confidential compute hardware:
+```bash
+# List all nodes with their labels and identify the confidential node pool members
+kubectl get nodes --show-labels | grep cvmnodepool
+
+# Confirm the OS SKU and VM size labels on the confidential node(s)
+kubectl get nodes -l kubernetes.azure.com/agentpool=cvmnodepool \
+ -o custom-columns=NAME:.metadata.name,OS_SKU:.metadata.labels.kubernetes\\.azure\\.com/os-sku,VM_SIZE:.metadata.labels.node\\.kubernetes\\.io/instance-type
+```
+
+The `OS_SKU` value of `AzureLinux` and a `VM_SIZE` matching the **Confidential VM Size** dashboard credential confirm that the node uses the selected AMD SEV-SNP confidential compute family.
+
---
---
-## Task 7: Deploy Attestation Verification Pod
+## Task 4: Deploy Attestation Verification Pod
💡 **Deploy a sample pod that retrieves attestation data to prove it's running on a confidential VM node.**
### Step 1: Upload the Attestation Pod YAML File
-1. The attestation pod YAML file is located at `walkthrough/challenge-5/resources/cvm-attestation-pod.yaml` in this repository.
+1. The attestation pod YAML file is located at `walkthrough/challenge-05/resources/cvm-attestation-pod.yaml` in this repository.
2. In Azure Cloud Shell, click the **Upload/Download files** button (📁) in the toolbar
@@ -369,7 +240,7 @@ Wait until the pod status shows **Running**.
---
-## Task 8: Retrieve and Analyze Attestation Report
+## Task 5: Retrieve and Analyze Attestation Report
💡 **Examine the attestation JWT token to verify the pod is running on genuine confidential hardware.**
@@ -478,7 +349,7 @@ The attestation report contains important security validation fields:
---
-## Task 9: Explore AKS Resources in Azure Portal
+## Task 6: Explore AKS Resources in Azure Portal
💡 **Navigate the Azure Portal to view workloads, logs, and node pool configurations.**
@@ -511,40 +382,14 @@ The attestation report contains important security validation fields:
---
----
-
-## Task 10: Clean Up Resources
-
-💡 **Delete all resources to avoid ongoing charges.**
-
-### Using Azure Portal
-
-1. Go to **Resource groups** in the Azure Portal
-2. Find and select your resource group `${ATTENDEE_ID}`
-3. Click **Delete resource group** in the top menu
-
- ![Screenshot placeholder: Delete resource group button]
-
-4. Type the resource group name to confirm deletion
-5. Click **Delete**
-
- ![Screenshot placeholder: Confirm delete resource group]
-
-6. Wait for the deletion to complete (this may take several minutes)
-
-### Using Cloud Shell
-
-Alternatively, you can delete the resource group using Cloud Shell:
-
-```bash
-az group delete --name ${ATTENDEE_ID} --yes --no-wait
-```
+> [!NOTE]
+> This AKS cluster and its node pools are shared lab infrastructure also used in Challenge 7. Do not delete the resource group, the cluster, or the node pools.
---
## Key Takeaways
-In this challenge, you successfully deployed and validated Azure Confidential Computing in AKS with guest attestation. Here are the key concepts and best practices:
+In this challenge, you successfully validated Azure Confidential Computing in AKS with guest attestation. Here are the key concepts and best practices:
### Confidential Computing in Kubernetes
@@ -566,13 +411,13 @@ In this challenge, you successfully deployed and validated Azure Confidential Co
✅ **Mixed Node Pools** - Combine standard and confidential node pools in the same cluster for cost optimization (use confidential nodes only for sensitive workloads)
-✅ **Preview Features** - Confidential VM support in AKS requires feature registration and may have regional limitations
+✅ **Idempotent Infrastructure** - The AKS cluster and Confidential VM node pool were provisioned ahead of time via idempotent Bicep, letting you focus on validation and attestation
✅ **Portal and CLI Management** - AKS supports both Azure Portal and Azure CLI for managing confidential node pools
### Production Best Practices
-✅ **Node Pool Sizing** - Start with DC-series VMs (e.g., Standard_DC2as_v5) and scale based on workload requirements
+✅ **Node Pool Sizing** - Start with an AMD SEV-SNP DCasv5/DCasv6 size and scale based on workload requirements
✅ **Workload Isolation** - Use Kubernetes namespaces, network policies, and RBAC in addition to confidential computing
@@ -630,11 +475,11 @@ In this challenge, you successfully deployed and validated Azure Confidential Co
## Troubleshooting Tips
-### Node Pool Creation Fails
+### Node Pool Not Visible or Unexpected VM Size
-- Verify that the `AzureLinuxCVMPreview` feature is registered
-- Check that the region (North Europe) supports Confidential VM sizes
-- Ensure you have sufficient quota for `Standard_DC2as_v5` VMs
+- Confirm you're viewing the correct resource group and AKS cluster from your dashboard credentials
+- Verify the node pool name matches your **AKS Confidential Node Pool** credential (`cvmnodepool`)
+- If the VM size or status looks incorrect, contact your lab administrators — this environment is pre-provisioned via automation
### Pod Stays in Pending State
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 48c2057d5..980bf6f58 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
@@ -2,9 +2,9 @@
[Previous Challenge Solution](../challenge-04/solution-04.md) - **[Home](../../Readme.md)** - [Next Challenge Solution](../challenge-06/solution-06.md)
-**Estimated Duration:** 90-120 minutes
+**Estimated Duration:** 45-60 minutes
-> 💡 **Objective:** Learn how to deploy and validate guest attestation on Azure Confidential VMs in AKS to ensure business logic only executes in trusted, hardware-backed confidential computing environments. You will create an AKS cluster using Azure CLI, add a Confidential VM node pool, deploy attestation workloads, and verify cryptographic proof of node integrity before processing sensitive operations.
+> 💡 **Objective:** Learn how to validate guest attestation on Azure Confidential VMs in AKS to ensure business logic only executes in trusted, hardware-backed confidential computing environments. Using the AKS cluster and Confidential VM node pool that have already been provisioned for this lab, you will inspect the node pool configuration, deploy attestation workloads, and verify cryptographic proof of node integrity before processing sensitive operations.
---
@@ -12,13 +12,25 @@
Please ensure that you successfully verified the [General prerequisites](../../Readme.md#general-prerequisites) before continuing with this challenge.
-- Azure subscription with Contributor permissions on your resource group
+- Azure subscription with Reader/Contributor permissions on your resource group (pre-assigned)
- Azure CLI >= 2.54 or access to Azure Portal
- **Linux/Bash environment** — Azure Cloud Shell (Bash), WSL2 on Windows, or a native Linux/macOS terminal
- `kubectl` command-line tool (can be installed via `az aks install-cli`)
- Basic understanding of Kubernetes concepts (pods, deployments, node pools)
- Basic understanding of confidential computing concepts
+## Pre-Provisioned Infrastructure
+
+The shared lab automation has already deployed an AKS cluster with a standard system node pool and an AMD SEV-SNP Confidential VM user node pool named `cvmnodepool`. The exact v5 or v6 size is selected from subscription availability and quota. This cluster is shared with Challenge 7 — do not delete it. Retrieve the following values from your lab dashboard credentials:
+
+| Dashboard Credential | Description |
+|---|---|
+| **Resource Group Name** | Your dedicated resource group |
+| **Sovereign Lab Region** | Region selected after capacity checks |
+| **Confidential VM Size** | AMD SEV-SNP size selected during shared preparation |
+| **Sovereign Lab AKS Cluster** | Name of the pre-provisioned AKS cluster |
+| **AKS Confidential Node Pool** | Name of the Confidential VM node pool (`cvmnodepool`) |
+
## Scenario Context
You are a cloud security engineer at a European healthcare organization that processes sensitive patient data in containerized applications. Your organization has adopted Kubernetes (AKS) for container orchestration but must comply with strict data protection regulations including GDPR and healthcare privacy requirements.
@@ -31,7 +43,7 @@ Your mandate includes:
- **Zero Trust for Kubernetes**: Validate the integrity of worker nodes before deploying sensitive workloads
- **Compliance and Auditability**: Provide cryptographic evidence that workloads execute in compliant infrastructure
-In this challenge, you'll deploy Azure Confidential Computing node pools in AKS using Azure CLI. You'll configure a confidential VM node pool with AMD SEV-SNP technology, deploy attestation-aware workloads, and verify that containers run in hardware-protected environments before processing sensitive operations.
+In this challenge, you'll inspect the pre-provisioned Azure Confidential Computing node pool in AKS using Azure CLI and kubectl. You'll validate the confidential VM node pool configuration (AMD SEV-SNP technology), deploy attestation-aware workloads, and verify that containers run in hardware-protected environments before processing sensitive operations.
### Understanding Confidential VMs in AKS
@@ -68,9 +80,9 @@ The AKS deployment patterns and attestation verification workflows have been ada
---
-## Task 1: Configure Environment and Register Preview Features
+## Task 1: Configure Environment Variables
-💡 **Set up your environment variables and register the required Azure preview features for Confidential VM support in AKS.**
+💡 **Set up your environment variables using the values from your lab dashboard credentials for the pre-provisioned AKS cluster.**
### Step 1: Configure Environment Variables
@@ -84,108 +96,79 @@ The AKS deployment patterns and attestation verification workflows have been ada
### Linux/Bash
```bash
-# Set common variables
-# Customize ATTENDEE_ID for each participant
-RESOURCE_GROUP="labuser-xx" # Change this for each participant (e.g., labuser-01, labuser-02,...)
-
-ATTENDEE_ID="${RESOURCE_GROUP}"
-
-# Generate a unique random hash suffix (different on each run)
-HASH_SUFFIX=$(echo -n "$ATTENDEE_ID-$(date +%s)-$RANDOM" | md5sum | cut -c1-6)
-
-LOCATION="northeurope"
-AKS_CLUSTER_NAME="aks-cvmcluster$HASH_SUFFIX"
-DNS_LABEL="cvmcluster$HASH_SUFFIX"
-ADMIN_USERNAME="azureuser"
-KEYVAULT_NAME="kv-cc-${HASH_SUFFIX}" # Must be globally unique
-SSH_KEY_NAME="cc-${ATTENDEE_ID}-key"
-ATTESTATION_NAME="attest${HASH_SUFFIX}"
+# Populate these from your lab dashboard credentials
+RESOURCE_GROUP="" # From the "Resource Group Name" credential
+LOCATION="" # From the "Sovereign Lab Region" credential
+CONFIDENTIAL_VM_SIZE="" # From the "Confidential VM Size" credential
+AKS_CLUSTER_NAME="" # From the "Sovereign Lab AKS Cluster" credential
+NODE_POOL_NAME="cvmnodepool" # From the "AKS Confidential Node Pool" credential
```
> [!WARNING]
> If your Azure Cloud Shell session times out (e.g. during a break), the variables defined above will be lost and must be re-defined before continuing. We recommend saving them in a local text file on your machine so you can quickly copy and paste them back into a new session.
-### Step 2: Install Required AKS Extensions
-
-```bash
-# Install / Update `aks-preview` extension
-az extension add --name aks-preview
-az extension update --name aks-preview
-
-# Register the `AzureLinuxCVMPreview` feature flag
-az feature register --namespace "Microsoft.ContainerService" --name "AzureLinuxCVMPreview"
-
-# Verify the registration status - status should show "Registered"
-az feature show --namespace Microsoft.ContainerService --name AzureLinuxCVMPreview
-
-# Refresh the registration of the `Microsoft.ContainerService` resource provider
-az provider register --namespace Microsoft.ContainerService
-```
-
-> [!WARNING]
-> For all Microsoft-hosted events, these features and providers below are already registered. Ignore any error messages due to lack of permissions.
-
---
-## Task 2: Create Azure Kubernetes Service cluster
+## Task 2: Connect to the Pre-Provisioned AKS Cluster
-💡 **Deploy the foundational AKS cluster with a standard system node pool. You'll add the Confidential VM node pool in the next task.**
+💡 **Retrieve credentials for the AKS cluster that has already been deployed for you and confirm connectivity.**
-### Step 1: Create AKS cluster
+### Step 1: Connect to the Cluster
```bash
-# Create an AKS cluster
-az aks create --resource-group $RESOURCE_GROUP --name $AKS_CLUSTER_NAME --node-count 1 --location $LOCATION --generate-ssh-keys --node-vm-size Standard_D2s_v5
+# Connect to the pre-provisioned cluster
+az aks get-credentials --resource-group $RESOURCE_GROUP --name $AKS_CLUSTER_NAME --overwrite-existing
-# Connect to the cluster
-az aks get-credentials --resource-group $RESOURCE_GROUP --name $AKS_CLUSTER_NAME
+# Confirm connectivity and list nodes
+kubectl get nodes
```
-At this point, you should see an AKS cluster in your resource group:
+At this point, you should see the pre-provisioned AKS cluster in your resource group:

---
-## Task 3: Add Confidential VM Node Pool
+## Task 3: Inspect the Confidential VM Node Pool
-💡 **Create a dedicated node pool with Confidential VM compute resources for running sensitive workloads.**
+💡 **Validate that the pre-provisioned Confidential VM node pool is configured correctly using both the Azure CLI and kubectl.**
-### Step 1: Add the Confidential Node Pool
+### Step 1: Verify VM Size and Image
```bash
-az aks nodepool add --resource-group $RESOURCE_GROUP --cluster-name $AKS_CLUSTER_NAME --name cvmnodepool --node-count 1 --node-vm-size Standard_DC2as_v5
+# Verify that the node pool uses a Confidential VM
+az aks nodepool show --resource-group $RESOURCE_GROUP --cluster-name $AKS_CLUSTER_NAME --name $NODE_POOL_NAME --query 'vmSize'
+
+# Verify that the node pool uses a Confidential VM image
+az aks nodepool list --resource-group $RESOURCE_GROUP --cluster-name $AKS_CLUSTER_NAME --query "[?name=='$NODE_POOL_NAME'].nodeImageVersion" -o tsv
```
-At this point, you should see another AKS node pool in your cluster:
+At this point, you should see the confidential node pool alongside the system node pool:

----
-
-## Task 4: Verify Confidential Node Pool Configuration
-
-💡 **Validate that the Confidential VM node pool is properly configured and running with the correct VM size and image.**
-
-### Step 1: Verify VM Size and Image
+### Step 2: Inspect Node Labels and Security Type
```bash
-# Verify that the node pool uses a Confidential VM
-az aks nodepool show --resource-group $RESOURCE_GROUP --cluster-name $AKS_CLUSTER_NAME --name cvmnodepool --query 'vmSize'
+# List all nodes with their labels and identify the confidential node pool members
+kubectl get nodes --show-labels | grep "$NODE_POOL_NAME"
-# Verify that the node pool uses a Confidential VM image
-az aks nodepool list --resource-group $RESOURCE_GROUP --cluster-name $AKS_CLUSTER_NAME --query "[?name=='cvmnodepool'].nodeImageVersion" -o tsv
+# Confirm the OS SKU and VM size labels on the confidential node(s)
+kubectl get nodes -l kubernetes.azure.com/agentpool=$NODE_POOL_NAME \
+ -o custom-columns=NAME:.metadata.name,OS_SKU:.metadata.labels.kubernetes\\.azure\\.com/os-sku,VM_SIZE:.metadata.labels.node\\.kubernetes\\.io/instance-type
```
+The `OS_SKU` value of `AzureLinux` and a `VM_SIZE` matching `$CONFIDENTIAL_VM_SIZE` confirm that the node uses the selected AMD SEV-SNP confidential compute family.
+
---
-## Task 5: Deploy and Verify Attestation Pod
+## Task 4: Deploy and Verify Attestation Pod
💡 **Deploy a sample pod that retrieves attestation data to prove it's running on a confidential VM node.**
### Step 1: Deploy the Attestation Pod
-1. The attestation pod YAML file is located at `walkthrough/challenge-5/resources/cvm-attestation-pod.yaml` in this repository (if using Cloud Shell, upload the file via the **Manage files** menu option).
+1. The attestation pod YAML file is located at `walkthrough/challenge-05/resources/cvm-attestation-pod.yaml` in this repository (if using Cloud Shell, upload the file via the **Manage files** menu option).
2. Apply the YAML file using the file from the repository:
@@ -317,25 +300,14 @@ The attestation report contains important security validation fields:
- **x-ms-sevsnpvm-is-debuggable**: `false` - VM is not debuggable (production setting)
- **x-ms-sevsnpvm-launchmeasurement**: Launch measurement for integrity verification
----
-
-## Task 6: Clean Up Resources
-
-💡 **Delete all resources to avoid ongoing charges.**
-
-### Step 1: Delete Resource Group
-
-```bash
-az group delete --name $RESOURCE_GROUP --yes --no-wait
-```
-
-⚠️ **Warning**: This command will permanently delete all resources in the resource group including the AKS cluster, node pools, and all associated resources.
+> [!NOTE]
+> This AKS cluster and its node pools are shared lab infrastructure also used in Challenge 7. Do not delete the resource group, the cluster, or the node pools.
---
## Key Takeaways
-In this challenge, you successfully deployed and validated Azure Confidential Computing in AKS with guest attestation. Here are the key concepts and best practices:
+In this challenge, you successfully validated Azure Confidential Computing in AKS with guest attestation. Here are the key concepts and best practices:
### Confidential Computing in Kubernetes
@@ -357,13 +329,13 @@ In this challenge, you successfully deployed and validated Azure Confidential Co
✅ **Mixed Node Pools** - Combine standard and confidential node pools in the same cluster for cost optimization (use confidential nodes only for sensitive workloads)
-✅ **Preview Features** - Confidential VM support in AKS requires feature registration via `az feature register`
+✅ **Idempotent Infrastructure** - The AKS cluster and Confidential VM node pool were provisioned ahead of time via idempotent Bicep, letting you focus on validation and attestation
-✅ **CLI Management** - Azure CLI provides efficient commands for managing AKS clusters and confidential node pools
+✅ **CLI Management** - Azure CLI provides efficient commands for inspecting and validating AKS clusters and confidential node pools
### Production Best Practices
-✅ **Node Pool Sizing** - Start with DC-series VMs (e.g., Standard_DC2as_v5) and scale based on workload requirements
+✅ **Node Pool Sizing** - Start with an AMD SEV-SNP DCasv5/DCasv6 size and scale based on workload requirements
✅ **Workload Isolation** - Use Kubernetes namespaces, network policies, and RBAC in addition to confidential computing
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 505572f8d..5acc7aa65 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
@@ -1,6 +1,6 @@
# Walkthrough Challenge 6 - Operating a Sovereign Hybrid Cloud with Azure Arc & Azure Local
-[Previous Challenge Solution](../challenge-05/solution-05.md) - **[Home](../../Readme.md)**
+[Previous Challenge Solution](../challenge-05/solution-05.md) - **[Home](../../Readme.md)** - [Next Challenge Solution](../challenge-07/solution-07.md)
**Estimated Duration:** 60-90 minutes
diff --git a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-07/solution-07.md b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-07/solution-07.md
new file mode 100644
index 000000000..f1771610b
--- /dev/null
+++ b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-07/solution-07.md
@@ -0,0 +1,332 @@
+# Walkthrough Challenge 7 - Adaptive Apps Across Sovereign Environments
+
+[Previous Challenge Solution](../challenge-06/solution-06.md) - **[Home](../../Readme.md)**
+
+Duration: 60 minutes
+
+## Prerequisites
+
+Please ensure that you successfully verified the [General prerequisites](../../Readme.md#general-prerequisites) before continuing.
+
+Install these tools on your workstation or use a development container that includes them:
+
+* Azure CLI with the `bastion` extension
+* `kubectl`
+* `jq` and OpenSSL
+* [Radius CLI (`rad`)](https://docs.radapp.io/getting-started/install/)
+
+Set the values shown on your lab dashboard:
+
+```bash
+export AZURE_SUBSCRIPTION=""
+export RESOURCE_GROUP=""
+export AKS_CLUSTER=""
+export K3S_VM=""
+export BASTION_NAME=""
+export K3S_KUBECONFIG="$HOME/.kube/adaptive-apps-k3s.yaml"
+export K3S_CONTEXT="k3s-azure-vm"
+
+az account set --subscription "$AZURE_SUBSCRIPTION"
+```
+
+> [!NOTE]
+> The platform creates stable resource names and credentials. Re-running the lab deployment converges on the same names and replaces the isolated resource group only when region fallback is required.
+
+## Task 1: Connect to and validate both platforms (10 minutes)
+
+💡 **Start with explicit target discipline. A Kubernetes context and a Radius workspace are independent selectors.**
+
+### Validate AKS
+
+```bash
+unset KUBECONFIG
+az aks get-credentials \
+ --resource-group "$RESOURCE_GROUP" \
+ --name "$AKS_CLUSTER" \
+ --overwrite-existing
+
+kubectl config rename-context "$AKS_CLUSTER" aks-sovereign-lab 2>/dev/null || true
+kubectl config use-context aks-sovereign-lab
+kubectl get --raw='/readyz'
+kubectl wait --for=condition=Ready nodes --all --timeout=10m
+```
+
+Confirm the sovereign-ready AKS features:
+
+```bash
+az aks show \
+ --resource-group "$RESOURCE_GROUP" \
+ --name "$AKS_CLUSTER" \
+ --query '{oidc:oidcIssuerProfile.enabled,workloadIdentity:securityProfile.workloadIdentity.enabled,serviceMesh:serviceMeshProfile.mode}' \
+ --output table
+```
+
+### Retrieve the K3s kubeconfig securely
+
+The kubeconfig is retrieved in bounded chunks through Azure VM Run Command. It is written with user-only permissions and changed to use the localhost tunnel endpoint.
+
+```bash
+mkdir -p "$(dirname "$K3S_KUBECONFIG")"
+chmod 0700 "$(dirname "$K3S_KUBECONFIG")"
+ENCODED_KUBECONFIG="$(mktemp)"
+DECODED_KUBECONFIG="$(mktemp)"
+
+LENGTH_RESPONSE="$(az vm run-command invoke \
+ --resource-group "$RESOURCE_GROUP" \
+ --name "$K3S_VM" \
+ --command-id RunShellScript \
+ --scripts 'encoded="$(base64 -w0 /etc/rancher/k3s/k3s.yaml)"; printf "K3S_LENGTH:%s\n" "${#encoded}"' \
+ --output json)"
+ENCODED_LENGTH="$(printf '%s' "$LENGTH_RESPONSE" \
+ | jq -r '.value[]?.message // empty' \
+ | sed -n 's/^K3S_LENGTH:\([0-9][0-9]*\)$/\1/p' \
+ | tail -n 1)"
+
+test -n "$ENCODED_LENGTH"
+: >"$ENCODED_KUBECONFIG"
+for ((START = 1; START <= ENCODED_LENGTH; START += 1800)); do
+ END=$((START + 1799))
+ RESPONSE="$(az vm run-command invoke \
+ --resource-group "$RESOURCE_GROUP" \
+ --name "$K3S_VM" \
+ --command-id RunShellScript \
+ --scripts "printf 'K3S_CHUNK_BEGIN\\n'; base64 -w0 /etc/rancher/k3s/k3s.yaml | cut -c ${START}-${END}; printf '\\nK3S_CHUNK_END\\n'" \
+ --output json)"
+ CHUNK="$(printf '%s' "$RESPONSE" \
+ | jq -r '.value[]?.message // empty' \
+ | sed -n '/^K3S_CHUNK_BEGIN$/,/^K3S_CHUNK_END$/p' \
+ | sed '1d;$d' \
+ | tr -d '\r\n')"
+ test -n "$CHUNK"
+ printf '%s' "$CHUNK" >>"$ENCODED_KUBECONFIG"
+done
+
+test "$(wc -c <"$ENCODED_KUBECONFIG" | tr -d '[:space:]')" = "$ENCODED_LENGTH"
+openssl base64 -d -A -in "$ENCODED_KUBECONFIG" -out "$DECODED_KUBECONFIG"
+sed 's#server: https://[^:]*:6443#server: https://127.0.0.1:16443#' \
+ "$DECODED_KUBECONFIG" >"$K3S_KUBECONFIG"
+chmod 0600 "$K3S_KUBECONFIG"
+KUBECONFIG="$K3S_KUBECONFIG" kubectl config rename-context default "$K3S_CONTEXT" 2>/dev/null || true
+rm -f "$ENCODED_KUBECONFIG" "$DECODED_KUBECONFIG"
+
+# Radius resolves installation contexts from the default kubeconfig. Merge the
+# K3s context there while keeping the dedicated file as the explicit selector.
+touch "$HOME/.kube/config"
+MERGED_KUBECONFIG="$(mktemp)"
+KUBECONFIG="$HOME/.kube/config:$K3S_KUBECONFIG" \
+ kubectl config view --flatten >"$MERGED_KUBECONFIG"
+install -m 0600 "$MERGED_KUBECONFIG" "$HOME/.kube/config"
+rm -f "$MERGED_KUBECONFIG"
+```
+
+Start the Bastion tunnel in a second terminal and leave it running:
+
+```bash
+az network bastion tunnel \
+ --name "$BASTION_NAME" \
+ --resource-group "$RESOURCE_GROUP" \
+ --target-resource-id "/subscriptions/${AZURE_SUBSCRIPTION}/resourceGroups/${RESOURCE_GROUP}/providers/Microsoft.Compute/virtualMachines/${K3S_VM}" \
+ --resource-port 6443 \
+ --port 16443
+```
+
+Return to the first terminal and validate K3s:
+
+```bash
+export KUBECONFIG="$K3S_KUBECONFIG"
+kubectl config use-context "$K3S_CONTEXT"
+kubectl get --raw='/readyz'
+kubectl wait --for=condition=Ready nodes --all --timeout=10m
+
+az vm show \
+ --resource-group "$RESOURCE_GROUP" \
+ --name "$K3S_VM" \
+ --show-details \
+ --query publicIps \
+ --output tsv
+```
+
+✅ The readiness endpoint returns `ok`, the node is `Ready`, and the final command returns an empty value because the VM has no public IP.
+
+## Task 2: Install federated Radius control planes (20 minutes)
+
+🔑 **A federated control plane keeps each site operationally independent. Shared source control can promote the same application model without introducing a runtime dependency between sites.**
+
+### Install Radius on K3s
+
+```bash
+export KUBECONFIG="$K3S_KUBECONFIG"
+kubectl config use-context "$K3S_CONTEXT"
+
+rad install kubernetes --kubecontext "$K3S_CONTEXT"
+rad workspace create kubernetes ws-local-prod \
+ --context "$K3S_CONTEXT" \
+ --force
+rad workspace switch ws-local-prod
+rad group show rg-trading >/dev/null 2>&1 || rad group create rg-trading
+rad group switch rg-trading
+rad env show env-local-prod >/dev/null 2>&1 || rad env create env-local-prod \
+ --group rg-trading \
+ --kubernetes-namespace env-local-prod
+rad env switch env-local-prod
+
+kubectl wait --for=condition=Available deployments --all \
+ --namespace radius-system \
+ --timeout=10m
+```
+
+### Install Radius on AKS
+
+```bash
+unset KUBECONFIG
+kubectl config use-context aks-sovereign-lab
+
+rad install kubernetes --kubecontext aks-sovereign-lab
+rad workspace create kubernetes ws-azure-prod \
+ --context aks-sovereign-lab \
+ --force
+rad workspace switch ws-azure-prod
+rad group show rg-trading >/dev/null 2>&1 || rad group create rg-trading
+rad group switch rg-trading
+rad env show env-azure-prod >/dev/null 2>&1 || rad env create env-azure-prod \
+ --group rg-trading \
+ --kubernetes-namespace env-azure-prod
+rad env switch env-azure-prod
+
+kubectl wait --for=condition=Available deployments --all \
+ --namespace radius-system \
+ --timeout=10m
+```
+
+💡 **This challenge does not register an Azure provider with Radius. The portable application uses Kubernetes compute only. Later Adaptive Apps challenges can add workload identity and recipes for Azure-managed capabilities without changing the application boundary.**
+
+## Task 3: Create one portable application model (10 minutes)
+
+💥 **Create a Radius Bicep file that contains no AKS cluster name, VM address, namespace, or cloud-specific resource.**
+
+```bash
+cat >sovereign-web.bicep <<'BICEP'
+extension radius
+
+@description('The Radius environment ID injected by the Radius CLI.')
+param environment string
+
+resource app 'Applications.Core/applications@2023-10-01-preview' = {
+ name: 'sovereign-web'
+ properties: {
+ environment: environment
+ }
+}
+
+resource web 'Applications.Core/containers@2023-10-01-preview' = {
+ name: 'web'
+ properties: {
+ application: app.id
+ container: {
+ image: 'nginx:1.27-alpine'
+ ports: {
+ http: {
+ containerPort: 80
+ }
+ }
+ }
+ }
+}
+BICEP
+```
+
+Review the file and identify the portability boundary:
+
+* The application team owns the application, image, and requested port.
+* The selected Radius environment owns the Kubernetes namespace and target control plane.
+* A platform team can later attach recipes to portable capability resources without adding platform details to this application model.
+
+## Task 4: Deploy the unchanged model twice (15 minutes)
+
+### Deploy to K3s
+
+```bash
+export KUBECONFIG="$K3S_KUBECONFIG"
+kubectl config use-context "$K3S_CONTEXT"
+rad workspace switch ws-local-prod
+rad group switch rg-trading
+rad env switch env-local-prod
+
+test "$(kubectl config current-context)" = "$K3S_CONTEXT"
+rad deploy sovereign-web.bicep \
+ --workspace ws-local-prod \
+ --group rg-trading \
+ --environment env-local-prod
+
+rad app graph --application sovereign-web
+rad resource list --application sovereign-web
+```
+
+Expose the application, browse to , and stop the foreground process with Ctrl+C:
+
+```bash
+rad resource expose Applications.Core/containers web \
+ --application sovereign-web \
+ --port 8080 \
+ --remote-port 80
+```
+
+### Deploy the same file to AKS
+
+```bash
+unset KUBECONFIG
+kubectl config use-context aks-sovereign-lab
+rad workspace switch ws-azure-prod
+rad group switch rg-trading
+rad env switch env-azure-prod
+
+test "$(kubectl config current-context)" = 'aks-sovereign-lab'
+rad deploy sovereign-web.bicep \
+ --workspace ws-azure-prod \
+ --group rg-trading \
+ --environment env-azure-prod
+
+rad app graph --application sovereign-web
+rad resource list --application sovereign-web
+```
+
+Expose the AKS application and browse to :
+
+```bash
+rad resource expose Applications.Core/containers web \
+ --application sovereign-web \
+ --port 8080 \
+ --remote-port 80
+```
+
+> [!IMPORTANT]
+> Do not edit `sovereign-web.bicep` between deployments. Changing the target selectors is the point of the exercise.
+
+## Task 5: Validate site independence (5 minutes)
+
+Stop the AKS exposure, switch back to K3s, and inspect its application without selecting or contacting the AKS Radius control plane:
+
+```bash
+export KUBECONFIG="$K3S_KUBECONFIG"
+kubectl config use-context "$K3S_CONTEXT"
+rad workspace switch ws-local-prod
+rad app graph --application sovereign-web
+kubectl get pods --all-namespaces --selector radapp.io/application=sovereign-web
+```
+
+🔑 **The two applications have the same name and model but independent state. A loss of AKS or connectivity to Azure does not remove the K3s control plane or its running application. Data synchronization, configuration promotion, and disconnected software distribution remain separate architecture decisions.**
+
+## Validation
+
+You have completed the challenge when:
+
+* Both clusters are healthy and the K3s VM has no public IP.
+* Both `radius-system` installations are healthy.
+* The workspace target matches the Kubernetes context before every deployment.
+* `sovereign-web.bicep` remains unchanged.
+* Both Radius control planes show an independent `sovereign-web` graph.
+* Each deployment displays the NGINX page through local port forwarding.
+* You can explain why federated control planes improve site autonomy and where Radius recipes fit when managed and local capability implementations differ.
+
+> [!WARNING]
+> Delete the lab resource group promptly after the workshop. AKS nodes, the K3s VM, NAT Gateway, and Azure Bastion accrue hourly charges.
\ No newline at end of file