Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions 03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ After completing this Hackathon you will:
| 1 | Using Azure native platform controls (e.g. Policy, RBAC etc) to enforce sovereign controls in the public cloud | [Challenge](./challenges/challenge-01.md) | [Solution](./walkthrough/challenge-01/solution-01.md) | 45 min | Jan Egil Ring |
| 2 | Encryption at rest with Customer Managed Keys in Azure Key Vault | [Challenge](./challenges/challenge-02.md) | [Solution](./walkthrough/challenge-02/solution-02.md) | 30 min | Ye Zhang |
| 3 | Encryption in transit - enforcing TLS | [Challenge](./challenges/challenge-03.md) | [Solution](./walkthrough/challenge-03/solution-03.md) | 30 min | Ye Zhang |
| 4 | Encryption in use with Azure Confidential Compute - VM | [Challenge](./challenges/challenge-04.md) | [Solution](./walkthrough/challenge-04/solution-04.md) | 30 min | Murali Rao Yelamanchili / Ye Zhang |
| 4 | Runtime attestation with Confidential and Standard ACI | [Challenge](./challenges/challenge-04.md) | [Solution](./walkthrough/challenge-04/solution-04.md) | 30-45 min | Murali Rao Yelamanchili / Ye Zhang |
| 5 | Encryption in use with Confidential VMs/Node Pools in Azure Kubernetes Service (AKS) | [Challenge](./challenges/challenge-05.md) | [Solution](./walkthrough/challenge-05/solution-05.md) | 30 min | Murali Rao Yelamanchili / Ye Zhang |
| 6 | Operating Sovereign in a hybrid environment with Azure Local and Azure Arc | [Challenge](./challenges/challenge-06.md) | [Solution](./walkthrough/challenge-06/solution-06.md) | 60-90 min | Jan Egil Ring / Thomas Maurer |
| 7 | Adaptive Apps across sovereign Azure and private-cloud environments with Radius | [Challenge](./challenges/challenge-07.md) | [Solution](./walkthrough/challenge-07/solution-07.md) | 60 min | Dylan de Jong / Jan Egil Ring / Wesley Backelant |
Expand All @@ -68,10 +68,14 @@ In order to use the Hackathon time most effectively, the following tasks should
4. [Azure CLI](https://learn.microsoft.com/cli/azure/install-azure-cli). **Hint:** Make sure to use the latest version available.
5. Challenge 7: `kubectl`, `jq`, OpenSSL, the [Radius CLI](https://docs.radapp.io/getting-started/install/), and the Azure CLI `bastion` extension

The shared Challenge 4/5/7 platform requires **4 AMD SEV-SNP confidential-family vCPUs per participant**. Automation evaluates `Standard_DC2as_v5` and `Standard_DC2as_v6` across the configured regions, preferring the generally available v5 family. With the default two labs per subscription, prepare a minimum family quota of 8 vCPUs in at least one configured region. For example:
The shared Challenge 5/7 AKS platform requires **4 AMD SEV-SNP confidential-family vCPUs per participant** for two Ubuntu nodes, plus **12 DSv5 vCPUs** for two system nodes and one K3s VM: **16 regional vCPUs** in total. There is no standalone Confidential VM. Automation evaluates `Standard_DC2as_v5` and `Standard_DC2as_v6` across the configured regions. With two labs per subscription, allow 8 confidential-family, 24 DSv5 and 32 regional vCPUs before deployment. These are steady-state requirements; also reserve upgrade surge headroom (for example one additional node per pool: 2 confidential plus 4 DSv5 vCPUs per concurrent cluster upgrade).

The default candidate order is Sweden Central/v5, Spain Central/v5, Sweden Central/v6, then Spain Central/v6. Shared preparation persists the first candidate with supported VM sizes and sufficient confidential-family, DSv5-family, and regional quota so every participant deployment in that subscription uses the same selection.

On 2026-09-21, read-only `az vm list-skus --all` checks in the validation subscription returned `Standard_DC2as_v6` in **both Sweden Central and Spain Central**, with no restrictions; `Standard_DC2as_v5` was not listed in either. This is a dated subscription-specific observation, not a capacity guarantee or proof of AKS deployment success. Recheck SKUs, quota and AKS support for each event. Ubuntu CVM pools are supported as described in [AKS confidential VM guidance](https://learn.microsoft.com/azure/aks/use-cvm).

Challenge 4 independently deploys ACI/ACR in **North Europe**, the walkthrough's validated Confidential ACI region. It does not use VM-family quota, the old standalone CVM, or a custom attestation provider. Confirm Confidential ACI quota/capacity separately; VM availability in Sweden/Spain does not establish Confidential ACI availability there. Shared automation registers `Microsoft.ContainerInstance` and `Microsoft.ContainerRegistry`. Challenge 5 uses the actual Console AKS region, not Challenge 4's `LOCATION` value.

```powershell
./resources/manual-setup/subscription-preparations/2-vcpu-quotas.ps1 -Region swedencentral -NumberOfLabUsers 2 -ConfidentialVmGeneration v5 -SubmitQuotaRequests
```
Expand All @@ -94,17 +98,23 @@ Use the AZQR report for planning and preferred-region ordering, not as the deplo

The main cost driver for this Hackathon is virtual machines:

- **LocalBox** cost is approximately 100-110 USD per subscription per day. For hosted events, schedule provisioning at least 24 hours ahead and budget for preparation time; avoid leaving it running for a week by default. Follow the [hosted-event budget guidance](./resources/hosted-events/readme.md).
- **Challenges 4, 5, and 7** share one pre-provisioned participant platform: a two-node AKS system pool, one Confidential VM AKS node, one standalone Confidential VM, one K3s VM, Azure Bastion Standard, and a NAT Gateway. Budget approximately 45-55 USD per participant per day, depending on region and data transfer. The platform starts during lab deployment so participants can focus on validation rather than waiting for capacity-sensitive resources.
- Plan subscription quotas for at least 12 general-purpose vCPUs and 4 DCasv5- or DCasv6-family confidential vCPUs per participant. Keep the default maximum of two participants per subscription unless larger aggregate increases have been approved in advance.
- **LocalBox** cost is approximately 100-110 USD per subscription per day. Console's shared-deployment estimate is USD 110/day per subscription, separate from participant costs. For hosted events, schedule provisioning at least 24 hours ahead and budget for preparation time; avoid leaving it running for a week by default. Follow the [hosted-event budget guidance](./resources/hosted-events/readme.md).
- **Challenges 5 and 7** share one pre-provisioned cluster: two `Standard_D4s_v5` system nodes and two Ubuntu `Standard_DC2as_v5/v6` confidential nodes, plus a `Standard_D4s_v5` K3s VM, Bastion Standard and NAT Gateway. The removed standalone CVM is replaced by the second confidential node, so baseline confidential compute remains four vCPUs; sharing also avoids a second AKS system pool.
- Example Linux PAYG compute in Spain Central from the [Azure Retail Prices API](https://prices.azure.com/api/retail/prices), checked 2026-09-21: three D4s v5 at USD 0.214/hour plus two DC2as v6 at USD 0.117/hour equals **USD 21.02/day** for compute alone. Disks, Bastion, NAT, public IPs, LoadBalancers, storage and traffic are additional.
- Use **USD 35-45 per participant per day** as a planning range including a modest allowance for those services and Challenge 4's two 1-vCPU/2-GiB ACI groups, Basic ACR and image build. The Console estimate is USD 45/day, not a pricing quote. ACI duration, confidential pricing, builds and egress can change the total; validate a regional Pricing Calculator estimate before approval.
- Plan subscription quotas for at least 12 general-purpose vCPUs and 4 confidential vCPUs per participant, plus upgrade headroom. Keep two participants per subscription unless larger aggregate increases have been approved. Challenge 5 application cleanup does not release node quota because Challenge 7 still uses the cluster.

For a 50-participant event, the shared participant platforms cost approximately 2,250-2,750 USD per day while deployed, in addition to the optional LocalBox environment.
For a 50-participant event, this planning range is USD 1,750-2,250 per day, plus LocalBox for each participating subscription if Challenge 6 is enabled.
There will also be smaller costs for services such as Key Vault, storage, and monitoring. Budget separately for the approved Defender for Servers plan used in Challenge 6.
An Azure Pricing Calculator estimate is available [here](https://azure.com/e/1a7aec76a3e049cba57cda6742025373).
This estimate can be adjusted for fewer/more students, running the VMs shorter/longer and adding additional services if desired.

If you plan to run this Hackathon in your own subscription on a limited budget, skip the optional Challenge 6 environment and remove the participant resource group immediately after finishing the event.

### Existing event migration

The revised template is intended for fresh provisioning. Existing AzureLinux confidential pools are not automatically converted to Ubuntu. Removing the old VM, NIC/subnet or attestation declarations does not delete deployed resources or stored Console credentials; an incremental redeployment can also fail if the removed subnet is still in use. Use a fresh event or have the organizer plan workload-safe pool replacement and obsolete-resource/credential cleanup explicitly. Do not rerun the region-fallback deployment on an active lab as a migration shortcut: it may recycle the resource group. No automatic teardown is added by these changes.

## Contributors

- Thomas Maurer [GitHub](https://github.com/thomasmaurer); [LinkedIn](https://www.linkedin.com/in/thomasmaurer2/)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ VM node pool and verify the execution environment with a fresh MAA token.

## Actions

- Deploy an AKS cluster with managed identity and automatic upgrade channels.
- Add and label an AMD SEV-SNP Confidential VM node pool.
- Connect to the provided AKS cluster shared with Challenge 7.
- Validate its two Ubuntu AMD SEV-SNP nodes labelled `workload=confidential`.
- Deploy the Azure Voting App to the confidential nodes.
- Deploy the newer runtime attestation UI to the same node pool.
- Verify pod placement and inspect the attestation claims.
- Remove the AKS cluster without deleting the shared attendee resource group.
- Remove only Challenge 5 applications from `challenge-05`, retaining AKS and its pools for Challenge 7.

## Success criteria

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Your organization must run workloads in a sovereign cloud while still leveraging
## Actions

* Explore the LocalBox hybrid infrastructure in the Azure Portal
* Deploy a sample application to AKS on Azure Local
* 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
Expand All @@ -21,6 +22,7 @@ Your organization must run workloads in a sovereign cloud while still leveraging
* 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 deployed a sample application to AKS on Azure Local
* You understand how Azure Arc provides a unified control plane for sovereign hybrid scenarios

## Learning resources
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,7 @@ if(-not $selectedCandidate) {
$confidentialVmSize = $selectedCandidate.VmSize
$confidentialQuotaName = $selectedCandidate.QuotaName
$deploymentLocations = @($selectedCandidate.Location)
$quotaRequirements = @(
@{ Name = $confidentialQuotaName; Required = 4 }
@{ Name = 'StandardDSv5Family'; Required = 12 }
@{ Name = 'cores'; Required = 16 }
)
$quotaRequirements = @(Get-MhhSovereignComputeRequirements -ConfidentialQuotaName $confidentialQuotaName)
$regionReadinessSummary = @()
foreach($candidateLocation in $deploymentLocations) {
$requiredVmSizes = @($confidentialVmSize, 'Standard_D4s_v5')
Expand Down Expand Up @@ -102,8 +98,8 @@ foreach($candidateLocation in $deploymentLocations) {
$available = $limit - $current
$regionReadinessSummary += "${candidateLocation}/$($requirement.Name): ${current}/${limit} vCPUs used"

if($available -lt $requirement.Required) {
throw "The shared compute selection is no longer ready ($($regionReadinessSummary -join '; ')). $($requirement.Required) available $($requirement.Name) vCPUs are required per participant."
if($available -lt $requirement.PerParticipant) {
throw "The shared compute selection is no longer ready ($($regionReadinessSummary -join '; ')). $($requirement.PerParticipant) available $($requirement.Name) vCPUs are required per participant."
}
}
}
Expand All @@ -118,8 +114,9 @@ $effectiveLocation = $deploymentLocations[0]
# Provider registration is initiated once per subscription by shared-deploy-lab.ps1.
# Registration is asynchronous, so verify the providers required by the shared lab.
$sovereignLabProviders = @(
'Microsoft.Attestation',
'Microsoft.Compute',
'Microsoft.ContainerInstance',
'Microsoft.ContainerRegistry',
'Microsoft.ContainerService',
'Microsoft.ManagedIdentity',
'Microsoft.Network'
Expand All @@ -145,9 +142,8 @@ foreach($providerNamespace in $sovereignLabProviders) {
# on retries, so subscription and resource-group role assignments are applied after it.
$nameSuffix = $stableHash.Substring(0, 8)
$k3sAdminPassword = New-MhhStablePassword -Purpose 'adaptive-apps-k3s-admin' -Length 24
$cvmAdminPassword = New-MhhStablePassword -Purpose 'sovereign-cvm-admin' -Length 24

Write-Host "Deploying the shared Sovereign Cloud platform for Challenges 4, 5, and 7..."
Write-Host "Deploying the shared Sovereign Cloud platform for Challenges 5 and 7..."
$sovereignLabResult = Invoke-MhhDeploymentWithRegionFallback `
-PreferredLocations $deploymentLocations `
-ResourceGroupName $ResourceGroupName `
Expand All @@ -156,13 +152,12 @@ $sovereignLabResult = Invoke-MhhDeploymentWithRegionFallback `
-TemplateParameterObject @{
nameSuffix = $nameSuffix
adminPassword = $k3sAdminPassword
cvmAdminPassword = $cvmAdminPassword
confidentialVmSize = $confidentialVmSize
} `
-DeploymentNamePrefix 'sovereign-lab' `
-Tag @{
workload = 'sovereign-lab'
challenges = '4,5,7'
challenges = '5,7'
CostControl = 'Ignore'
}

Expand All @@ -181,13 +176,9 @@ New-AzSubscriptionDeployment `
} | Out-Null

@{"HackboxCredential" = @{ name = "Sovereign Lab Region"; value = $sovereignLabResult.LocationUsed; note = "Region selected after capacity checks" }}
@{"HackboxCredential" = @{ name = "Confidential VM Size"; value = $confidentialVmSize; note = "AMD SEV-SNP size selected during shared preparation" }}
@{"HackboxCredential" = @{ name = "AKS Confidential Node Size"; value = $confidentialVmSize; note = "Two Ubuntu AMD SEV-SNP nodes; four confidential vCPUs per participant" }}
@{"HackboxCredential" = @{ name = "Sovereign Lab AKS Cluster"; value = $sovereignLabResult.Outputs.aksClusterName; note = "Shared by Challenges 5 and 7" }}
@{"HackboxCredential" = @{ name = "AKS Confidential Node Pool"; value = $sovereignLabResult.Outputs.confidentialNodePoolName; note = "Challenge 5" }}
@{"HackboxCredential" = @{ name = "Confidential VM"; value = $sovereignLabResult.Outputs.confidentialVmName; note = "Challenge 4" }}
@{"HackboxCredential" = @{ name = "Confidential VM Admin Username"; value = "azureuser"; note = $sovereignLabResult.Outputs.confidentialVmName }}
@{"HackboxCredential" = @{ name = "Confidential VM Admin Password"; value = $cvmAdminPassword; note = $sovereignLabResult.Outputs.confidentialVmName }}
@{"HackboxCredential" = @{ name = "Attestation Provider"; value = $sovereignLabResult.Outputs.attestationProviderName; note = $sovereignLabResult.Outputs.attestationProviderUri }}
@{"HackboxCredential" = @{ name = "Sovereign Lab K3s VM"; value = $sovereignLabResult.Outputs.k3sVmName; note = "Private local/edge execution environment" }}
@{"HackboxCredential" = @{ name = "Sovereign Lab Bastion"; value = $sovereignLabResult.Outputs.bastionName; note = "Shared private access path" }}
@{"HackboxCredential" = @{ name = "K3s VM Admin Username"; value = "azureuser"; note = $sovereignLabResult.Outputs.k3sVmName }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
"deploymentType": "resourcegroup",
"labsPerSubscription": 2,
"preferredLocation": "swedencentral, spaincentral",
"estimatedDailyCostsUsd": 55.0
"estimatedDailyCostsUsd": 45.0,
"estimatedSharedDeploymentDailyCostsUsd": 110.0
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,18 @@ function Get-MhhResponseHeaderValue {
return @($header.Value)[0]
}

function Get-MhhSovereignComputeRequirements {
param([Parameter(Mandatory)][string]$ConfidentialQuotaName)

$generalVcpus = (2 + 1) * 4
$confidentialVcpus = 2 * 2
@(
@{ Name = $ConfidentialQuotaName; PerParticipant = $confidentialVcpus }
@{ Name = 'StandardDSv5Family'; PerParticipant = $generalVcpus }
@{ Name = 'cores'; PerParticipant = $generalVcpus + $confidentialVcpus }
)
}

function Get-MhhConfidentialComputeCandidates {
param(
[string[]]$PreferredLocation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ $providers = @(
"Microsoft.Kubernetes",
"Microsoft.KubernetesConfiguration",
"Microsoft.ContainerService",
"Microsoft.ContainerInstance",
"Microsoft.ContainerRegistry",

# Extended Location (for Azure Local)
"Microsoft.ExtendedLocation"
Expand Down Expand Up @@ -156,37 +158,6 @@ if ($networkProvider.RegistrationState -ne 'Registered') {
throw "Resource provider Microsoft.Network did not return to Registered state within 5 minutes."
}

$aksFeatureName = "AzureLinuxCVMPreview"
$aksFeature = Get-AzProviderFeature `
-ProviderNamespace "Microsoft.ContainerService" `
-FeatureName $aksFeatureName `
-ErrorAction Stop

if ($aksFeature.RegistrationState -ne 'Registered') {
Write-Host " [REGISTERING] Microsoft.ContainerService/$aksFeatureName..." -ForegroundColor Yellow
Register-AzProviderFeature `
-ProviderNamespace "Microsoft.ContainerService" `
-FeatureName $aksFeatureName `
-ErrorAction Stop | Out-Null

for ($attempt = 1; $attempt -le 90; $attempt++) {
Start-Sleep -Seconds 10
$aksFeature = Get-AzProviderFeature `
-ProviderNamespace "Microsoft.ContainerService" `
-FeatureName $aksFeatureName `
-ErrorAction Stop
if ($aksFeature.RegistrationState -eq 'Registered') {
break
}
Write-Host " Waiting for feature registration ($attempt/90): $($aksFeature.RegistrationState)" -ForegroundColor Gray
}
}

if ($aksFeature.RegistrationState -ne 'Registered') {
throw "Provider feature Microsoft.ContainerService/$aksFeatureName did not reach Registered state within 15 minutes (current state: $($aksFeature.RegistrationState))."
}

Write-Host " [REGISTERED] Microsoft.ContainerService/$aksFeatureName" -ForegroundColor Green
Register-AzResourceProvider -ProviderNamespace "Microsoft.ContainerService" -ErrorAction Stop | Out-Null

for ($attempt = 1; $attempt -le 30; $attempt++) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,7 @@ foreach($candidate in $confidentialCandidates) {

$regionalUsage = Get-AzVMUsage -Location $candidateLocation -ErrorAction Stop
$locationReady = $true
$quotaRequirements = @(
@{ Name = $candidate.QuotaName; PerParticipant = 4 }
@{ Name = 'StandardDSv5Family'; PerParticipant = 12 }
@{ Name = 'cores'; PerParticipant = 16 }
)
$quotaRequirements = @(Get-MhhSovereignComputeRequirements -ConfidentialQuotaName $candidate.QuotaName)
foreach($requirement in $quotaRequirements) {
$quotaName = $requirement.Name
$quotaRequired = $requirement.PerParticipant * $participantCount
Expand Down
Loading
Loading