|
| 1 | +--- |
| 2 | +title: Validate Signed Container Images for AKS on Azure Local |
| 3 | +description: Learn how to validate signed container images for AKS on Azure Local deployments to ensure image integrity and prevent supply chain attacks. |
| 4 | +author: sethmanheim |
| 5 | +ms.topic: how-to |
| 6 | +ms.date: 07/25/2025 |
| 7 | +ms.author: sethm |
| 8 | +ms.reviewer: leslielin |
| 9 | +ms.lastreviewed: 07/25/2025 |
| 10 | + |
| 11 | +--- |
| 12 | + |
| 13 | +# Validate signed container images for AKS on Azure Local deployments |
| 14 | + |
| 15 | +[!INCLUDE [hci-applies-to-23h2](includes/hci-applies-to-23h2.md)] |
| 16 | + |
| 17 | +Container image signing verifies the integrity and authenticity of images before deployment, ensuring a trusted infrastructure. This verification is vital in production environments, regulated industries, and automated CI/CD pipelines, where validating image sources helps to both prevent supply chain attacks and maintain compliance. |
| 18 | + |
| 19 | +This article describes how to validate signed container images for AKS on Azure Local deployments. |
| 20 | + |
| 21 | +## Prerequisites |
| 22 | + |
| 23 | +Before you begin, ensure you have the following prerequisites: |
| 24 | + |
| 25 | +1. **Cluster configuration:** Deploy an AKS Arc cluster with these requirements |
| 26 | + |
| 27 | + - One control plane node. |
| 28 | + - At least one Linux node. |
| 29 | + - (Optional) Add a Windows node pool if you need to validate Windows container images. |
| 30 | + |
| 31 | +1. **A Windows machine jump box** is required to run the image validation script. The machine must meet these prerequisites: |
| 32 | + |
| 33 | + - **Network access**: The jump box must be able to communicate with the Azure Local physical boxes where the AKS Arc cluster is deployed and route traffic to the IP addresses assigned to the AKS Arc cluster, including all control plane and worker nodes. |
| 34 | + - **Required tools installed**: Install these tools on the jump box: **curl**, **ssh**, and **scp**. These tools enable secure remote access, file transfers, and HTTP-based interactions with your cluster or management endpoints. |
| 35 | + - **SSH access**: The private SSH key used during cluster creation must be present on the jump box. |
| 36 | + - This key enables connections to individual nodes (Linux or Windows). |
| 37 | + - For details on generating or retrieving the key, see [Configure SSH keys for a cluster](/azure/aks/aksarc/configure-ssh-keys). |
| 38 | + |
| 39 | +1. **Install the Kubernetes CLI** |
| 40 | + |
| 41 | + You can use the Kubernetes CLI, [kubectl](https://kubernetes.io/docs/reference/kubectl/), to connect to your Kubernetes cluster. Use the following Azure CLI or Azure PowerShell commands to install **kubectl**: |
| 42 | + |
| 43 | + # [Azure CLI](#tab/cli) |
| 44 | + |
| 45 | + Install **kubectl** locally using the [az aks install-cli](/cli/azure/aks?view=azure-cli-latest#az-aks-install-cli&preserve-view=true) command. |
| 46 | + |
| 47 | + # [PowerShell](#tab/powershell) |
| 48 | + |
| 49 | + Install **kubectl** locally using the [Install-AzAksCliTool](/powershell/module/az.aks/install-azaksclitool?view=azps-14.2.0&preserve-view=true) cmdlet. |
| 50 | + |
| 51 | + --- |
| 52 | + |
| 53 | +## Step 1: download the image validation script |
| 54 | + |
| 55 | +1. Open a PowerShell window as an administrator and create a temporary folder at **c:\imagesign**: |
| 56 | + |
| 57 | + ```powershell |
| 58 | + mkdir c:\imagesign |
| 59 | + cd c:\imagesign |
| 60 | + ``` |
| 61 | + |
| 62 | +1. Run the following commands to download the [prerequisite.ps1](https://raw.githubusercontent.com/Azure/aksArc/refs/heads/main/scripts/ImageSignValidation/prerequisite.ps1) script to the jump box and save it in **c:\imagesign**: |
| 63 | + |
| 64 | + ```powershell |
| 65 | + $giturl = "https://raw.githubusercontent.com/Azure/aksArc/refs/heads/main/scripts/ImageSignValidation/prerequisite.ps1" |
| 66 | + Invoke-WebRequest -Uri $giturl -OutFile C:\imagesign\prerequisite.ps1 -UseBasicParsing |
| 67 | + Unblock-File .\prerequisite.ps1 |
| 68 | + Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process -Force |
| 69 | + ``` |
| 70 | + |
| 71 | +1. Execute the script |
| 72 | + |
| 73 | + ```powershell |
| 74 | + .\prerequisite.ps1 |
| 75 | + ``` |
| 76 | + |
| 77 | + Sample output: |
| 78 | + |
| 79 | + ```output |
| 80 | + 2025-05-10 00:39:27 [INFO] The current folder is C:\imagesign |
| 81 | + 2025-05-10 00:39:27 [INFO] Linux folder location is C:\imagesign\linux |
| 82 | + 2025-05-10 00:39:28 [INFO] Executable not found at C:\imagesign\linux\notation. Proceeding with download and extraction. |
| 83 | + 2025-05-10 00:39:28 [INFO] Downloading file from https://github.com/notaryproject/notation/releases/download/v1.3.1/notation_1.3.1_linux_amd64.tar.gz |
| 84 | + 2025-05-10 00:39:36 [INFO] Extracting file to C:\imagesign\linux |
| 85 | + 2025-05-10 00:39:36 [INFO] Downloading certificate from https://www.microsoft.com/pkiops/certs/Microsoft%20Supply%20Chain%20RSA%20Root%20CA%202022.crt |
| 86 | + 2025-05-10 00:39:36 [INFO] Certificate downloaded to C:\imagesign\linux\ca.crt |
| 87 | + 2025-05-10 00:39:36 [INFO] Downloading certificate from http://www.microsoft.com/pki/certs/MicRooCerAut_2010-06-23.crt |
| 88 | + 2025-05-10 00:39:36 [INFO] Certificate downloaded to C:\imagesign\linux\tsa.crt |
| 89 | + 2025-05-10 00:39:36 [INFO] Windows folder location is C:\imagesign\win |
| 90 | + 2025-05-10 00:39:36 [INFO] Executable not found at C:\imagesign\win\notation.exe. Proceeding with download and extraction. |
| 91 | + 2025-05-10 00:39:36 [INFO] Downloading file from https://github.com/notaryproject/notation/releases/download/v1.3.1/notation_1.3.1_windows_amd64.zip |
| 92 | + 2025-05-10 00:39:44 [INFO] Extracting file to C:\imagesign\win |
| 93 | + 2025-05-10 00:39:44 [INFO] Downloading certificate from https://www.microsoft.com/pkiops/certs/Microsoft%20Supply%20Chain%20RSA%20Root%20CA%202022.crt |
| 94 | + 2025-05-10 00:39:44 [INFO] Certificate downloaded to C:\imagesign\win\ca.crt |
| 95 | + 2025-05-10 00:39:44 [INFO] Downloading certificate from http://www.microsoft.com/pki/certs/MicRooCerAut_2010-06-23.crt |
| 96 | + 2025-05-10 00:39:44 [INFO] Certificate downloaded to C:\imagesign\win\tsa.crt |
| 97 | + ``` |
| 98 | + |
| 99 | +1. Two folders will be created: **linux** for Linux files and **win** for Windows files. |
| 100 | + |
| 101 | + ### [Linux](#tab/linux) |
| 102 | + |
| 103 | + Copy **LinuxImageValidate.py** to the **c:\imagesign\linux** folder: |
| 104 | + |
| 105 | + ```powershell |
| 106 | + $giturllinux = "https://raw.githubusercontent.com/Azure/aksArc/refs/heads/main/scripts/ImageSignValidation/LinuxImageValidate.py" |
| 107 | + Invoke-WebRequest -Uri $giturllinux -OutFile C:\imagesign\linux\LinuxImageValidate.py -UseBasicParsing |
| 108 | + Unblock-File C:\imagesign\linux\LinuxImageValidate.py |
| 109 | + ``` |
| 110 | + |
| 111 | + ### [Windows](#tab/windows) |
| 112 | + |
| 113 | + Copy **WindowsImageValidate.ps1** to the **c:\imagesign\win** folder: |
| 114 | + |
| 115 | + ```powershell |
| 116 | + $giturlwin = "https://raw.githubusercontent.com/Azure/aksArc/refs/heads/main/scripts/ImageSignValidation/WindowsImageValidate.ps1" |
| 117 | + Invoke-WebRequest -Uri $giturlwin -OutFile C:\imagesign\win\WindowsImageValidate.ps1 -UseBasicParsing |
| 118 | + Unblock-File C:\imagesign\win\WindowsImageValidate.ps1 |
| 119 | + Invoke-WebRequest -Uri $giturlwin -OutFile C:\imagesign\win\WindowsImageValidate.py -UseBasicParsing |
| 120 | + Unblock-File C:\imagesign\win\WindowsImageValidate.py |
| 121 | + ``` |
| 122 | + |
| 123 | + --- |
| 124 | + |
| 125 | +## Step 2: get the control plane and worker node IP addresses |
| 126 | + |
| 127 | +Follow steps 1 and 2 in the [Use SSH to connect to worker nodes](ssh-connect-to-windows-and-linux-worker-nodes.md#use-ssh-to-connect-to-worker-nodes) section to obtain IP addresses. |
| 128 | + |
| 129 | +### Sample command |
| 130 | + |
| 131 | +```powershell |
| 132 | +kubectl --kubeconfig /path/to/aks-cluster-kubeconfig get nodes -o wide |
| 133 | +``` |
| 134 | + |
| 135 | +Sample output: |
| 136 | + |
| 137 | +| NAME | STATUS | ROLES | AGE | VERSION | INTERNAL-IP | EXTERNAL-IP | OS-IMAGE | KERNEL-VERSION | CONTAINER-RUNTIME | |
| 138 | +|-------------------|--------|--------------|-------|---------|-----------------|-------------|---------------------------|------------------------|-----------------------------| |
| 139 | +| moc-lsbe393il9d | Ready | control-plane| 3h14m | 1.30.4 | 100.0.12.133 | None | CBL-Mariner/Linux | 5.15.173.1-2.cm2 | containerd://1.6.26 | |
| 140 | +| moc-lzwagtkjah5 | Ready | None | 3h12m | 1.30.4 | 100.0.12.134 | None | CBL-Mariner/Linux | 5.15.173.1-2.cm2 | containerd://1.6.26 | |
| 141 | +| moc-wlcjnwn5n02 | Ready | None | 14m | 1.30.4 | 100.0.12.135 | None | Windows Server 2022 Datacenter | 10.0.20348.2700 | containerd://1.6.21+Azure | |
| 142 | + |
| 143 | +From this sample output: |
| 144 | + |
| 145 | +- Control plane IP is 100.0.12.133 (where the ROLES value is `control-plane` and OS image is `CBL-Mariner/Linux`). |
| 146 | +- Linux node IP is 100.0.12.134 (where the ROLES value is `none` and OS image is `CBL-Mariner/Linux`). |
| 147 | +- Windows node IP is 100.0.12.135 (where the OS image is `Windows Server 2022`). |
| 148 | + |
| 149 | +## Step 3: run the image validation script on the control plane and worker nodes |
| 150 | + |
| 151 | +### [Linux](#tab/linux) |
| 152 | + |
| 153 | +These steps work for both the control plane node and Linux worker node since they use the **CBL-Mariner/Linux** OS image. |
| 154 | + |
| 155 | +1. Check if the commands can be run on the Linux VM (assuming the private key is in **C:\Users\Administrator\.ssh**): |
| 156 | + |
| 157 | + ```bash |
| 158 | + ssh -i "C:\Users\Administrator\.ssh\id_rsa" -o StrictHostKeyChecking=no [email protected] "sudo crictl images --no-trunc" |
| 159 | + ``` |
| 160 | + |
| 161 | +1. Copy the Linux-specific files to the Linux VM: |
| 162 | + |
| 163 | + ```bash |
| 164 | + scp -i "C:\Users\Administrator\.ssh\id_rsa" C: \imagesign \linux \* [email protected]:. |
| 165 | + ``` |
| 166 | + |
| 167 | +1. Mark the notation binary file as executable: |
| 168 | + |
| 169 | + ```bash |
| 170 | + ssh -i "C:\Users\Administrator\.ssh\id_rsa" -o StrictHostKeyChecking=no [email protected] "sudo chmod +x notation" |
| 171 | + ``` |
| 172 | + |
| 173 | +1. Execute commands to validate image signature verification. This step can take around 2-5 minutes: |
| 174 | + |
| 175 | + ```bash |
| 176 | + ssh -i "C:\Users\Administrator\.ssh\id_rsa" -o StrictHostKeyChecking=no [email protected] "sudo python3 LinuxImageValidate.py" |
| 177 | + ``` |
| 178 | + |
| 179 | +1. Copy the output file to the local directory: |
| 180 | + |
| 181 | + ```bash |
| 182 | + ssh -i "C:\Users\Administrator\.ssh\id_rsa" -o StrictHostKeyChecking=no [email protected] "sudo cat imagevalidation_results_linux.json" > imagevalidation_results_controlplane.json |
| 183 | + ``` |
| 184 | + |
| 185 | +1. Clean up all files copied to the VM: |
| 186 | + |
| 187 | + ```bash |
| 188 | + ssh -i "C:\Users\Administrator\.ssh\id_rsa" -o StrictHostKeyChecking=no [email protected] rm LinuxImageValidate.py notation tsa.crt ca.crt LICENSE imagevalidation_results_linux.json results.yaml |
| 189 | + ``` |
| 190 | + |
| 191 | +1. Clean up the IP reference from the SSH cache: |
| 192 | + |
| 193 | + ```bash |
| 194 | + ssh-keygen -R 100.0.12.133 |
| 195 | + ``` |
| 196 | + |
| 197 | + Sample output: |
| 198 | + |
| 199 | + ```output |
| 200 | + { |
| 201 | + "failed_signed_images": [ |
| 202 | + "mcr.microsoft.com/mssql/server:2017-latest", |
| 203 | + "mcr.microsoft.com/oss/kubernetes/coredns:v1.9.3", |
| 204 | + "mcr.microsoft.com/oss/kubernetes/pause:3.9" |
| 205 | + ], |
| 206 | + "passed_signed_images": [ |
| 207 | + "mcr.microsoft.com/oss/kubernetes/kube-proxy:v1.28.3", |
| 208 | + "mcr.microsoft.com/oss/kubernetes/kube-proxy:v1.28.5", |
| 209 | + "mcr.microsoft.com/oss/kubernetes/kube-proxy:v1.28.9", |
| 210 | + "mcr.microsoft.com/oss/kubernetes/kube-proxy:v1.29.2", |
| 211 | + "mcr.microsoft.com/oss/kubernetes/kube-proxy:v1.29.4", |
| 212 | + "mcr.microsoft.com/oss/kubernetes/kube-proxy:v1.29.7", |
| 213 | + "mcr.microsoft.com/oss/kubernetes/kube-proxy:v1.29.9", |
| 214 | + "mcr.microsoft.com/oss/kubernetes/kube-proxy:v1.30.3", |
| 215 | + "mcr.microsoft.com/oss/kubernetes/kube-proxy:v1.30.4", |
| 216 | + "mcr.microsoft.com/oss/kubernetes/kube-rbac-proxy:v0.18.1", |
| 217 | + "mcr.microsoft.com/oss/kubernetes/kube-scheduler:v1.28.12-hotfix.20240719", |
| 218 | + "mcr.microsoft.com/oss/kubernetes/kube-scheduler:v1.28.14-hotfix.20240918", |
| 219 | + "mcr.microsoft.com/oss/kubernetes/kube-scheduler:v1.29.7", |
| 220 | + "mcr.microsoft.com/oss/kubernetes/kube-scheduler:v1.29.9", |
| 221 | + "mcr.microsoft.com/oss/kubernetes/kube-scheduler:v1.30.3", |
| 222 | + "mcr.microsoft.com/oss/kubernetes/kube-scheduler:v1.30.4", |
| 223 | + "mcr.microsoft.com/oss/kubernetes/pause:3.9-hotfix-20230808", |
| 224 | + "mcr.microsoft.com/oss/v2/kubernetes-csi/csi-driver-nfs:v4.9.0-1" |
| 225 | + ] |
| 226 | + } |
| 227 | + ``` |
| 228 | + |
| 229 | + > [!NOTE] |
| 230 | + > If the **failed_signed_images** list is empty, all images are signed correctly. Otherwise, the list shows images that failed signature verification. |
| 231 | +
|
| 232 | +### [Windows](#tab/windows) |
| 233 | + |
| 234 | +These steps work on all supported Windows OS worker nodes. |
| 235 | + |
| 236 | +1. Check if the commands can be run on the Windows VM (assuming the private key is in folder **C:\Users\Administrator\.ssh**): |
| 237 | + |
| 238 | + ```bash |
| 239 | + ssh -i "C:\Users\Administrator\.ssh\id_rsa" -o StrictHostKeyChecking=no [email protected] "crictl images --no-trunc" |
| 240 | + ``` |
| 241 | + |
| 242 | +1. Copy the Windows-specific files inside the Windows VM: |
| 243 | + |
| 244 | + ```bash |
| 245 | + ssh -i "C:\Users\Administrator\.ssh\id_rsa" [email protected] "powershell -ExecutionPolicy Bypass mkdir c:\win" |
| 246 | + scp -i "C:\Users\Administrator\.ssh\id_rsa" C: \imagesign \win \ca.crt [email protected]:c: \win \ca.crt |
| 247 | + scp -i "C:\Users\Administrator\.ssh\id_rsa" C: \imagesign \win \notation.exe [email protected]:c: \win \notation.exe |
| 248 | + scp -i "C:\Users\Administrator\.ssh\id_rsa" C: \imagesign \win \tsa.crt [email protected]:c: \win \tsa.crt |
| 249 | + scp -i "C:\Users\Administrator\.ssh\id_rsa" C: \imagesign \win \WindowsImageValidate.ps1 [email protected]:c: \win \WindowsImageValidate.ps1 |
| 250 | + ``` |
| 251 | + |
| 252 | +1. Execute commands to validate image signature verification. This step can take around 2-5 minutes: |
| 253 | + |
| 254 | + ```bash |
| 255 | + ssh -i "C:\Users\Administrator\.ssh\id_rsa" [email protected] "powershell -ExecutionPolicy Bypass -File c:\win\WindowsImageValidate.ps1" |
| 256 | + ``` |
| 257 | + |
| 258 | +1. Copy the output file to the local directory: |
| 259 | + |
| 260 | + ```bash |
| 261 | + scp -i "C:\Users\Administrator\.ssh\id_rsa" [email protected]:c: \win \imagevalidation_results_windows.json C: \imagesign \imagevalidation_results_windows.json |
| 262 | + ``` |
| 263 | + |
| 264 | +1. Clean up all files copied to the VM: |
| 265 | + |
| 266 | + ```bash |
| 267 | + ssh -i "C:\Users\Administrator\.ssh\id_rsa" [email protected] "powershell -ExecutionPolicy Bypass remove-item -force c:\win" |
| 268 | + ``` |
| 269 | + |
| 270 | +1. Clean up IP reference from the SSH cache: |
| 271 | + |
| 272 | + ```bash |
| 273 | + ssh-keygen -R 100.0.12.135 |
| 274 | + ``` |
| 275 | + |
| 276 | + Sample output: |
| 277 | + |
| 278 | + ```output |
| 279 | + { |
| 280 | + "failed_signed_images": [ |
| 281 | + ], |
| 282 | + "passed_signed_images": [ |
| 283 | + "mcr.microsoft.com/oss/kubernetes-csi/livenessprobe@sha256:20fd8754d36efc52ff0a837e646909102be5d47600a8656804aecd4eff52b7c7", |
| 284 | + "mcr.microsoft.com/oss/kubernetes/pause@sha256:2b5d81a9d7f04299f45ed1b6822de018188f463914fcbf5592f39087c9adead1", |
| 285 | + "mcr.microsoft.com/windows/nanoserver@sha256:f82cb05e20c4bfa93a007c9f073f83febd8bc6d16f98a3208f3baa486aafcdf4" |
| 286 | + ], |
| 287 | + "failed_signed_images": [ |
| 288 | + ] |
| 289 | + } |
| 290 | + ``` |
| 291 | + |
| 292 | + > [!NOTE] |
| 293 | + > If the **failed_signed_images** list is empty, all images are signed correctly. Otherwise, the list shows images that failed signature verification. |
| 294 | +
|
| 295 | +--- |
| 296 | + |
| 297 | +## Next steps |
| 298 | + |
| 299 | +- [AKS on Azure Local overview](overview.md) |
| 300 | +- [Use Azure role-based access control (RBAC) for Kubernetes authorization](azure-rbac-local.md) |
0 commit comments