From 91ff8926c0cc9f8610ec511cb55855b3be52a317 Mon Sep 17 00:00:00 2001 From: sinmentis Date: Wed, 7 Oct 2026 13:10:52 +1300 Subject: [PATCH 1/8] feat: support a shared Windows 2025 Gen2 image AB#39994891 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1e0bbdd6-8fe8-4eef-a0ff-bc3fdd4b8122 --- .pipelines/e2e-windows.yaml | 2 + ....build-and-test-windows-vhds-template.yaml | 2 + e2e/README.md | 13 +++ e2e/scenario/scenario_win.go | 67 ++++++----- ...nsure_sig_trusted_launch_supported_spec.sh | 106 ++++++++++++++++++ .../packer/ensure_sig_vhd_exists_spec.sh | 71 ++++++++++++ .../generate_vhd_publishing_info_spec.sh | 10 ++ .../resolve_security_type_feature_spec.sh | 25 +++++ .../produce-packer-settings-functions.sh | 59 ++++++++++ 9 files changed, 327 insertions(+), 28 deletions(-) create mode 100644 spec/vhdbuilder/packer/ensure_sig_trusted_launch_supported_spec.sh create mode 100644 spec/vhdbuilder/packer/resolve_security_type_feature_spec.sh diff --git a/.pipelines/e2e-windows.yaml b/.pipelines/e2e-windows.yaml index 87fda7d10a0..907f110ca29 100644 --- a/.pipelines/e2e-windows.yaml +++ b/.pipelines/e2e-windows.yaml @@ -1,6 +1,8 @@ name: $(Date:yyyyMMdd)$(Rev:.r) variables: TAGS_TO_RUN: "os=windows" + # The shared-image scenario is covered by the VHD-build gate using that build's image. + TAGS_TO_SKIP: "name=Windows2025Gen2_TrustedLaunch_SharedImage" SKIP_E2E_TESTS: false # if SIG_FOR_PRODUCTION is true, then the VHDs are deleted from the gallery before the e2e tests are run. SIG_FOR_PRODUCTION: False diff --git a/.pipelines/templates/.build-and-test-windows-vhds-template.yaml b/.pipelines/templates/.build-and-test-windows-vhds-template.yaml index c0bcb8e24e6..4ef1bec9965 100644 --- a/.pipelines/templates/.build-and-test-windows-vhds-template.yaml +++ b/.pipelines/templates/.build-and-test-windows-vhds-template.yaml @@ -151,6 +151,8 @@ stages: windowsSku: 2025-gen2 hyperVGeneration: V2 architecture: X86_64 + enableTrustedLaunch: False + trustedLaunchSupported: True buildVmSize: ${{ parameters.buildVmSize }} build: ${{ parameters.build2025gen2 }} vhddebug: ${{ parameters.vhddebug }} diff --git a/e2e/README.md b/e2e/README.md index 0e9404bf3a8..81f75ac2aae 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -108,6 +108,19 @@ alongside cluster preparation. Setup, validators, and cleanup batches share `scenario/concurrent.go`, which waits for every task without canceling peers on failure. Each caller decides whether an already-canceled context prevents its tasks from starting. +## Shared Windows Server 2025 Gen2 image + +`Windows2025Gen2` and `Windows2025Gen2_TrustedLaunch_SharedImage` use the same +`windows-2025-gen2` image. The latter enables Secure Boot and vTPM on the VMSS. +The image definition is published as `TrustedLaunchSupported`; this capability +does not enable Trusted Launch on standard nodes. + +`Windows2025Gen2TrustedLaunch` continues to validate the dedicated +`windows-2025-gen2-tl` image during the shared-image rollout. +The shared-image scenario runs in the Windows VHD-build gate, which selects +that build's image. The general Windows job skips it while main-tagged images +can still predate the capability boundary. + ## VM size configuration Resource SKU catalogues are loaded once per location using the runner's single diff --git a/e2e/scenario/scenario_win.go b/e2e/scenario/scenario_win.go index 31fc797d187..2920cec84a4 100644 --- a/e2e/scenario/scenario_win.go +++ b/e2e/scenario/scenario_win.go @@ -220,34 +220,45 @@ var _ = Register(&Scenario{ }, }) -var _ = Register(&Scenario{ - Name: "Windows2025Gen2TrustedLaunch", - Description: "Windows Server 2025 Gen2 Trusted Launch (Secure Boot + vTPM)", - Config: Config{ - Cluster: ClusterAzureNetwork, - VHD: config.VHDWindows2025Gen2TL, - VMConfigMutator: func(vmss *armcompute.VirtualMachineScaleSet) { - vmss.Properties = addTrustedLaunchToVMSS(vmss.Properties) - }, - BootstrapConfigMutatorWithError: func(_ context.Context, _ *Cluster, configuration *datamodel.NodeBootstrappingConfiguration) error { - return Windows2025BootstrapConfigMutator(configuration) - }, - Validator: func(ctx context.Context, s *Scenario) error { - return errors.Join( - ValidateWindowsVersionFromWindowsSettings(ctx, s, "2025-gen2-tl"), - ValidateWindowsProductName(ctx, s, "Windows Server 2025 Datacenter"), - ValidateWindowsDisplayVersion(ctx, s, "24H2"), - ValidateFileHasContent(ctx, s, "/k/kubeletstart.ps1", "--container-runtime=remote"), - ValidateKubeletArgs(ctx, s), - ValidateContainerdWindowsPriorityClass(ctx, s), - ValidateCiliumIsNotRunningWindows(ctx, s), - ValidateDotnetNotInstalledWindows(ctx, s), - ValidateWindowsSystemServicesRestartConfiguration(ctx, s), - ValidateCollectWindowsLogsScript(ctx, s), - ) - }, - }, -}) +func init() { + for _, tc := range []struct { + name string + vhd *config.Image + windowsSKU string + }{ + {"Windows2025Gen2TrustedLaunch", config.VHDWindows2025Gen2TL, "2025-gen2-tl"}, + {"Windows2025Gen2_TrustedLaunch_SharedImage", config.VHDWindows2025Gen2, "2025-gen2"}, + } { + Register(&Scenario{ + Name: tc.name, + Description: "Windows Server 2025 Gen2 Trusted Launch (Secure Boot + vTPM)", + Config: Config{ + Cluster: ClusterAzureNetwork, + VHD: tc.vhd, + VMConfigMutator: func(vmss *armcompute.VirtualMachineScaleSet) { + vmss.Properties = addTrustedLaunchToVMSS(vmss.Properties) + }, + BootstrapConfigMutatorWithError: func(_ context.Context, _ *Cluster, configuration *datamodel.NodeBootstrappingConfiguration) error { + return Windows2025BootstrapConfigMutator(configuration) + }, + Validator: func(ctx context.Context, s *Scenario) error { + return errors.Join( + ValidateWindowsVersionFromWindowsSettings(ctx, s, tc.windowsSKU), + ValidateWindowsProductName(ctx, s, "Windows Server 2025 Datacenter"), + ValidateWindowsDisplayVersion(ctx, s, "24H2"), + ValidateFileHasContent(ctx, s, "/k/kubeletstart.ps1", "--container-runtime=remote"), + ValidateKubeletArgs(ctx, s), + ValidateContainerdWindowsPriorityClass(ctx, s), + ValidateCiliumIsNotRunningWindows(ctx, s), + ValidateDotnetNotInstalledWindows(ctx, s), + ValidateWindowsSystemServicesRestartConfiguration(ctx, s), + ValidateCollectWindowsLogsScript(ctx, s), + ) + }, + }, + }) + } +} var _ = Register(&Scenario{ Name: "Windows2025Gen2_WindowsCiliumNetworking", diff --git a/spec/vhdbuilder/packer/ensure_sig_trusted_launch_supported_spec.sh b/spec/vhdbuilder/packer/ensure_sig_trusted_launch_supported_spec.sh new file mode 100644 index 00000000000..d268442b9da --- /dev/null +++ b/spec/vhdbuilder/packer/ensure_sig_trusted_launch_supported_spec.sh @@ -0,0 +1,106 @@ +#!/bin/bash +# shellcheck disable=SC2329 + +Describe 'ensure_sig_trusted_launch_supported' + Include './vhdbuilder/packer/produce-packer-settings-functions.sh' + + setup() { + TEST_DIR=$(mktemp -d "${SHELLSPEC_WORKDIR}/trusted-launch.XXXXXX") + AZ_CALLS="${TEST_DIR}/az-calls" + PUT_BODY="${TEST_DIR}/put-body" + : > "${AZ_CALLS}" + AZURE_RESOURCE_GROUP_NAME="test-rg" + SIG_GALLERY_NAME="test-gallery" + SIG_IMAGE_NAME="windows-2025-gen2" + SIG_IMAGE_VERSION="26100.1.261006" + DEFINITION_ID="/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.Compute/galleries/test-gallery/images/windows-2025-gen2" + INITIAL_DEFINITION='{"id":"definition-id","name":"windows-2025-gen2","type":"Microsoft.Compute/galleries/images","location":"eastus","tags":{"preserve":"true"},"properties":{"hyperVGeneration":"V2","provisioningState":"Succeeded","features":[{"name":"DiskControllerTypes","value":"SCSI,NVMe"}]}}' + UPDATED_DEFINITION='{"properties":{"provisioningState":"Succeeded","features":[{"name":"SecurityType","value":"TrustedLaunchSupported"}]}}' + FAIL_OPERATION="" + } + BeforeEach 'setup' + + az() { + printf '%s\n' "$*" >> "${AZ_CALLS}" + case "$1 $2 $3" in + 'rest --method get') + if [ "${FAIL_OPERATION}" = "get" ]; then return 1; fi + if [ -f "${PUT_BODY}" ]; then + printf '%s\n' "${UPDATED_DEFINITION}" + else + printf '%s\n' "${INITIAL_DEFINITION}" + fi + ;; + 'rest --method put') + if [ "${FAIL_OPERATION}" = "put" ]; then return 1; fi + shift 3 + while [ "$#" -gt 0 ]; do + if [ "$1" = "--body" ]; then + printf '%s\n' "$2" > "${PUT_BODY}" + break + fi + shift + done + ;; + 'sig image-definition wait') + if [ "${FAIL_OPERATION}" = "wait" ]; then return 1; fi + ;; + *) return 1 ;; + esac + } + + It 'adds versioned capability while preserving the existing definition' + When call ensure_sig_trusted_launch_supported "${DEFINITION_ID}" + The status should be success + The output should include 'from version 26100.1.261006' + The contents of file "${AZ_CALLS}" should include '--updated --interval 5 --timeout 120' + The value "$(jq -cr '[.id, .name, .type, .properties.provisioningState]' "${PUT_BODY}")" should equal '[null,null,null,null]' + The value "$(jq -cr '[.location, .tags, .properties.hyperVGeneration, .properties.allowUpdateImage]' "${PUT_BODY}")" should equal '["eastus",{"preserve":"true"},"V2",true]' + The value "$(jq -cr '.properties.features' "${PUT_BODY}")" should equal '[{"name":"DiskControllerTypes","value":"SCSI,NVMe"},{"name":"SecurityType","value":"TrustedLaunchSupported","startsAtVersion":"26100.1.261006"}]' + End + + It 'does not move the boundary of an already-supported definition' + INITIAL_DEFINITION='{"properties":{"hyperVGeneration":"V2","features":[{"name":"SecurityType","value":"TrustedLaunchSupported","startsAtVersion":"26100.1.260901"}]}}' + + When call ensure_sig_trusted_launch_supported "${DEFINITION_ID}" + The status should be success + The output should equal '' + The contents of file "${AZ_CALLS}" should not include '--method put' + The file "${PUT_BODY}" should not be exist + End + + It 'refuses to mark a Gen1 definition as capable' + INITIAL_DEFINITION='{"properties":{"hyperVGeneration":"V1","features":[]}}' + + When call ensure_sig_trusted_launch_supported "${DEFINITION_ID}" + The status should be failure + The stderr should include 'must be Gen2' + The contents of file "${AZ_CALLS}" should not include '--method put' + End + + Describe 'Azure failures' + Parameters + get 'Failed to read image definition' + put 'Failed to update Trusted Launch support' + wait 'Timed out waiting for Trusted Launch support' + End + + It "stops when $1 fails" + FAIL_OPERATION="$1" + + When call ensure_sig_trusted_launch_supported "${DEFINITION_ID}" + The status should be failure + The stderr should include "$2" + The output should not include 'did not converge' + End + End + + It 'rejects a completed update with incorrect capability' + UPDATED_DEFINITION='{"properties":{"provisioningState":"Succeeded","features":[{"name":"SecurityType","value":"TrustedLaunch"}]}}' + + When call ensure_sig_trusted_launch_supported "${DEFINITION_ID}" + The status should be failure + The output should include 'Updating image definition' + The stderr should include 'did not converge to TrustedLaunchSupported' + End +End diff --git a/spec/vhdbuilder/packer/ensure_sig_vhd_exists_spec.sh b/spec/vhdbuilder/packer/ensure_sig_vhd_exists_spec.sh index ea07aa4c7aa..f7f068b4c65 100644 --- a/spec/vhdbuilder/packer/ensure_sig_vhd_exists_spec.sh +++ b/spec/vhdbuilder/packer/ensure_sig_vhd_exists_spec.sh @@ -18,6 +18,7 @@ Describe 'ensure_sig_vhd_exists function' HYPERV_GENERATION="" OS_TYPE="" ENABLE_TRUSTED_LAUNCH="" + TRUSTED_LAUNCH_SUPPORTED="False" # Mock variables to control az command behavior MOCK_AZ_SIG_SHOW_STATE="" @@ -125,7 +126,77 @@ Describe 'ensure_sig_vhd_exists function' BeforeEach 'setup_environment' + Describe 'shared Windows Gen2 image creation' + Parameters + False True TrustedLaunchSupported + True True TrustedLaunch + False False '' + End + + It "preserves the expected security capability with TL enabled=$1 supported=$2" + MODE="windowsVhdMode" + AZURE_RESOURCE_GROUP_NAME="test-rg" + SIG_GALLERY_NAME="test-gallery" + SIG_IMAGE_NAME="windows-2025-gen2" + AZURE_LOCATION="eastus" + OS_TYPE="Windows" + HYPERV_GENERATION="V2" + ARCHITECTURE="x64" + ENABLE_TRUSTED_LAUNCH="$1" + TRUSTED_LAUNCH_SUPPORTED="$2" + MOCK_AZ_SIG_SHOW_EXISTS="true" + MOCK_AZ_SIG_SHOW_STATE="Succeeded" + MOCK_AZ_SIG_IMAGE_DEFINITION_EXISTS="false" + + When call ensure_sig_vhd_exists + The status should be success + The output should include '--gallery-image-definition windows-2025-gen2' + The output should include 'DiskControllerTypes=SCSI,NVMe' + if [ -n "$3" ]; then + The output should include "SecurityType=$3" + else + The output should not include 'SecurityType=' + fi + End + End + Describe 'Basic function execution' + It 'reconciles capability for an existing shared Windows Gen2 definition' + MODE="windowsVhdMode" + ENABLE_TRUSTED_LAUNCH="False" + TRUSTED_LAUNCH_SUPPORTED="True" + MOCK_AZ_SIG_SHOW_EXISTS="true" + MOCK_AZ_SIG_SHOW_STATE="Succeeded" + MOCK_AZ_SIG_IMAGE_DEFINITION_EXISTS="true" + + # shellcheck disable=SC2329 + ensure_sig_trusted_launch_supported() { + echo "Reconciled $1" + } + + When call ensure_sig_vhd_exists + The status should be success + The output should include 'Reconciled mock-value' + End + + It 'propagates an existing shared Windows capability update failure' + MODE="windowsVhdMode" + ENABLE_TRUSTED_LAUNCH="False" + TRUSTED_LAUNCH_SUPPORTED="True" + MOCK_AZ_SIG_SHOW_EXISTS="true" + MOCK_AZ_SIG_SHOW_STATE="Succeeded" + MOCK_AZ_SIG_IMAGE_DEFINITION_EXISTS="true" + + # shellcheck disable=SC2329 + ensure_sig_trusted_launch_supported() { + return 1 + } + + When call ensure_sig_vhd_exists + The status should be failure + The output should include 'Image definition' + End + It 'should complete successfully with minimal setup when gallery does not exist' MODE="linuxVhdMode" AZURE_RESOURCE_GROUP_NAME="test-rg" diff --git a/spec/vhdbuilder/packer/generate_vhd_publishing_info_spec.sh b/spec/vhdbuilder/packer/generate_vhd_publishing_info_spec.sh index 8edecbcf47b..4680390deac 100644 --- a/spec/vhdbuilder/packer/generate_vhd_publishing_info_spec.sh +++ b/spec/vhdbuilder/packer/generate_vhd_publishing_info_spec.sh @@ -221,6 +221,16 @@ Describe 'generate-vhd-publishing-info.sh' End Describe 'Windows output' + It 'publishes shared Windows Gen2 capability without relying on a TL image name' + OS_NAME=windows OFFER_NAME=Windows SKU_NAME=windows-2025-gen2 SECURITY_TYPE_FEATURE=TrustedLaunchSupported + AZ_VERSIONS='' + + When call generate_publishing_info + The status should be success + The output should include 'COPY ME ---> https://example.invalid/images/test.vhd' + The value "$(read_metadata '[.os_name, .sku_name, .hyperv_generation, .security_type_feature]')" should equal '["windows","windows-2025-gen2","V2","TrustedLaunchSupported"]' + End + Parameters '' stale-gallery diff --git a/spec/vhdbuilder/packer/resolve_security_type_feature_spec.sh b/spec/vhdbuilder/packer/resolve_security_type_feature_spec.sh new file mode 100644 index 00000000000..d35beee4ebb --- /dev/null +++ b/spec/vhdbuilder/packer/resolve_security_type_feature_spec.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +Describe 'resolve_security_type_feature' + Include './vhdbuilder/packer/produce-packer-settings-functions.sh' + + Parameters + False True None TrustedLaunchSupported + True True None TrustedLaunch + True False None TrustedLaunch + False False cvm ConfidentialVMSupported + False False None Standard + true true None TrustedLaunch + false true None TrustedLaunchSupported + End + + It "resolves enabled=$1 supported=$2 flags=$3 to $4" + ENABLE_TRUSTED_LAUNCH="$1" + TRUSTED_LAUNCH_SUPPORTED="$2" + FEATURE_FLAGS="$3" + + When call resolve_security_type_feature + The status should be success + The variable SECURITY_TYPE_FEATURE should equal "$4" + End +End diff --git a/vhdbuilder/packer/produce-packer-settings-functions.sh b/vhdbuilder/packer/produce-packer-settings-functions.sh index e2173dd5a37..bf041d704ad 100644 --- a/vhdbuilder/packer/produce-packer-settings-functions.sh +++ b/vhdbuilder/packer/produce-packer-settings-functions.sh @@ -465,6 +465,58 @@ function prepare_windows_vhd() { fi } +function ensure_sig_trusted_launch_supported() { + local definition_id="$1" + local definition_uri="${definition_id}?api-version=2024-03-03" + local definition + if ! definition=$(az rest --method get --uri "${definition_uri}" -o json); then + echo "Failed to read image definition ${definition_id} for Trusted Launch support" >&2 + return 1 + fi + + local security_type + security_type=$(jq -r '(.properties.features // []) | map(select(.name == "SecurityType")) | .[0].value // ""' <<<"${definition}") + if [ "${security_type}" = "TrustedLaunchSupported" ]; then + return 0 + fi + if [ "$(jq -r '.properties.hyperVGeneration' <<<"${definition}")" != "V2" ]; then + echo "Image definition ${definition_id} must be Gen2 to support Trusted Launch" >&2 + return 1 + fi + + local body + body=$(jq --arg version "${SIG_IMAGE_VERSION:?SIG_IMAGE_VERSION is required to update Trusted Launch support}" ' + del(.id, .name, .type, .systemData, .properties.provisioningState) + | .properties.allowUpdateImage = true + | .properties.features = ( + ((.properties.features // []) | map(select(.name != "SecurityType"))) + + [{name: "SecurityType", value: "TrustedLaunchSupported", startsAtVersion: $version}] + )' <<<"${definition}") || return 1 + echo "Updating image definition ${definition_id} to TrustedLaunchSupported from version ${SIG_IMAGE_VERSION}" + if ! az rest --method put --uri "${definition_uri}" --body "${body}" -o none; then + echo "Failed to update Trusted Launch support for ${definition_id}" >&2 + return 1 + fi + if ! az sig image-definition wait \ + --resource-group "${AZURE_RESOURCE_GROUP_NAME}" \ + --gallery-name "${SIG_GALLERY_NAME}" \ + --gallery-image-definition "${SIG_IMAGE_NAME}" \ + --updated --interval 5 --timeout 120; then + echo "Timed out waiting for Trusted Launch support on ${definition_id}" >&2 + return 1 + fi + if ! definition=$(az rest --method get --uri "${definition_uri}" -o json); then + echo "Failed to verify Trusted Launch support for ${definition_id}" >&2 + return 1 + fi + if ! jq -e '.properties.provisioningState == "Succeeded" and + any(.properties.features[]; .name == "SecurityType" and .value == "TrustedLaunchSupported")' \ + <<<"${definition}" >/dev/null; then + echo "Image definition ${definition_id} did not converge to TrustedLaunchSupported" >&2 + return 1 + fi +} + function ensure_sig_vhd_exists() { echo "SIG existence checking for $MODE" @@ -666,5 +718,12 @@ function ensure_sig_vhd_exists() { fi else echo "Image definition ${SIG_IMAGE_NAME} existing in gallery ${SIG_GALLERY_NAME} resource group ${AZURE_RESOURCE_GROUP_NAME}" + if [ "${MODE}" = "windowsVhdMode" ] && + [ "${ENABLE_TRUSTED_LAUNCH,,}" != "true" ] && + [ "${TRUSTED_LAUNCH_SUPPORTED,,}" = "true" ]; then + local definition_id + definition_id=$(jq -r '.id' <<<"${id}") + ensure_sig_trusted_launch_supported "${definition_id}" || return 1 + fi fi } From 391485c8b2e37deab361827665fcbb62dac517e3 Mon Sep 17 00:00:00 2001 From: sinmentis Date: Thu, 8 Oct 2026 16:53:42 +1300 Subject: [PATCH 2/8] fix: verify the Trusted Launch capability boundary Reject migration results with a missing or changed startsAtVersion. AB#39994891 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1e0bbdd6-8fe8-4eef-a0ff-bc3fdd4b8122 --- ...nsure_sig_trusted_launch_supported_spec.sh | 19 ++++++++++++++++++- .../produce-packer-settings-functions.sh | 6 +++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/spec/vhdbuilder/packer/ensure_sig_trusted_launch_supported_spec.sh b/spec/vhdbuilder/packer/ensure_sig_trusted_launch_supported_spec.sh index d268442b9da..81fb8ec00d7 100644 --- a/spec/vhdbuilder/packer/ensure_sig_trusted_launch_supported_spec.sh +++ b/spec/vhdbuilder/packer/ensure_sig_trusted_launch_supported_spec.sh @@ -15,7 +15,7 @@ Describe 'ensure_sig_trusted_launch_supported' SIG_IMAGE_VERSION="26100.1.261006" DEFINITION_ID="/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.Compute/galleries/test-gallery/images/windows-2025-gen2" INITIAL_DEFINITION='{"id":"definition-id","name":"windows-2025-gen2","type":"Microsoft.Compute/galleries/images","location":"eastus","tags":{"preserve":"true"},"properties":{"hyperVGeneration":"V2","provisioningState":"Succeeded","features":[{"name":"DiskControllerTypes","value":"SCSI,NVMe"}]}}' - UPDATED_DEFINITION='{"properties":{"provisioningState":"Succeeded","features":[{"name":"SecurityType","value":"TrustedLaunchSupported"}]}}' + UPDATED_DEFINITION='{"properties":{"provisioningState":"Succeeded","features":[{"name":"SecurityType","value":"TrustedLaunchSupported","startsAtVersion":"26100.1.261006"}]}}' FAIL_OPERATION="" } BeforeEach 'setup' @@ -103,4 +103,21 @@ Describe 'ensure_sig_trusted_launch_supported' The output should include 'Updating image definition' The stderr should include 'did not converge to TrustedLaunchSupported' End + + Describe 'returned capability boundary' + Parameters + missing '{"properties":{"provisioningState":"Succeeded","features":[{"name":"SecurityType","value":"TrustedLaunchSupported"}]}}' + older '{"properties":{"provisioningState":"Succeeded","features":[{"name":"SecurityType","value":"TrustedLaunchSupported","startsAtVersion":"26100.1.260901"}]}}' + newer '{"properties":{"provisioningState":"Succeeded","features":[{"name":"SecurityType","value":"TrustedLaunchSupported","startsAtVersion":"26100.1.261007"}]}}' + End + + It "rejects a completed update with a $1 boundary" + UPDATED_DEFINITION="$2" + + When call ensure_sig_trusted_launch_supported "${DEFINITION_ID}" + The status should be failure + The output should include 'Updating image definition' + The stderr should include 'did not converge to TrustedLaunchSupported' + End + End End diff --git a/vhdbuilder/packer/produce-packer-settings-functions.sh b/vhdbuilder/packer/produce-packer-settings-functions.sh index bf041d704ad..c383527c26d 100644 --- a/vhdbuilder/packer/produce-packer-settings-functions.sh +++ b/vhdbuilder/packer/produce-packer-settings-functions.sh @@ -509,10 +509,10 @@ function ensure_sig_trusted_launch_supported() { echo "Failed to verify Trusted Launch support for ${definition_id}" >&2 return 1 fi - if ! jq -e '.properties.provisioningState == "Succeeded" and - any(.properties.features[]; .name == "SecurityType" and .value == "TrustedLaunchSupported")' \ + if ! jq -e --arg version "${SIG_IMAGE_VERSION}" '.properties.provisioningState == "Succeeded" and + any(.properties.features[]; .name == "SecurityType" and .value == "TrustedLaunchSupported" and .startsAtVersion == $version)' \ <<<"${definition}" >/dev/null; then - echo "Image definition ${definition_id} did not converge to TrustedLaunchSupported" >&2 + echo "Image definition ${definition_id} did not converge to TrustedLaunchSupported from version ${SIG_IMAGE_VERSION}" >&2 return 1 fi } From 3f937d853469f11b092923e352acb7fe98c79a54 Mon Sep 17 00:00:00 2001 From: sinmentis Date: Thu, 8 Oct 2026 20:37:20 +1300 Subject: [PATCH 3/8] fix: enforce Windows image capability boundaries during builds Compare existing boundaries numerically and initialize new shared definitions with a verified versioned capability before building. AB#39994891 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1e0bbdd6-8fe8-4eef-a0ff-bc3fdd4b8122 --- ...nsure_sig_trusted_launch_supported_spec.sh | 79 +++++++++++++++++++ .../packer/ensure_sig_vhd_exists_spec.sh | 15 +++- .../produce-packer-settings-functions.sh | 38 ++++++++- 3 files changed, 128 insertions(+), 4 deletions(-) diff --git a/spec/vhdbuilder/packer/ensure_sig_trusted_launch_supported_spec.sh b/spec/vhdbuilder/packer/ensure_sig_trusted_launch_supported_spec.sh index 81fb8ec00d7..2b4ac85c008 100644 --- a/spec/vhdbuilder/packer/ensure_sig_trusted_launch_supported_spec.sh +++ b/spec/vhdbuilder/packer/ensure_sig_trusted_launch_supported_spec.sh @@ -27,6 +27,8 @@ Describe 'ensure_sig_trusted_launch_supported' if [ "${FAIL_OPERATION}" = "get" ]; then return 1; fi if [ -f "${PUT_BODY}" ]; then printf '%s\n' "${UPDATED_DEFINITION}" + elif [ -f "${TEST_DIR}/created-definition" ]; then + cat "${TEST_DIR}/created-definition" else printf '%s\n' "${INITIAL_DEFINITION}" fi @@ -45,6 +47,20 @@ Describe 'ensure_sig_trusted_launch_supported' 'sig image-definition wait') if [ "${FAIL_OPERATION}" = "wait" ]; then return 1; fi ;; + 'sig show --resource-group') + printf '%s\n' '{"provisioningState":"Succeeded"}' + ;; + 'sig image-definition show') + if [ ! -f "${TEST_DIR}/created-definition" ]; then return 1; fi + printf '{"id":"%s"}\n' "${DEFINITION_ID}" + ;; + 'sig image-definition create') + local security_type="Standard" + case "$*" in *SecurityType=TrustedLaunchSupported*) security_type="TrustedLaunchSupported" ;; esac + jq --arg security_type "${security_type}" ' + .properties.features += [{name: "SecurityType", value: $security_type}] + ' <<<"${INITIAL_DEFINITION}" > "${TEST_DIR}/created-definition" + ;; *) return 1 ;; esac } @@ -59,6 +75,25 @@ Describe 'ensure_sig_trusted_launch_supported' The value "$(jq -cr '.properties.features' "${PUT_BODY}")" should equal '[{"name":"DiskControllerTypes","value":"SCSI,NVMe"},{"name":"SecurityType","value":"TrustedLaunchSupported","startsAtVersion":"26100.1.261006"}]' End + It 'creates a new shared Windows definition with a verified capability boundary' + MODE="windowsVhdMode" + ARCHITECTURE="x64" + FEATURE_FLAGS="" + HYPERV_GENERATION="V2" + OS_TYPE="Windows" + AZURE_LOCATION="eastus" + ENABLE_TRUSTED_LAUNCH="False" + TRUSTED_LAUNCH_SUPPORTED="True" + + When call ensure_sig_vhd_exists + The status should be success + The output should include 'Updating image definition' + The stderr should equal '' + The contents of file "${AZ_CALLS}" should include 'SecurityType=Standard' + The file "${PUT_BODY}" should be exist + The value "$(jq -r '.properties.features[] | select(.name == "SecurityType").startsAtVersion' "${PUT_BODY}")" should equal '26100.1.261006' + End + It 'does not move the boundary of an already-supported definition' INITIAL_DEFINITION='{"properties":{"hyperVGeneration":"V2","features":[{"name":"SecurityType","value":"TrustedLaunchSupported","startsAtVersion":"26100.1.260901"}]}}' @@ -69,6 +104,50 @@ Describe 'ensure_sig_trusted_launch_supported' The file "${PUT_BODY}" should not be exist End + Describe 'valid existing capability boundaries' + Parameters + equal '26100.1.261006' + numeric '26100.1.9' + minor '26100.0.999999' + End + + It "preserves an existing $1 boundary at or below the build version" + INITIAL_DEFINITION=$(jq --arg version "$2" '.properties.hyperVGeneration = "V2" | .properties.features[0].startsAtVersion = $version' <<<"${UPDATED_DEFINITION}") + + When call ensure_sig_trusted_launch_supported "${DEFINITION_ID}" + The status should be success + The output should equal '' + The contents of file "${AZ_CALLS}" should not include '--method put' + The file "${PUT_BODY}" should not be exist + End + End + + Describe 'invalid existing capability boundaries' + Parameters + missing '' + malformed 'invalid' + short '26100.1' + nonnumeric '26100.1.bad' + future '26100.1.261007' + futureminor '26100.10.1' + End + + It "rejects an existing $1 boundary without updating it" + INITIAL_DEFINITION=$(jq --arg version "$2" ' + .properties.hyperVGeneration = "V2" | + if $version == "" then del(.properties.features[0].startsAtVersion) + else .properties.features[0].startsAtVersion = $version end + ' <<<"${UPDATED_DEFINITION}") + + When call ensure_sig_trusted_launch_supported "${DEFINITION_ID}" + The status should be failure + The stderr should include 'cannot support build version' + The output should equal '' + The contents of file "${AZ_CALLS}" should not include '--method put' + The file "${PUT_BODY}" should not be exist + End + End + It 'refuses to mark a Gen1 definition as capable' INITIAL_DEFINITION='{"properties":{"hyperVGeneration":"V1","features":[]}}' diff --git a/spec/vhdbuilder/packer/ensure_sig_vhd_exists_spec.sh b/spec/vhdbuilder/packer/ensure_sig_vhd_exists_spec.sh index f7f068b4c65..26c191ed609 100644 --- a/spec/vhdbuilder/packer/ensure_sig_vhd_exists_spec.sh +++ b/spec/vhdbuilder/packer/ensure_sig_vhd_exists_spec.sh @@ -62,6 +62,7 @@ Describe 'ensure_sig_vhd_exists function' return 0 ;; "create") + MOCK_AZ_SIG_IMAGE_DEFINITION_EXISTS="true" echo "az $*" return 0 ;; @@ -128,12 +129,12 @@ Describe 'ensure_sig_vhd_exists function' Describe 'shared Windows Gen2 image creation' Parameters - False True TrustedLaunchSupported + False True Standard True True TrustedLaunch False False '' End - It "preserves the expected security capability with TL enabled=$1 supported=$2" + It "creates the expected initial security capability with TL enabled=$1 supported=$2" MODE="windowsVhdMode" AZURE_RESOURCE_GROUP_NAME="test-rg" SIG_GALLERY_NAME="test-gallery" @@ -148,6 +149,11 @@ Describe 'ensure_sig_vhd_exists function' MOCK_AZ_SIG_SHOW_STATE="Succeeded" MOCK_AZ_SIG_IMAGE_DEFINITION_EXISTS="false" + # shellcheck disable=SC2329 + ensure_sig_trusted_launch_supported() { + echo "Reconciled $1" + } + When call ensure_sig_vhd_exists The status should be success The output should include '--gallery-image-definition windows-2025-gen2' @@ -157,6 +163,11 @@ Describe 'ensure_sig_vhd_exists function' else The output should not include 'SecurityType=' fi + if [ "$1" = "False" ] && [ "$2" = "True" ]; then + The output should include 'Reconciled mock-value' + else + The output should not include 'Reconciled' + fi End End diff --git a/vhdbuilder/packer/produce-packer-settings-functions.sh b/vhdbuilder/packer/produce-packer-settings-functions.sh index c383527c26d..b5e0de926c0 100644 --- a/vhdbuilder/packer/produce-packer-settings-functions.sh +++ b/vhdbuilder/packer/produce-packer-settings-functions.sh @@ -477,6 +477,18 @@ function ensure_sig_trusted_launch_supported() { local security_type security_type=$(jq -r '(.properties.features // []) | map(select(.name == "SecurityType")) | .[0].value // ""' <<<"${definition}") if [ "${security_type}" = "TrustedLaunchSupported" ]; then + if ! jq -e --arg version "${SIG_IMAGE_VERSION:-}" ' + def gallery_version: + if type == "string" and test("^[0-9]+\\.[0-9]+\\.[0-9]+$") then + split(".") | map(tonumber) | + if all(.[]; . <= 2147483647) then . else error("gallery version component out of range") end + else error("invalid gallery image version") end; + ((.properties.features | map(select(.name == "SecurityType")) | .[0].startsAtVersion) | gallery_version) + <= ($version | gallery_version) + ' <<<"${definition}" >/dev/null; then + echo "Image definition ${definition_id} cannot support build version ${SIG_IMAGE_VERSION:-}: missing, invalid, or newer Trusted Launch boundary" >&2 + return 1 + fi return 0 fi if [ "$(jq -r '.properties.hyperVGeneration' <<<"${definition}")" != "V2" ]; then @@ -519,6 +531,10 @@ function ensure_sig_trusted_launch_supported() { function ensure_sig_vhd_exists() { echo "SIG existence checking for $MODE" + local supported_features="DiskControllerTypes=SCSI,NVMe SecurityType=TrustedLaunchSupported" + if [ "${MODE}" = "windowsVhdMode" ]; then + supported_features="DiskControllerTypes=SCSI,NVMe SecurityType=Standard" + fi is_need_create=true state=$(az sig show --resource-group "${AZURE_RESOURCE_GROUP_NAME}" --gallery-name "${SIG_GALLERY_NAME}" | jq -r '.provisioningState') || state="" @@ -619,7 +635,7 @@ function ensure_sig_vhd_exists() { --hyper-v-generation ${HYPERV_GENERATION} \ --location ${AZURE_LOCATION} \ --architecture Arm64 \ - --features "DiskControllerTypes=SCSI,NVMe SecurityType=TrustedLaunchSupported" + --features "${supported_features}" else az sig image-definition create \ --resource-group ${AZURE_RESOURCE_GROUP_NAME} \ @@ -700,7 +716,7 @@ function ensure_sig_vhd_exists() { --os-type ${OS_TYPE} \ --hyper-v-generation ${HYPERV_GENERATION} \ --location ${AZURE_LOCATION} \ - --features "DiskControllerTypes=SCSI,NVMe SecurityType=TrustedLaunchSupported" + --features "${supported_features}" else # For vanilla Gen2, mark only NVMe az sig image-definition create \ @@ -716,6 +732,24 @@ function ensure_sig_vhd_exists() { --features DiskControllerTypes=SCSI,NVMe fi fi + local create_status=$? + if [ "${create_status}" -ne 0 ]; then + return "${create_status}" + fi + if [ "${MODE}" = "windowsVhdMode" ] && + [ "${ENABLE_TRUSTED_LAUNCH,,}" != "true" ] && + [ "${TRUSTED_LAUNCH_SUPPORTED,,}" = "true" ]; then + if ! id=$(az sig image-definition show \ + --resource-group "${AZURE_RESOURCE_GROUP_NAME}" \ + --gallery-name "${SIG_GALLERY_NAME}" \ + --gallery-image-definition "${SIG_IMAGE_NAME}"); then + echo "Failed to read newly created image definition ${SIG_IMAGE_NAME}" >&2 + return 1 + fi + local new_definition_id + new_definition_id=$(jq -er '.id' <<<"${id}") || return 1 + ensure_sig_trusted_launch_supported "${new_definition_id}" || return 1 + fi else echo "Image definition ${SIG_IMAGE_NAME} existing in gallery ${SIG_GALLERY_NAME} resource group ${AZURE_RESOURCE_GROUP_NAME}" if [ "${MODE}" = "windowsVhdMode" ] && From 17c50bdd5d4ebd766597559569f194a97f787ffb Mon Sep 17 00:00:00 2001 From: sinmentis Date: Thu, 15 Oct 2026 16:03:30 +1300 Subject: [PATCH 4/8] fix: accept unbounded capable staging image definitions Reuse existing TrustedLaunchSupported staging definitions without a boundary or resource updates. Continue validating explicit boundaries and verifying newly migrated definitions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1e0bbdd6-8fe8-4eef-a0ff-bc3fdd4b8122 --- e2e/README.md | 6 +++ ...nsure_sig_trusted_launch_supported_spec.sh | 43 +++++++++++++++++-- .../produce-packer-settings-functions.sh | 8 ++-- 3 files changed, 51 insertions(+), 6 deletions(-) diff --git a/e2e/README.md b/e2e/README.md index 81f75ac2aae..d7636f6fd0a 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -115,6 +115,12 @@ Each caller decides whether an already-canceled context prevents its tasks from The image definition is published as `TrustedLaunchSupported`; this capability does not enable Trusted Launch on standard nodes. +The builder reuses already-capable staging definitions without `startsAtVersion` +without modifying them. Explicit boundaries must be valid and at or below the +build version; new capability migrations still establish and verify a boundary. +The VHD-build gate validates the actual build's image, not historical versions. +Production publication and RP image-version admission remain separate checks. + `Windows2025Gen2TrustedLaunch` continues to validate the dedicated `windows-2025-gen2-tl` image during the shared-image rollout. The shared-image scenario runs in the Windows VHD-build gate, which selects diff --git a/spec/vhdbuilder/packer/ensure_sig_trusted_launch_supported_spec.sh b/spec/vhdbuilder/packer/ensure_sig_trusted_launch_supported_spec.sh index 2b4ac85c008..fd021c142b7 100644 --- a/spec/vhdbuilder/packer/ensure_sig_trusted_launch_supported_spec.sh +++ b/spec/vhdbuilder/packer/ensure_sig_trusted_launch_supported_spec.sh @@ -104,6 +104,43 @@ Describe 'ensure_sig_trusted_launch_supported' The file "${PUT_BODY}" should not be exist End + Describe 'existing capability without a boundary' + Parameters + absent '{"name":"SecurityType","value":"TrustedLaunchSupported"}' + null '{"name":"SecurityType","value":"TrustedLaunchSupported","startsAtVersion":null}' + End + + It "accepts an $1 boundary without modifying the staging definition" + INITIAL_DEFINITION=$(jq --argjson feature "$2" ' + .properties.features += [$feature] + ' <<<"${INITIAL_DEFINITION}") + + When call ensure_sig_trusted_launch_supported "${DEFINITION_ID}" + The status should be success + The output should equal '' + The stderr should equal '' + The contents of file "${AZ_CALLS}" should not include '--method put' + The contents of file "${AZ_CALLS}" should not include 'image-definition wait' + The file "${PUT_BODY}" should not be exist + End + End + + It 'reuses an existing supported staging definition without a boundary during build setup' + MODE="windowsVhdMode" + ENABLE_TRUSTED_LAUNCH="False" + TRUSTED_LAUNCH_SUPPORTED="True" + jq '.properties.features += [{name:"SecurityType",value:"TrustedLaunchSupported"}]' \ + <<<"${INITIAL_DEFINITION}" > "${TEST_DIR}/created-definition" + + When call ensure_sig_vhd_exists + The status should be success + The output should include 'Image definition windows-2025-gen2 existing' + The stderr should equal '' + The contents of file "${AZ_CALLS}" should not include '--method put' + The contents of file "${AZ_CALLS}" should not include 'image-definition create' + The file "${PUT_BODY}" should not be exist + End + Describe 'valid existing capability boundaries' Parameters equal '26100.1.261006' @@ -124,19 +161,19 @@ Describe 'ensure_sig_trusted_launch_supported' Describe 'invalid existing capability boundaries' Parameters - missing '' + empty '' malformed 'invalid' short '26100.1' nonnumeric '26100.1.bad' future '26100.1.261007' futureminor '26100.10.1' + outofrange '26100.1.2147483648' End It "rejects an existing $1 boundary without updating it" INITIAL_DEFINITION=$(jq --arg version "$2" ' .properties.hyperVGeneration = "V2" | - if $version == "" then del(.properties.features[0].startsAtVersion) - else .properties.features[0].startsAtVersion = $version end + .properties.features[0].startsAtVersion = $version ' <<<"${UPDATED_DEFINITION}") When call ensure_sig_trusted_launch_supported "${DEFINITION_ID}" diff --git a/vhdbuilder/packer/produce-packer-settings-functions.sh b/vhdbuilder/packer/produce-packer-settings-functions.sh index b5e0de926c0..bca533bb852 100644 --- a/vhdbuilder/packer/produce-packer-settings-functions.sh +++ b/vhdbuilder/packer/produce-packer-settings-functions.sh @@ -477,16 +477,18 @@ function ensure_sig_trusted_launch_supported() { local security_type security_type=$(jq -r '(.properties.features // []) | map(select(.name == "SecurityType")) | .[0].value // ""' <<<"${definition}") if [ "${security_type}" = "TrustedLaunchSupported" ]; then + # Existing staging definitions may advertise support without a version boundary. if ! jq -e --arg version "${SIG_IMAGE_VERSION:-}" ' def gallery_version: if type == "string" and test("^[0-9]+\\.[0-9]+\\.[0-9]+$") then split(".") | map(tonumber) | if all(.[]; . <= 2147483647) then . else error("gallery version component out of range") end else error("invalid gallery image version") end; - ((.properties.features | map(select(.name == "SecurityType")) | .[0].startsAtVersion) | gallery_version) - <= ($version | gallery_version) + (.properties.features | map(select(.name == "SecurityType")) | .[0].startsAtVersion) as $start + | if $start == null then true + else ($start | gallery_version) <= ($version | gallery_version) end ' <<<"${definition}" >/dev/null; then - echo "Image definition ${definition_id} cannot support build version ${SIG_IMAGE_VERSION:-}: missing, invalid, or newer Trusted Launch boundary" >&2 + echo "Image definition ${definition_id} cannot support build version ${SIG_IMAGE_VERSION:-}: invalid or newer Trusted Launch boundary" >&2 return 1 fi return 0 From 5c37014a3192694552b682e6519f1f6e3cfd76b4 Mon Sep 17 00:00:00 2001 From: sinmentis Date: Thu, 15 Oct 2026 18:55:25 +1300 Subject: [PATCH 5/8] test: enable shared Windows Trusted Launch in general E2E Run the shared-image scenario in both Windows E2E lanes and keep the documentation consistent with the active selectors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1e0bbdd6-8fe8-4eef-a0ff-bc3fdd4b8122 --- .pipelines/e2e-windows.yaml | 2 -- e2e/README.md | 5 ++--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/.pipelines/e2e-windows.yaml b/.pipelines/e2e-windows.yaml index 907f110ca29..87fda7d10a0 100644 --- a/.pipelines/e2e-windows.yaml +++ b/.pipelines/e2e-windows.yaml @@ -1,8 +1,6 @@ name: $(Date:yyyyMMdd)$(Rev:.r) variables: TAGS_TO_RUN: "os=windows" - # The shared-image scenario is covered by the VHD-build gate using that build's image. - TAGS_TO_SKIP: "name=Windows2025Gen2_TrustedLaunch_SharedImage" SKIP_E2E_TESTS: false # if SIG_FOR_PRODUCTION is true, then the VHDs are deleted from the gallery before the e2e tests are run. SIG_FOR_PRODUCTION: False diff --git a/e2e/README.md b/e2e/README.md index d7636f6fd0a..4248cdd15c4 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -123,9 +123,8 @@ Production publication and RP image-version admission remain separate checks. `Windows2025Gen2TrustedLaunch` continues to validate the dedicated `windows-2025-gen2-tl` image during the shared-image rollout. -The shared-image scenario runs in the Windows VHD-build gate, which selects -that build's image. The general Windows job skips it while main-tagged images -can still predate the capability boundary. +The shared-image scenario runs in both the Windows VHD-build gate, using that +build's image, and the general Windows E2E job, using the main-tagged test image. ## VM size configuration From 421218a1b334e432b7ce91c89712d6f2d9259ad7 Mon Sep 17 00:00:00 2001 From: sinmentis Date: Thu, 22 Oct 2026 10:06:23 +1300 Subject: [PATCH 6/8] refactor: create shared Windows staging definitions directly Keep bounded updates for existing definitions and reuse already-capable definitions without boundary backfill. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1e0bbdd6-8fe8-4eef-a0ff-bc3fdd4b8122 --- ...nsure_sig_trusted_launch_supported_spec.sh | 54 +++++++++++++++++-- .../packer/ensure_sig_vhd_exists_spec.sh | 8 +-- .../generate_vhd_publishing_info_spec.sh | 3 +- vhdbuilder/packer/README.md | 14 +++++ .../produce-packer-settings-functions.sh | 17 ------ 5 files changed, 67 insertions(+), 29 deletions(-) diff --git a/spec/vhdbuilder/packer/ensure_sig_trusted_launch_supported_spec.sh b/spec/vhdbuilder/packer/ensure_sig_trusted_launch_supported_spec.sh index fd021c142b7..5cf6a2433a0 100644 --- a/spec/vhdbuilder/packer/ensure_sig_trusted_launch_supported_spec.sh +++ b/spec/vhdbuilder/packer/ensure_sig_trusted_launch_supported_spec.sh @@ -5,7 +5,8 @@ Describe 'ensure_sig_trusted_launch_supported' Include './vhdbuilder/packer/produce-packer-settings-functions.sh' setup() { - TEST_DIR=$(mktemp -d "${SHELLSPEC_WORKDIR}/trusted-launch.XXXXXX") + TEST_DIR="${SHELLSPEC_WORKDIR}/trusted-launch.${SHELLSPEC_EXAMPLE_NO}" + mkdir -p "${TEST_DIR}" AZ_CALLS="${TEST_DIR}/az-calls" PUT_BODY="${TEST_DIR}/put-body" : > "${AZ_CALLS}" @@ -55,6 +56,7 @@ Describe 'ensure_sig_trusted_launch_supported' printf '{"id":"%s"}\n' "${DEFINITION_ID}" ;; 'sig image-definition create') + if [ "${FAIL_OPERATION}" = "create" ]; then return 23; fi local security_type="Standard" case "$*" in *SecurityType=TrustedLaunchSupported*) security_type="TrustedLaunchSupported" ;; esac jq --arg security_type "${security_type}" ' @@ -75,7 +77,7 @@ Describe 'ensure_sig_trusted_launch_supported' The value "$(jq -cr '.properties.features' "${PUT_BODY}")" should equal '[{"name":"DiskControllerTypes","value":"SCSI,NVMe"},{"name":"SecurityType","value":"TrustedLaunchSupported","startsAtVersion":"26100.1.261006"}]' End - It 'creates a new shared Windows definition with a verified capability boundary' + It 'creates a new shared Windows definition capable directly without a migration' MODE="windowsVhdMode" ARCHITECTURE="x64" FEATURE_FLAGS="" @@ -85,13 +87,54 @@ Describe 'ensure_sig_trusted_launch_supported' ENABLE_TRUSTED_LAUNCH="False" TRUSTED_LAUNCH_SUPPORTED="True" + When call ensure_sig_vhd_exists + The status should be success + The output should include 'Creating image definition windows-2025-gen2' + The output should not include 'Updating image definition' + The stderr should equal '' + The contents of file "${AZ_CALLS}" should include 'DiskControllerTypes=SCSI,NVMe SecurityType=TrustedLaunchSupported' + The contents of file "${AZ_CALLS}" should not include 'rest --method' + The contents of file "${AZ_CALLS}" should not include 'image-definition wait' + The value "$(grep -c '^sig image-definition create ' "${AZ_CALLS}")" should equal 1 + The value "$(grep -c '^sig image-definition show ' "${AZ_CALLS}")" should equal 1 + The file "${PUT_BODY}" should not be exist + The value "$(jq -c '.properties.features' "${TEST_DIR}/created-definition")" should equal '[{"name":"DiskControllerTypes","value":"SCSI,NVMe"},{"name":"SecurityType","value":"TrustedLaunchSupported"}]' + End + + It 'propagates a new shared Windows definition creation failure without migration' + MODE="windowsVhdMode" + ARCHITECTURE="x64" + FEATURE_FLAGS="" + HYPERV_GENERATION="V2" + OS_TYPE="Windows" + AZURE_LOCATION="eastus" + ENABLE_TRUSTED_LAUNCH="False" + TRUSTED_LAUNCH_SUPPORTED="True" + FAIL_OPERATION="create" + + When call ensure_sig_vhd_exists + The status should equal 23 + The output should include 'Creating image definition windows-2025-gen2' + The contents of file "${AZ_CALLS}" should not include 'rest --method' + The contents of file "${AZ_CALLS}" should not include 'image-definition wait' + The file "${PUT_BODY}" should not be exist + End + + It 'migrates an existing Standard staging definition during build setup' + MODE="windowsVhdMode" + ENABLE_TRUSTED_LAUNCH="False" + TRUSTED_LAUNCH_SUPPORTED="True" + jq '.properties.features += [{name:"SecurityType",value:"Standard"}]' \ + <<<"${INITIAL_DEFINITION}" > "${TEST_DIR}/created-definition" + When call ensure_sig_vhd_exists The status should be success The output should include 'Updating image definition' The stderr should equal '' - The contents of file "${AZ_CALLS}" should include 'SecurityType=Standard' - The file "${PUT_BODY}" should be exist - The value "$(jq -r '.properties.features[] | select(.name == "SecurityType").startsAtVersion' "${PUT_BODY}")" should equal '26100.1.261006' + The contents of file "${AZ_CALLS}" should not include 'image-definition create' + The contents of file "${AZ_CALLS}" should include '--updated --interval 5 --timeout 120' + The value "$(grep -c '^rest --method get ' "${AZ_CALLS}")" should equal 2 + The value "$(jq -c '.properties.features' "${PUT_BODY}")" should equal '[{"name":"DiskControllerTypes","value":"SCSI,NVMe"},{"name":"SecurityType","value":"TrustedLaunchSupported","startsAtVersion":"26100.1.261006"}]' End It 'does not move the boundary of an already-supported definition' @@ -137,6 +180,7 @@ Describe 'ensure_sig_trusted_launch_supported' The output should include 'Image definition windows-2025-gen2 existing' The stderr should equal '' The contents of file "${AZ_CALLS}" should not include '--method put' + The contents of file "${AZ_CALLS}" should not include 'image-definition wait' The contents of file "${AZ_CALLS}" should not include 'image-definition create' The file "${PUT_BODY}" should not be exist End diff --git a/spec/vhdbuilder/packer/ensure_sig_vhd_exists_spec.sh b/spec/vhdbuilder/packer/ensure_sig_vhd_exists_spec.sh index 26c191ed609..1b9bee26272 100644 --- a/spec/vhdbuilder/packer/ensure_sig_vhd_exists_spec.sh +++ b/spec/vhdbuilder/packer/ensure_sig_vhd_exists_spec.sh @@ -129,7 +129,7 @@ Describe 'ensure_sig_vhd_exists function' Describe 'shared Windows Gen2 image creation' Parameters - False True Standard + False True TrustedLaunchSupported True True TrustedLaunch False False '' End @@ -163,11 +163,7 @@ Describe 'ensure_sig_vhd_exists function' else The output should not include 'SecurityType=' fi - if [ "$1" = "False" ] && [ "$2" = "True" ]; then - The output should include 'Reconciled mock-value' - else - The output should not include 'Reconciled' - fi + The output should not include 'Reconciled' End End diff --git a/spec/vhdbuilder/packer/generate_vhd_publishing_info_spec.sh b/spec/vhdbuilder/packer/generate_vhd_publishing_info_spec.sh index 4680390deac..1a7660f69ab 100644 --- a/spec/vhdbuilder/packer/generate_vhd_publishing_info_spec.sh +++ b/spec/vhdbuilder/packer/generate_vhd_publishing_info_spec.sh @@ -4,7 +4,8 @@ Describe 'generate-vhd-publishing-info.sh' setup() { SCRIPT_PATH="${PWD}/vhdbuilder/packer/generate-vhd-publishing-info.sh" - TEST_DIR=$(mktemp -d "${SHELLSPEC_WORKDIR}/publishing-info.XXXXXX") + TEST_DIR="${SHELLSPEC_WORKDIR}/publishing-info.${SHELLSPEC_EXAMPLE_NO}" + mkdir -p "${TEST_DIR}" OUTPUT="${TEST_DIR}/vhd-publishing-info.json" export AZ_CALLS="${TEST_DIR}/az-calls" : > "$AZ_CALLS" diff --git a/vhdbuilder/packer/README.md b/vhdbuilder/packer/README.md index dc5523e20be..8586a987fde 100644 --- a/vhdbuilder/packer/README.md +++ b/vhdbuilder/packer/README.md @@ -17,6 +17,20 @@ external-vhd-src->(func/init: ensure dest existing/static gallery/definition, in # Roadmap Goal1: remove mariner workflow so things will be simplified. +# Shared Windows 2025 Gen2 staging definitions + +The ordinary `windows-2025-gen2` image advertises `TrustedLaunchSupported`, so the +same image can serve Standard and Trusted Launch virtual machines. New staging +definitions are created with that capability directly, without a follow-up +capability update or `startsAtVersion` boundary because they have no old versions. +Dedicated Trusted Launch builds and their metadata precedence are unchanged. + +An existing Standard definition is updated in place, preserving unrelated features +and setting `startsAtVersion` to the current build version. The builder waits for +completion and reads back the capability and boundary. Already-capable definitions +with an absent or null boundary are reused without a write or boundary backfill. +An explicit boundary must be valid and at or below the current build version. + # Linux build steps The pipeline's `Build Go binaries` step runs `make -f packer.mk -j4 build-tools` diff --git a/vhdbuilder/packer/produce-packer-settings-functions.sh b/vhdbuilder/packer/produce-packer-settings-functions.sh index bca533bb852..659e9bb9c36 100644 --- a/vhdbuilder/packer/produce-packer-settings-functions.sh +++ b/vhdbuilder/packer/produce-packer-settings-functions.sh @@ -534,9 +534,6 @@ function ensure_sig_trusted_launch_supported() { function ensure_sig_vhd_exists() { echo "SIG existence checking for $MODE" local supported_features="DiskControllerTypes=SCSI,NVMe SecurityType=TrustedLaunchSupported" - if [ "${MODE}" = "windowsVhdMode" ]; then - supported_features="DiskControllerTypes=SCSI,NVMe SecurityType=Standard" - fi is_need_create=true state=$(az sig show --resource-group "${AZURE_RESOURCE_GROUP_NAME}" --gallery-name "${SIG_GALLERY_NAME}" | jq -r '.provisioningState') || state="" @@ -738,20 +735,6 @@ function ensure_sig_vhd_exists() { if [ "${create_status}" -ne 0 ]; then return "${create_status}" fi - if [ "${MODE}" = "windowsVhdMode" ] && - [ "${ENABLE_TRUSTED_LAUNCH,,}" != "true" ] && - [ "${TRUSTED_LAUNCH_SUPPORTED,,}" = "true" ]; then - if ! id=$(az sig image-definition show \ - --resource-group "${AZURE_RESOURCE_GROUP_NAME}" \ - --gallery-name "${SIG_GALLERY_NAME}" \ - --gallery-image-definition "${SIG_IMAGE_NAME}"); then - echo "Failed to read newly created image definition ${SIG_IMAGE_NAME}" >&2 - return 1 - fi - local new_definition_id - new_definition_id=$(jq -er '.id' <<<"${id}") || return 1 - ensure_sig_trusted_launch_supported "${new_definition_id}" || return 1 - fi else echo "Image definition ${SIG_IMAGE_NAME} existing in gallery ${SIG_GALLERY_NAME} resource group ${AZURE_RESOURCE_GROUP_NAME}" if [ "${MODE}" = "windowsVhdMode" ] && From 2946c3a8a4665e6423106bd2fb444b4a14ab452d Mon Sep 17 00:00:00 2001 From: sinmentis Date: Fri, 23 Oct 2026 13:27:10 +1300 Subject: [PATCH 7/8] refactor: simplify shared Windows image capability updates Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1e0bbdd6-8fe8-4eef-a0ff-bc3fdd4b8122 --- ...nsure_sig_trusted_launch_supported_spec.sh | 90 ++++++++++--------- vhdbuilder/packer/README.md | 7 +- .../produce-packer-settings-functions.sh | 33 +++---- 3 files changed, 63 insertions(+), 67 deletions(-) diff --git a/spec/vhdbuilder/packer/ensure_sig_trusted_launch_supported_spec.sh b/spec/vhdbuilder/packer/ensure_sig_trusted_launch_supported_spec.sh index 5cf6a2433a0..403f77f76da 100644 --- a/spec/vhdbuilder/packer/ensure_sig_trusted_launch_supported_spec.sh +++ b/spec/vhdbuilder/packer/ensure_sig_trusted_launch_supported_spec.sh @@ -8,7 +8,7 @@ Describe 'ensure_sig_trusted_launch_supported' TEST_DIR="${SHELLSPEC_WORKDIR}/trusted-launch.${SHELLSPEC_EXAMPLE_NO}" mkdir -p "${TEST_DIR}" AZ_CALLS="${TEST_DIR}/az-calls" - PUT_BODY="${TEST_DIR}/put-body" + UPDATE_FEATURES="${TEST_DIR}/update-features" : > "${AZ_CALLS}" AZURE_RESOURCE_GROUP_NAME="test-rg" SIG_GALLERY_NAME="test-gallery" @@ -24,29 +24,26 @@ Describe 'ensure_sig_trusted_launch_supported' az() { printf '%s\n' "$*" >> "${AZ_CALLS}" case "$1 $2 $3" in - 'rest --method get') - if [ "${FAIL_OPERATION}" = "get" ]; then return 1; fi - if [ -f "${PUT_BODY}" ]; then - printf '%s\n' "${UPDATED_DEFINITION}" - elif [ -f "${TEST_DIR}/created-definition" ]; then + 'resource show --ids') + if [ "${FAIL_OPERATION}" = "show" ]; then return 1; fi + if [ -f "${TEST_DIR}/created-definition" ]; then cat "${TEST_DIR}/created-definition" else printf '%s\n' "${INITIAL_DEFINITION}" fi ;; - 'rest --method put') - if [ "${FAIL_OPERATION}" = "put" ]; then return 1; fi - shift 3 + 'resource update --ids') + if [ "${FAIL_OPERATION}" = "update" ]; then return 1; fi while [ "$#" -gt 0 ]; do - if [ "$1" = "--body" ]; then - printf '%s\n' "$2" > "${PUT_BODY}" - break - fi + case "$1" in + properties.features=*) + printf '%s\n' "${1#properties.features=}" > "${UPDATE_FEATURES}" + break + ;; + esac shift done - ;; - 'sig image-definition wait') - if [ "${FAIL_OPERATION}" = "wait" ]; then return 1; fi + printf '%s\n' "${UPDATED_DEFINITION}" ;; 'sig show --resource-group') printf '%s\n' '{"provisioningState":"Succeeded"}' @@ -71,10 +68,13 @@ Describe 'ensure_sig_trusted_launch_supported' When call ensure_sig_trusted_launch_supported "${DEFINITION_ID}" The status should be success The output should include 'from version 26100.1.261006' - The contents of file "${AZ_CALLS}" should include '--updated --interval 5 --timeout 120' - The value "$(jq -cr '[.id, .name, .type, .properties.provisioningState]' "${PUT_BODY}")" should equal '[null,null,null,null]' - The value "$(jq -cr '[.location, .tags, .properties.hyperVGeneration, .properties.allowUpdateImage]' "${PUT_BODY}")" should equal '["eastus",{"preserve":"true"},"V2",true]' - The value "$(jq -cr '.properties.features' "${PUT_BODY}")" should equal '[{"name":"DiskControllerTypes","value":"SCSI,NVMe"},{"name":"SecurityType","value":"TrustedLaunchSupported","startsAtVersion":"26100.1.261006"}]' + The contents of file "${AZ_CALLS}" should include "resource show --ids ${DEFINITION_ID} --api-version 2024-03-03" + The contents of file "${AZ_CALLS}" should include "resource update --ids ${DEFINITION_ID} --api-version 2024-03-03 --set properties.allowUpdateImage=true properties.features=" + The contents of file "${AZ_CALLS}" should include '--remove properties.provisioningState' + The contents of file "${AZ_CALLS}" should not include '--no-wait' + The contents of file "${AZ_CALLS}" should not include 'image-definition wait' + The value "$(grep -c '^resource show ' "${AZ_CALLS}")" should equal 1 + The value "$(jq -c '.' "${UPDATE_FEATURES}")" should equal '[{"name":"DiskControllerTypes","value":"SCSI,NVMe"},{"name":"SecurityType","value":"TrustedLaunchSupported","startsAtVersion":"26100.1.261006"}]' End It 'creates a new shared Windows definition capable directly without a migration' @@ -93,11 +93,11 @@ Describe 'ensure_sig_trusted_launch_supported' The output should not include 'Updating image definition' The stderr should equal '' The contents of file "${AZ_CALLS}" should include 'DiskControllerTypes=SCSI,NVMe SecurityType=TrustedLaunchSupported' - The contents of file "${AZ_CALLS}" should not include 'rest --method' + The contents of file "${AZ_CALLS}" should not include 'resource update' The contents of file "${AZ_CALLS}" should not include 'image-definition wait' The value "$(grep -c '^sig image-definition create ' "${AZ_CALLS}")" should equal 1 The value "$(grep -c '^sig image-definition show ' "${AZ_CALLS}")" should equal 1 - The file "${PUT_BODY}" should not be exist + The file "${UPDATE_FEATURES}" should not be exist The value "$(jq -c '.properties.features' "${TEST_DIR}/created-definition")" should equal '[{"name":"DiskControllerTypes","value":"SCSI,NVMe"},{"name":"SecurityType","value":"TrustedLaunchSupported"}]' End @@ -115,9 +115,9 @@ Describe 'ensure_sig_trusted_launch_supported' When call ensure_sig_vhd_exists The status should equal 23 The output should include 'Creating image definition windows-2025-gen2' - The contents of file "${AZ_CALLS}" should not include 'rest --method' + The contents of file "${AZ_CALLS}" should not include 'resource update' The contents of file "${AZ_CALLS}" should not include 'image-definition wait' - The file "${PUT_BODY}" should not be exist + The file "${UPDATE_FEATURES}" should not be exist End It 'migrates an existing Standard staging definition during build setup' @@ -132,9 +132,9 @@ Describe 'ensure_sig_trusted_launch_supported' The output should include 'Updating image definition' The stderr should equal '' The contents of file "${AZ_CALLS}" should not include 'image-definition create' - The contents of file "${AZ_CALLS}" should include '--updated --interval 5 --timeout 120' - The value "$(grep -c '^rest --method get ' "${AZ_CALLS}")" should equal 2 - The value "$(jq -c '.properties.features' "${PUT_BODY}")" should equal '[{"name":"DiskControllerTypes","value":"SCSI,NVMe"},{"name":"SecurityType","value":"TrustedLaunchSupported","startsAtVersion":"26100.1.261006"}]' + The contents of file "${AZ_CALLS}" should include '--set properties.allowUpdateImage=true properties.features=' + The value "$(grep -c '^resource show ' "${AZ_CALLS}")" should equal 1 + The value "$(jq -c '.' "${UPDATE_FEATURES}")" should equal '[{"name":"DiskControllerTypes","value":"SCSI,NVMe"},{"name":"SecurityType","value":"TrustedLaunchSupported","startsAtVersion":"26100.1.261006"}]' End It 'does not move the boundary of an already-supported definition' @@ -143,8 +143,8 @@ Describe 'ensure_sig_trusted_launch_supported' When call ensure_sig_trusted_launch_supported "${DEFINITION_ID}" The status should be success The output should equal '' - The contents of file "${AZ_CALLS}" should not include '--method put' - The file "${PUT_BODY}" should not be exist + The contents of file "${AZ_CALLS}" should not include 'resource update' + The file "${UPDATE_FEATURES}" should not be exist End Describe 'existing capability without a boundary' @@ -162,9 +162,9 @@ Describe 'ensure_sig_trusted_launch_supported' The status should be success The output should equal '' The stderr should equal '' - The contents of file "${AZ_CALLS}" should not include '--method put' + The contents of file "${AZ_CALLS}" should not include 'resource update' The contents of file "${AZ_CALLS}" should not include 'image-definition wait' - The file "${PUT_BODY}" should not be exist + The file "${UPDATE_FEATURES}" should not be exist End End @@ -179,10 +179,10 @@ Describe 'ensure_sig_trusted_launch_supported' The status should be success The output should include 'Image definition windows-2025-gen2 existing' The stderr should equal '' - The contents of file "${AZ_CALLS}" should not include '--method put' + The contents of file "${AZ_CALLS}" should not include 'resource update' The contents of file "${AZ_CALLS}" should not include 'image-definition wait' The contents of file "${AZ_CALLS}" should not include 'image-definition create' - The file "${PUT_BODY}" should not be exist + The file "${UPDATE_FEATURES}" should not be exist End Describe 'valid existing capability boundaries' @@ -198,8 +198,8 @@ Describe 'ensure_sig_trusted_launch_supported' When call ensure_sig_trusted_launch_supported "${DEFINITION_ID}" The status should be success The output should equal '' - The contents of file "${AZ_CALLS}" should not include '--method put' - The file "${PUT_BODY}" should not be exist + The contents of file "${AZ_CALLS}" should not include 'resource update' + The file "${UPDATE_FEATURES}" should not be exist End End @@ -224,8 +224,8 @@ Describe 'ensure_sig_trusted_launch_supported' The status should be failure The stderr should include 'cannot support build version' The output should equal '' - The contents of file "${AZ_CALLS}" should not include '--method put' - The file "${PUT_BODY}" should not be exist + The contents of file "${AZ_CALLS}" should not include 'resource update' + The file "${UPDATE_FEATURES}" should not be exist End End @@ -235,14 +235,13 @@ Describe 'ensure_sig_trusted_launch_supported' When call ensure_sig_trusted_launch_supported "${DEFINITION_ID}" The status should be failure The stderr should include 'must be Gen2' - The contents of file "${AZ_CALLS}" should not include '--method put' + The contents of file "${AZ_CALLS}" should not include 'resource update' End Describe 'Azure failures' Parameters - get 'Failed to read image definition' - put 'Failed to update Trusted Launch support' - wait 'Timed out waiting for Trusted Launch support' + show 'Failed to read image definition' + update 'Failed to update Trusted Launch support' End It "stops when $1 fails" @@ -264,6 +263,15 @@ Describe 'ensure_sig_trusted_launch_supported' The stderr should include 'did not converge to TrustedLaunchSupported' End + It 'rejects an update that has not succeeded' + UPDATED_DEFINITION=$(jq '.properties.provisioningState = "Updating"' <<<"${UPDATED_DEFINITION}") + + When call ensure_sig_trusted_launch_supported "${DEFINITION_ID}" + The status should be failure + The output should include 'Updating image definition' + The stderr should include 'did not converge to TrustedLaunchSupported' + End + Describe 'returned capability boundary' Parameters missing '{"properties":{"provisioningState":"Succeeded","features":[{"name":"SecurityType","value":"TrustedLaunchSupported"}]}}' diff --git a/vhdbuilder/packer/README.md b/vhdbuilder/packer/README.md index 8586a987fde..b0741294605 100644 --- a/vhdbuilder/packer/README.md +++ b/vhdbuilder/packer/README.md @@ -26,9 +26,10 @@ capability update or `startsAtVersion` boundary because they have no old version Dedicated Trusted Launch builds and their metadata precedence are unchanged. An existing Standard definition is updated in place, preserving unrelated features -and setting `startsAtVersion` to the current build version. The builder waits for -completion and reads back the capability and boundary. Already-capable definitions -with an absent or null boundary are reused without a write or boundary backfill. +and setting `startsAtVersion` to the current build version. Azure CLI performs the +update and waits for completion; the builder checks the returned capability and +boundary. Already-capable definitions with an absent or null boundary are reused +without a write or boundary backfill. An explicit boundary must be valid and at or below the current build version. # Linux build steps diff --git a/vhdbuilder/packer/produce-packer-settings-functions.sh b/vhdbuilder/packer/produce-packer-settings-functions.sh index 659e9bb9c36..ddc1302f863 100644 --- a/vhdbuilder/packer/produce-packer-settings-functions.sh +++ b/vhdbuilder/packer/produce-packer-settings-functions.sh @@ -467,9 +467,9 @@ function prepare_windows_vhd() { function ensure_sig_trusted_launch_supported() { local definition_id="$1" - local definition_uri="${definition_id}?api-version=2024-03-03" + local api_version="2024-03-03" local definition - if ! definition=$(az rest --method get --uri "${definition_uri}" -o json); then + if ! definition=$(az resource show --ids "${definition_id}" --api-version "${api_version}" -o json); then echo "Failed to read image definition ${definition_id} for Trusted Launch support" >&2 return 1 fi @@ -498,31 +498,18 @@ function ensure_sig_trusted_launch_supported() { return 1 fi - local body - body=$(jq --arg version "${SIG_IMAGE_VERSION:?SIG_IMAGE_VERSION is required to update Trusted Launch support}" ' - del(.id, .name, .type, .systemData, .properties.provisioningState) - | .properties.allowUpdateImage = true - | .properties.features = ( - ((.properties.features // []) | map(select(.name != "SecurityType"))) - + [{name: "SecurityType", value: "TrustedLaunchSupported", startsAtVersion: $version}] - )' <<<"${definition}") || return 1 + local features + features=$(jq -c --arg version "${SIG_IMAGE_VERSION:?SIG_IMAGE_VERSION is required to update Trusted Launch support}" ' + ((.properties.features // []) | map(select(.name != "SecurityType"))) + + [{name: "SecurityType", value: "TrustedLaunchSupported", startsAtVersion: $version}] + ' <<<"${definition}") || return 1 echo "Updating image definition ${definition_id} to TrustedLaunchSupported from version ${SIG_IMAGE_VERSION}" - if ! az rest --method put --uri "${definition_uri}" --body "${body}" -o none; then + if ! definition=$(az resource update --ids "${definition_id}" --api-version "${api_version}" \ + --set properties.allowUpdateImage=true "properties.features=${features}" \ + --remove properties.provisioningState -o json); then echo "Failed to update Trusted Launch support for ${definition_id}" >&2 return 1 fi - if ! az sig image-definition wait \ - --resource-group "${AZURE_RESOURCE_GROUP_NAME}" \ - --gallery-name "${SIG_GALLERY_NAME}" \ - --gallery-image-definition "${SIG_IMAGE_NAME}" \ - --updated --interval 5 --timeout 120; then - echo "Timed out waiting for Trusted Launch support on ${definition_id}" >&2 - return 1 - fi - if ! definition=$(az rest --method get --uri "${definition_uri}" -o json); then - echo "Failed to verify Trusted Launch support for ${definition_id}" >&2 - return 1 - fi if ! jq -e --arg version "${SIG_IMAGE_VERSION}" '.properties.provisioningState == "Succeeded" and any(.properties.features[]; .name == "SecurityType" and .value == "TrustedLaunchSupported" and .startsAtVersion == $version)' \ <<<"${definition}" >/dev/null; then From d89d49c0c10cbb9a4a364eee2cfe175df7fd158e Mon Sep 17 00:00:00 2001 From: sinmentis Date: Fri, 23 Oct 2026 14:00:11 +1300 Subject: [PATCH 8/8] feat: mark Windows Gen2 images as Trusted Launch capable Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1e0bbdd6-8fe8-4eef-a0ff-bc3fdd4b8122 --- .pipelines/.vsts-vhd-builder-pr-windows.yaml | 4 +-- .../.build-and-test-windows-vhd-template.yaml | 6 +---- ....build-and-test-windows-vhds-template.yaml | 5 +--- e2e/README.md | 18 ++++++++----- e2e/scenario/scenario_win.go | 27 +++++++++++++++++++ .../packer/ensure_sig_vhd_exists_spec.sh | 21 ++++++++------- .../generate_vhd_publishing_info_spec.sh | 11 +++++--- vhdbuilder/packer/README.md | 10 ++++--- 8 files changed, 68 insertions(+), 34 deletions(-) diff --git a/.pipelines/.vsts-vhd-builder-pr-windows.yaml b/.pipelines/.vsts-vhd-builder-pr-windows.yaml index 591414b307a..95917727941 100644 --- a/.pipelines/.vsts-vhd-builder-pr-windows.yaml +++ b/.pipelines/.vsts-vhd-builder-pr-windows.yaml @@ -72,9 +72,9 @@ stages: useOverrides: False enableBackfillCleanup: True buildVmSize: Standard_D16ds_v5 - # 2022 gen1 is the default, so build that for PRs + # Keep the default Gen1 image and cover both security modes on Gen2. build2022containerd: True - build2022containerdgen2: False + build2022containerdgen2: True # 2025 gen2 is the default, so build that for PRs build2025: False build2025gen2: True diff --git a/.pipelines/templates/.build-and-test-windows-vhd-template.yaml b/.pipelines/templates/.build-and-test-windows-vhd-template.yaml index 8a85f6a009a..81b3174c5ba 100644 --- a/.pipelines/templates/.build-and-test-windows-vhd-template.yaml +++ b/.pipelines/templates/.build-and-test-windows-vhd-template.yaml @@ -36,10 +36,6 @@ parameters: type: boolean displayName: Enable Trusted Launch on the SIG image definition default: False - - name: trustedLaunchSupported - type: boolean - displayName: Determines if the image is capable of supporting Trusted Launch - default: False - name: skipExtensionCheck displayName: Skip Extension Check type: boolean @@ -91,7 +87,7 @@ stages: hyperVGeneration: ${{ parameters.hyperVGeneration }} architecture: ${{ parameters.architecture }} enableTrustedLaunch: ${{ parameters.enableTrustedLaunch }} - trustedLaunchSupported: ${{ parameters.trustedLaunchSupported }} + trustedLaunchSupported: ${{ eq(parameters.hyperVGeneration, 'V2') }} buildVmSize: ${{ parameters.buildVmSize }} build: ${{ parameters.build }} vhddebug: ${{ parameters.vhddebug }} diff --git a/.pipelines/templates/.build-and-test-windows-vhds-template.yaml b/.pipelines/templates/.build-and-test-windows-vhds-template.yaml index 4ef1bec9965..7dd37e9a771 100644 --- a/.pipelines/templates/.build-and-test-windows-vhds-template.yaml +++ b/.pipelines/templates/.build-and-test-windows-vhds-template.yaml @@ -151,8 +151,6 @@ stages: windowsSku: 2025-gen2 hyperVGeneration: V2 architecture: X86_64 - enableTrustedLaunch: False - trustedLaunchSupported: True buildVmSize: ${{ parameters.buildVmSize }} build: ${{ parameters.build2025gen2 }} vhddebug: ${{ parameters.vhddebug }} @@ -176,9 +174,8 @@ stages: windowsSku: 2025-gen2-tl hyperVGeneration: V2 architecture: X86_64 - # TODO: replace enableTrustedLaunch with trustedLaunchSupported when / as able - by default we shouldn't need to build dedicated trusted launch images + # TODO: retire the dedicated image after Trusted Launch moves to the shared Gen2 image. enableTrustedLaunch: True - trustedLaunchSupported: True buildVmSize: ${{ parameters.buildVmSize }} build: ${{ parameters.build2025gen2tl }} vhddebug: ${{ parameters.vhddebug }} diff --git a/e2e/README.md b/e2e/README.md index 4248cdd15c4..81f507d0d1b 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -108,12 +108,18 @@ alongside cluster preparation. Setup, validators, and cleanup batches share `scenario/concurrent.go`, which waits for every task without canceling peers on failure. Each caller decides whether an already-canceled context prevents its tasks from starting. -## Shared Windows Server 2025 Gen2 image +## Shared Windows Gen2 images -`Windows2025Gen2` and `Windows2025Gen2_TrustedLaunch_SharedImage` use the same -`windows-2025-gen2` image. The latter enables Secure Boot and vTPM on the VMSS. -The image definition is published as `TrustedLaunchSupported`; this capability -does not enable Trusted Launch on standard nodes. +The ordinary WS2022 and WS2025 Gen2 images are published as `TrustedLaunchSupported`. +Each has a separate scenario that enables Secure Boot and vTPM on the VMSS: + +| Image | Standard scenario | Trusted Launch scenario | +| --- | --- | --- | +| `windows-2022-containerd-gen2` | `Windows2022Gen2AzureNetwork` | `Windows2022Gen2_TrustedLaunch_SharedImage` | +| `windows-2025-gen2` | `Windows2025Gen2` | `Windows2025Gen2_TrustedLaunch_SharedImage` | + +Separate nodes are needed because their VM security profiles differ. These direct +image tests do not change AKS RP's OS eligibility checks. The builder reuses already-capable staging definitions without `startsAtVersion` without modifying them. Explicit boundaries must be valid and at or below the @@ -123,7 +129,7 @@ Production publication and RP image-version admission remain separate checks. `Windows2025Gen2TrustedLaunch` continues to validate the dedicated `windows-2025-gen2-tl` image during the shared-image rollout. -The shared-image scenario runs in both the Windows VHD-build gate, using that +The shared-image scenarios run in both the Windows VHD-build gate, using that build's image, and the general Windows E2E job, using the main-tagged test image. ## VM size configuration diff --git a/e2e/scenario/scenario_win.go b/e2e/scenario/scenario_win.go index 2920cec84a4..431b4c78df3 100644 --- a/e2e/scenario/scenario_win.go +++ b/e2e/scenario/scenario_win.go @@ -140,6 +140,33 @@ var _ = Register(&Scenario{ }, }) +var _ = Register(&Scenario{ + Name: "Windows2022Gen2_TrustedLaunch_SharedImage", + Description: "Windows Server 2022 Gen2 Trusted Launch (Secure Boot + vTPM)", + Config: Config{ + Cluster: ClusterAzureNetwork, + VHD: config.VHDWindows2022ContainerdGen2, + VMConfigMutator: func(vmss *armcompute.VirtualMachineScaleSet) { + vmss.Properties = addTrustedLaunchToVMSS(vmss.Properties) + }, + BootstrapConfigMutator: EmptyBootstrapConfigMutator, + Validator: func(ctx context.Context, s *Scenario) error { + return errors.Join( + ValidateWindowsVersionFromWindowsSettings(ctx, s, "2022-containerd-gen2"), + ValidateWindowsProductName(ctx, s, "Windows Server 2022 Datacenter"), + ValidateWindowsDisplayVersion(ctx, s, "21H2"), + ValidateFileHasContent(ctx, s, "/k/kubeletstart.ps1", "--container-runtime=remote"), + ValidateKubeletArgs(ctx, s), + ValidateContainerdWindowsPriorityClass(ctx, s), + ValidateCiliumIsNotRunningWindows(ctx, s), + ValidateDotnetNotInstalledWindows(ctx, s), + ValidateWindowsSystemServicesRestartConfiguration(ctx, s), + ValidateCollectWindowsLogsScript(ctx, s), + ) + }, + }, +}) + var _ = Register(&Scenario{ Name: "Windows2022Gen2AzureOverlayNetworkDualStack", SkipReason: "Dual stack tests are not working yet", diff --git a/spec/vhdbuilder/packer/ensure_sig_vhd_exists_spec.sh b/spec/vhdbuilder/packer/ensure_sig_vhd_exists_spec.sh index 1b9bee26272..0ae454c107b 100644 --- a/spec/vhdbuilder/packer/ensure_sig_vhd_exists_spec.sh +++ b/spec/vhdbuilder/packer/ensure_sig_vhd_exists_spec.sh @@ -129,22 +129,23 @@ Describe 'ensure_sig_vhd_exists function' Describe 'shared Windows Gen2 image creation' Parameters - False True TrustedLaunchSupported - True True TrustedLaunch - False False '' + windows-2022-containerd-gen2 False True TrustedLaunchSupported + windows-2025-gen2 False True TrustedLaunchSupported + windows-2025-gen2-tl True True TrustedLaunch + windows-2025-gen2 False False '' End - It "creates the expected initial security capability with TL enabled=$1 supported=$2" + It "creates $1 with TL enabled=$2 supported=$3" MODE="windowsVhdMode" AZURE_RESOURCE_GROUP_NAME="test-rg" SIG_GALLERY_NAME="test-gallery" - SIG_IMAGE_NAME="windows-2025-gen2" + SIG_IMAGE_NAME="$1" AZURE_LOCATION="eastus" OS_TYPE="Windows" HYPERV_GENERATION="V2" ARCHITECTURE="x64" - ENABLE_TRUSTED_LAUNCH="$1" - TRUSTED_LAUNCH_SUPPORTED="$2" + ENABLE_TRUSTED_LAUNCH="$2" + TRUSTED_LAUNCH_SUPPORTED="$3" MOCK_AZ_SIG_SHOW_EXISTS="true" MOCK_AZ_SIG_SHOW_STATE="Succeeded" MOCK_AZ_SIG_IMAGE_DEFINITION_EXISTS="false" @@ -156,10 +157,10 @@ Describe 'ensure_sig_vhd_exists function' When call ensure_sig_vhd_exists The status should be success - The output should include '--gallery-image-definition windows-2025-gen2' + The output should include "--gallery-image-definition $1" The output should include 'DiskControllerTypes=SCSI,NVMe' - if [ -n "$3" ]; then - The output should include "SecurityType=$3" + if [ -n "$4" ]; then + The output should include "SecurityType=$4" else The output should not include 'SecurityType=' fi diff --git a/spec/vhdbuilder/packer/generate_vhd_publishing_info_spec.sh b/spec/vhdbuilder/packer/generate_vhd_publishing_info_spec.sh index 1a7660f69ab..e5cca863c26 100644 --- a/spec/vhdbuilder/packer/generate_vhd_publishing_info_spec.sh +++ b/spec/vhdbuilder/packer/generate_vhd_publishing_info_spec.sh @@ -222,14 +222,19 @@ Describe 'generate-vhd-publishing-info.sh' End Describe 'Windows output' - It 'publishes shared Windows Gen2 capability without relying on a TL image name' - OS_NAME=windows OFFER_NAME=Windows SKU_NAME=windows-2025-gen2 SECURITY_TYPE_FEATURE=TrustedLaunchSupported + Parameters + windows-2022-containerd-gen2 + windows-2025-gen2 + End + + It "publishes $1 capability without relying on a TL image name" + OS_NAME=windows OFFER_NAME=Windows SKU_NAME="$1" SECURITY_TYPE_FEATURE=TrustedLaunchSupported AZ_VERSIONS='' When call generate_publishing_info The status should be success The output should include 'COPY ME ---> https://example.invalid/images/test.vhd' - The value "$(read_metadata '[.os_name, .sku_name, .hyperv_generation, .security_type_feature]')" should equal '["windows","windows-2025-gen2","V2","TrustedLaunchSupported"]' + The value "$(read_metadata '[.os_name, .sku_name, .hyperv_generation, .security_type_feature]')" should equal "[\"windows\",\"$1\",\"V2\",\"TrustedLaunchSupported\"]" End Parameters diff --git a/vhdbuilder/packer/README.md b/vhdbuilder/packer/README.md index b0741294605..a20b3bab0fd 100644 --- a/vhdbuilder/packer/README.md +++ b/vhdbuilder/packer/README.md @@ -17,13 +17,15 @@ external-vhd-src->(func/init: ensure dest existing/static gallery/definition, in # Roadmap Goal1: remove mariner workflow so things will be simplified. -# Shared Windows 2025 Gen2 staging definitions +# Shared Windows Gen2 staging definitions -The ordinary `windows-2025-gen2` image advertises `TrustedLaunchSupported`, so the -same image can serve Standard and Trusted Launch virtual machines. New staging -definitions are created with that capability directly, without a follow-up +The Windows build template sets `trustedLaunchSupported` for every Gen2 image. +Ordinary WS2022 and WS2025 Gen2 images advertise `TrustedLaunchSupported`, so the +same image can serve Standard and Trusted Launch virtual machines. Gen1 is unchanged. +New staging definitions are created with that capability directly, without a follow-up capability update or `startsAtVersion` boundary because they have no old versions. Dedicated Trusted Launch builds and their metadata precedence are unchanged. +This image capability does not change AKS RP's OS eligibility checks. An existing Standard definition is updated in place, preserving unrelated features and setting `startsAtVersion` to the current build version. Azure CLI performs the