From bb5ce923616ced65468f01bc5853845b3e84bbfe Mon Sep 17 00:00:00 2001 From: Jan Egil Ring Date: Wed, 21 Oct 2026 05:12:15 +0000 Subject: [PATCH 1/2] Improve LocalBox preparation and add a shared reference VM Prepare a shared Windows reference VM by default with guest management and periodic update assessment. Recover matching failed image imports, skip completed storage changes, and prepare networking and AKS before VM images. Simplify invocation while preserving validation and WhatIf, and document Defender onboarding and assessment timing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../challenges/challenge-06.md | 12 +- .../resources/hosted-events/readme.md | 1 + .../resources/localbox/manual-preparation.md | 4 +- .../resources/localbox/readme.md | 66 ++- .../resources/prepare-localbox.ps1 | 328 ++++++++++- .../resources/test-localbox-capacity.ps1 | 100 +--- .../resources/tests/localbox.health.tests.ps1 | 22 + .../tests/prepare-localbox.tests.ps1 | 537 +++++++++++++++++- .../resources/tests/readme.md | 38 ++ .../defender-recommendations-populated.png | Bin 0 -> 179625 bytes .../walkthrough/challenge-06/solution-06.md | 48 +- 11 files changed, 1024 insertions(+), 132 deletions(-) create mode 100644 03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-06/images/defender-recommendations-populated.png diff --git a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/challenges/challenge-06.md b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/challenges/challenge-06.md index d7b924615..eaced9551 100644 --- a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/challenges/challenge-06.md +++ b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/challenges/challenge-06.md @@ -19,6 +19,16 @@ Before starting, return your Challenge 1 exercise policies to **DoNotEnforce**. * 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 +MDE onboarding can take up to an hour after Arc connects when Endpoint integration +is already established. Continue with Update Manager and the AKS tasks while waiting. +Recommendations may appear later; you do not need a nonzero count to complete the +challenge. Record pending assessment separately from confirmed protection. + +If your facilitator provides `sov-ref-vm`, inspect its existing Defender and Update +Manager results while your own VM onboards. **Do not modify, run assessments on, +patch, restart or delete this shared VM.** Still complete the deployment and +assessment steps on your own VM, and record which VM each observation describes. + ### Review the AKS load balancer and deploy an app * Review the preinstalled **MetalLB load balancer**: check that it is healthy and understand how it assigns IP addresses to applications @@ -34,7 +44,7 @@ In a real deployment, clients would reach the app through its MetalLB IP address * You can navigate and understand the LocalBox hybrid environment in the Azure Portal * 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 verified Defender for Servers plan coverage, recorded MDE extension/onboarding status separately, and reviewed available recommendations or recorded that assessment is pending/not yet confirmed * You have completed an Azure Update Manager assessment for your VM and reviewed the results, including when no updates are pending * You can explain MetalLB's role and have checked that the preinstalled load balancer is healthy * Your sample application runs in your team's namespace and has a load-balancer IP address diff --git a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/hosted-events/readme.md b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/hosted-events/readme.md index f4e7d84d5..8b10777e4 100644 --- a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/hosted-events/readme.md +++ b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/hosted-events/readme.md @@ -47,6 +47,7 @@ The shared hook submits `localbox-*` in `rg-localbox-shared`, then waits up to s 2. If the Client password is unknown, an authorized event lead/coach uses the Azure VM's **Help > Reset password** to set a known password for the `arcdemo` host account, then connects to `LocalBox-Client` through Bastion. Do not expose this password through participant credentials or job logs. A host password reset does not change the nested-node Windows credentials. See [LocalBox credentials](#localbox-credentials). 3. Copy **Lab Group ObjectId** from the Console's **Credentials** tab for the preparation prompt or `-AksAdminGroupObjectId`. Confirm that intended Azure lab identities/coaches are members; see the group dependency below. 4. Follow [LocalBox preparation](../localbox/readme.md), once per shared subscription, in elevated PowerShell 7. It creates AKS and then installs/verifies the shared MetalLB extension and reserved ARP pool. Azure authentication uses the Client VM's managed identity; preparation constructs the nested Windows credential locally from the installed configuration without printing it. An explicit `-NodeCredential` overrides that value after a nested-account rotation. + Preparation includes `sov-ref-vm` by default; run it **the day before the event** for inspection while participant VMs onboard, or opt out with `-IncludeReferenceVm:$false`. Follow the [reference readiness checks](../localbox/readme.md#reference-readiness-and-participant-use); Console plan configuration and successful provisioning do not establish completed VM assessments. Participants must not modify this shared VM; no RBAC restrictions are added. 5. Run [Pester health checks](../tests/readme.md) for LocalBox and every selected participant lab. A control-plane-only pass does not establish full readiness. 6. Console shared setup enables and verifies **Defender for Servers Plan 2** and **Endpoint protection** on every event subscription. Complete the [Defender readiness checklist](../localbox/manual-preparation.md#defender-for-servers-readiness-organizer) and [participant VM readiness exercise](../localbox/manual-preparation.md#step-6-test-the-environment), including guest management, actual Defender onboarding and Update Manager. Verified subscription settings do not prove that a VM has completed onboarding. 7. Verify an intended participant can use Entra-authenticated `az connectedk8s proxy` and a private application port-forward from their Codespace. Participants inspect MetalLB, not install it, and never need LocalBox-Client access. This validates the lab access path, not routing to the MetalLB service VIP. diff --git a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/localbox/manual-preparation.md b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/localbox/manual-preparation.md index fabfdb0a2..541a8309d 100644 --- a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/localbox/manual-preparation.md +++ b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/localbox/manual-preparation.md @@ -54,7 +54,7 @@ You should also see updated values in the Azure Portal: ![Create logical network](./img/add_logical_network_02.jpg) 5. Enter the following parameter values: - **IPv4 address space**: 192.168.200.0 /24 (256 addresses) -- **IP Pools**: 192.168.200.10 - 192.168.200.199, only after verifying DHCP exclusions and infrastructure reservations; never include the network, broadcast or gateway addresses +- **IP Pools**: 192.168.200.10 - 192.168.200.199 for the standard greenfield LocalBox configuration. For customized networks, verify DHCP exclusions and infrastructure reservations; never include the network, broadcast or gateway addresses - **Default gateway**: 192.168.200.1 - **DNS Servers**: 192.168.1.254 - **VLAN ID**: 200 @@ -116,6 +116,8 @@ do not repeatedly toggle integration to force deployment. ### Step 6: Test the Environment +The preparation script includes a [reference VM](readme.md#reference-vm) by default; verify its assessment results before the event, or opt out with `-IncludeReferenceVm:$false`. This does not replace the participant-identity test below. + Before participants begin, follow the [Challenge 6 walkthrough](../../walkthrough/challenge-06/solution-06.md) using a normal participant identity: 1. Create a Windows Server 2025 VM in that participant's assigned resource group on the prepared LocalBox logical network, with **Enable guest management** selected diff --git a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/localbox/readme.md b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/localbox/readme.md index 342e3219a..b2622b945 100644 --- a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/localbox/readme.md +++ b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/localbox/readme.md @@ -12,7 +12,8 @@ Run [prepare-localbox.ps1](../prepare-localbox.ps1) **inside LocalBox-Client**, - `Microsoft.KubernetesRuntime` registered, and its tenant-specific service-principal **object ID** available for MetalLB. Updated Console setup stores this nonsecret ID in the shared resource group's `microhack-k8s-runtime-object-id` tag. Manual organizers supply `-KubernetesRuntimeObjectId`; see [MetalLB preparation](#metallb-preparation) below. The Client managed identity needs write access to cluster extensions and KubernetesRuntime load balancers at the AKS connected-cluster scope; do not grant participants those permissions for installation. - The installed Jumpstart configuration referenced by `$env:LocalBoxConfigFile` (or `-ConfigPath`), containing `SDNDomainFQDN` and `SDNAdminPassword`. Preparation constructs the nested administrator credential locally without printing it. Supply `-NodeCredential` to override it if the nested password/account has changed. Azure managed identity cannot authenticate Windows PowerShell Direct inside nested nodes. - `Microsoft.EdgeMarketplace` registered by the subscription owner. The Marketplace image workflow also requires the Azure Connected Machine Resource Manager role for the `Microsoft.AzureStackHCI` resource-provider identity on the image resource group; ask the deployment owner to verify this prerequisite. No automatic privilege escalation is attempted. -- Verified DHCP exclusions/static reservations for both node/VM pools **and the AKS service VIP range**, sufficient backing `V:` capacity and event budget. Dynamic disks do not add physical backing capacity. +- The standard greenfield LocalBox network configuration. For customized networks, review DHCP exclusions/static reservations for both node/VM pools **and the AKS service VIP range** before preparation. +- Sufficient backing `V:` capacity and event budget. Dynamic disks do not add physical backing capacity. For the standard Jumpstart configuration, the nested-node administrator is `jumpstart\Administrator`. The Client VM's `arcdemo` account is not the nested-node administrator. Use the nested domain's credentials (adjust the domain if customized), not a password reset solely on the Client VM. @@ -25,15 +26,19 @@ Download files, inspect them, then execute. Do not pipe downloaded content into ```powershell $base = 'https://raw.githubusercontent.com/microsoft/MicroHack/refs/heads/main/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources' Invoke-WebRequest "$base/prepare-localbox.ps1" -OutFile './prepare-localbox.ps1' -./prepare-localbox.ps1 -AddressReservationsConfirmed -WhatIf -./prepare-localbox.ps1 -AddressReservationsConfirmed +./prepare-localbox.ps1 -WhatIf +./prepare-localbox.ps1 ``` If the nested account was rotated, pass a current `PSCredential` through `-NodeCredential`; do not substitute the reset Client password. Missing configuration credentials produce an actionable error instead of exposing their contents or prompting unexpectedly. The script prompts for the Entra group object ID, or accepts `-AksAdminGroupObjectId`. Subscription, resource group and configuration path default from Jumpstart's machine environment. Explicit scope values must match this Client's deployment. Run only one preparation session at a time. -If the Console-owned group is not available yet, `-SkipAks` explicitly prepares only storage, the VM image and both networks. It does not fabricate a group or grant anyone cluster access. The manifest records this as a partial run, and full health checks fail until preparation is rerun without `-SkipAks` using a valid admin-group ID. +The reference VM is **enabled by default**; pass `-IncludeReferenceVm:$false` to skip its preparation. Operation confirmation prompts are off under PowerShell's default confirmation preference, including storage changes and recovery of a matching Failed image. Use `-Confirm` to request confirmations, or `-Confirm:$false` if your session has a stricter preference. `-WhatIf` and all configuration/health safety checks remain active. Group-ID and first-creation credential prompts are separate; supply `-AksAdminGroupObjectId` and `-ReferenceVmCredential` to avoid those inputs. + +Preparation runs in this order: **storage -> logical networks -> AKS, proxy access and MetalLB -> VM image -> reference VM (unless disabled)**. A late Marketplace image failure therefore leaves the networks and AKS resources in place for the next run to reuse; it does not roll them back. The final manifest is written only after the requested preparation finishes. + +If the Console-owned group is not available yet, `-SkipAks` prepares storage, both networks, the VM image and the reference VM (unless disabled). It does not fabricate a group or grant anyone cluster access. The manifest records this as a partial run, and full health checks fail until preparation is rerun without `-SkipAks` using a valid admin-group ID. `-WhatIf` installs missing CLI extensions as a deliberate local-tooling exception, then performs authenticated reads and validation. It does not provision Azure resources, assign roles or change Hyper-V or storage. The RBAC step only reports the planned group/cluster assignment, without querying a cluster that may not exist yet; it does not validate role-assignment permissions. It can create and remove a temporary local CLI profile. It does not establish that later resource creation will succeed. @@ -43,15 +48,26 @@ If the Console-owned group is not available yet, `-SkipAks` explicitly prepares | --- | --- | | Storage | One stable-named 1 TiB dynamic VHDX per node; grow `UserStorage_1` to 1 TiB, never shrink or grow it again on every rerun | | VM image | `2025-datacenter-azure-edition-smalldisk-01`, matching the walkthrough screenshot; Marketplace Windows Server 2025 Azure Edition smalldisk, explicitly on `UserStorage1` | -| VM network | `localbox-vm-lnet-vlan200`, VLAN 200, `192.168.200.0/24`, pool `.10-.199` after reservation review | +| VM network | `localbox-vm-lnet-vlan200`, VLAN 200, `192.168.200.0/24`, pool `.10-.199` for the standard LocalBox configuration | | AKS network | `localbox-aks-lnet-vlan110`, VLAN 110, `10.10.0.0/24`, nodes `.101-.199` | | AKS control plane / service reservation | `10.10.0.5`; `.10-.100` reserved for MetalLB service VIPs, separate from nodes `.101-.199` | | AKS cluster | `localbox-aks`, one control-plane node and three Linux workers; `Standard_A4_v2` defaults, subject to installed-version capacity/CLI validation | | AKS proxy access | Azure Arc Enabled Kubernetes Cluster User Role for `AksAdminGroupObjectId`, scoped to the connected-cluster resource; matching direct or inherited grants are reused | | MetalLB | `arcnetworking` Arc extension (`microsoft.arcnetworking`), ARP pool `aks-pool`, addresses from `AKSVIPStartIP` through `AKSVIPEndIP` (standard config: `10.10.0.10-10.10.0.100`) | +| Reference VM | `sov-ref-vm`, Windows Server 2025, 2 vCPU / 4 GiB RAM, shared VM network and `UserStorage1`; enabled by default, opt out with `-IncludeReferenceVm:$false` | | Output | `C:\LocalBox\sovereign-localbox.json`, nonsecret IDs and expected configuration for health tests | -Topology defaults come from the installed Jumpstart config, except the VM pool bounds and configurable resource names/sizes. The network/broadcast addresses, gateways and Jumpstart infrastructure reservations must not be allocated. `-AddressReservationsConfirmed` is your explicit confirmation that DHCP/static reservations were reviewed; the script cannot safely infer all DHCP leases from Azure. It does not change routers or DHCP configuration. +Topology defaults come from the installed Jumpstart config, except the VM pool bounds and configurable resource names/sizes. Preparation assumes the standard greenfield LocalBox topology and requires no manual address-reservation acknowledgement. It still validates subnet boundaries, gateways, configured infrastructure addresses, and AKS node/control-plane/VIP separation. These checks do not inspect live DHCP leases/exclusions or discover additional static consumers. Review those separately for customized networks; the script does not change routers or DHCP configuration. + +## Reruns and failed image imports + +Rerun with the same settings after resolving a failure. Storage is checked against its **actual state**, not a completion flag: existing disks are not recreated or reattached, and a sufficiently sized virtual disk is not resized. The partition-extension step (including its confirmation and supported-size query) is skipped once the partition fills the disk, allowing for its alignment/GPT tail. If a previous run grew the virtual disk but stopped before extending the partition, the next run completes only that unfinished step. + +Read-only health checks still run, and preparation still waits for active storage jobs before dependent operations. A rerun must not bypass unhealthy or busy storage simply because an earlier run completed. + +If the configured Marketplace image exists in **Failed** state, preparation verifies its region, custom location, storage path and image settings, then **deletes that exact image and retries its import**. Use `-Confirm` to request confirmation first. It verifies that deletion has finished before creating the replacement. A matching deletion already in progress is awaited. `-WhatIf` previews recovery without deleting or importing anything. Healthy/in-progress images are reused or awaited; conflicting or incomplete image metadata requires organizer review and is never deleted automatically. + +There is **one import attempt per run**, not an endless retry loop. If the new attempt also fails, its original CLI error is reported and the failed resource remains available for investigation. For example, `GenerateTokenFromEdgeMarketplaceServiceFailed` with `NotFound` means Edge Marketplace could not issue the image-download SAS token; it does not by itself prove a transient outage. Check the requested image's availability, provider prerequisites and service status before another retry. See [Microsoft's image-management guidance](https://learn.microsoft.com/azure/azure-local/manage/virtual-machine-manage-image#delete-vm-image). ## MetalLB preparation @@ -76,7 +92,7 @@ grant Graph permissions to the Client identity. The VIP reservation is checked against the AKS subnet, gateway, control-plane address and node pool. Do not use `10.10.0.150-10.10.0.160` from older screenshots: -it overlaps the node pool. The address-reservation confirmation includes DHCP and +it overlaps the node pool. For customized networks, also review DHCP exclusions and other static consumers of the service VIPs. Matching extensions and pools are reused. Conflicting identities, address ranges, @@ -122,9 +138,43 @@ Use `-ImageVersion` to pin a Marketplace version, `-KubernetesVersion` for a sup If an image was imported manually before preparation under the documented name but on another storage path, preparation stops before disk expansion. The facilitator must review its dependencies; only if the image is confirmed unused should it be removed manually so preparation can import it on `UserStorage1` under the same documented name. Otherwise, resolve placement with the Azure Local administrator before proceeding. Do not change image names or use `-RemoveUserStorage2` to work around this conflict. Automation never deletes or moves an existing image. +## Reference VM + +Preparation includes one shared Windows VM for Challenge 6 by default, preferably prepared **the day before the event**. Opt out with `-IncludeReferenceVm:$false`. It uses the prepared image, VM network and storage. The fixed VM/computer name `sov-ref-vm` is 10 characters, below Windows' **15-character** limit. Allow for its additional memory, disk growth, address consumption and Defender costs. + +On an **existing LocalBox**, copy the updated [prepare-localbox.ps1](../prepare-localbox.ps1) to the Client (or download it from your published branch/commit). In elevated PowerShell 7, rerun with the same settings used for the original preparation: + +```powershell +./prepare-localbox.ps1 -WhatIf +./prepare-localbox.ps1 +``` + +Supply the existing AKS group ID when prompted, or pass `-AksAdminGroupObjectId`. Preserve any original nondefault image/network/AKS settings. This is an idempotent preparation rerun, **not** a new LocalBox deployment. It still validates/reconciles the normal shared resources; do not use `-SkipAks` just to bypass those checks. + +Creation prompts for a **separate local administrator credential**, not the nested domain administrator. Retain it securely for organizer maintenance; alternatively supply `-ReferenceVmCredential (Get-Credential -UserName 'localadmin')`. The password is passed through a restricted temporary ARM parameter file, never through CLI arguments or the manifest, and the file is removed after submission. Do not share the credential with participants. + +The script requests guest management and **periodic update assessment** (`AutomaticByPlatform`); it does not install patches, toggle Defender or force MDE installation. Defender for Servers P2 and Endpoint protection must already be enabled, and the VM needs outbound access to Arc, Defender and its configured update source. Resources carry `SecurityControl=Ignore`, `CostControl=Ignore` and `MicroHackPurpose=LocalBoxReferenceVM`. + +Matching resources are reused without password resets, resizing or redeployment. Conflicting/partial resources cause an explicit failure for organizer inspection; they are never deleted or overwritten. Reruns with `-IncludeReferenceVm:$false` neither manage nor delete the VM, and preserve its existing manifest entry (including the original observation timestamp). + +### Reference readiness and participant use + +The manifest's `ReferenceVm` entry records nonsecret IDs and an observed power/Arc/MDE status. **Provisioning is not proof of completed onboarding or assessment.** Pending, disconnected or failed states must be resolved before advertising the VM as ready. Health checks reread live status rather than trusting the saved snapshot. + +Before the workshop, verify: + +1. `sov-ref-vm` is running with **Guest management: Enabled (Connected)**. +2. `MDE.Windows` is **Succeeded**; verify actual device reporting through the Microsoft Defender portal with an authorized organizer account. +3. Defender for Cloud has assessment evidence for this VM. Review available recommendations or a completed assessment with no findings; an empty list alone is not sufficient. +4. Azure Update Manager shows a **successful assessment and timestamp**. Periodic assessment is asynchronous; an organizer can run **Check for updates** on this VM if no result is available yet. Do not install patches just to prepare the demonstration. + +The script intentionally leaves Defender assessment and update-result readiness as `NotVerified`; these portal checks remain required even after automated health tests pass. See the [Defender readiness checklist](manual-preparation.md#defender-for-servers-readiness-organizer). + +Participants still create and assess **their own VM**. While waiting, they may inspect this reference VM's existing security and update results. **Do not modify, assess, patch, restart, remediate or delete the shared reference VM.** This is an instruction, **not an RBAC-enforced read-only boundary**: existing shared resource-group VM Contributor grants remain unchanged. Verify participant visibility before the event; do not grant access to LocalBox-Client or publish the reference administrator credential. + ## Optional storage consolidation -On a new, empty environment, pass `-RemoveUserStorage2` to request removal of the unused second Azure storage path, CSV and virtual disk. Each destructive operation requires confirmation. The script refuses consolidation when storage is unhealthy/busy, the volume is not empty, its identity is ambiguous, or existing LocalBox workload resources are found. Do this **before** image/AKS creation. Never use confirmation suppression to override a failed safety check. +On a new, empty environment, pass `-RemoveUserStorage2` to request removal of the unused second Azure storage path, CSV and virtual disk. This remains opt-in; use `-WhatIf` to preview or `-Confirm` to request confirmation. The script refuses consolidation when storage is unhealthy/busy, the volume is not empty, its identity is ambiguous, or existing LocalBox workload resources are found. Do this **before** image/AKS creation. Confirmation suppression does not override a failed safety check. If existing workloads use the secondary volume, do not remove it. Use the [manual reference](manual-preparation.md) with an Azure Local administrator to assess storage and placement. Image placement is explicitly `UserStorage1`; retaining `UserStorage2` can still affect other workload placement and requires the participant VM smoke test. diff --git a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/prepare-localbox.ps1 b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/prepare-localbox.ps1 index 15b25cd35..4a7475566 100644 --- a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/prepare-localbox.ps1 +++ b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/prepare-localbox.ps1 @@ -14,6 +14,12 @@ Kubernetes Cluster User Role for proxy access, reusing applicable existing grant It also installs the MetalLB Arc extension and an ARP pool from the configured service VIP reservation. The Runtime provider object ID comes from the organizer, not a Graph lookup using the Client managed identity. +IncludeReferenceVm defaults to true and creates sov-ref-vm for participant inspection, +with a separate local credential, guest management and periodic update assessment. +Operations run without confirmation under the default PowerShell preference; +use Confirm to request prompts or WhatIf to preview changes. +Assumes the standard greenfield LocalBox network configuration. Configured address +ranges and known reservations are validated; live DHCP leases are not inspected. Dot-source this file to load its functions without running provisioning. #> [CmdletBinding(SupportsShouldProcess)] @@ -24,6 +30,8 @@ param( [string]$AksAdminGroupObjectId, [string]$KubernetesRuntimeObjectId, [switch]$SkipAks, + [switch]$IncludeReferenceVm = $true, + [pscredential]$ReferenceVmCredential, [pscredential]$NodeCredential, [string]$VmSwitchName, [string]$ImageName = '2025-datacenter-azure-edition-smalldisk-01', @@ -37,7 +45,6 @@ param( [ValidateRange(1, 10)][int]$NodeCount = 3, [ValidateRange(512, 4096)][int]$StorageSizeGB = 1024, [switch]$RemoveUserStorage2, - [switch]$AddressReservationsConfirmed, [ValidateRange(1, 720)][int]$TimeoutMinutes = 360, [string]$ManifestPath = 'C:\LocalBox\sovereign-localbox.json' ) @@ -202,9 +209,215 @@ function Get-LocalBoxResource { elseif ($Id -match '/providers/Microsoft\.KubernetesRuntime/loadBalancers/') { $arguments += @('--api-version', '2024-08-01') } + elseif ($Id -match '/providers/Microsoft\.AzureStackHCI/virtualMachineInstances/') { + $arguments += @('--api-version', '2024-01-01') + } + elseif ($Id -match '/providers/Microsoft\.HybridCompute/machines/') { + $arguments += @('--api-version', '2024-07-10') + } Invoke-LocalBoxAz $arguments } +function New-LocalBoxVmTemplate { + param([switch]$EnablePeriodicAssessment) + $machineId = "[resourceId('Microsoft.HybridCompute/machines', parameters('name'))]" + $nicId = "[resourceId('Microsoft.AzureStackHCI/networkInterfaces', concat(parameters('name'), '-nic'))]" + $extendedLocation = @{ type = 'CustomLocation'; name = "[parameters('customLocationId')]" } + $machine = @{ + type = 'Microsoft.HybridCompute/machines'; apiVersion = '2023-10-03-preview' + name = "[parameters('name')]"; location = "[parameters('location')]" + kind = 'HCI'; identity = @{ type = 'SystemAssigned' }; tags = "[parameters('tags')]" + } + if ($EnablePeriodicAssessment) { + $machine.apiVersion = '2024-07-10' + $machine.properties = @{ osProfile = @{ windowsConfiguration = @{ + patchSettings = @{ assessmentMode = 'AutomaticByPlatform' } + } } } + } + @{ + '$schema' = 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#' + contentVersion = '1.0.0.0' + parameters = @{ + name = @{ type = 'string'; minLength = 1; maxLength = 15 }; location = @{ type = 'string' } + customLocationId = @{ type = 'string' }; imageId = @{ type = 'string' } + networkId = @{ type = 'string' }; storageId = @{ type = 'string' } + processors = @{ type = 'int' }; memoryMB = @{ type = 'int' } + adminUsername = @{ type = 'string' }; adminPassword = @{ type = 'securestring' } + tags = @{ type = 'object' } + } + resources = @( + $machine + @{ + type = 'Microsoft.AzureStackHCI/networkInterfaces'; apiVersion = '2024-01-01' + name = "[concat(parameters('name'), '-nic')]"; location = "[parameters('location')]" + extendedLocation = $extendedLocation; tags = "[parameters('tags')]" + properties = @{ ipConfigurations = @(@{ + name = 'ipconfig1'; properties = @{ subnet = @{ id = "[parameters('networkId')]" } } + }) } + } + @{ + type = 'Microsoft.AzureStackHCI/virtualMachineInstances'; apiVersion = '2024-01-01' + name = 'default'; scope = $machineId; extendedLocation = $extendedLocation + dependsOn = @($machineId, $nicId) + properties = @{ + hardwareProfile = @{ vmSize = 'Custom'; processors = "[parameters('processors')]"; memoryMB = "[parameters('memoryMB')]" } + osProfile = @{ + adminUsername = "[parameters('adminUsername')]"; adminPassword = "[parameters('adminPassword')]" + computerName = "[parameters('name')]" + windowsConfiguration = @{ provisionVMAgent = $true; provisionVMConfigAgent = $true } + } + storageProfile = @{ vmConfigStoragePathId = "[parameters('storageId')]"; imageReference = @{ id = "[parameters('imageId')]" } } + networkProfile = @{ networkInterfaces = @(@{ id = $nicId }) } + } + } + ) + } +} + +function Submit-LocalBoxVM { + param([hashtable]$State, [hashtable]$Entry, [pscredential]$Credential, [hashtable]$Tags, [switch]$EnablePeriodicAssessment) + if ($Entry.Name -notmatch '^(?=.{1,15}$)(?=.*[a-zA-Z])[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?$') { + throw 'VM computer name must contain 1-15 letters, digits or internal hyphens, and cannot be all numeric.' + } + $directory = Join-Path ([IO.Path]::GetTempPath()) "lbvm-$([guid]::NewGuid())" + try { + if ($IsWindows) { + New-Item -ItemType Directory -Path $directory -ErrorAction Stop | Out-Null + $acl = Get-Acl -LiteralPath $directory + $acl.SetAccessRuleProtection($true, $false) + $identity = [Security.Principal.WindowsIdentity]::GetCurrent().User + $rule = [Security.AccessControl.FileSystemAccessRule]::new($identity, 'FullControl', 'ContainerInherit, ObjectInherit', 'None', 'Allow') + $acl.SetAccessRule($rule) + Set-Acl -LiteralPath $directory -AclObject $acl -ErrorAction Stop + } + else { [IO.Directory]::CreateDirectory($directory, [IO.UnixFileMode]::UserRead -bor [IO.UnixFileMode]::UserWrite -bor [IO.UnixFileMode]::UserExecute) | Out-Null } + $templatePath = Join-Path $directory 'vm.json' + New-LocalBoxVmTemplate -EnablePeriodicAssessment:$EnablePeriodicAssessment | ConvertTo-Json -Depth 30 | + Set-Content -LiteralPath $templatePath -ErrorAction Stop + $values = @{ + name = $Entry.Name; location = $State.Location; customLocationId = $State.CustomLocationId + imageId = $State.ImageId; networkId = $State.NetworkId; storageId = $State.StorageId + processors = $State.ProcessorCount; memoryMB = $State.MemoryMB + adminUsername = $Credential.UserName; adminPassword = $Credential.GetNetworkCredential().Password + tags = $Tags + } + $parameters = @{} + foreach ($key in $values.Keys) { $parameters[$key] = @{ value = $values[$key] } } + $parameterPath = Join-Path $directory 'parameters.json' + @{ '$schema' = 'https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#'; contentVersion = '1.0.0.0'; parameters = $parameters } | + ConvertTo-Json -Depth 10 | Set-Content -LiteralPath $parameterPath -ErrorAction Stop + $arguments = @('--subscription', $State.SubscriptionId, '--resource-group', $State.ResourceGroupName, + '--name', $Entry.Name, '--template-file', $templatePath, '--parameters', "@$parameterPath") + $preview = Invoke-LocalBoxAz (@('deployment', 'group', 'what-if') + $arguments + @('--no-pretty-print', '--result-format', 'ResourceIdOnly')) + $machineId = "/subscriptions/$($State.SubscriptionId)/resourceGroups/$($State.ResourceGroupName)/providers/Microsoft.HybridCompute/machines/$($Entry.Name)" + $allowed = @($machineId, "$machineId/providers/Microsoft.AzureStackHCI/virtualMachineInstances/default", + "/subscriptions/$($State.SubscriptionId)/resourceGroups/$($State.ResourceGroupName)/providers/Microsoft.AzureStackHCI/networkInterfaces/$($Entry.Name)-nic") + $creates = @($preview.changes | Where-Object changeType -eq 'Create') + $unsafe = @($preview.changes | Where-Object { + ($_.resourceId -iin $allowed -and $_.changeType -ne 'Create') -or + ($_.resourceId -inotIn $allowed -and $_.changeType -notin @('Ignore', 'NoChange')) + }) + if ($preview.status -ne 'Succeeded' -or $creates.Count -ne 3 -or + @($creates.resourceId | Sort-Object -Unique).Count -ne 3 -or $unsafe.Count) { + $Entry.SubmissionError = 'ARM what-if did not confirm exactly the three new VM resources with no changes to existing resources.' + throw $Entry.SubmissionError + } + Invoke-LocalBoxAz (@('deployment', 'group', 'create') + $arguments + @('--no-wait')) -NoOutput + } + catch { throw "VM submission failed for $($Entry.Name). Inspect its ARM deployment; resources may still be provisioning. CLI details are withheld to protect the VM credential." } + finally { + $values = $null + $parameters = $null + if (Test-Path -LiteralPath $directory) { Remove-Item -LiteralPath $directory -Recurse -Force -ErrorAction Stop } + } +} + +function Get-LocalBoxReferenceVmReadiness { + param([string]$MachineId) + $machine = Get-LocalBoxResource $MachineId + $instance = Get-LocalBoxResource "$MachineId/providers/Microsoft.AzureStackHCI/virtualMachineInstances/default" + if ($machine.properties -isnot [System.Collections.IDictionary] -or -not $instance.properties.status.powerState) { + throw "Reference VM $MachineId returned incomplete machine/power data. Inspect its provisioning state." + } + $extensions = @(Get-LocalBoxArmCollection "https://management.azure.com$MachineId/extensions?api-version=2024-07-10") + $mde = @($extensions | Where-Object { $_.properties.publisher -eq 'Microsoft.Azure.AzureDefenderForServers' -and $_.properties.type -eq 'MDE.Windows' }) + if ($mde.Count -gt 1 -or ($mde.Count -eq 1 -and -not $mde[0].properties.provisioningState)) { + throw "Reference VM $MachineId returned unexpected MDE extension status." + } + @{ + ObservedAt = [datetime]::UtcNow.ToString('o') + PowerState = $instance.properties.status.powerState + ArcStatus = if ($machine.properties.status) { $machine.properties.status } else { 'NotReported' } + MdeProvisioningState = if ($mde.Count) { $mde[0].properties.provisioningState } else { 'Pending' } + PeriodicAssessmentMode = $machine.properties.osProfile.windowsConfiguration.patchSettings.assessmentMode + DefenderAssessments = 'NotVerified' + UpdateAssessmentResults = 'NotVerified' + } +} + +function Sync-LocalBoxReferenceVm { + [CmdletBinding(SupportsShouldProcess)] + param([hashtable]$Settings, [string]$Location, [string]$CustomLocationId, [string]$ImageId, [string]$NetworkId, [string]$StorageId) + if (-not $Settings.IncludeReferenceVm) { return } + $name = 'sov-ref-vm' + $scope = "/subscriptions/$($Settings.SubscriptionId)/resourceGroups/$($Settings.ResourceGroupName)" + $machineId = "$scope/providers/Microsoft.HybridCompute/machines/$name" + $nicId = "$scope/providers/Microsoft.AzureStackHCI/networkInterfaces/$name-nic" + $instanceId = "$machineId/providers/Microsoft.AzureStackHCI/virtualMachineInstances/default" + $tags = @{ MicroHackPurpose = 'LocalBoxReferenceVM'; SecurityControl = 'Ignore'; CostControl = 'Ignore' } + $expected = @{ + extendedLocation = @{ name = $CustomLocationId } + properties = @{ + hardwareProfile = @{ vmSize = 'Custom'; processors = 2; memoryMB = 4096 } + osProfile = @{ computerName = $name; windowsConfiguration = @{ provisionVMAgent = $true; provisionVMConfigAgent = $true } } + storageProfile = @{ vmConfigStoragePathId = $StorageId; imageReference = @{ id = $ImageId } } + networkProfile = @{ networkInterfaces = @(@{ id = $nicId }) } + } + } + $machineExpected = @{ kind = 'HCI'; location = $Location; tags = $tags; identity = @{ type = 'SystemAssigned' } + properties = @{ osProfile = @{ windowsConfiguration = @{ patchSettings = @{ assessmentMode = 'AutomaticByPlatform' } } } } } + $nicExpected = @{ location = $Location; extendedLocation = @{ name = $CustomLocationId }; tags = $tags + properties = @{ ipConfigurations = @(@{ name = 'ipconfig1'; properties = @{ subnet = @{ id = $NetworkId } } }) } } + $resources = @(Invoke-LocalBoxAz @('resource', 'list', '--subscription', $Settings.SubscriptionId, '--resource-group', $Settings.ResourceGroupName)) + $existing = @($resources | Where-Object id -ieq $machineId) + if ($existing.Count) { + Assert-LocalBoxProperties (Get-LocalBoxResource $machineId) $machineExpected + Assert-LocalBoxProperties (Get-LocalBoxResource $nicId) $nicExpected + Assert-LocalBoxProperties (Get-LocalBoxResource $instanceId) $expected + Write-Host "Reusing reference VM $name without changing credentials or configuration." + } + elseif (@($resources | Where-Object { $_.id -iin @($nicId, $instanceId) }).Count) { + throw 'Partial or conflicting reference-VM resources exist. Inspect them before retrying; preparation will not overwrite or delete them.' + } + elseif ($PSCmdlet.ShouldProcess($machineId, 'Create shared reference VM (2 vCPU, 4 GiB RAM, guest management and periodic update assessment)')) { + $credential = $Settings.ReferenceVmCredential + if (-not $credential) { + $credential = Get-Credential -UserName 'localadmin' -Message 'Separate local administrator for sov-ref-vm; retain securely and do not share with participants' + } + if (-not $credential) { throw 'A separate reference-VM credential is required; nested domain credentials are not reused.' } + $state = @{ + SubscriptionId = $Settings.SubscriptionId; ResourceGroupName = $Settings.ResourceGroupName; Location = $Location + CustomLocationId = $CustomLocationId; ImageId = $ImageId; NetworkId = $NetworkId; StorageId = $StorageId + ProcessorCount = 2; MemoryMB = 4096 + } + Submit-LocalBoxVM -State $state -Entry @{ Name = $name } -Credential $credential -Tags $tags -EnablePeriodicAssessment + Wait-LocalBoxResource "$scope/providers/Microsoft.Resources/deployments/$name" -TimeoutSeconds ($Settings.TimeoutMinutes * 60) | Out-Null + } + else { return } + if ($WhatIfPreference) { return } + Assert-LocalBoxProperties (Wait-LocalBoxResource $instanceId -TimeoutSeconds ($Settings.TimeoutMinutes * 60)) $expected + Assert-LocalBoxProperties (Get-LocalBoxResource $machineId) $machineExpected + Assert-LocalBoxProperties (Get-LocalBoxResource $nicId) $nicExpected + $readiness = Get-LocalBoxReferenceVmReadiness $machineId + Write-Host "Reference VM: power=$($readiness.PowerState), Arc=$($readiness.ArcStatus), MDE=$($readiness.MdeProvisioningState)." + Write-Warning 'Reference VM is for inspection only, not enforced read-only by RBAC. Verify Defender and Update Manager assessment results before the event; provisioning does not prove assessment completion.' + @{ + Name = $name; MachineId = $machineId; InstanceId = $instanceId; NicId = $nicId + Expected = $expected; MachineExpected = $machineExpected; NicExpected = $nicExpected + Readiness = $readiness + } +} + function Resolve-LocalBoxStoragePath { param( [array]$Resources, @@ -276,6 +489,41 @@ function Sync-LocalBoxResource { } } +function Sync-LocalBoxImage { + [CmdletBinding(SupportsShouldProcess)] + param([string]$Id, [hashtable]$Expected, [string[]]$CreateArguments, [int]$TimeoutSeconds = 21600) + if ($Id -notmatch '^/subscriptions/([^/]+)/resourceGroups/([^/]+)/providers/Microsoft\.AzureStackHCI/marketplaceGalleryImages/[^/]+$') { + throw 'Image recovery requires an exact Azure Local Marketplace image resource ID.' + } + $listArguments = @('resource', 'list', '--subscription', $Matches[1], '--resource-group', $Matches[2]) + $existing = @((Invoke-LocalBoxAz $listArguments) | Where-Object id -ieq $Id) + if ($existing.Count) { + $image = Get-LocalBoxResource $Id + if ($image.properties.provisioningState -in @('Failed', 'Deleting')) { + Assert-LocalBoxProperties $image $Expected + if ($image.properties.provisioningState -eq 'Failed') { + Write-Warning "Image import failed for $Id. Review its deployment error before retrying; recovery does not fix Marketplace availability or configuration errors." + if (-not $PSCmdlet.ShouldProcess($Id, 'Delete matching Failed image and retry import once')) { + if ($WhatIfPreference) { return } + throw 'Failed-image recovery was declined. No image was deleted; preparation cannot continue with this image.' + } + Invoke-LocalBoxAz @('resource', 'delete', '--ids', $Id, '--api-version', '2024-01-01') -TimeoutSeconds $TimeoutSeconds -NoOutput + } + if ($WhatIfPreference) { + Write-Host "Image deletion is in progress; a real run will wait for removal before importing $Id." + return + } + $deadline = [datetime]::UtcNow.AddSeconds($TimeoutSeconds) + while (@((Invoke-LocalBoxAz $listArguments) | Where-Object id -ieq $Id).Count) { + if ([datetime]::UtcNow -ge $deadline) { throw "Timed out waiting for image deletion: $Id. No replacement import was started." } + Write-Host "Waiting for failed image removal: $Id..." + Start-Sleep -Seconds 15 + } + } + } + Sync-LocalBoxResource -Id $Id -Expected $Expected -CreateArguments $CreateArguments -TimeoutSeconds $TimeoutSeconds -WhatIf:$WhatIfPreference +} + function Get-LocalBoxMetalLbPlan { param([Parameter(Mandatory)][hashtable]$Configuration, [Parameter(Mandatory)][string]$ClusterId) if ($ClusterId -notmatch '^/subscriptions/[^/]+/resourceGroups/[^/]+/providers/Microsoft\.Kubernetes/connectedClusters/[^/]+$') { @@ -496,8 +744,31 @@ function Wait-LocalBoxStorageReady { } while ($true) } +function Get-LocalBoxPartitionState { + param([string]$Node, [pscredential]$Credential) + $state = Invoke-Command -VMName $Node -Credential $Credential -ErrorAction Stop -ScriptBlock { + $ErrorActionPreference = 'Stop' + $partitions = @(Get-Volume -FileSystemLabel 'UserStorage_1' | Get-Partition) + if ($partitions.Count -ne 1) { throw 'Cannot uniquely resolve the UserStorage_1 partition.' } + $partition = $partitions[0] + $disk = $partition | Get-Disk + if ($partition.Size -le 0 -or $partition.Offset -lt 0 -or $disk.Size -lt ($partition.Offset + $partition.Size)) { + throw 'Invalid UserStorage_1 disk/partition sizes; refusing partition expansion.' + } + # Ignore the alignment/GPT tail, without running the costly supported-size query on every rerun. + @{ + Size = $partition.Size + NeedsExpansion = ($disk.Size - $partition.Offset - $partition.Size) -gt 1MB + } + } + if ($state.NeedsExpansion -isnot [bool] -or $state.Size -le 0) { + throw 'No valid UserStorage_1 partition state was returned; preparation cannot safely skip or perform expansion.' + } + return $state +} + function Initialize-LocalBoxStorage { - [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] + [CmdletBinding(SupportsShouldProcess)] param( [hashtable]$Configuration, [pscredential]$Credential, [int]$DesiredSizeGB, [string]$Subscription, [string]$ResourceGroup, [switch]$RemoveSecondary @@ -560,15 +831,24 @@ function Initialize-LocalBoxStorage { throw 'Insufficient supported storage capacity including resiliency and reserve. No resize performed.' } Resize-VirtualDisk -FriendlyName 'UserStorage_1' -Size $target + Update-HostStorageCache -ErrorAction Stop } } - if ($PSCmdlet.ShouldProcess('UserStorage_1 partition', 'Extend partition to existing virtual disk size')) { + $partitionState = Get-LocalBoxPartitionState $nodes[0] $Credential + $needsExpansion = $partitionState.NeedsExpansion -or ($WhatIfPreference -and $state.Size -lt ([uint64]$DesiredSizeGB * 1GB)) + if ($needsExpansion -and $PSCmdlet.ShouldProcess('UserStorage_1 partition', 'Extend partition to existing virtual disk size')) { Invoke-Command -VMName $nodes[0] -Credential $Credential -ErrorAction Stop -ScriptBlock { $ErrorActionPreference = 'Stop' $partition = Get-Volume -FileSystemLabel 'UserStorage_1' | Get-Partition $maximum = ($partition | Get-PartitionSupportedSize).SizeMax if ($maximum -gt $partition.Size) { $partition | Resize-Partition -Size $maximum } } + if ((Get-LocalBoxPartitionState $nodes[0] $Credential).NeedsExpansion) { + throw 'UserStorage_1 still has unexpanded partition space. Inspect the disk/partition state before retrying.' + } + } + elseif (-not $needsExpansion) { + Write-Host 'UserStorage_1 partition already fills the disk; no partition expansion needed.' } } @@ -593,9 +873,6 @@ function Invoke-LocalBoxPreparation { Test-LocalBoxAddressPool $config.AKSIPPrefix $config.AKSNodeStartIP $config.AKSNodeEndIP $config.AKSGWIP @($config.AKSControlPlaneIP) -ReservedRanges @($vipRange) Test-LocalBoxAddressPool $config.AKSIPPrefix $config.AKSControlPlaneIP $config.AKSControlPlaneIP $config.AKSGWIP -ReservedRanges @($vipRange) if ($config.vmVLAN -eq $config.AKSVLAN -or $config.vmIpPrefix -eq $config.AKSIPPrefix) { throw 'VMs and AKS must use separate VLANs and subnets.' } - if (-not $Settings.AddressReservationsConfirmed) { - throw 'Verify BOTH VM/node pools and the AKS service VIP reservation against DHCP leases/exclusions and static reservations, then pass -AddressReservationsConfirmed. The script does not alter DHCP/router configuration.' - } $Settings.NodeCredential = Resolve-LocalBoxNodeCredential -Configuration $config -Credential $Settings.NodeCredential $previousConfig = $env:AZURE_CONFIG_DIR $previousExtensions = $env:AZURE_EXTENSION_DIR @@ -657,8 +934,6 @@ function Invoke-LocalBoxPreparation { } if ($Settings.VmSwitchName -notin $state.Switches) { throw "External switch not present on $node." } } - $provider = Invoke-LocalBoxAz @('provider', 'show', '--namespace', 'Microsoft.EdgeMarketplace') - if ($provider.registrationState -ne 'Registered') { throw 'Ask the subscription administrator to register Microsoft.EdgeMarketplace before importing the image.' } $storage = Resolve-LocalBoxStoragePath -Resources $resources -Name UserStorage1 -CustomLocationId $custom.id $storageId = $storage.id $imageId = "$scope/providers/Microsoft.AzureStackHCI/marketplaceGalleryImages/$($Settings.ImageName)" @@ -671,17 +946,6 @@ function Invoke-LocalBoxPreparation { $location = $custom.location $timeout = $Settings.TimeoutMinutes * 60 $common = @('--subscription', $Settings.SubscriptionId, '--resource-group', $Settings.ResourceGroupName, '--location', $location, '--custom-location', $custom.id) - $imageExpected = @{ - extendedLocation = @{ name = $custom.id }; location = $location - properties = @{ containerId = $storageId; osType = 'Windows'; identifier = @{ - publisher = 'microsoftwindowsserver'; offer = 'windowsserver'; sku = '2025-datacenter-azure-edition-smalldisk' - } } - } - if ($Settings.ImageVersion -ne 'latest') { $imageExpected.properties.version = @{ name = $Settings.ImageVersion } } - Write-Host "Preparing VM image $($Settings.ImageName); download can take several hours..." - $image = Sync-LocalBoxResource -Id $imageId -Expected $imageExpected -TimeoutSeconds $timeout -CreateArguments (@('stack-hci-vm', 'image', 'create') + $common + @( - '--name', $Settings.ImageName, '--os-type', 'Windows', '--publisher', 'microsoftwindowsserver', '--offer', 'windowsserver', - '--sku', '2025-datacenter-azure-edition-smalldisk', '--version', $Settings.ImageVersion, '--storage-path-id', $storageId)) $networks = @( @{ Name = 'localbox-vm-lnet-vlan200'; Prefix = $config.vmIpPrefix; Gateway = $config.vmGateway; Dns = $config.vmDNS; Vlan = $config.vmVLAN; Start = $Settings.VmPoolStart; End = $Settings.VmPoolEnd }, @{ Name = 'localbox-aks-lnet-vlan110'; Prefix = $config.AKSIPPrefix; Gateway = $config.AKSGWIP; Dns = $config.AKSDNSIP; Vlan = $config.AKSVLAN; Start = $config.AKSNodeStartIP; End = $config.AKSNodeEndIP } @@ -739,6 +1003,21 @@ function Invoke-LocalBoxPreparation { Sync-LocalBoxMetalLb -ClusterId $aksId -Plan $metalLb -RuntimeObjectId $Settings.KubernetesRuntimeObjectId ` -TimeoutSeconds $timeout -WhatIf:$WhatIfPreference } + $provider = Invoke-LocalBoxAz @('provider', 'show', '--namespace', 'Microsoft.EdgeMarketplace') + if ($provider.registrationState -ne 'Registered') { throw 'Ask the subscription administrator to register Microsoft.EdgeMarketplace before importing the image.' } + $imageExpected = @{ + extendedLocation = @{ name = $custom.id }; location = $location + properties = @{ containerId = $storageId; osType = 'Windows'; identifier = @{ + publisher = 'microsoftwindowsserver'; offer = 'windowsserver'; sku = '2025-datacenter-azure-edition-smalldisk' + } } + } + if ($Settings.ImageVersion -ne 'latest') { $imageExpected.properties.version = @{ name = $Settings.ImageVersion } } + Write-Host "Preparing VM image $($Settings.ImageName); download can take several hours..." + $image = Sync-LocalBoxImage -Id $imageId -Expected $imageExpected -TimeoutSeconds $timeout -CreateArguments (@('stack-hci-vm', 'image', 'create') + $common + @( + '--name', $Settings.ImageName, '--os-type', 'Windows', '--publisher', 'microsoftwindowsserver', '--offer', 'windowsserver', + '--sku', '2025-datacenter-azure-edition-smalldisk', '--version', $Settings.ImageVersion, '--storage-path-id', $storageId)) + $referenceVm = Sync-LocalBoxReferenceVm -Settings $Settings -Location $location -CustomLocationId $custom.id ` + -ImageId $imageId -NetworkId $networks[0].Id -StorageId $storageId -WhatIf:$WhatIfPreference $manifest = @{ SubscriptionId = $Settings.SubscriptionId; ResourceGroupName = $Settings.ResourceGroupName; CustomLocationId = $custom.id ClusterId = $cluster[0].id; BridgeId = $bridge.id; ExtensionId = $extensions[0]; StorageId = $storageId @@ -749,6 +1028,14 @@ function Invoke-LocalBoxPreparation { AksPreparationSkipped = [bool]$Settings.SkipAks MetalLb = $metalLb } + if ($referenceVm) { $manifest.ReferenceVm = $referenceVm } + elseif (Test-Path -LiteralPath $Settings.ManifestPath) { + $previousManifest = Get-Content -LiteralPath $Settings.ManifestPath -Raw -ErrorAction Stop | ConvertFrom-Json -AsHashtable + if ($previousManifest.SubscriptionId -ieq $Settings.SubscriptionId -and + $previousManifest.ResourceGroupName -ieq $Settings.ResourceGroupName -and $previousManifest.ReferenceVm) { + $manifest.ReferenceVm = $previousManifest.ReferenceVm + } + } if ($PSCmdlet.ShouldProcess($Settings.ManifestPath, 'Write nonsecret preparation manifest')) { $manifest | ConvertTo-Json -Depth 30 | Set-Content -LiteralPath $Settings.ManifestPath -Encoding utf8 } @@ -770,10 +1057,11 @@ if ($MyInvocation.InvocationName -ne '.') { SubscriptionId = $SubscriptionId; ResourceGroupName = $ResourceGroupName; ConfigPath = $ConfigPath AksAdminGroupObjectId = $AksAdminGroupObjectId; KubernetesRuntimeObjectId = $KubernetesRuntimeObjectId SkipAks = [bool]$SkipAks; NodeCredential = $NodeCredential; VmSwitchName = $VmSwitchName + IncludeReferenceVm = [bool]$IncludeReferenceVm; ReferenceVmCredential = $ReferenceVmCredential ImageName = $ImageName; ImageVersion = $ImageVersion; VmPoolStart = $VmPoolStart; VmPoolEnd = $VmPoolEnd AksClusterName = $AksClusterName; KubernetesVersion = $KubernetesVersion; NodeVmSize = $NodeVmSize ControlPlaneVmSize = $ControlPlaneVmSize; NodeCount = $NodeCount; StorageSizeGB = $StorageSizeGB - RemoveUserStorage2 = [bool]$RemoveUserStorage2; AddressReservationsConfirmed = [bool]$AddressReservationsConfirmed + RemoveUserStorage2 = [bool]$RemoveUserStorage2 TimeoutMinutes = $TimeoutMinutes; ManifestPath = $ManifestPath } Invoke-LocalBoxPreparation -Settings $settings diff --git a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/test-localbox-capacity.ps1 b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/test-localbox-capacity.ps1 index 3b125e7ae..206fd7d8f 100644 --- a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/test-localbox-capacity.ps1 +++ b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/test-localbox-capacity.ps1 @@ -135,54 +135,6 @@ function Assert-LocalBoxCapacityHeadroom { } } -function New-LocalBoxCapacityTemplate { - $machineId = "[resourceId('Microsoft.HybridCompute/machines', parameters('name'))]" - $nicId = "[resourceId('Microsoft.AzureStackHCI/networkInterfaces', concat(parameters('name'), '-nic'))]" - $extendedLocation = @{ type = 'CustomLocation'; name = "[parameters('customLocationId')]" } - @{ - '$schema' = 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#' - contentVersion = '1.0.0.0' - parameters = @{ - name = @{ type = 'string'; maxLength = 15 }; location = @{ type = 'string' } - customLocationId = @{ type = 'string' }; imageId = @{ type = 'string' } - networkId = @{ type = 'string' }; storageId = @{ type = 'string' } - processors = @{ type = 'int' }; memoryMB = @{ type = 'int' } - adminUsername = @{ type = 'string' }; adminPassword = @{ type = 'securestring' } - tags = @{ type = 'object' } - } - resources = @( - @{ - type = 'Microsoft.HybridCompute/machines'; apiVersion = '2023-10-03-preview' - name = "[parameters('name')]"; location = "[parameters('location')]" - kind = 'HCI'; identity = @{ type = 'SystemAssigned' }; tags = "[parameters('tags')]" - } - @{ - type = 'Microsoft.AzureStackHCI/networkInterfaces'; apiVersion = '2024-01-01' - name = "[concat(parameters('name'), '-nic')]"; location = "[parameters('location')]" - extendedLocation = $extendedLocation; tags = "[parameters('tags')]" - properties = @{ ipConfigurations = @(@{ - name = 'ipconfig1'; properties = @{ subnet = @{ id = "[parameters('networkId')]" } } - }) } - } - @{ - type = 'Microsoft.AzureStackHCI/virtualMachineInstances'; apiVersion = '2024-01-01' - name = 'default'; scope = $machineId; extendedLocation = $extendedLocation - dependsOn = @($machineId, $nicId) - properties = @{ - hardwareProfile = @{ vmSize = 'Custom'; processors = "[parameters('processors')]"; memoryMB = "[parameters('memoryMB')]" } - osProfile = @{ - adminUsername = "[parameters('adminUsername')]"; adminPassword = "[parameters('adminPassword')]" - computerName = "[parameters('name')]" - windowsConfiguration = @{ provisionVMAgent = $true; provisionVMConfigAgent = $true } - } - storageProfile = @{ vmConfigStoragePathId = "[parameters('storageId')]"; imageReference = @{ id = "[parameters('imageId')]" } } - networkProfile = @{ networkInterfaces = @(@{ id = $nicId }) } - } - } - ) - } -} - function Save-LocalBoxCapacityState { param([hashtable]$State, [string]$Path) $State.UpdatedAt = [datetime]::UtcNow.ToString('o') @@ -231,56 +183,8 @@ function Assert-LocalBoxCapacityOwnership { function Submit-LocalBoxCapacityVM { param([hashtable]$State, [hashtable]$Entry, [pscredential]$Credential) - $directory = Join-Path ([IO.Path]::GetTempPath()) "lbcap-$([guid]::NewGuid())" - try { - if ($IsWindows) { - New-Item -ItemType Directory -Path $directory -ErrorAction Stop | Out-Null - $acl = Get-Acl -LiteralPath $directory - $acl.SetAccessRuleProtection($true, $false) - $identity = [Security.Principal.WindowsIdentity]::GetCurrent().User - $rule = [Security.AccessControl.FileSystemAccessRule]::new($identity, 'FullControl', 'ContainerInherit, ObjectInherit', 'None', 'Allow') - $acl.SetAccessRule($rule) - Set-Acl -LiteralPath $directory -AclObject $acl -ErrorAction Stop - } - else { [IO.Directory]::CreateDirectory($directory, [IO.UnixFileMode]::UserRead -bor [IO.UnixFileMode]::UserWrite -bor [IO.UnixFileMode]::UserExecute) | Out-Null } - $templatePath = Join-Path $directory 'vm.json' - New-LocalBoxCapacityTemplate | ConvertTo-Json -Depth 30 | Set-Content -LiteralPath $templatePath -ErrorAction Stop - $values = @{ - name = $Entry.Name; location = $State.Location; customLocationId = $State.CustomLocationId - imageId = $State.ImageId; networkId = $State.NetworkId; storageId = $State.StorageId - processors = $State.ProcessorCount; memoryMB = $State.MemoryMB - adminUsername = $Credential.UserName; adminPassword = $Credential.GetNetworkCredential().Password - tags = @{ MicroHackPurpose = 'LocalBoxCapacityTest'; MicroHackRunId = $State.RunId } - } - $parameters = @{} - foreach ($key in $values.Keys) { $parameters[$key] = @{ value = $values[$key] } } - $parameterPath = Join-Path $directory 'parameters.json' - @{ '$schema' = 'https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#'; contentVersion = '1.0.0.0'; parameters = $parameters } | - ConvertTo-Json -Depth 10 | Set-Content -LiteralPath $parameterPath -ErrorAction Stop - $arguments = @('--subscription', $State.SubscriptionId, '--resource-group', $State.ResourceGroupName, - '--name', $Entry.Name, '--template-file', $templatePath, '--parameters', "@$parameterPath") - $preview = Invoke-LocalBoxAz (@('deployment', 'group', 'what-if') + $arguments + @('--no-pretty-print', '--result-format', 'ResourceIdOnly')) - $machineId = "/subscriptions/$($State.SubscriptionId)/resourceGroups/$($State.ResourceGroupName)/providers/Microsoft.HybridCompute/machines/$($Entry.Name)" - $allowed = @($machineId, "$machineId/providers/Microsoft.AzureStackHCI/virtualMachineInstances/default", - "/subscriptions/$($State.SubscriptionId)/resourceGroups/$($State.ResourceGroupName)/providers/Microsoft.AzureStackHCI/networkInterfaces/$($Entry.Name)-nic") - $creates = @($preview.changes | Where-Object changeType -eq 'Create') - $unsafe = @($preview.changes | Where-Object { - ($_.resourceId -iin $allowed -and $_.changeType -ne 'Create') -or - ($_.resourceId -inotIn $allowed -and $_.changeType -notin @('Ignore', 'NoChange')) - }) - if ($preview.status -ne 'Succeeded' -or $creates.Count -ne 3 -or - @($creates.resourceId | Sort-Object -Unique).Count -ne 3 -or $unsafe.Count) { - $Entry.SubmissionError = 'ARM what-if did not confirm exactly the three new test resources with no changes to existing resources.' - throw $Entry.SubmissionError - } - Invoke-LocalBoxAz (@('deployment', 'group', 'create') + $arguments + @('--no-wait')) -NoOutput - } - catch { throw "VM submission failed for $($Entry.Name). Inspect its ARM deployment; resources may still be provisioning. CLI details are withheld to protect the VM credential." } - finally { - $values = $null - $parameters = $null - if (Test-Path -LiteralPath $directory) { Remove-Item -LiteralPath $directory -Recurse -Force -ErrorAction Stop } - } + Submit-LocalBoxVM -State $State -Entry $Entry -Credential $Credential ` + -Tags @{ MicroHackPurpose = 'LocalBoxCapacityTest'; MicroHackRunId = $State.RunId } } function Update-LocalBoxCapacityVM { diff --git a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/tests/localbox.health.tests.ps1 b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/tests/localbox.health.tests.ps1 index beeb7d1be..2e3f58c9f 100644 --- a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/tests/localbox.health.tests.ps1 +++ b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/tests/localbox.health.tests.ps1 @@ -72,6 +72,28 @@ Describe 'Shared LocalBox control plane' { } } +if ($LocalBox.ReferenceVm) { + Describe 'Optional shared reference VM' { + It 'matches the organizer-owned VM, network and guest-management configuration' { + $reference = $LocalBox.ReferenceVm + $reference.MachineId | Should -Be "/subscriptions/$($LocalBox.SubscriptionId)/resourceGroups/$($LocalBox.ResourceGroupName)/providers/Microsoft.HybridCompute/machines/sov-ref-vm" + $reference.InstanceId | Should -Be "$($reference.MachineId)/providers/Microsoft.AzureStackHCI/virtualMachineInstances/default" + Assert-LocalBoxProperties (Get-LocalBoxResource $reference.MachineId) $reference.MachineExpected + Assert-LocalBoxProperties (Get-LocalBoxResource $reference.NicId) $reference.NicExpected + Assert-LocalBoxProperties (Wait-LocalBoxResource $reference.InstanceId -TimeoutSeconds $TimeoutSeconds) $reference.Expected + } + It 'is running and Arc-connected with successful MDE installation (not proof of completed assessments)' { + Wait-SovereignCheck -TimeoutSeconds $TimeoutSeconds -Check { + $readiness = Get-LocalBoxReferenceVmReadiness $LocalBox.ReferenceVm.MachineId + $readiness.PowerState | Should -Be 'Running' + $readiness.ArcStatus | Should -Be 'Connected' + $readiness.MdeProvisioningState | Should -Be 'Succeeded' + $readiness.PeriodicAssessmentMode | Should -Be 'AutomaticByPlatform' + } + } + } +} + Describe 'LocalBox runtime health' -Skip:($Mode -ne 'Full') { It 'has running healthy nested nodes and sufficient storage' { $IsWindows | Should -BeTrue -Because 'Full LocalBox tests run on LocalBox-Client' diff --git a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/tests/prepare-localbox.tests.ps1 b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/tests/prepare-localbox.tests.ps1 index e19272d60..398282af5 100644 --- a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/tests/prepare-localbox.tests.ps1 +++ b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/tests/prepare-localbox.tests.ps1 @@ -154,7 +154,7 @@ Describe 'LocalBox capacity lifecycle' { Should -Invoke Invoke-LocalBoxAz -Times 0 -ParameterFilter { $Arguments[0] -eq 'resource' } } It 'uses a secure password parameter and explicitly targets the prepared storage and network' { - $template = New-LocalBoxCapacityTemplate + $template = New-LocalBoxVmTemplate $template.parameters.adminPassword.type | Should -Be 'securestring' $template.resources.Count | Should -Be 3 $vm = $template.resources | Where-Object type -eq 'Microsoft.AzureStackHCI/virtualMachineInstances' @@ -451,6 +451,12 @@ Describe 'LocalBox capacity secure submission' { Should -Invoke Invoke-LocalBoxAz -Times 1 -Exactly -ParameterFilter { $Arguments[2] -eq 'create' -and $NoOutput -and $Arguments -contains '--no-wait' } foreach ($path in $paths) { Test-Path -LiteralPath $path | Should -BeFalse } } + It 'accepts the exact 15-character Windows computer-name limit' { + $entry.Name = 'abcdefghijklmno' + foreach ($change in $preview.changes) { $change.resourceId = $change.resourceId.Replace('lc-test-001', $entry.Name) } + Submit-LocalBoxCapacityVM $state $entry $credential + Should -Invoke Invoke-LocalBoxAz -Times 1 -Exactly -ParameterFilter { $Arguments[2] -eq 'create' } + } It 'rejects changes outside the three new VM resources' -TestCases @( @{ Field = 'changeType'; Value = 'Modify' } @{ Field = 'resourceId'; Value = '/subscriptions/test/resourceGroups/shared/providers/Microsoft.HybridCompute/machines/student' } @@ -500,6 +506,315 @@ Describe 'LocalBox capacity secure submission' { } } +Describe 'LocalBox preparation defaults' { + It 'does not expose the removed manual address-reservation acknowledgement' { + (Get-Command "$PSScriptRoot/../prepare-localbox.ps1").Parameters.ContainsKey('AddressReservationsConfirmed') | Should -BeFalse + ${function:Invoke-LocalBoxPreparation}.ToString() | Should -Not -Match 'AddressReservationsConfirmed' + } + It 'includes the reference VM when the switch is omitted' { + & { + . "$PSScriptRoot/../prepare-localbox.ps1" + [bool]$IncludeReferenceVm + } | Should -BeTrue + } + It 'accepts an explicit reference-VM opt-out' { + & { + . "$PSScriptRoot/../prepare-localbox.ps1" -IncludeReferenceVm:$false + [bool]$IncludeReferenceVm + } | Should -BeFalse + } + It 'keeps script-level Confirm and WhatIf available without prompting at the default threshold' { + $command = Get-Command "$PSScriptRoot/../prepare-localbox.ps1" + $binding = $command.ScriptBlock.Attributes | Where-Object { $_ -is [Management.Automation.CmdletBindingAttribute] } + $binding.SupportsShouldProcess | Should -BeTrue + [int]$binding.ConfirmImpact | Should -BeLessThan ([int][Management.Automation.ConfirmImpact]::High) + $command.Parameters.ContainsKey('Confirm') | Should -BeTrue + $command.Parameters.ContainsKey('WhatIf') | Should -BeTrue + } + It 'does not require default confirmation for , while preserving explicit confirmation and previews' -TestCases @( + @{ FunctionName = 'Initialize-LocalBoxStorage' } + @{ FunctionName = 'Sync-LocalBoxImage' } + @{ FunctionName = 'Sync-LocalBoxResource' } + @{ FunctionName = 'Sync-LocalBoxReferenceVm' } + @{ FunctionName = 'Invoke-LocalBoxPreparation' } + ) { + param($FunctionName) + $command = Get-Command $FunctionName + $binding = $command.ScriptBlock.Attributes | Where-Object { $_ -is [Management.Automation.CmdletBindingAttribute] } + $binding.SupportsShouldProcess | Should -BeTrue + [int]$binding.ConfirmImpact | Should -BeLessThan ([int][Management.Automation.ConfirmImpact]::High) + $command.Parameters.ContainsKey('Confirm') | Should -BeTrue + $command.Parameters.ContainsKey('WhatIf') | Should -BeTrue + } +} + +Describe 'LocalBox greenfield address validation' { + BeforeAll { + $source = ${function:Invoke-LocalBoxPreparation}.ToString() + $start = $source.IndexOf('Test-LocalBoxAddressPool $config.vmIpPrefix') + $end = $source.IndexOf('$Settings.NodeCredential =', $start) + $addressChecks = [scriptblock]::Create($source.Substring($start, $end - $start)) + } + BeforeEach { + $Settings = @{ VmPoolStart = '192.168.200.10'; VmPoolEnd = '192.168.200.199' } + $config = @{ + vmIpPrefix = '192.168.200.0/24'; vmGateway = '192.168.200.1'; dcVLAN200IP = '192.168.200.205'; vmVLAN = 200 + AKSIPPrefix = '10.10.0.0/24'; AKSGWIP = '10.10.0.1'; AKSControlPlaneIP = '10.10.0.5'; AKSVLAN = 110 + AKSVIPStartIP = '10.10.0.10'; AKSVIPEndIP = '10.10.0.100'; AKSNodeStartIP = '10.10.0.101'; AKSNodeEndIP = '10.10.0.199' + } + } + It 'accepts the standard topology without a manual acknowledgement' { + { & $addressChecks } | Should -Not -Throw + } + It 'still rejects configured address conflicts' -TestCases @( + @{ Conflict = 'gateway'; Message = '*reserved address*' } + @{ Conflict = 'infrastructure'; Message = '*reserved address*' } + @{ Conflict = 'VIP overlap'; Message = '*overlaps reserved range*' } + @{ Conflict = 'shared VLAN'; Message = '*separate VLANs and subnets*' } + ) { + param($Conflict, $Message) + switch ($Conflict) { + 'gateway' { $Settings.VmPoolStart = '192.168.200.1' } + 'infrastructure' { $Settings.VmPoolEnd = '192.168.200.210' } + 'VIP overlap' { $config.AKSNodeStartIP = '10.10.0.100' } + 'shared VLAN' { $config.AKSVLAN = $config.vmVLAN } + } + { & $addressChecks } | Should -Throw $Message + } +} + +Describe 'Optional LocalBox reference VM' { + BeforeEach { + $settings = @{ SubscriptionId = 'test'; ResourceGroupName = 'shared'; IncludeReferenceVm = $true; TimeoutMinutes = 30 } + $scope = '/subscriptions/test/resourceGroups/shared' + $machineId = "$scope/providers/Microsoft.HybridCompute/machines/sov-ref-vm" + $nicId = "$scope/providers/Microsoft.AzureStackHCI/networkInterfaces/sov-ref-vm-nic" + $instanceId = "$machineId/providers/Microsoft.AzureStackHCI/virtualMachineInstances/default" + $tags = @{ MicroHackPurpose = 'LocalBoxReferenceVM'; SecurityControl = 'Ignore'; CostControl = 'Ignore' } + $machine = @{ kind = 'HCI'; location = 'westeurope'; tags = $tags; identity = @{ type = 'SystemAssigned' } + properties = @{ status = 'Connected'; osProfile = @{ windowsConfiguration = @{ patchSettings = @{ assessmentMode = 'AutomaticByPlatform' } } } } } + $instance = @{ + extendedLocation = @{ name = 'custom' } + properties = @{ + provisioningState = 'Succeeded'; status = @{ powerState = 'Running' } + hardwareProfile = @{ vmSize = 'Custom'; processors = 2; memoryMB = 4096 } + osProfile = @{ computerName = 'sov-ref-vm'; windowsConfiguration = @{ provisionVMAgent = $true; provisionVMConfigAgent = $true } } + storageProfile = @{ vmConfigStoragePathId = 'storage'; imageReference = @{ id = 'image' } } + networkProfile = @{ networkInterfaces = @(@{ id = $nicId }) } + } + } + $nic = @{ location = 'westeurope'; extendedLocation = @{ name = 'custom' }; tags = $tags + properties = @{ ipConfigurations = @(@{ name = 'ipconfig1'; properties = @{ subnet = @{ id = 'network' } } }) } } + $referenceArguments = @{ Settings = $settings; Location = 'westeurope'; CustomLocationId = 'custom'; ImageId = 'image'; NetworkId = 'network'; StorageId = 'storage' } + Mock Invoke-LocalBoxAz { @() } + Mock Get-LocalBoxResource { + if ($Id -eq $machineId) { return $machine } + if ($Id -eq $nicId) { return $nic } + if ($Id -eq $instanceId) { return $instance } + throw "Unexpected resource $Id" + } + Mock Wait-LocalBoxResource { + if ($Id -like '*/Microsoft.Resources/deployments/sov-ref-vm') { return @{ properties = @{ provisioningState = 'Succeeded' } } } + if ($Id -eq $instanceId) { return $instance } + throw "Unexpected wait $Id" + } + Mock Get-LocalBoxArmCollection { @() } + Mock Submit-LocalBoxVM {} + Mock Get-Credential { [pscredential]::new('localadmin', (ConvertTo-SecureString 'Mock!Password9-not-real' -AsPlainText -Force)) } + } + It 'does nothing when explicitly disabled' { + $settings.IncludeReferenceVm = $false + Sync-LocalBoxReferenceVm @referenceArguments | Should -BeNullOrEmpty + Should -Invoke Invoke-LocalBoxAz -Times 0 + Should -Invoke Get-Credential -Times 0 + Should -Invoke Submit-LocalBoxVM -Times 0 + } + It 'previews without credentials, provisioning, polling or a success-shaped manifest' { + Sync-LocalBoxReferenceVm @referenceArguments -WhatIf | Should -BeNullOrEmpty + Should -Invoke Get-Credential -Times 0 + Should -Invoke Submit-LocalBoxVM -Times 0 + Should -Invoke Wait-LocalBoxResource -Times 0 + } + It 'creates one short-named tagged VM using a separate credential and periodic assessment' { + $reference = Sync-LocalBoxReferenceVm @referenceArguments + $reference.Name | Should -Be 'sov-ref-vm' + $reference.Name.Length | Should -BeLessOrEqual 15 + $reference.MachineId | Should -Be $machineId + $reference.Readiness.ArcStatus | Should -Be 'Connected' + $reference.Readiness.MdeProvisioningState | Should -Be 'Pending' + $reference.Readiness.DefenderAssessments | Should -Be 'NotVerified' + $reference.Readiness.UpdateAssessmentResults | Should -Be 'NotVerified' + $reference | ConvertTo-Json -Depth 30 | Should -Not -Match 'Mock!Password|adminPassword|localadmin' + Should -Invoke Submit-LocalBoxVM -Times 1 -Exactly -ParameterFilter { + $Entry.Name -eq 'sov-ref-vm' -and $Credential.UserName -eq 'localadmin' -and + $State.ResourceGroupName -eq 'shared' -and $State.ProcessorCount -eq 2 -and $State.MemoryMB -eq 4096 -and + $EnablePeriodicAssessment -and $Tags.SecurityControl -eq 'Ignore' -and $Tags.CostControl -eq 'Ignore' + } + } + It 'uses a supplied credential without prompting' { + $settings.ReferenceVmCredential = [pscredential]::new('refadmin', (ConvertTo-SecureString 'Mock!Password9-not-real' -AsPlainText -Force)) + Sync-LocalBoxReferenceVm @referenceArguments | Out-Null + Should -Invoke Get-Credential -Times 0 + Should -Invoke Submit-LocalBoxVM -Times 1 -ParameterFilter { $Credential.UserName -eq 'refadmin' } + } + It 'reuses matching resources without resetting credentials or redeploying' { + Mock Invoke-LocalBoxAz { @(@{ id = $machineId }) } + Sync-LocalBoxReferenceVm @referenceArguments | Out-Null + Should -Invoke Submit-LocalBoxVM -Times 0 + Should -Invoke Get-Credential -Times 0 + } + It 'rejects an unrelated machine at the stable name' { + Mock Invoke-LocalBoxAz { @(@{ id = $machineId }) } + $machine.tags = @{ MicroHackPurpose = 'ParticipantVM' } + { Sync-LocalBoxReferenceVm @referenceArguments } | Should -Throw + Should -Invoke Submit-LocalBoxVM -Times 0 + } + It 'rejects changed hardware rather than resizing it' { + Mock Invoke-LocalBoxAz { @(@{ id = $machineId }) } + $instance.properties.hardwareProfile.memoryMB = 8192 + { Sync-LocalBoxReferenceVm @referenceArguments } | Should -Throw '*Conflicting*memoryMB*' + Should -Invoke Submit-LocalBoxVM -Times 0 + } + It 'rejects a partial deployment rather than overwriting its NIC' { + Mock Invoke-LocalBoxAz { @(@{ id = $nicId }) } + { Sync-LocalBoxReferenceVm @referenceArguments } | Should -Throw '*Partial or conflicting*' + Should -Invoke Submit-LocalBoxVM -Times 0 + } + It 'does not hide failed deployment polling' { + Mock Wait-LocalBoxResource { throw 'Deployment Failed' } + { Sync-LocalBoxReferenceVm @referenceArguments } | Should -Throw '*Deployment Failed*' + } + It 'reports actual disconnected guest status without declaring protection ready' { + $machine.properties.status = 'Disconnected' + (Get-LocalBoxReferenceVmReadiness $machineId).ArcStatus | Should -Be 'Disconnected' + } + It 'reports MDE extension state separately from assessment completion' -TestCases @( + @{ State = 'Creating' }; @{ State = 'Succeeded' }; @{ State = 'Failed' } + ) { + param($State) + Mock Get-LocalBoxArmCollection { @(@{ properties = @{ publisher = 'Microsoft.Azure.AzureDefenderForServers'; type = 'MDE.Windows'; provisioningState = $State } }) } + $readiness = Get-LocalBoxReferenceVmReadiness $machineId + $readiness.MdeProvisioningState | Should -Be $State + $readiness.DefenderAssessments | Should -Be 'NotVerified' + } + It 'reports an unreported guest status explicitly while initial onboarding is pending' { + $machine.properties.Remove('status') + (Get-LocalBoxReferenceVmReadiness $machineId).ArcStatus | Should -Be 'NotReported' + } + It 'surfaces malformed responses or failed extension reads instead of reporting readiness' { + $instance.properties.status.Remove('powerState') + { Get-LocalBoxReferenceVmReadiness $machineId } | Should -Throw '*incomplete machine/power data*' + $instance.properties.status.powerState = 'Running' + Mock Get-LocalBoxArmCollection { throw 'Forbidden' } + { Get-LocalBoxReferenceVmReadiness $machineId } | Should -Throw '*Forbidden*' + } + It 'only adds periodic assessment to reference templates and leaves capacity behavior unchanged' { + $template = New-LocalBoxVmTemplate -EnablePeriodicAssessment + $template.resources[0].apiVersion | Should -Be '2024-07-10' + $template.resources[0].properties.osProfile.windowsConfiguration.patchSettings.assessmentMode | Should -Be 'AutomaticByPlatform' + (New-LocalBoxVmTemplate).resources[0].ContainsKey('properties') | Should -BeFalse + $template.parameters.adminPassword.type | Should -Be 'securestring' + $template.parameters.name.maxLength | Should -Be 15 + $template.resources[2].properties.osProfile.computerName | Should -Be "[parameters('name')]" + } +} + +Describe 'LocalBox VM name validation' { + It 'rejects Windows-invalid names before making any Azure request' -TestCases @( + @{ Name = 'sixteenlettersxxx' }; @{ Name = '12345' }; @{ Name = '-bad' }; @{ Name = 'bad_' }; @{ Name = '' } + ) { + param($Name) + Mock Invoke-LocalBoxAz { throw 'Unexpected Azure request' } + { Submit-LocalBoxVM -State @{} -Entry @{ Name = $Name } } | Should -Throw '*1-15*' + Should -Invoke Invoke-LocalBoxAz -Times 0 + } +} + +Describe 'Reference VM preparation wiring and manifest' { + BeforeAll { + $preparation = [scriptblock]::Create(${function:Invoke-LocalBoxPreparation}.ToString()).Ast + $invokeStep = [scriptblock]::Create($preparation.Find({ + param($node) + $node -is [Management.Automation.Language.AssignmentStatementAst] -and $node.Left.Extent.Text -eq '$referenceVm' + }, $true).Extent.Text) + $manifestStep = [scriptblock]::Create($preparation.Find({ + param($node) + $node -is [Management.Automation.Language.IfStatementAst] -and $node.Clauses[0].Item1.Extent.Text -eq '$referenceVm' + }, $true).Extent.Text) + } + BeforeEach { + $Settings = @{ SubscriptionId = 'test'; ResourceGroupName = 'shared'; ManifestPath = "$TestDrive/manifest.json"; IncludeReferenceVm = $true } + $manifest = @{} + $referenceVm = $null + $location = 'australiaeast' + $custom = @{ id = 'custom' } + $imageId = 'image' + $networks = @(@{ Id = 'vm-network' }, @{ Id = 'aks-network' }) + $storageId = 'storage' + } + It 'wires the enabled reference VM to the prepared image and VM network, not the AKS network' { + Mock Sync-LocalBoxReferenceVm { @{ Name = 'sov-ref-vm' } } + . $invokeStep + . $manifestStep + $manifest.ReferenceVm.Name | Should -Be 'sov-ref-vm' + Should -Invoke Sync-LocalBoxReferenceVm -Times 1 -ParameterFilter { + $Settings.IncludeReferenceVm -and $Location -eq 'australiaeast' -and $CustomLocationId -eq 'custom' -and + $ImageId -eq 'image' -and $NetworkId -eq 'vm-network' -and $StorageId -eq 'storage' + } + } + It 'preserves reference inventory and its original observation time when explicitly opted out' { + $Settings.IncludeReferenceVm = $false + @{ SubscriptionId = 'test'; ResourceGroupName = 'shared'; ReferenceVm = @{ Name = 'sov-ref-vm'; Readiness = @{ ObservedAt = 'original' } } } | + ConvertTo-Json -Depth 5 | Set-Content $Settings.ManifestPath + . $manifestStep + $manifest.ReferenceVm.Name | Should -Be 'sov-ref-vm' + $manifest.ReferenceVm.Readiness.ObservedAt | Should -Be 'original' + } + It 'does not import reference inventory from a different scope' { + @{ SubscriptionId = 'other'; ResourceGroupName = 'shared'; ReferenceVm = @{ Name = 'sov-ref-vm' } } | + ConvertTo-Json -Depth 5 | Set-Content $Settings.ManifestPath + . $manifestStep + $manifest.ContainsKey('ReferenceVm') | Should -BeFalse + } + It 'leaves a normal manifest unchanged when there is no reference VM' { + . $manifestStep + $manifest.ContainsKey('ReferenceVm') | Should -BeFalse + } +} + +Describe 'Reference VM live health assertions' { + BeforeAll { + $health = [Management.Automation.Language.Parser]::ParseFile("$PSScriptRoot/localbox.health.tests.ps1", [ref]$null, [ref]$null) + $referenceCheck = $health.Find({ + param($node) + $node -is [Management.Automation.Language.CommandAst] -and $node.GetCommandName() -eq 'It' -and + $node.CommandElements[1].Value -like 'is running and Arc-connected*' + }, $true).CommandElements[-1].ScriptBlock.GetScriptBlock() + } + BeforeEach { + $LocalBox = @{ ReferenceVm = @{ MachineId = 'reference' } } + $TimeoutSeconds = 1 + $status = @{ PowerState = 'Running'; ArcStatus = 'Connected'; MdeProvisioningState = 'Succeeded'; PeriodicAssessmentMode = 'AutomaticByPlatform' } + Mock Get-LocalBoxReferenceVmReadiness { $status } + Mock Wait-SovereignCheck { & $Check } + } + It 'passes with connected guest management and successful MDE installation' { + { & $referenceCheck } | Should -Not -Throw + } + It 'rejects incomplete live reference readiness' -TestCases @( + @{ Field = 'PowerState'; Value = 'Off' } + @{ Field = 'ArcStatus'; Value = 'NotReported' } + @{ Field = 'ArcStatus'; Value = 'Disconnected' } + @{ Field = 'MdeProvisioningState'; Value = 'Pending' } + @{ Field = 'MdeProvisioningState'; Value = 'Failed' } + @{ Field = 'PeriodicAssessmentMode'; Value = 'ImageDefault' } + ) { + param($Field, $Value) + $status[$Field] = $Value + { & $referenceCheck } | Should -Throw + } +} + Describe 'Health runner defaults and feedback' { BeforeAll { function kubectl {} @@ -1400,6 +1715,155 @@ Describe 'Resource reconciliation' { } } +Describe 'LocalBox failed image recovery' { + BeforeEach { + $imageId = '/subscriptions/test/resourceGroups/localbox/providers/Microsoft.AzureStackHCI/marketplaceGalleryImages/windows' + $expected = @{ location = 'australiaeast'; extendedLocation = @{ name = 'custom' } + properties = @{ containerId = 'storage'; osType = 'Windows' + identifier = @{ publisher = 'microsoftwindowsserver'; offer = 'windowsserver'; sku = '2025-datacenter-azure-edition-smalldisk' } } } + $actual = $expected | ConvertTo-Json -Depth 10 | ConvertFrom-Json -AsHashtable + $actual.properties.provisioningState = 'Failed' + $script:imagePresent = $true + $createArguments = @('stack-hci-vm', 'image', 'create', '--name', 'windows') + Mock Invoke-LocalBoxAz { + if ($Arguments[1] -eq 'list' -and $script:imagePresent) { @(@{ id = $imageId }) } + elseif ($Arguments[1] -eq 'delete') { $script:imagePresent = $false } + } + Mock Get-LocalBoxResource { $actual } + Mock Wait-LocalBoxResource { $expected } + Mock Start-Sleep {} + } + It 'removes only the matching Failed image, verifies absence and imports once' { + Sync-LocalBoxImage $imageId $expected $createArguments -Confirm:$false | Out-Null + Should -Invoke Invoke-LocalBoxAz -Times 1 -Exactly -ParameterFilter { + $Arguments[1] -eq 'delete' -and $Arguments -contains $imageId -and $Arguments -contains '2024-01-01' + } + Should -Invoke Invoke-LocalBoxAz -Times 1 -Exactly -ParameterFilter { $Arguments[2] -eq 'create' } + Should -Invoke Invoke-LocalBoxAz -Times 3 -Exactly -ParameterFilter { + $Arguments[1] -eq 'list' -and $Arguments -contains 'test' -and $Arguments -contains 'localbox' + } + } + It 'never deletes healthy or in-progress images' -TestCases @( + @{ State = 'Succeeded' }; @{ State = 'Creating' } + ) { + param($State) + $actual.properties.provisioningState = $State + Sync-LocalBoxImage $imageId $expected $createArguments -Confirm:$false | Out-Null + Should -Invoke Invoke-LocalBoxAz -Times 0 -ParameterFilter { $Arguments -contains 'delete' -or $Arguments -contains 'create' } + Should -Invoke Wait-LocalBoxResource -Times 1 -Exactly + } + It 'does not remove conflicting Failed images' -TestCases @( + @{ Field = 'location' }; @{ Field = 'storage' }; @{ Field = 'custom' }; @{ Field = 'sku' }; @{ Field = 'missing' } + ) { + param($Field) + switch ($Field) { + 'location' { $actual.location = 'westeurope' } + 'storage' { $actual.properties.containerId = 'different' } + 'custom' { $actual.extendedLocation.name = 'different' } + 'sku' { $actual.properties.identifier.sku = 'different' } + 'missing' { $actual.properties.Remove('identifier') } + } + { Sync-LocalBoxImage $imageId $expected $createArguments -Confirm:$false } | Should -Throw + Should -Invoke Invoke-LocalBoxAz -Times 0 -ParameterFilter { $Arguments -contains 'delete' -or $Arguments -contains 'create' } + } + It 'does not delete or import a Failed image during WhatIf' { + Sync-LocalBoxImage $imageId $expected $createArguments -WhatIf | Should -BeNullOrEmpty + Should -Invoke Invoke-LocalBoxAz -Times 0 -ParameterFilter { $Arguments -contains 'delete' -or $Arguments -contains 'create' } + Should -Invoke Wait-LocalBoxResource -Times 0 + } + It 'waits for a previously started deletion without submitting another delete' { + $actual.properties.provisioningState = 'Deleting' + $script:imageLists = 0 + Mock Invoke-LocalBoxAz { + if ($Arguments[1] -eq 'list') { + $script:imageLists++ + if ($script:imageLists -le 2) { @(@{ id = $imageId }) } + } + } + Sync-LocalBoxImage $imageId $expected $createArguments -Confirm:$false | Out-Null + Should -Invoke Invoke-LocalBoxAz -Times 0 -ParameterFilter { $Arguments -contains 'delete' } + Should -Invoke Invoke-LocalBoxAz -Times 1 -Exactly -ParameterFilter { $Arguments[2] -eq 'create' } + Should -Invoke Start-Sleep -Times 1 -Exactly + } + It 'does not wait for deletion during WhatIf' { + $actual.properties.provisioningState = 'Deleting' + Sync-LocalBoxImage $imageId $expected $createArguments -WhatIf | Should -BeNullOrEmpty + Should -Invoke Start-Sleep -Times 0 + Should -Invoke Invoke-LocalBoxAz -Times 0 -ParameterFilter { $Arguments -contains 'delete' -or $Arguments -contains 'create' } + } + It 'does not recreate when deletion fails' { + Mock Invoke-LocalBoxAz { throw 'AuthorizationFailed' } -ParameterFilter { $Arguments[1] -eq 'delete' } + { Sync-LocalBoxImage $imageId $expected $createArguments -Confirm:$false } | Should -Throw '*AuthorizationFailed*' + Should -Invoke Invoke-LocalBoxAz -Times 0 -ParameterFilter { $Arguments[2] -eq 'create' } + } + It 'bounds deletion waits and does not import over a remaining object' { + Mock Invoke-LocalBoxAz {} -ParameterFilter { $Arguments[1] -eq 'delete' } + { Sync-LocalBoxImage $imageId $expected $createArguments -TimeoutSeconds 0 -Confirm:$false } | Should -Throw '*Timed out waiting for image deletion*' + Should -Invoke Invoke-LocalBoxAz -Times 0 -ParameterFilter { $Arguments[2] -eq 'create' } + } + It 'surfaces a fresh import failure without deleting it or looping on retries' { + $script:imagePresent = $false + Mock Invoke-LocalBoxAz { throw 'GenerateTokenFromEdgeMarketplaceServiceFailed: NotFound' } -ParameterFilter { $Arguments[2] -eq 'create' } + { Sync-LocalBoxImage $imageId $expected $createArguments -Confirm:$false } | Should -Throw '*GenerateTokenFromEdgeMarketplaceServiceFailed*' + Should -Invoke Invoke-LocalBoxAz -Times 1 -Exactly -ParameterFilter { $Arguments[2] -eq 'create' } + Should -Invoke Invoke-LocalBoxAz -Times 0 -ParameterFilter { $Arguments -contains 'delete' } + } + It 'refuses recovery for a non-image resource ID' { + { Sync-LocalBoxImage '/subscriptions/test/resourceGroups/localbox/providers/Microsoft.AzureStackHCI/logicalNetworks/net' $expected $createArguments } | Should -Throw '*exact Azure Local Marketplace image*' + Should -Invoke Invoke-LocalBoxAz -Times 0 + } +} + +Describe 'LocalBox preparation dependency order' { + BeforeAll { + $source = ${function:Invoke-LocalBoxPreparation}.ToString() + $start = $source.IndexOf('$location = $custom.location') + $end = $source.IndexOf('$manifest = @{', $start) + $prepareWorkloads = [scriptblock]::Create("[CmdletBinding(SupportsShouldProcess)]param()`n" + $source.Substring($start, $end - $start)) + } + BeforeEach { + $events = [Collections.Generic.List[string]]::new() + $scope = '/subscriptions/test/resourceGroups/localbox' + $Settings = @{ SubscriptionId = 'test'; ResourceGroupName = 'localbox'; TimeoutMinutes = 1; ImageName = 'windows'; ImageVersion = 'latest' + AksClusterName = 'localbox-aks'; NodeCount = 3; NodeVmSize = 'node'; ControlPlaneVmSize = 'control'; SkipAks = $false; IncludeReferenceVm = $true + KubernetesRuntimeObjectId = '11111111-1111-1111-1111-111111111111' } + $custom = @{ id = 'custom'; location = 'australiaeast' } + $config = @{ AKSControlPlaneIP = '10.10.0.5' } + $storageId = 'storage' + $imageId = 'image' + $groupId = 'group' + $metalLb = @{} + Mock Sync-LocalBoxResource { $events.Add("network:$($Id.Split('/')[-1])") } + Mock Invoke-LocalBoxAz { + if ($Arguments[0] -eq 'aksarc') { + $events.Add($(if ($Arguments -contains '--validate') { 'aks-validate' } else { 'aks-create' })) + } + elseif ($Arguments[0] -eq 'provider') { @{ registrationState = 'Registered' } } + } + Mock Wait-LocalBoxResource { @{ properties = @{ connectivityStatus = 'Connected' } } } + Mock Assert-LocalBoxProperties {} + Mock Sync-LocalBoxAksProxyRole { $events.Add('proxy') } + Mock Sync-LocalBoxMetalLb { $events.Add('metallb') } + Mock Sync-LocalBoxImage { $events.Add('image') } + Mock Sync-LocalBoxReferenceVm { $events.Add('reference') } + } + It 'completes networks, AKS and MetalLB before importing the image and reference VM' { + & $prepareWorkloads -Confirm:$false + $events -join ',' | Should -Be 'network:localbox-vm-lnet-vlan200,network:localbox-aks-lnet-vlan110,aks-validate,aks-create,proxy,metallb,image,reference' + } + It 'preserves completed infrastructure when the late image stage fails and does not start the reference VM' { + Mock Sync-LocalBoxImage { $events.Add('image-failed'); throw 'Import failed' } + { & $prepareWorkloads -Confirm:$false } | Should -Throw '*Import failed*' + $events -join ',' | Should -Be 'network:localbox-vm-lnet-vlan200,network:localbox-aks-lnet-vlan110,aks-validate,aks-create,proxy,metallb,image-failed' + Should -Invoke Sync-LocalBoxReferenceVm -Times 0 + } + It 'preserves the explicit SkipAks behavior while keeping the VM stages last' { + $Settings.SkipAks = $true + & $prepareWorkloads -Confirm:$false + $events -join ',' | Should -Be 'network:localbox-vm-lnet-vlan200,network:localbox-aks-lnet-vlan110,image,reference' + } +} + Describe 'LocalBox image storage conflicts' { BeforeEach { $scope = '/subscriptions/test/resourceGroups/localbox/providers/Microsoft.AzureStackHCI' @@ -1878,6 +2342,7 @@ Describe 'Storage execution safety' { Mock New-VHD {} Mock Add-VMHardDiskDrive {} Mock Invoke-Command {} + Mock Get-LocalBoxPartitionState { @{ Size = 1TB - 2MB; NeedsExpansion = $false } } Mock Get-LocalBoxNodeState { @{ PoolHealth = 'Healthy'; DiskHealth = 'Healthy'; VolumeHealth = 'Healthy'; NodesUp = $true; StorageJobs = 0; UnhealthyPhysicalDisks = 0; Size = 679GB } } } It 'does not create, attach or resize anything during WhatIf' { @@ -1893,7 +2358,77 @@ Describe 'Storage execution safety' { Initialize-LocalBoxStorage -Configuration $configuration -Credential $credential -DesiredSizeGB 1024 -Confirm:$false Should -Invoke New-VHD -Times 0 Should -Invoke Add-VMHardDiskDrive -Times 0 + Should -Invoke Invoke-Command -Times 0 + } + It 'resumes partition expansion after an interrupted virtual-disk resize, then skips it on the next run' { + Mock Test-Path { $true } + Mock Get-VMHardDiskDrive { @(@{ Path = Join-Path $TestDrive "$VMName-microhack-s2d.vhdx" }) } + Mock Get-LocalBoxNodeState { @{ PoolHealth = 'Healthy'; DiskHealth = 'Healthy'; VolumeHealth = 'Healthy'; NodesUp = $true; StorageJobs = 0; UnhealthyPhysicalDisks = 0; Size = 1TB } } + $script:partitionReads = 0 + Mock Get-LocalBoxPartitionState { + $script:partitionReads++ + @{ Size = 679GB; NeedsExpansion = $script:partitionReads -eq 1 } + } + Initialize-LocalBoxStorage -Configuration $configuration -Credential $credential -DesiredSizeGB 1024 -Confirm:$false + Initialize-LocalBoxStorage -Configuration $configuration -Credential $credential -DesiredSizeGB 1024 -Confirm:$false + Should -Invoke New-VHD -Times 0 + Should -Invoke Add-VMHardDiskDrive -Times 0 Should -Invoke Invoke-Command -Times 0 -ParameterFilter { $ScriptBlock.ToString() -match 'Resize-VirtualDisk' } + Should -Invoke Invoke-Command -Times 1 -Exactly -ParameterFilter { $ScriptBlock.ToString() -match 'Resize-Partition' } + } + It 'reports an incomplete partition expansion instead of claiming completion' { + Mock Get-LocalBoxPartitionState { @{ Size = 679GB; NeedsExpansion = $true } } + { Initialize-LocalBoxStorage -Configuration $configuration -Credential $credential -DesiredSizeGB 1024 -Confirm:$false } | + Should -Throw '*still has unexpanded partition space*' + } + It 'does not expand an unfinished partition during WhatIf' { + Mock Get-LocalBoxPartitionState { @{ Size = 679GB; NeedsExpansion = $true } } + Initialize-LocalBoxStorage -Configuration $configuration -Credential $credential -DesiredSizeGB 1024 -WhatIf + Should -Invoke Invoke-Command -Times 0 + } +} + +Describe 'LocalBox partition capacity detection' { + BeforeAll { + function Get-Volume { param($FileSystemLabel) } + function Get-Partition { param([Parameter(ValueFromPipeline)]$InputObject) } + function Get-Disk { param([Parameter(ValueFromPipeline)]$InputObject) } + function Get-PartitionSupportedSize { throw 'Unexpected expensive supported-size query' } + } + BeforeEach { + $partition = @{ Size = 1TB - 2MB; Offset = 1MB } + $credential = [pscredential]::new('test-user', [Security.SecureString]::new()) + Mock Invoke-Command { & $ScriptBlock } + Mock Get-Volume { @{ FileSystemLabel = 'UserStorage_1' } } + Mock Get-Partition { $partition } + Mock Get-Disk { @{ Size = 1TB } } + Mock Get-PartitionSupportedSize { throw 'Unexpected expensive supported-size query' } + } + It 'ignores an alignment/GPT tail of at most 1 MiB' -TestCases @( + @{ Tail = 0 }; @{ Tail = 512KB }; @{ Tail = 1MB } + ) { + param($Tail) + $partition.Size = 1TB - 1MB - $Tail + (Get-LocalBoxPartitionState 'node' $credential).NeedsExpansion | Should -BeFalse + Should -Invoke Get-PartitionSupportedSize -Times 0 + } + It 'detects actual unfinished expansion above the alignment allowance' -TestCases @( + @{ Tail = 1MB + 1 }; @{ Tail = 345GB } + ) { + param($Tail) + $partition.Size = 1TB - 1MB - $Tail + (Get-LocalBoxPartitionState 'node' $credential).NeedsExpansion | Should -BeTrue + Should -Invoke Get-PartitionSupportedSize -Times 0 + } + It 'rejects ambiguous partitions and missing remoting output' { + Mock Get-Partition { @($partition, $partition) } + { Get-LocalBoxPartitionState 'node' $credential } | Should -Throw '*uniquely resolve*' + Mock Invoke-Command {} + { Get-LocalBoxPartitionState 'node' $credential } | Should -Throw '*No valid*' + } + It 'rejects invalid disk geometry rather than silently skipping work' { + $partition.Size = 2TB + { Get-LocalBoxPartitionState 'node' $credential } | Should -Throw '*Invalid*' } } diff --git a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/tests/readme.md b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/tests/readme.md index 3163ce4cb..8dad83ad5 100644 --- a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/tests/readme.md +++ b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/tests/readme.md @@ -43,6 +43,44 @@ exception while retaining the four European regions. These checks make no Azure changes; a fresh Console deployment and participant VM creation remain required to validate regional service availability and effective inherited policies. +## Reference-VM checks + +Run `Invoke-Pester ./prepare-localbox.tests.ps1 -Output Detailed` for the offline +preparation and capacity regression suite. Reference-VM tests cover default inclusion, explicit opt-out and WhatIf, +the 15-character Windows name limit, separate credentials, resource reuse and +conflicts, periodic assessment configuration, and pending/failed MDE status. +The shared VM submission tests check secure parameter-file transport and cleanup, +error redaction, and an ARM preview restricted to three new VM resources. + +When the preparation manifest contains `ReferenceVm`, the live LocalBox health +suite additionally checks the expected machine/NIC/instance, running state, Arc +connection, MDE installation and periodic assessment mode. Older manifests without +that optional entry retain the normal test set. These checks do **not** verify +ongoing Defender device reporting or completed Defender/Update Manager results; +perform the [organizer reference readiness checks](../localbox/readme.md#reference-readiness-and-participant-use) +before the workshop. The new reference-VM path still requires a live Windows/LocalBox +integration test; offline mocks are not deployment evidence. + +## Preparation recovery and ordering checks + +Default/preflight tests verify that no manual address-reservation switch is +required for the standard greenfield topology, while configured gateway, +infrastructure-address, AKS/VIP and VLAN conflicts still stop preparation. + +The same offline suite checks the standard nonprompting confirmation impact, +continued support for explicit `-Confirm`/`-WhatIf`, and safe recovery of matching +Failed Marketplace images, conflicting-image protection, bounded deletion waits, +and a single import attempt per run. It executes the preparation workload sequence +with mocked Azure calls to verify that networks, AKS and MetalLB precede image +import and the optional reference VM, including when image import fails. + +Storage tests verify no repeated disk/partition mutations on a completed rerun, +resumption of an interrupted partition expansion, and detection of unallocated +space without calling `Get-PartitionSupportedSize` on an already expanded +partition. The alignment/GPT allowance is tested at and above 1 MiB. Live +LocalBox validation is still required for actual Windows storage behavior and +Marketplace failure recovery. + ## Hosted Defender unit tests Run `Invoke-Pester ./hosted-defender.tests.ps1 -Output Detailed` for the focused diff --git a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-06/images/defender-recommendations-populated.png b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/walkthrough/challenge-06/images/defender-recommendations-populated.png new file mode 100644 index 0000000000000000000000000000000000000000..d03a3f8a3b41d5b3503300f356b10f8669e75400 GIT binary patch literal 179625 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

M}-TxLz_naRIH9PNo3S^O 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

(PxidV1)olrWjQ>FH% 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 Endpoint protection** is **On**, even if the Servers plan is enabled. These subscription-level settings are context, not evidence that your individual VM has completed onboarding. > [!NOTE] -> A newly created VM can take time to appear and complete onboarding. If it is missing, first recheck **Guest management: Enabled (Connected)** and the selected subscription/resource group, then refresh Inventory. Ask the facilitator to check plan coverage and extension onboarding if it remains missing or unprotected. An Inventory entry alone does not prove that Defender for Servers protection is active. Do not enable a paid subscription-level plan yourself in a hosted lab. +> Automatic MDE onboarding is asynchronous. [Microsoft documents up to one hour for new machines](https://learn.microsoft.com/azure/defender-for-cloud/enable-defender-for-endpoint#enable-defender-for-endpoint-integration-on-a-subscription) after integration is established; first-time integration in a tenant can take up to 12 hours. Keep your VM powered on with **Guest management: Enabled (Connected)**. Continue with Tasks 4 and 5 while waiting; do not toggle Defender settings or install the extension manually to hurry it along. + +Open **your VM -> Extensions** and look for **MDE.Windows**, then check its provisioning +status is **Succeeded**. Record the extension/onboarding status separately from the +Servers plan status. Successful extension installation is not proof of ongoing device +reporting; ask the facilitator to verify the device in the Microsoft Defender portal +if you do not have access. If the extension is absent after the expected onboarding +window, or reports a failure, ask the facilitator to check guest connectivity and +Endpoint protection. Do not change subscription-level settings yourself. ### 3.2 Review Your VM's Security Posture +If your facilitator has prepared **`sov-ref-vm`** in the shared LocalBox resource +group, you may inspect its existing recommendations while your own VM is still +onboarding. Record the reference VM's resource ID separately. **Inspect only: do +not remediate, change extensions, restart or delete it.** Return to your own VM +for the exercise; the reference does not replace creating and assessing your VM. + 1. From your VM's resource details in Defender for Cloud, open its **Recommendations**. Alternatively, open your Azure Local VM resource and select **Settings -> Security** to view its recommendations 2. Review the assessment status and any recommendations for that VM. If navigating from the subscription-wide **Recommendations** view, verify the affected resource is your VM by resource ID 3. For an available recommendation, inspect its severity, reason, and remediation guidance @@ -229,6 +243,17 @@ The example subscription uses **Servers Plan 2** with **Full** monitoring covera The example VM shows **Microsoft Defender for Servers On** and **No recommendations to display**. Use **View all recommendations in Defender for Cloud** to investigate further, keeping the results scoped to your own VM. +The following screenshot shows recommendations after assessments had time to populate: + +Azure Arc VM win01 Security page with Defender for Servers On, populated update and Windows configuration recommendations, and zero security alerts. + +In this workshop test, the view was empty shortly after MDE installation and +contained recommendations when checked the next morning, about **10 hours after +the MDE extension was created**. They may have appeared earlier between checks; +this is an observed example, not a required wait or guaranteed processing time. +Your findings and counts will differ. Continue the workshop rather than waiting +for recommendations to appear. + Depending on the enabled plan and completed assessments, recommendations may concern: | Area | What to investigate | @@ -236,11 +261,24 @@ Depending on the enabled plan and completed assessments, recommendations may con | System updates | Missing OS security updates | | Endpoint protection | Defender for Endpoint onboarding or protection status | | Vulnerabilities | Detected software vulnerabilities and remediation guidance | +| OS configuration | Windows security settings assessed against the configured baseline | Only investigate or remediate your own VM. Do not apply subscription-wide fixes or change shared LocalBox infrastructure as part of this exercise. > [!NOTE] -> Recommendations may still be pending on a fresh VM, and an assessed VM may have no active recommendations. Record the displayed assessment status separately from the verified protection status. An empty recommendations list does not demonstrate completed assessment or compliance. You can complete Task 4 while waiting, then return to review your VM's results. +> **Zero recommendations is a valid observation, not proof that assessment is complete.** MDE installation, telemetry collection, security assessments, and display of recommendations happen separately. Recommendations can appear later than the extension, and different checks have different evaluation schedules; the one-hour onboarding window is not a deadline for recommendations. A machine with completed assessments can also have no applicable findings. + +Use **View all recommendations in Defender for Cloud**, keep the scope on your VM, +and review any available assessment status and timestamp. Record one of these outcomes: + +- Findings available: explain one recommendation and its proposed remediation. +- Assessments completed with no findings: record that outcome. +- No assessment evidence yet: record **assessment pending/not yet confirmed**, not "fully compliant." + +You do not need a nonzero recommendation count to complete this exercise. Continue +with the remaining tasks and revisit later if time permits; do not weaken security +to generate findings. [Reviewing recommendations](https://learn.microsoft.com/azure/defender-for-cloud/review-security-recommendations) +provides more detail on the available views and filters. ### 3.3 Review Security Alerts (if any) @@ -265,6 +303,10 @@ No alerts is an expected outcome for a new lab VM; do not generate an alert or s **Use Azure Update Manager to assess OS updates on the same Windows Server VM you created in Task 2.** +You may also view existing assessment results and timestamps on `sov-ref-vm`, if +provided by the facilitator. **Do not select the shared reference VM for Check +for updates, patch installation, scheduling or any configuration change.** + ### 4.1 Locate Your VM 1. In the Azure Portal, search for **Azure Update Manager** @@ -554,7 +596,7 @@ Before completing this challenge, verify: - [ ] You can navigate and understand the LocalBox hybrid environment in the Azure Portal - [ ] You have deployed your own VM on Azure Local and verified that guest management is Enabled (Connected) -- [ ] You have verified Defender for Servers coverage for your VM and reviewed its available recommendations, or recorded that its assessment is still pending +- [ ] You have verified Defender for Servers plan coverage, recorded MDE extension/onboarding status separately, and reviewed available recommendations or recorded that assessment is pending/not yet confirmed; a nonzero recommendation count is not required - [ ] You have completed an Azure Update Manager assessment for your VM and reviewed its timestamp and results, including when no updates are pending - [ ] You verified the organizer-prepared `arcnetworking` extension is Succeeded, MetalLB workloads are healthy, and `aks-pool` has the reserved ARP VIP range - [ ] Your application rolled out successfully in your team's namespace and received an IP from that pool From 9c2f11c1ba664d7d03d6c708a217bf5657b389f8 Mon Sep 17 00:00:00 2001 From: Jan Egil Ring Date: Wed, 21 Oct 2026 07:47:44 +0000 Subject: [PATCH 2/2] Resolve LocalBox image versions from the Edge Marketplace catalog Select and verify cluster-advertised versions before importing or recovering failed images. Preserve working image reuse, validate explicit versions, and document the Azure Compute versus Edge Marketplace catalog mismatch with regression coverage. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../resources/localbox/readme.md | 8 +- .../resources/prepare-localbox.ps1 | 98 ++++++++++---- .../tests/prepare-localbox.tests.ps1 | 123 ++++++++++++++++-- .../resources/tests/readme.md | 8 +- 4 files changed, 199 insertions(+), 38 deletions(-) diff --git a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/localbox/readme.md b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/localbox/readme.md index b2622b945..c74989a08 100644 --- a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/localbox/readme.md +++ b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/localbox/readme.md @@ -11,7 +11,7 @@ Run [prepare-localbox.ps1](../prepare-localbox.ps1) **inside LocalBox-Client**, - An existing Entra AKS admin-group object ID. Hosted events use **Lab Group ObjectId** from the Console's **Credentials** tab, published by [shared automation](../hosted-events/readme.md#console-group-dependency). Group membership is not validated through Graph by this script. - `Microsoft.KubernetesRuntime` registered, and its tenant-specific service-principal **object ID** available for MetalLB. Updated Console setup stores this nonsecret ID in the shared resource group's `microhack-k8s-runtime-object-id` tag. Manual organizers supply `-KubernetesRuntimeObjectId`; see [MetalLB preparation](#metallb-preparation) below. The Client managed identity needs write access to cluster extensions and KubernetesRuntime load balancers at the AKS connected-cluster scope; do not grant participants those permissions for installation. - The installed Jumpstart configuration referenced by `$env:LocalBoxConfigFile` (or `-ConfigPath`), containing `SDNDomainFQDN` and `SDNAdminPassword`. Preparation constructs the nested administrator credential locally without printing it. Supply `-NodeCredential` to override it if the nested password/account has changed. Azure managed identity cannot authenticate Windows PowerShell Direct inside nested nodes. -- `Microsoft.EdgeMarketplace` registered by the subscription owner. The Marketplace image workflow also requires the Azure Connected Machine Resource Manager role for the `Microsoft.AzureStackHCI` resource-provider identity on the image resource group; ask the deployment owner to verify this prerequisite. No automatic privilege escalation is attempted. +- `Microsoft.EdgeMarketplace` registered by the subscription owner. The Client managed identity needs read access to the cluster's Edge Marketplace offers for image-version discovery. The Marketplace image workflow also requires the Azure Connected Machine Resource Manager role for the `Microsoft.AzureStackHCI` resource-provider identity on the image resource group; ask the deployment owner to verify this prerequisite. No automatic privilege escalation is attempted. - The standard greenfield LocalBox network configuration. For customized networks, review DHCP exclusions/static reservations for both node/VM pools **and the AKS service VIP range** before preparation. - Sufficient backing `V:` capacity and event budget. Dynamic disks do not add physical backing capacity. @@ -67,7 +67,11 @@ Read-only health checks still run, and preparation still waits for active storag If the configured Marketplace image exists in **Failed** state, preparation verifies its region, custom location, storage path and image settings, then **deletes that exact image and retries its import**. Use `-Confirm` to request confirmation first. It verifies that deletion has finished before creating the replacement. A matching deletion already in progress is awaited. `-WhatIf` previews recovery without deleting or importing anything. Healthy/in-progress images are reused or awaited; conflicting or incomplete image metadata requires organizer review and is never deleted automatically. -There is **one import attempt per run**, not an endless retry loop. If the new attempt also fails, its original CLI error is reported and the failed resource remains available for investigation. For example, `GenerateTokenFromEdgeMarketplaceServiceFailed` with `NotFound` means Edge Marketplace could not issue the image-download SAS token; it does not by itself prove a transient outage. Check the requested image's availability, provider prerequisites and service status before another retry. See [Microsoft's image-management guidance](https://learn.microsoft.com/azure/azure-local/manage/virtual-machine-manage-image#delete-vm-image). +Before a new import or failed-image deletion, the default `-ImageVersion latest` is resolved to the newest version **advertised by this Azure Local cluster's Edge Marketplace catalog**, and that exact version is passed to the import command. An explicit `-ImageVersion` must also be listed there. Catalog lookup failures stop preparation without deleting the failed image or guessing a fallback version. Existing healthy/in-progress images are reused or awaited without a catalog lookup or automatic upgrade. + +This avoids a mismatch observed on 21 October 2026: Azure Compute exposed `26100.33650.261013`, but the cluster's Edge Marketplace catalog only advertised versions through `26100.33438.260905` for the same Windows Server 2025 smalldisk SKU. Passing `latest` directly had resolved to the newer, unavailable Edge version and failed with `GenerateTokenFromEdgeMarketplaceServiceFailed` / `NotFound`. The script discovers the available version rather than hard-coding either example. + +There is **one import attempt per run**, not an endless retry loop. If the new attempt also fails, its original CLI error is reported and the failed resource remains available for investigation. The SAS-token error does not by itself prove a transient outage; check catalog availability, provider prerequisites and service status before another retry. See [Microsoft's image-management guidance](https://learn.microsoft.com/azure/azure-local/manage/virtual-machine-manage-image#delete-vm-image). ## MetalLB preparation diff --git a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/prepare-localbox.ps1 b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/prepare-localbox.ps1 index 4a7475566..38ce54429 100644 --- a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/prepare-localbox.ps1 +++ b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/prepare-localbox.ps1 @@ -20,6 +20,8 @@ Operations run without confirmation under the default PowerShell preference; use Confirm to request prompts or WhatIf to preview changes. Assumes the standard greenfield LocalBox network configuration. Configured address ranges and known reservations are validated; live DHCP leases are not inspected. +New image imports resolve latest from this cluster's Edge Marketplace catalog, +not the Azure Compute catalog; existing working images are reused unchanged. Dot-source this file to load its functions without running provisioning. #> [CmdletBinding(SupportsShouldProcess)] @@ -489,9 +491,51 @@ function Sync-LocalBoxResource { } } +function Resolve-LocalBoxImageVersion { + param( + [Parameter(Mandatory)][string]$ClusterId, + [Parameter(Mandatory)][hashtable]$Identifier, + [ValidateNotNullOrEmpty()][string]$RequestedVersion = 'latest' + ) + if ($ClusterId -notmatch '^/subscriptions/[^/]+/resourceGroups/[^/]+/providers/Microsoft\.AzureStackHCI/clusters/[^/]+$') { + throw 'Image version discovery requires the Azure Local cluster resource ID.' + } + foreach ($field in @('publisher', 'offer', 'sku')) { + if ([string]::IsNullOrWhiteSpace($Identifier[$field])) { throw "Image identifier is missing $field." } + } + $offerId = [uri]::EscapeDataString("$($Identifier.publisher):$($Identifier.offer)") + $offer = Invoke-LocalBoxAz @('rest', '--method', 'get', '--url', + "https://management.azure.com$ClusterId/providers/Microsoft.EdgeMarketplace/offers/${offerId}?api-version=2023-08-01") + if ($offer.properties.marketplaceSkus -isnot [System.Collections.IList]) { + throw 'Edge Marketplace returned no valid SKU catalog for this cluster; no image import or deletion was attempted.' + } + $skus = @($offer.properties.marketplaceSkus | Where-Object marketplaceSkuId -eq $Identifier.sku) + if ($skus.Count -ne 1 -or $skus[0].marketplaceSkuVersions -isnot [System.Collections.IList] -or + $skus[0].marketplaceSkuVersions.Count -eq 0) { + throw "Edge Marketplace does not advertise one usable version list for SKU '$($Identifier.sku)' on this cluster." + } + $versions = @($skus[0].marketplaceSkuVersions | ForEach-Object { + $parsed = $null + if (-not [version]::TryParse($_.name, [ref]$parsed)) { throw 'Edge Marketplace returned an invalid image version.' } + [string]$_.name + }) + if ($RequestedVersion -eq 'latest') { + $selected = $versions | Sort-Object { [version]$_ } -Descending | Select-Object -First 1 + } + elseif ($RequestedVersion -in $versions) { $selected = $RequestedVersion } + else { + throw "Image version '$RequestedVersion' is not advertised by this cluster's Edge Marketplace catalog for '$($Identifier.sku)'. Select a listed version or use latest." + } + Write-Host "Using Edge Marketplace image version $selected for $($Identifier.sku)." + return $selected +} + function Sync-LocalBoxImage { [CmdletBinding(SupportsShouldProcess)] - param([string]$Id, [hashtable]$Expected, [string[]]$CreateArguments, [int]$TimeoutSeconds = 21600) + param( + [string]$Id, [hashtable]$Expected, [string[]]$CreateArguments, + [Parameter(Mandatory)][string]$ClusterId, [int]$TimeoutSeconds = 21600 + ) if ($Id -notmatch '^/subscriptions/([^/]+)/resourceGroups/([^/]+)/providers/Microsoft\.AzureStackHCI/marketplaceGalleryImages/[^/]+$') { throw 'Image recovery requires an exact Azure Local Marketplace image resource ID.' } @@ -499,29 +543,37 @@ function Sync-LocalBoxImage { $existing = @((Invoke-LocalBoxAz $listArguments) | Where-Object id -ieq $Id) if ($existing.Count) { $image = Get-LocalBoxResource $Id - if ($image.properties.provisioningState -in @('Failed', 'Deleting')) { - Assert-LocalBoxProperties $image $Expected - if ($image.properties.provisioningState -eq 'Failed') { - Write-Warning "Image import failed for $Id. Review its deployment error before retrying; recovery does not fix Marketplace availability or configuration errors." - if (-not $PSCmdlet.ShouldProcess($Id, 'Delete matching Failed image and retry import once')) { - if ($WhatIfPreference) { return } - throw 'Failed-image recovery was declined. No image was deleted; preparation cannot continue with this image.' - } - Invoke-LocalBoxAz @('resource', 'delete', '--ids', $Id, '--api-version', '2024-01-01') -TimeoutSeconds $TimeoutSeconds -NoOutput - } - if ($WhatIfPreference) { - Write-Host "Image deletion is in progress; a real run will wait for removal before importing $Id." - return - } - $deadline = [datetime]::UtcNow.AddSeconds($TimeoutSeconds) - while (@((Invoke-LocalBoxAz $listArguments) | Where-Object id -ieq $Id).Count) { - if ([datetime]::UtcNow -ge $deadline) { throw "Timed out waiting for image deletion: $Id. No replacement import was started." } - Write-Host "Waiting for failed image removal: $Id..." - Start-Sleep -Seconds 15 + if ($image.properties.provisioningState -notin @('Failed', 'Deleting')) { + return Sync-LocalBoxResource -Id $Id -Expected $Expected -CreateArguments $CreateArguments -TimeoutSeconds $TimeoutSeconds -WhatIf:$WhatIfPreference + } + Assert-LocalBoxProperties $image $Expected + } + $requestedVersion = if ($Expected.properties.Contains('version')) { $Expected.properties.version.name } else { 'latest' } + $version = Resolve-LocalBoxImageVersion -ClusterId $ClusterId -Identifier $Expected.properties.identifier -RequestedVersion $requestedVersion + $createExpected = $Expected.Clone() + $createExpected.properties = $Expected.properties.Clone() + $createExpected.properties.version = @{ name = $version } + if ($existing.Count) { + if ($image.properties.provisioningState -eq 'Failed') { + Write-Warning "Image import failed for $Id. Retrying with catalog-listed version $version." + if (-not $PSCmdlet.ShouldProcess($Id, 'Delete matching Failed image and retry import once')) { + if ($WhatIfPreference) { return } + throw 'Failed-image recovery was declined. No image was deleted; preparation cannot continue with this image.' } + Invoke-LocalBoxAz @('resource', 'delete', '--ids', $Id, '--api-version', '2024-01-01') -TimeoutSeconds $TimeoutSeconds -NoOutput + } + if ($WhatIfPreference) { + Write-Host "Image deletion is in progress; a real run will wait for removal before importing $Id." + return + } + $deadline = [datetime]::UtcNow.AddSeconds($TimeoutSeconds) + while (@((Invoke-LocalBoxAz $listArguments) | Where-Object id -ieq $Id).Count) { + if ([datetime]::UtcNow -ge $deadline) { throw "Timed out waiting for image deletion: $Id. No replacement import was started." } + Write-Host "Waiting for failed image removal: $Id..." + Start-Sleep -Seconds 15 } } - Sync-LocalBoxResource -Id $Id -Expected $Expected -CreateArguments $CreateArguments -TimeoutSeconds $TimeoutSeconds -WhatIf:$WhatIfPreference + Sync-LocalBoxResource -Id $Id -Expected $createExpected -CreateArguments ($CreateArguments + @('--version', $version)) -TimeoutSeconds $TimeoutSeconds -WhatIf:$WhatIfPreference } function Get-LocalBoxMetalLbPlan { @@ -1013,9 +1065,9 @@ function Invoke-LocalBoxPreparation { } if ($Settings.ImageVersion -ne 'latest') { $imageExpected.properties.version = @{ name = $Settings.ImageVersion } } Write-Host "Preparing VM image $($Settings.ImageName); download can take several hours..." - $image = Sync-LocalBoxImage -Id $imageId -Expected $imageExpected -TimeoutSeconds $timeout -CreateArguments (@('stack-hci-vm', 'image', 'create') + $common + @( + $image = Sync-LocalBoxImage -Id $imageId -Expected $imageExpected -ClusterId $cluster[0].id -TimeoutSeconds $timeout -CreateArguments (@('stack-hci-vm', 'image', 'create') + $common + @( '--name', $Settings.ImageName, '--os-type', 'Windows', '--publisher', 'microsoftwindowsserver', '--offer', 'windowsserver', - '--sku', '2025-datacenter-azure-edition-smalldisk', '--version', $Settings.ImageVersion, '--storage-path-id', $storageId)) + '--sku', '2025-datacenter-azure-edition-smalldisk', '--storage-path-id', $storageId)) $referenceVm = Sync-LocalBoxReferenceVm -Settings $Settings -Location $location -CustomLocationId $custom.id ` -ImageId $imageId -NetworkId $networks[0].Id -StorageId $storageId -WhatIf:$WhatIfPreference $manifest = @{ diff --git a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/tests/prepare-localbox.tests.ps1 b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/tests/prepare-localbox.tests.ps1 index 398282af5..4950a7b2b 100644 --- a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/tests/prepare-localbox.tests.ps1 +++ b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/tests/prepare-localbox.tests.ps1 @@ -1715,6 +1715,62 @@ Describe 'Resource reconciliation' { } } +Describe 'LocalBox Edge Marketplace image versions' { + BeforeEach { + $clusterId = '/subscriptions/test/resourceGroups/localbox/providers/Microsoft.AzureStackHCI/clusters/localboxcluster' + $identifier = @{ publisher = 'microsoftwindowsserver'; offer = 'windowsserver'; sku = '2025-datacenter-azure-edition-smalldisk' } + $sku = @{ marketplaceSkuId = $identifier.sku; marketplaceSkuVersions = @( + @{ name = '26100.33222.260810' }, @{ name = '26100.33438.260905' } + ) } + $catalog = @{ properties = @{ marketplaceSkus = @($sku) } } + Mock Invoke-LocalBoxAz { $catalog } + } + It 'resolves latest from the cluster catalog rather than the newer Azure Compute image' { + Resolve-LocalBoxImageVersion $clusterId $identifier | Should -Be '26100.33438.260905' + Should -Invoke Invoke-LocalBoxAz -Times 1 -Exactly -ParameterFilter { + $Arguments[0] -eq 'rest' -and $Arguments -contains 'get' -and + $Arguments -contains "https://management.azure.com$clusterId/providers/Microsoft.EdgeMarketplace/offers/microsoftwindowsserver%3Awindowsserver?api-version=2023-08-01" + } + } + It 'sorts versions numerically rather than lexically or by catalog order' { + $sku.marketplaceSkuVersions = @(@{ name = '26100.999.260905' }, @{ name = '26100.1000.260905' }) + Resolve-LocalBoxImageVersion $clusterId $identifier | Should -Be '26100.1000.260905' + } + It 'honors an explicit catalog-listed version' { + Resolve-LocalBoxImageVersion $clusterId $identifier '26100.33222.260810' | Should -Be '26100.33222.260810' + } + It 'rejects a version available in Azure Compute but absent from Edge Marketplace' { + { Resolve-LocalBoxImageVersion $clusterId $identifier '26100.33650.261013' } | Should -Throw '*not advertised*' + } + It 'supports deserialized ArrayList catalog collections' { + $catalog.properties.marketplaceSkus = [Collections.ArrayList]::new(@($sku)) + $sku.marketplaceSkuVersions = [Collections.ArrayList]::new(@(@{ name = '26100.33438.260905' })) + Resolve-LocalBoxImageVersion $clusterId $identifier | Should -Be '26100.33438.260905' + } + It 'fails explicitly for unusable catalogs' -TestCases @( + @{ Invalid = 'missing catalog' }; @{ Invalid = 'missing SKU' }; @{ Invalid = 'duplicate SKU' } + @{ Invalid = 'empty versions' }; @{ Invalid = 'invalid version' } + ) { + param($Invalid) + switch ($Invalid) { + 'missing catalog' { $catalog.properties.Remove('marketplaceSkus') } + 'missing SKU' { $sku.marketplaceSkuId = 'different' } + 'duplicate SKU' { $catalog.properties.marketplaceSkus = @($sku, $sku) } + 'empty versions' { $sku.marketplaceSkuVersions = @() } + 'invalid version' { $sku.marketplaceSkuVersions = @(@{ name = 'latest' }) } + } + { Resolve-LocalBoxImageVersion $clusterId $identifier } | Should -Throw '*Edge Marketplace*' + } + It 'propagates catalog authorization failures without a fallback version' { + Mock Invoke-LocalBoxAz { throw 'AuthorizationFailed' } + { Resolve-LocalBoxImageVersion $clusterId $identifier } | Should -Throw '*AuthorizationFailed*' + } + It 'rejects a wrong resource scope before querying the catalog' { + { Resolve-LocalBoxImageVersion '/subscriptions/test/resourceGroups/localbox' $identifier } | Should -Throw '*cluster resource ID*' + Should -Invoke Invoke-LocalBoxAz -Times 0 + } +} + Describe 'LocalBox failed image recovery' { BeforeEach { $imageId = '/subscriptions/test/resourceGroups/localbox/providers/Microsoft.AzureStackHCI/marketplaceGalleryImages/windows' @@ -1723,22 +1779,35 @@ Describe 'LocalBox failed image recovery' { identifier = @{ publisher = 'microsoftwindowsserver'; offer = 'windowsserver'; sku = '2025-datacenter-azure-edition-smalldisk' } } } $actual = $expected | ConvertTo-Json -Depth 10 | ConvertFrom-Json -AsHashtable $actual.properties.provisioningState = 'Failed' + $actual.properties.version = @{ name = '26100.33650.261013' } $script:imagePresent = $true $createArguments = @('stack-hci-vm', 'image', 'create', '--name', 'windows') + $syncArguments = @{ + Id = $imageId; Expected = $expected; CreateArguments = $createArguments + ClusterId = '/subscriptions/test/resourceGroups/localbox/providers/Microsoft.AzureStackHCI/clusters/localboxcluster' + } Mock Invoke-LocalBoxAz { if ($Arguments[1] -eq 'list' -and $script:imagePresent) { @(@{ id = $imageId }) } elseif ($Arguments[1] -eq 'delete') { $script:imagePresent = $false } } Mock Get-LocalBoxResource { $actual } - Mock Wait-LocalBoxResource { $expected } + Mock Wait-LocalBoxResource { + $imported = $expected | ConvertTo-Json -Depth 10 | ConvertFrom-Json -AsHashtable + $imported.properties.version = @{ name = '26100.33438.260905' } + $imported + } + Mock Resolve-LocalBoxImageVersion { '26100.33438.260905' } Mock Start-Sleep {} } It 'removes only the matching Failed image, verifies absence and imports once' { - Sync-LocalBoxImage $imageId $expected $createArguments -Confirm:$false | Out-Null + Sync-LocalBoxImage @syncArguments -Confirm:$false | Out-Null Should -Invoke Invoke-LocalBoxAz -Times 1 -Exactly -ParameterFilter { $Arguments[1] -eq 'delete' -and $Arguments -contains $imageId -and $Arguments -contains '2024-01-01' } - Should -Invoke Invoke-LocalBoxAz -Times 1 -Exactly -ParameterFilter { $Arguments[2] -eq 'create' } + Should -Invoke Invoke-LocalBoxAz -Times 1 -Exactly -ParameterFilter { + $Arguments[2] -eq 'create' -and $Arguments[$Arguments.IndexOf('--version') + 1] -eq '26100.33438.260905' + } + $expected.properties.Contains('version') | Should -BeFalse Should -Invoke Invoke-LocalBoxAz -Times 3 -Exactly -ParameterFilter { $Arguments[1] -eq 'list' -and $Arguments -contains 'test' -and $Arguments -contains 'localbox' } @@ -1748,9 +1817,10 @@ Describe 'LocalBox failed image recovery' { ) { param($State) $actual.properties.provisioningState = $State - Sync-LocalBoxImage $imageId $expected $createArguments -Confirm:$false | Out-Null + Sync-LocalBoxImage @syncArguments -Confirm:$false | Out-Null Should -Invoke Invoke-LocalBoxAz -Times 0 -ParameterFilter { $Arguments -contains 'delete' -or $Arguments -contains 'create' } Should -Invoke Wait-LocalBoxResource -Times 1 -Exactly + Should -Invoke Resolve-LocalBoxImageVersion -Times 0 } It 'does not remove conflicting Failed images' -TestCases @( @{ Field = 'location' }; @{ Field = 'storage' }; @{ Field = 'custom' }; @{ Field = 'sku' }; @{ Field = 'missing' } @@ -1763,11 +1833,11 @@ Describe 'LocalBox failed image recovery' { 'sku' { $actual.properties.identifier.sku = 'different' } 'missing' { $actual.properties.Remove('identifier') } } - { Sync-LocalBoxImage $imageId $expected $createArguments -Confirm:$false } | Should -Throw + { Sync-LocalBoxImage @syncArguments -Confirm:$false } | Should -Throw Should -Invoke Invoke-LocalBoxAz -Times 0 -ParameterFilter { $Arguments -contains 'delete' -or $Arguments -contains 'create' } } It 'does not delete or import a Failed image during WhatIf' { - Sync-LocalBoxImage $imageId $expected $createArguments -WhatIf | Should -BeNullOrEmpty + Sync-LocalBoxImage @syncArguments -WhatIf | Should -BeNullOrEmpty Should -Invoke Invoke-LocalBoxAz -Times 0 -ParameterFilter { $Arguments -contains 'delete' -or $Arguments -contains 'create' } Should -Invoke Wait-LocalBoxResource -Times 0 } @@ -1780,38 +1850,63 @@ Describe 'LocalBox failed image recovery' { if ($script:imageLists -le 2) { @(@{ id = $imageId }) } } } - Sync-LocalBoxImage $imageId $expected $createArguments -Confirm:$false | Out-Null + Sync-LocalBoxImage @syncArguments -Confirm:$false | Out-Null Should -Invoke Invoke-LocalBoxAz -Times 0 -ParameterFilter { $Arguments -contains 'delete' } Should -Invoke Invoke-LocalBoxAz -Times 1 -Exactly -ParameterFilter { $Arguments[2] -eq 'create' } Should -Invoke Start-Sleep -Times 1 -Exactly } It 'does not wait for deletion during WhatIf' { $actual.properties.provisioningState = 'Deleting' - Sync-LocalBoxImage $imageId $expected $createArguments -WhatIf | Should -BeNullOrEmpty + Sync-LocalBoxImage @syncArguments -WhatIf | Should -BeNullOrEmpty Should -Invoke Start-Sleep -Times 0 Should -Invoke Invoke-LocalBoxAz -Times 0 -ParameterFilter { $Arguments -contains 'delete' -or $Arguments -contains 'create' } } It 'does not recreate when deletion fails' { Mock Invoke-LocalBoxAz { throw 'AuthorizationFailed' } -ParameterFilter { $Arguments[1] -eq 'delete' } - { Sync-LocalBoxImage $imageId $expected $createArguments -Confirm:$false } | Should -Throw '*AuthorizationFailed*' + { Sync-LocalBoxImage @syncArguments -Confirm:$false } | Should -Throw '*AuthorizationFailed*' Should -Invoke Invoke-LocalBoxAz -Times 0 -ParameterFilter { $Arguments[2] -eq 'create' } } It 'bounds deletion waits and does not import over a remaining object' { Mock Invoke-LocalBoxAz {} -ParameterFilter { $Arguments[1] -eq 'delete' } - { Sync-LocalBoxImage $imageId $expected $createArguments -TimeoutSeconds 0 -Confirm:$false } | Should -Throw '*Timed out waiting for image deletion*' + { Sync-LocalBoxImage @syncArguments -TimeoutSeconds 0 -Confirm:$false } | Should -Throw '*Timed out waiting for image deletion*' Should -Invoke Invoke-LocalBoxAz -Times 0 -ParameterFilter { $Arguments[2] -eq 'create' } } It 'surfaces a fresh import failure without deleting it or looping on retries' { $script:imagePresent = $false Mock Invoke-LocalBoxAz { throw 'GenerateTokenFromEdgeMarketplaceServiceFailed: NotFound' } -ParameterFilter { $Arguments[2] -eq 'create' } - { Sync-LocalBoxImage $imageId $expected $createArguments -Confirm:$false } | Should -Throw '*GenerateTokenFromEdgeMarketplaceServiceFailed*' + { Sync-LocalBoxImage @syncArguments -Confirm:$false } | Should -Throw '*GenerateTokenFromEdgeMarketplaceServiceFailed*' Should -Invoke Invoke-LocalBoxAz -Times 1 -Exactly -ParameterFilter { $Arguments[2] -eq 'create' } Should -Invoke Invoke-LocalBoxAz -Times 0 -ParameterFilter { $Arguments -contains 'delete' } } It 'refuses recovery for a non-image resource ID' { - { Sync-LocalBoxImage '/subscriptions/test/resourceGroups/localbox/providers/Microsoft.AzureStackHCI/logicalNetworks/net' $expected $createArguments } | Should -Throw '*exact Azure Local Marketplace image*' + $syncArguments.Id = '/subscriptions/test/resourceGroups/localbox/providers/Microsoft.AzureStackHCI/logicalNetworks/net' + { Sync-LocalBoxImage @syncArguments } | Should -Throw '*exact Azure Local Marketplace image*' Should -Invoke Invoke-LocalBoxAz -Times 0 } + It 'preserves the failed image when catalog discovery fails' { + Mock Resolve-LocalBoxImageVersion { throw 'Catalog unavailable' } + { Sync-LocalBoxImage @syncArguments } | Should -Throw '*Catalog unavailable*' + Should -Invoke Invoke-LocalBoxAz -Times 0 -ParameterFilter { $Arguments -contains 'delete' -or $Arguments -contains 'create' } + } + It 'passes an explicit requested version through catalog validation for a new image' { + $script:imagePresent = $false + $expected.properties.version = @{ name = '26100.33438.260905' } + Sync-LocalBoxImage @syncArguments | Out-Null + Should -Invoke Resolve-LocalBoxImageVersion -Times 1 -Exactly -ParameterFilter { + $RequestedVersion -eq '26100.33438.260905' -and $ClusterId -eq $syncArguments.ClusterId + } + } + It 'resolves a new image during WhatIf without importing it' { + $script:imagePresent = $false + Sync-LocalBoxImage @syncArguments -WhatIf | Should -BeNullOrEmpty + Should -Invoke Resolve-LocalBoxImageVersion -Times 1 -Exactly + Should -Invoke Invoke-LocalBoxAz -Times 0 -ParameterFilter { $Arguments -contains 'delete' -or $Arguments -contains 'create' } + Should -Invoke Wait-LocalBoxResource -Times 0 + } + It 'verifies the imported version rather than accepting a different resolved image' { + Mock Wait-LocalBoxResource { $actual } + { Sync-LocalBoxImage @syncArguments -Confirm:$false } | Should -Throw '*Conflicting*version.name*' + } } Describe 'LocalBox preparation dependency order' { @@ -1828,6 +1923,7 @@ Describe 'LocalBox preparation dependency order' { AksClusterName = 'localbox-aks'; NodeCount = 3; NodeVmSize = 'node'; ControlPlaneVmSize = 'control'; SkipAks = $false; IncludeReferenceVm = $true KubernetesRuntimeObjectId = '11111111-1111-1111-1111-111111111111' } $custom = @{ id = 'custom'; location = 'australiaeast' } + $cluster = @(@{ id = "$scope/providers/Microsoft.AzureStackHCI/clusters/localboxcluster" }) $config = @{ AKSControlPlaneIP = '10.10.0.5' } $storageId = 'storage' $imageId = 'image' @@ -1850,6 +1946,9 @@ Describe 'LocalBox preparation dependency order' { It 'completes networks, AKS and MetalLB before importing the image and reference VM' { & $prepareWorkloads -Confirm:$false $events -join ',' | Should -Be 'network:localbox-vm-lnet-vlan200,network:localbox-aks-lnet-vlan110,aks-validate,aks-create,proxy,metallb,image,reference' + Should -Invoke Sync-LocalBoxImage -Times 1 -Exactly -ParameterFilter { + $ClusterId -eq "$scope/providers/Microsoft.AzureStackHCI/clusters/localboxcluster" -and $CreateArguments -notcontains '--version' + } } It 'preserves completed infrastructure when the late image stage fails and does not start the reference VM' { Mock Sync-LocalBoxImage { $events.Add('image-failed'); throw 'Import failed' } diff --git a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/tests/readme.md b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/tests/readme.md index 8dad83ad5..22970471b 100644 --- a/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/tests/readme.md +++ b/03-Azure/01-03-Infrastructure/01_Sovereign_Cloud/resources/tests/readme.md @@ -70,7 +70,13 @@ infrastructure-address, AKS/VIP and VLAN conflicts still stop preparation. The same offline suite checks the standard nonprompting confirmation impact, continued support for explicit `-Confirm`/`-WhatIf`, and safe recovery of matching Failed Marketplace images, conflicting-image protection, bounded deletion waits, -and a single import attempt per run. It executes the preparation workload sequence +and a single import attempt per run. Image-version tests cover numeric selection +from the cluster-scoped Edge Marketplace catalog, explicit version validation, +malformed/unavailable catalogs, discovery before deletion, unchanged reuse of +working images, and verification of the actual imported version. The resolver was +also run read-only against the live cluster catalog on 2026-10-21; it selected +`26100.33438.260905`, while Azure Compute exposed the newer `26100.33650.261013`. +It executes the preparation workload sequence with mocked Azure calls to verify that networks, AKS and MetalLB precede image import and the optional reference VM, including when image import fails.