diff --git a/README.md b/README.md index cd9f9b6fb..7bc5791c7 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ ### What works -- Creation of worker nodes on AWS, Digitalocean, Openstack, Azure, Google Cloud Platform, Nutanix, VMWare Cloud Director, VMWare Vsphere, Linode, Hetzner cloud and Kubevirt (experimental) +- Creation of worker nodes on AWS, Digitalocean, Openstack, Azure, Google Cloud Platform, Nutanix, VMWare Cloud Director, VMWare Vsphere, Linode, Hetzner cloud and Kubevirt. - Using Ubuntu, Flatcar or CentOS 7 distributions ([not all distributions work on all providers](/docs/operating-system.md)) ### Supported Kubernetes versions diff --git a/docs/operating-system.md b/docs/operating-system.md index 984606070..b489406b1 100644 --- a/docs/operating-system.md +++ b/docs/operating-system.md @@ -4,7 +4,7 @@ ### Cloud provider -| | Ubuntu | CentOS | Flatcar | RHEL | Amazon Linux 2 | Rocky Linux | Windows | +| | Ubuntu | CentOS | Flatcar | RHEL | Amazon Linux 2 | Rocky Linux | Windows[^1] | |---|---|---|---|---|---|---|---| | AWS | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | x | | Azure | ✓ | ✓ | ✓ | ✓ | x | ✓ | ✓ | @@ -18,6 +18,8 @@ | VMware Cloud Director | ✓ | x | x | x | x | x | x | | VSphere | ✓ | ✓ | ✓ | ✓ | x | ✓ | x | +[^1]: Windows support is considered experimental. + ## Configuring a operating system The operating system to use can be set via `machine.spec.providerConfig.operatingSystem`. @@ -45,3 +47,4 @@ Machine controller may work with other OS versions that are not listed in the ta | RHEL | 8.x | | Rocky Linux | 8.5 | | Ubuntu | 20.04 LTS, 22.04 LTS | +| Windows | windows-2019, windows-2022 | diff --git a/hack/verify-boilerplate.sh b/hack/verify-boilerplate.sh index 101dbdc31..ec6294ecd 100755 --- a/hack/verify-boilerplate.sh +++ b/hack/verify-boilerplate.sh @@ -22,4 +22,5 @@ boilerplate \ -boilerplates hack/boilerplate \ -exclude pkg/machines/v1alpha1 \ -exclude pkg/signals \ - -exclude pkg/userdata/scripts + -exclude pkg/userdata/scripts \ + -exclude image-builder/windows diff --git a/image-builder/windows/Makefile b/image-builder/windows/Makefile new file mode 100644 index 000000000..7ab34097c --- /dev/null +++ b/image-builder/windows/Makefile @@ -0,0 +1,91 @@ +# Copyright 2023 The Machine Controller Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +SHELL = /bin/bash -eu -o pipefail + +## -------------------------------------- +## Packer flags +## -------------------------------------- + +# Set Packer color to true if not already set in env variables +# Only valid for builds +ifneq (,$(findstring build-, $(MAKECMDGOALS))) + # A build target + PACKER_COLOR ?= true + PACKER_FLAGS += -color=$(PACKER_COLOR) +endif + +# If FOREGROUND=1 then Packer will set headless to false, causing local builds +# to build in the foreground, with a UI. This is very useful when debugging new +# platforms or issues with existing ones. +ifeq (1,$(strip $(FOREGROUND))) +PACKER_FLAGS += -var="headless=false" +endif + +# If ON_ERROR_ASK=1 then Packer will set -on-error to ask, causing the Packer +# build to pause when any error happens, instead of simply exiting. This is +# useful when debugging unknown issues logging into the remote machine via ssh. +ifeq (1,$(strip $(ON_ERROR_ASK))) +PACKER_FLAGS += -on-error=ask +endif + +# ssh_private_key_file and ssh_public_key are needed to pass ssh keypair +# from its host to the packer guest machine, so boot managers like ignition +# could make use of the key in its config. +# SSH_PRIVATE_KEY_FILE is name of the file that contains the private key. +# SSH_PUBLIC_KEY_FILE is name of the file that contains the public key. +ifneq (,$(strip $(SSH_PRIVATE_KEY_FILE))) +PACKER_FLAGS += -var ssh_private_key_file="$(SSH_PRIVATE_KEY_FILE)" +endif + +ifneq (,$(strip $(SSH_PUBLIC_KEY_FILE))) +PACKER_FLAGS += -var ssh_public_key="$(shell cat ${SSH_PUBLIC_KEY_FILE})" +endif + +# Since OpenSSH 9.0+ 'scp' uses SFTP protocol instead of legacy SCP protocol, which causes building errors like: +# +# bash: line 1: /usr/lib/sftp-server: No such file or directory\nscp: Connection closed\r\n"" +# +# However, -O option is not available in older OpenSSH version, so we cannot always set it as an option to use. +# To provide better out-of-the-box experience for users with newer versions of OpenSSH, we conditionally ensure +# -O is used when used OpenSSH version requires it. +# +# See https://github.com/kubernetes-sigs/image-builder/issues/859 and +# https://github.com/hashicorp/packer-plugin-ansible/issues/100 for more details. +ifeq ($(shell test $$(ssh -V 2>&1 | cut -d _ -f2 | cut -d . -f1) -ge 9; echo $$?),0) + # Use ?= to retain possible existing value of environment variable. If it is already declared, we assume user to be + # aware of OpenSSH version they use and it is up to the user to specify "-O" option as well if needed. + export ANSIBLE_SCP_EXTRA_ARGS ?= "-O" +endif + +# If DEBUG=1 then Packer will set -debug, enabling debug mode for builds, providing +# more verbose logging +ifeq (1,$(strip $(DEBUG))) +PACKER_FLAGS += -debug +endif + +COMMON_WINDOWS_VAR_FILES := packer/common/ansible-args-windows.json \ + packer/common/common.json \ + packer/common/cloudbase-init.json + +PACKER_WINDOWS_NODE_FLAGS := $(foreach f,$(abspath $(COMMON_WINDOWS_VAR_FILES)),-var-file="$(f)" ) + +azure-deps: + hack/ensure-ansible.sh + hack/ensure-ansible-windows.sh + hack/ensure-packer.sh + +build-azure-image: azure-deps + packer validate $(PACKER_WINDOWS_NODE_FLAGS) -var-file="$(abspath packer/azure/azure-config.json)" -var-file="$(abspath packer/azure/$(distribution))" $(abspath packer/azure/packer-windows.json) + packer build $(PACKER_WINDOWS_NODE_FLAGS) -var-file="$(abspath packer/azure/azure-config.json)" -var-file="$(abspath packer/azure/$(distribution))" $(abspath packer/azure/packer-windows.json) diff --git a/image-builder/windows/README.md b/image-builder/windows/README.md new file mode 100644 index 000000000..43a61d2ae --- /dev/null +++ b/image-builder/windows/README.md @@ -0,0 +1,28 @@ +# Build image for Windows Nodes + +These scripts are used to build a custom image for windows. The stock/base images don't have support for a provisioning utility that can process the userdata and perform the required configurations on the machine. To overcome this limitation, we resort to custom images with some pre-configurations including [cloudbase-init](https://cloudbase.it/cloudbase-init/); the provisioning utility for windows. + +This is an indirect clone of [image-builder-capi](https://github.com/kubernetes-sigs/image-builder/tree/master/images/capi) and extends it to make the images compatible with machine-controller. + +## Prerequisites + +- [Packer](https://www.packer.io/intro/getting-started/install.html) version >= 1.6.0 +- [Ansible](http://docs.ansible.com/ansible/latest/intro_installation.html) version >= 2.10.0 + +## Azure + +Following distributions are supported: + +- windows-2019 +- windows-2022 + +### Requirements + +- An Azure account +- The Azure CLI installed and configured +- Set environment variables for `AZURE_SUBSCRIPTION_ID`, `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET` +- Set optional environment variables `RESOURCE_GROUP_NAME`, `STORAGE_ACCOUNT_NAME`, `AZURE_LOCATION` & `GALLERY_NAME` to override the default values + +### Build command + +`make build-azure-image distribution=windows-2022.json` diff --git a/image-builder/windows/ansible/ansible_winrm.ps1 b/image-builder/windows/ansible/ansible_winrm.ps1 new file mode 100644 index 000000000..6800202af --- /dev/null +++ b/image-builder/windows/ansible/ansible_winrm.ps1 @@ -0,0 +1,51 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This file is from packer documentation: +# https://www.packer.io/docs/provisioners/ansible.html#winrm-communicator +# https://www.packer.io/docs/builders/amazon/ebs#connecting-to-windows-instances-using-winrm + +Write-Host "Changing PS execution policy to Unrestricted" -ForegroundColor Cyan +Set-ExecutionPolicy Unrestricted -Scope LocalMachine -Force -ErrorAction Ignore + +# Don't set this before Set-ExecutionPolicy as it throws an error +$ErrorActionPreference = "stop" + +Write-Host "Execution Policy configured" -ForegroundColor Cyan + +# Remove HTTP listener +Remove-Item -Path WSMan:\Localhost\listener\listener* -Recurse + +# Create a self-signed certificate to let ssl work +$Cert = New-SelfSignedCertificate -CertstoreLocation Cert:\LocalMachine\My -DnsName "packer" +New-Item -Path WSMan:\LocalHost\Listener -Transport HTTPS -Address * -CertificateThumbPrint $Cert.Thumbprint -Force + +# WinRM +write-output "Setting up WinRM" +write-host "(host) setting up WinRM" + +cmd.exe /c winrm quickconfig -q +cmd.exe /c winrm set "winrm/config" '@{MaxTimeoutms="1800000"}' +cmd.exe /c winrm set "winrm/config/winrs" '@{MaxMemoryPerShellMB="1024"}' +cmd.exe /c winrm set "winrm/config/service" '@{AllowUnencrypted="true"}' +cmd.exe /c winrm set "winrm/config/client" '@{AllowUnencrypted="true"}' +cmd.exe /c winrm set "winrm/config/service/auth" '@{Basic="true"}' +cmd.exe /c winrm set "winrm/config/client/auth" '@{Basic="true"}' +cmd.exe /c winrm set "winrm/config/service/auth" '@{CredSSP="true"}' +cmd.exe /c winrm set "winrm/config/listener?Address=*+Transport=HTTPS" "@{Port=`"5986`";Hostname=`"packer`";CertificateThumbprint=`"$($Cert.Thumbprint)`"}" +cmd.exe /c netsh advfirewall firewall set rule group="remote administration" new enable=yes +cmd.exe /c netsh firewall add portopening TCP 5986 "Port 5986" +cmd.exe /c net stop winrm +cmd.exe /c sc config winrm start= auto +cmd.exe /c net start winrm diff --git a/image-builder/windows/ansible/example.vars.yml b/image-builder/windows/ansible/example.vars.yml new file mode 100644 index 000000000..eb6a81581 --- /dev/null +++ b/image-builder/windows/ansible/example.vars.yml @@ -0,0 +1,29 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +cloudbase_init_url: https://github.com/cloudbase/cloudbase-init/releases/download/1.1.4/CloudbaseInitSetup_1_1_4_x64.msi + +windows_service_manager: 'nssm' +pause_image: "registry.k8s.io/pause:3.9" +load_additional_components: true +prepull: false +distribution_version: 2019 + +cloudbase_metadata_services: "cloudbaseinit.metadata.services.azureservice.AzureService, cloudbaseinit.metadata.services.ovfservice.OvfService" +cloudbase_plugins: "cloudbaseinit.plugins.common.ephemeraldisk.EphemeralDiskPlugin, cloudbaseinit.plugins.common.userdata.UserDataPlugin, cloudbaseinit.plugins.common.localscripts.LocalScriptsPlugin" +cloudbase_metadata_services_unattend: "cloudbaseinit.metadata.services.azureservice.AzureService, cloudbaseinit.metadata.services.ovfservice.OvfService" +cloudbase_plugins_unattend: "cloudbaseinit.plugins.common.mtu.MTUPlugin" + +debug_tools: true +additional_debug_files: "https://raw.githubusercontent.com/kubernetes-sigs/sig-windows-tools/master/hack/DebugWindowsNode.ps1" diff --git a/image-builder/windows/ansible/node_windows.yml b/image-builder/windows/ansible/node_windows.yml new file mode 100644 index 000000000..420053def --- /dev/null +++ b/image-builder/windows/ansible/node_windows.yml @@ -0,0 +1,90 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- hosts: all + vars: + node_custom_roles_pre: "" + node_custom_roles_post: "" + custom_role_names: "" + + tasks: + - name: Check if cloudbase-init url is set + set_fact: + install_cloudbase_init: '{{ true if (cloudbase_init_url is defined) and (cloudbase_init_url|length > 0) else false }}' + + # https://docs.ansible.com/ansible/latest/user_guide/windows_performance.html + - name: Optimise powershell + win_shell: | + function Optimize-PowershellAssemblies { + # NGEN powershell assembly, improves startup time of powershell by 10x + $old_path = $env:path + try { + $env:path = [Runtime.InteropServices.RuntimeEnvironment]::GetRuntimeDirectory() + [AppDomain]::CurrentDomain.GetAssemblies() | % { + if (! $_.location) {continue} + $Name = Split-Path $_.location -leaf + if ($Name.startswith("Microsoft.PowerShell.")) { + Write-Progress -Activity "Native Image Installation" -Status "$name" + ngen install $_.location | % {"`t$_"} + } + } + } finally { + $env:path = $old_path + } + } + Optimize-PowershellAssemblies + become: yes + become_method: runas + become_user: SYSTEM + + - name: Get Install Drive + win_shell: $env:SYSTEMDRIVE + register: systemdrive + + - name: Get Program Files Directory + win_shell: $env:ProgramFiles + register: programfiles + + - name: Get All Users profile path + win_shell: $env:ALLUSERSPROFILE.Replace("\", "\\") + register: alluserprofile + + - name: Get TEMP Directory + win_shell: $env:TEMP + register: tempdir + + - include_role: + name: "{{ role }}" + loop: "{{ node_custom_roles_pre.split() }}" + loop_control: + loop_var: role + when: node_custom_roles_pre != "" + - include_role: + name: systemprep + - include_role: + name: cloudbase-init + when: install_cloudbase_init + - include_role: + name: providers + - include_role: + name: "{{ role }}" + loop: "{{ custom_role_names.split() + node_custom_roles_post.split() }}" + loop_control: + loop_var: role + when: custom_role_names != "" or node_custom_roles_post != "" + + environment: + HTTP_PROXY: "{{ http_proxy | default('') }}" + HTTPS_PROXY: "{{ https_proxy | default('') }}" + NO_PROXY: "{{ no_proxy | default('') }}" diff --git a/image-builder/windows/ansible/roles/cloudbase-init/tasks/main.yml b/image-builder/windows/ansible/roles/cloudbase-init/tasks/main.yml new file mode 100644 index 000000000..e18f81047 --- /dev/null +++ b/image-builder/windows/ansible/roles/cloudbase-init/tasks/main.yml @@ -0,0 +1,53 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- name: Download Cloudbase-init + win_get_url: + url: "{{ cloudbase_init_url }}" + dest: '{{ tempdir.stdout | trim }}\CloudbaseInitSetup.msi' + register: installer + retries: 5 + delay: 3 + until: installer is not failed + +- name: Ensure log directory + win_file: + path: '{{ systemdrive.stdout | trim }}\logs' + state: directory + +- name: Install Cloudbase-init + win_package: + path: '{{ installer.dest }}' + log_path: '{{ systemdrive.stdout | trim }}\logs\cloudbase-install-log-{{lookup("pipe", "date +%Y%m%dT%H%M%S")}}.log' + +# configuration modified from https://github.com/cloudbase/windows-openstack-imaging-tools/tree/master/Examples/config/azure +- name: Set up cloudbase-init unattend configuration + win_template: + src: templates/cloudbase-init-unattend.conf + dest: '{{ programfiles.stdout | trim }}\Cloudbase Solutions\Cloudbase-Init\conf\cloudbase-init-unattend.conf' + +# configuration modified from https://github.com/cloudbase/windows-openstack-imaging-tools/tree/master/Examples/config/azure +- name: Set up cloudbase-init configuration + win_template: + src: templates/cloudbase-init.conf + dest: '{{ programfiles.stdout | trim }}\Cloudbase Solutions\Cloudbase-Init\conf\cloudbase-init.conf' + +- name: Configure set up complete + win_shell: | + # If this file already exists then the following command fails + Remove-Item -Force {{ systemdrive.stdout | trim }}\Windows\Setup\Scripts\SetupComplete.cmd + & "{{ programfiles.stdout | trim }}\Cloudbase Solutions\Cloudbase-Init\bin\SetSetupComplete.cmd" + become: yes + become_method: runas + become_user: System diff --git a/image-builder/windows/ansible/roles/cloudbase-init/templates/cloudbase-init-unattend.conf b/image-builder/windows/ansible/roles/cloudbase-init/templates/cloudbase-init-unattend.conf new file mode 100644 index 000000000..9d4e0aec4 --- /dev/null +++ b/image-builder/windows/ansible/roles/cloudbase-init/templates/cloudbase-init-unattend.conf @@ -0,0 +1,28 @@ +[DEFAULT] +# This configuration with SetUserPasswordPlugin and CreateUserPlugin will create a user admin +# and generate a 123 charater random password. SSH can be configred on the machine to enable access. +username=admin +groups=Administrators +inject_user_password=false +user_password_length=123 +first_logon_behaviour=no + +config_drive_raw_hhd=true +config_drive_cdrom=true +config_drive_vfat=true +bsdtar_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\bin\bsdtar.exe +mtools_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\bin\ +verbose=true +debug=true +logdir=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\log\ +logfile=cloudbase-init-unattend.log +default_log_levels=comtypes=INFO,suds=INFO,iso8601=WARN,requests=WARN +logging_serial_port_settings={{ cloudbase_logging_serial_port }} +mtu_use_dhcp_config=true +ntp_use_dhcp_config=true +local_scripts_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\LocalScripts\ +metadata_services={{ cloudbase_metadata_services_unattend }} +plugins={{ cloudbase_plugins_unattend }} +allow_reboot=false +stop_service_on_exit=false +check_latest_version=false diff --git a/image-builder/windows/ansible/roles/cloudbase-init/templates/cloudbase-init.conf b/image-builder/windows/ansible/roles/cloudbase-init/templates/cloudbase-init.conf new file mode 100644 index 000000000..1f73455ac --- /dev/null +++ b/image-builder/windows/ansible/roles/cloudbase-init/templates/cloudbase-init.conf @@ -0,0 +1,36 @@ +[DEFAULT] +# This configuration with SetUserPasswordPlugin and CreateUserPlugin will create a user admin +# and generate a 123 charater random password. SSH can be configred on the machine to enable access. +username=admin +groups=Administrators +inject_user_password=false +user_password_length=123 +first_logon_behaviour=no +rename_admin_user=true + +config_drive_raw_hhd=true +config_drive_cdrom=true +config_drive_vfat=true +bsdtar_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\bin\bsdtar.exe +mtools_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\bin\ +verbose=true +debug=true +logdir=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\log\ +logfile=cloudbase-init.log +default_log_levels=comtypes=INFO,suds=INFO,iso8601=WARN,requests=WARN +logging_serial_port_settings={{ cloudbase_logging_serial_port }} +mtu_use_dhcp_config=true +ntp_use_dhcp_config=true +local_scripts_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\LocalScripts\ + +san_policy=OnlineAll +trim_enabled=True + +metadata_report_provisioning_started=True +metadata_report_provisioning_completed=True +ephemeral_disk_volume_label="Temporary Storage" +netbios_host_name_compatibility={{ netbios_host_name_compatibility }} + +metadata_services={{ cloudbase_metadata_services }} +plugins=cloudbaseinit.plugins.common.userdata.UserDataPlugin, + {{ cloudbase_plugins }} diff --git a/image-builder/windows/ansible/roles/providers/defaults/main.yml b/image-builder/windows/ansible/roles/providers/defaults/main.yml new file mode 100644 index 000000000..20548e607 --- /dev/null +++ b/image-builder/windows/ansible/roles/providers/defaults/main.yml @@ -0,0 +1,15 @@ +# Copyright 2021 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +packer_builder_type: "" diff --git a/image-builder/windows/ansible/roles/providers/tasks/azure.yml b/image-builder/windows/ansible/roles/providers/tasks/azure.yml new file mode 100644 index 000000000..390bff941 --- /dev/null +++ b/image-builder/windows/ansible/roles/providers/tasks/azure.yml @@ -0,0 +1,48 @@ +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- + +- name: Create Azure wireserver access group + ansible.windows.win_group: + name: WireServerAccess + description: Controls access to the Azure WireServer + +# AzureGuestAgent and Cloudbase-init need access to wireserver otherwise VM doesn't boot +# So we give the users access via the firewall security filters +# https://docs.microsoft.com/en-us/powershell/module/netsecurity/set-netfirewallsecurityfilter +# +# Permissions set on the Firewall rule: +# S-1-1-0 is Everyone. We mark this as Allow (A) to ensure the Block is enforced for all users other than on the exception list. +# S-1-5-18 is LocalSystem used by AzureGuestAgent. We mark this as Deny (D) to add to Block exception list. +# We also add the newly created group WireServerAccess to the block exception list and add Cloudbase-init user later. +# +# View the details of the SDDL string used with ConvertFrom-SddlString and see well known sids: https://docs.microsoft.com/en-us/windows/win32/secauthz/well-known-sids +- name: Block traffic to 168.63.129.16 port 80 for cve-2021-27075 + win_shell: | + $wsg = Get-LocalGroup -n "WireServerAccess" + $r = New-NetFirewallRule -DisplayName 'Block-Outbound-168.63.129.16-port-80-for-cve-2021-27075' -Direction Outbound -RemoteAddress '168.63.129.16' -RemotePort '80' -Protocol TCP -Action Block + $r | Get-NetFirewallSecurityFilter | Set-NetFirewallSecurityFilter -LocalUser "O:LSD:(D;;CC;;;S-1-5-18)(D;;CC;;;$($wsg.SID.Value))(A;;CC;;;S-1-1-0)" + become: yes + become_method: runas + become_user: SYSTEM + +- name: Add users to WireServerAccessGroup + ansible.windows.win_group_membership: + name: WireServerAccess + members: + - cloudbase-init + +- name: Add additional users + ansible.windows.win_group_membership: + name: WireServerAccess + members: "{{ users }}" + vars: + users: "{{ wire_server_users.split(',') if (wire_server_users is defined) and (wire_server_users|length > 0) else [] }}" diff --git a/image-builder/windows/ansible/roles/providers/tasks/main.yml b/image-builder/windows/ansible/roles/providers/tasks/main.yml new file mode 100644 index 000000000..f9a7a6198 --- /dev/null +++ b/image-builder/windows/ansible/roles/providers/tasks/main.yml @@ -0,0 +1,14 @@ +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- + +- include_tasks: azure.yml + when: packer_builder_type.startswith('azure') diff --git a/image-builder/windows/ansible/roles/systemprep/defaults/main.yml b/image-builder/windows/ansible/roles/systemprep/defaults/main.yml new file mode 100644 index 000000000..d77195fea --- /dev/null +++ b/image-builder/windows/ansible/roles/systemprep/defaults/main.yml @@ -0,0 +1,17 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +windows_updates_kbs_numbers: "{{ windows_updates_kbs.split() if (windows_updates_kbs is defined) and (windows_updates_kbs|length > 0) else [] }}" +windows_updates_category_names: "{{ windows_updates_categories.split() if (windows_updates_categories is defined) and (windows_updates_categories|length > 0) else [] }}" +ssh_source_url: "{{ ssh_source_url if ssh_source_url is defined else ''}}" diff --git a/image-builder/windows/ansible/roles/systemprep/tasks/main.yml b/image-builder/windows/ansible/roles/systemprep/tasks/main.yml new file mode 100644 index 000000000..112e6e6cd --- /dev/null +++ b/image-builder/windows/ansible/roles/systemprep/tasks/main.yml @@ -0,0 +1,179 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +# This file was adapted from https://github.com/Azure/aks-engine/blob/master/vhd/packer/configure-windows-vhd.ps1 for ansible +- name: Remove Windows updates default registry settings + win_regedit: + path: HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\ + state: absent + delete_key: yes + +- name: Add Windows update registry path + win_regedit: + path: HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate + state: present + +- name: Add Windows automatic update registry path + win_regedit: + path: HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU + state: present + +# https://docs.microsoft.com/en-us/windows/deployment/update/waas-wu-settings#configuring-automatic-updates-by-editing-the-registry +- name: Disable Windows automatic updates in registry + win_regedit: + path: HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU + state: present + name: NoAutoUpdate + data: 1 + type: dword + +- name: Set Windows automatic updates to notify only in registry + win_regedit: + path: HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU + state: present + name: AUOptions + data: 2 + type: dword + +# Hyper-V messes with networking components on startup after the feature is enabled +# causing issues with communication over winrm and setting winrm to delayed start +# gives Hyper-V enough time to finish configuration before having packer continue. +- name: Set WinRm Service to delayed start + win_command: sc.exe config winrm start=delayed-auto + +# Best effort to update defender signatures +# This can fail if there is already a signature +# update running which means we will get them anyways +# Also at the time the VM is provisioned Defender will trigger any required updates +- name: Update Windows Defender signatures + win_shell: | + $service = Get-Service "Windefend" + $service.WaitForStatus("Running","00:5:00") + Update-MpSignature + ignore_errors: yes + +# Find KB Article numbers: +# - WS 2019 https://support.microsoft.com/en-us/help/4464619 +# - WS 2022 https://support.microsoft.com/topic/windows-server-2022-update-history-e1caa597-00c5-4ab9-9f3e-8212fe80b2ee +# Task to install specific updates by KB. All categories are specified as the module +# won't install the update unless the category matches. Setting windows_updates_kbs_numbers to [] +# will skip this task. +- name: Install Windows updates based on KB numbers + win_updates: + whitelist: "{{ windows_updates_kbs_numbers }}" + reboot: yes + category_names: + - Application + - Connectors + - CriticalUpdates + - DefinitionUpdates + - DeveloperKits + - Drivers + - FeaturePacks + - Guidance + - SecurityUpdates + - ServicePacks + - Tools + - UpdateRollups + - Updates + when: windows_updates_kbs_numbers|length > 0 + +# Task to install any outstanding updates that belong to specific categories. Setting +# windows_updates_category_names to [] will skip this task. +- name: Install Windows updates based on Categories + win_updates: + category_names: "{{ windows_updates_category_names }}" + reboot: yes + when: windows_updates_category_names|length > 0 + +- import_tasks: ssh-feature.yml + when: ssh_source_url == "" + +- import_tasks: ssh-archive.yml + when: ssh_source_url != "" + +- name: Set default SSH shell to Powershell + win_regedit: + path: HKLM:\SOFTWARE\OpenSSH + state: present + name: DefaultShell + data: '{{ systemdrive.stdout | trim }}\Windows\System32\WindowsPowerShell\v1.0\powershell.exe' + type: string + +- name: Create SSH program data folder + win_shell: If (-Not (Test-Path -Path "$env:ProgramData\ssh")) { mkdir "$env:ProgramData\ssh" } + +- name: Enable ssh login without a password + win_shell: Add-Content -Path "$env:ProgramData\ssh\sshd_config" -Value "PasswordAuthentication no`nPubkeyAuthentication yes" + +- name: Set SSH service startup mode to auto and ensure it is started + win_service: + name: sshd + start_mode: auto + state: started + +# Apply HNS flags for fixes that need to be enabled via Registry +# these eventually get turned on automatically and can be removed in future releases +- name: Apply HNS control Flags 0x40 and 0x10 in 2022-11B patches + win_regedit: + path: HKLM:\SYSTEM\CurrentControlSet\Services\hns\State + state: present + name: HNSControlFlag + data: 0x50 + type: dword + when: distribution_version == "2019" + +- name: Apply WCIFS fix + win_regedit: + path: HKLM:\SYSTEM\CurrentControlSet\Services\wcifs + state: present + name: WcifsSOPCountDisabled + data: 0 + type: dword + when: distribution_version == "2019" + +- name: Expand dynamic port range to 34000-65535 to avoid port exhaustion + win_shell: netsh int ipv4 set dynamicportrange tcp 34000 31536 + +- name: Add required Windows Features + win_feature: + name: + - Containers + - Hyper-V-PowerShell + state: present + register: win_feature + +# Due to a limitation in some CNI plugins the Hyper-V role needs to be installed in order +# to use the VMSwitch Powershell Cmdlets. +# An issue has been logged to have the networking components to be split out but until +# that is complete, environments that do not support running a hypervisor require the +# below which skips the CPU check for Hypervisor support and still installs the VMSwitch Cmlets +# when disable_hypervisor is set to true +# https://github.com/microsoft/Windows-Containers/issues/80 + +- name: Add Hyper-V + win_shell: | + dism /online /enable-feature /featurename:Microsoft-Hyper-V /all /NoRestart + register: hyperv_installed + failed_when: hyperv_installed.rc != 1 and hyperv_installed.rc != 0 + +- name: Disable Hypervisor + win_shell: | + dism /online /disable-feature /featurename:Microsoft-Hyper-V-Online /NoRestart + when: (disable_hypervisor | default(false) | bool) + register: hypervisor_disabled + failed_when: hypervisor_disabled.rc != 1 and hypervisor_disabled.rc != 0 + +- name: Reboot + win_reboot: diff --git a/image-builder/windows/ansible/roles/systemprep/tasks/ssh-archive.yml b/image-builder/windows/ansible/roles/systemprep/tasks/ssh-archive.yml new file mode 100644 index 000000000..a24ba332f --- /dev/null +++ b/image-builder/windows/ansible/roles/systemprep/tasks/ssh-archive.yml @@ -0,0 +1,73 @@ +# Copyright 2021 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +- name: Create OpenSSH directory structure + win_file: + path: "{{ item }}" + state: directory + loop: + - '{{ programfiles.stdout | trim }}\OpenSSH' + - '{{ alluserprofile.stdout | trim }}\ssh' + +# Win32-OpenSSH requires SYSTEM and Administrator groups having Write +# permissions on directory 'C:\Program Files\OpenSSH', authenticated +# users having only Read and Execute permissions on it, see: +# https://github.com/PowerShell/Win32-OpenSSH/wiki/Install-Win32-OpenSSH +# +# "Make sure binary location has the Write permissions to just to SYSTEM, +# Administrator groups. Authenticated users should and only have Read and +# Execute." +# +# Folder 'C:\Program Files\OpenSSH' inherits users and permissions from its +# parent folder when it is created, by default, SYSTEM and Administrator +# already have Write permissions on it, the only exception is the inherited +# user BUILTIN\Users has ReadAndExecute permission but only authenticated +# users are allowed to have such permission, this prevent us from connecting +# to the sshd server, just remove it. +- name: Disable inheritance of OpenSSH directory + win_acl_inheritance: + path: '{{ programfiles.stdout | trim }}\OpenSSH' + state: absent + reorganize: yes +- name: Remove permission for Users + win_acl: + path: '{{ programfiles.stdout | trim }}\OpenSSH' + user: BUILTIN\Users + rights: ReadAndExecute,Synchronize + type: allow + state: absent + inherit: 'None' + propagation: 'None' + +- name: Download OpenSSH Archive + win_get_url: + url: '{{ ssh_source_url }}' + dest: '{{ tempdir.stdout | trim }}\OpenSSH.zip' + register: ssh + retries: 5 + delay: 3 + until: ssh is not failed + +- name: Unzip OpenSSH Archive + win_unzip: + src: '{{ ssh.dest }}' + dest: '{{ tempdir.stdout | trim }}' + recurse: no + delete_archive: yes + +- name: Install OpenSSH + win_shell: | + Get-ChildItem -Path "{{ tempdir.stdout | trim }}\OpenSSH-Win64\*" -Recurse | Move-Item -Destination "{{ programfiles.stdout | trim }}\OpenSSH" + Get-ChildItem -Path "{{ programfiles.stdout | trim }}\OpenSSH" | Unblock-File + & 'C:\Program Files\OpenSSH\install-sshd.ps1' diff --git a/image-builder/windows/ansible/roles/systemprep/tasks/ssh-feature.yml b/image-builder/windows/ansible/roles/systemprep/tasks/ssh-feature.yml new file mode 100644 index 000000000..0e5de18e2 --- /dev/null +++ b/image-builder/windows/ansible/roles/systemprep/tasks/ssh-feature.yml @@ -0,0 +1,21 @@ +# Copyright 2021 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Requires admin rights to install +# https://docs.ansible.com/ansible/latest/user_guide/become.html#become-and-windows +- name: Install OpenSSH + win_shell: Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0 + become: yes + become_method: runas + become_user: SYSTEM diff --git a/image-builder/windows/hack/ensure-ansible-windows.sh b/image-builder/windows/hack/ensure-ansible-windows.sh new file mode 100755 index 000000000..4f0241aad --- /dev/null +++ b/image-builder/windows/hack/ensure-ansible-windows.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash + +# Copyright 2020 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -o errexit +set -o nounset +set -o pipefail + +[[ -n ${DEBUG:-} ]] && set -o xtrace + +source hack/utils.sh + +_version="0.4.2" + +if [[ ${HOSTOS} == "darwin" ]]; then + echo "IMPORTANT: Winrm connection plugin for Ansible on MacOS causes connection issues." + echo "See https://docs.ansible.com/ansible/latest/user_guide/windows_winrm.html#what-is-winrm for more details." + echo "To fix the issue provide the enviroment variable 'no_proxy=*'" + echo "Example call to build Windows images on MacOS: 'no_proxy=* make build-'" +fi + +# Change directories to the parent directory of the one in which this +# script is located. +cd "$(dirname "${BASH_SOURCE[0]}")/.." + +# Disable pip's version check and root user warning +export PIP_DISABLE_PIP_VERSION_CHECK=1 PIP_ROOT_USER_ACTION=ignore + +if pip3 show pywinrm >/dev/null 2>&1; then exit 0; fi + +ensure_py3 +pip3 install --user "pywinrm==${_version}" +if ! pip3 show pywinrm ; then exit 1; fi diff --git a/image-builder/windows/hack/ensure-ansible.sh b/image-builder/windows/hack/ensure-ansible.sh new file mode 100755 index 000000000..70f5b7a37 --- /dev/null +++ b/image-builder/windows/hack/ensure-ansible.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash + +# Copyright 2020 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -o errexit +set -o nounset +set -o pipefail + +[[ -n ${DEBUG:-} ]] && set -o xtrace + +source hack/utils.sh + +_version="2.11.5" + +# Change directories to the parent directory of the one in which this +# script is located. +cd "$(dirname "${BASH_SOURCE[0]}")/.." + +# Disable pip's version check and root user warning +export PIP_DISABLE_PIP_VERSION_CHECK=1 PIP_ROOT_USER_ACTION=ignore + +if ! command -v ansible >/dev/null 2>&1; then + ensure_py3 + pip3 install --user "ansible-core==${_version}" + ensure_py3_bin ansible + ensure_py3_bin ansible-playbook +fi + +ansible-galaxy collection install \ + community.general \ + ansible.posix \ + 'ansible.windows:>=1.7.0' \ + community.windows diff --git a/image-builder/windows/hack/ensure-packer.sh b/image-builder/windows/hack/ensure-packer.sh new file mode 100755 index 000000000..f1c3806ca --- /dev/null +++ b/image-builder/windows/hack/ensure-packer.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash + +# Copyright 2019 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -o errexit +set -o nounset +set -o pipefail + +[[ -n ${DEBUG:-} ]] && set -o xtrace + +_version="1.8.6" + +# Change directories to the parent directory of the one in which this +# script is located. +cd "$(dirname "${BASH_SOURCE[0]}")/.." + +source hack/utils.sh + +if command -v packer >/dev/null 2>&1; then exit 0; fi + +mkdir -p .local/bin && cd .local/bin + +SED="sed" +if command -v gsed >/dev/null; then + SED="gsed" +fi +if ! (${SED} --version 2>&1 | grep -q GNU); then + echo "!!! GNU sed is required. If on OS X, use 'brew install gnu-sed'." >&2 + exit 1 +fi + +_chkfile="packer_${_version}_SHA256SUMS" +_chk_url="https://releases.hashicorp.com/packer/${_version}/${_chkfile}" +_zipfile="packer_${_version}_${HOSTOS}_${HOSTARCH}.zip" +_zip_url="https://releases.hashicorp.com/packer/${_version}/${_zipfile}" +curl -SsLO "${_chk_url}" +curl -SsLO "${_zip_url}" +${SED} -i -n "/${HOSTOS}_${HOSTARCH}/p" "${_chkfile}" +checksum_sha256 "${_chkfile}" +unzip -o "${_zipfile}" +rm -f "${_chkfile}" "${_zipfile}" +echo "'packer' has been installed to $(pwd), make sure this directory is in your \$PATH" diff --git a/image-builder/windows/hack/utils.sh b/image-builder/windows/hack/utils.sh new file mode 100755 index 000000000..cfea5db00 --- /dev/null +++ b/image-builder/windows/hack/utils.sh @@ -0,0 +1,95 @@ +#!/usr/bin/env bash + +# Copyright 2020 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +case "${OSTYPE}" in +linux*) + HOSTOS=linux + ;; +darwin*) + HOSTOS=darwin + ;; +*) + echo "unsupported HOSTOS=${OSTYPE}" 1>&2 + exit 1 + ;; +esac + +_hostarch=$(uname -m) +case "${_hostarch}" in +*64*) + HOSTARCH=amd64 + ;; +*386*) + HOSTARCH=386 + ;; +*686*) + HOSTARCH=386 + ;; +*) + echo "unsupported HOSTARCH=${_hostarch}" 1>&2 + exit 1 + ;; +esac + +checksum_sha256() { + if command -v shasum >/dev/null 2>&1; then + shasum -a 256 -c "${1}" + elif command -v sha256sum >/dev/null 2>&1; then + sha256sum -c "${1}" + else + echo "missing shasum tool" 1>&2 + return 1 + fi +} + +get_shasum() { + local present_shasum='' + if command -v shasum >/dev/null 2>&1; then + present_shasum=$(shasum -a 256 "${1}"| awk -F' ' '{print $1}') + elif command -v sha256sum >/dev/null 2>&1; then + present_shasum=$(sha256sum "${1}" | awk -F' ' '{print $1}') + else + echo "missing shasum tool" 1>&2 + return 1 + fi + echo "$present_shasum" +} + +ensure_py3_bin() { + # If given executable is not available, the user Python bin dir is not in path + # This function assumes the executable to be checked was installed with + # pip3 install --user ... + if ! command -v "${1}" >/dev/null 2>&1; then + echo "User's Python3 binary directory must be in \$PATH" 1>&2 + echo "Location of package is:" 1>&2 + pip3 show --disable-pip-version-check ${2:-$1} | grep "Location" + echo "\$PATH is currently: $PATH" 1>&2 + exit 1 + fi +} + +ensure_py3() { + if ! command -v python3 >/dev/null 2>&1; then + echo "python3 binary must be in \$PATH" 1>&2 + exit 1 + fi + if ! command -v pip3 >/dev/null 2>&1; then + curl -SsL https://bootstrap.pypa.io/get-pip.py -o get-pip.py + python3 get-pip.py --user + rm -f get-pip.py + ensure_py3_bin pip3 + fi +} diff --git a/image-builder/windows/packer/azure/azure-config.json b/image-builder/windows/packer/azure/azure-config.json new file mode 100644 index 000000000..fdbafa2f0 --- /dev/null +++ b/image-builder/windows/packer/azure/azure-config.json @@ -0,0 +1,11 @@ +{ + "azure_location": "{{env `AZURE_LOCATION`}}", + "client_id": "{{env `AZURE_CLIENT_ID`}}", + "client_secret": "{{env `AZURE_CLIENT_SECRET`}}", + "resource_group_name": "{{env `RESOURCE_GROUP_NAME`}}", + "shared_image_gallery_name": "{{env `GALLERY_NAME`}}", + "storage_account_name": "{{env `STORAGE_ACCOUNT_NAME`}}", + "subscription_id": "{{env `AZURE_SUBSCRIPTION_ID`}}", + "tenant_id": "{{env `AZURE_TENANT_ID`}}", + "vm_size": "Standard_B2ms" +} diff --git a/image-builder/windows/packer/azure/packer-windows.json b/image-builder/windows/packer/azure/packer-windows.json new file mode 100644 index 000000000..506244f0a --- /dev/null +++ b/image-builder/windows/packer/azure/packer-windows.json @@ -0,0 +1,132 @@ +{ + "builders": [ + { + "azure_tags": { + "build_date": "{{isotime}}", + "build_timestamp": "{{user `build_timestamp`}}", + "creationTimestamp": "{{isotime \"2006-01-02T15:04:05Z\"}}", + "os_version": "{{user `image_sku`}}" + }, + "client_id": "{{user `client_id`}}", + "client_secret": "{{user `client_secret`}}", + "communicator": "winrm", + "image_offer": "{{user `image_offer` }}", + "image_publisher": "{{user `image_publisher` }}", + "image_sku": "{{user `image_sku`}}", + "image_version": "{{user `image_version`}}", + "managed_image_name": "{{user `image_name`}}-{{user `build_timestamp`}}", + "managed_image_resource_group_name": "{{user `resource_group_name`}}", + "build_resource_group_name": "{{user `resource_group_name`}}", + "build_key_vault_name": "machine-controller", + "name": "{{user `build_name`}}", + "os_disk_size_gb": "{{user `os_disk_size_gb`}}", + "os_type": "Windows", + "private_virtual_network_with_public_ip": "{{user `private_virtual_network_with_public_ip`}}", + "subscription_id": "{{user `subscription_id`}}", + "tenant_id": "{{user `tenant_id`}}", + "type": "azure-arm", + "virtual_network_name": "{{user `virtual_network_name`}}", + "virtual_network_resource_group_name": "{{user `virtual_network_resource_group_name`}}", + "virtual_network_subnet_name": "{{user `virtual_network_subnet_name`}}", + "vm_size": "{{user `vm_size`}}", + "winrm_insecure": true, + "winrm_timeout": "10m", + "winrm_use_ssl": true, + "winrm_username": "packer" + } + ], + "post-processors": [ + { + "custom_data": { + "build_date": "{{isotime}}", + "build_name": "{{user `build_name`}}", + "build_timestamp": "{{user `build_timestamp`}}", + "build_type": "node", + "os_name": "{{user `distro_name`}}", + "resource_group_name": "{{user `resource_group_name`}}", + "storage_account_name": "{{user `storage_account_name`}}" + }, + "output": "{{user `manifest_output`}}", + "strip_path": true, + "type": "manifest" + } + ], + "provisioners": [ + { + "elevated_password": "{{.WinRMPassword}}", + "elevated_user": "packer", + "script": "ansible/ansible_winrm.ps1", + "type": "powershell" + }, + { + "extra_arguments": [ + "-e", + "ansible_winrm_server_cert_validation=ignore ansible_winrm_operation_timeout_sec=120 ansible_winrm_read_timeout_sec=150", + "--extra-vars", + "{{user `ansible_common_vars`}}", + "--extra-vars", + "{{user `azure_extra_vars`}}", + "--extra-vars", + "{{user `ansible_extra_vars`}}", + "--extra-vars", + "{{user `ansible_user_vars`}}" + ], + "max_retries": 5, + "pause_before": "15s", + "playbook_file": "ansible/node_windows.yml", + "type": "ansible", + "use_proxy": false, + "user": "packer" + }, + { + "restart_timeout": "10m", + "type": "windows-restart" + }, + { + "elevated_password": "{{.WinRMPassword}}", + "elevated_user": "packer", + "script": "packer/azure/scripts/sysprep.ps1", + "type": "powershell" + } + ], + "variables": { + "additional_debug_files": null, + "ansible_common_vars": "", + "ansible_extra_vars": "", + "ansible_user_vars": "", + "azure_extra_vars": "wire_server_users={{user `wire_server_users`}}", + "azure_location": null, + "build_name": null, + "build_resource_group_name": "{{ env `BUILD_RESOURCE_GROUP_NAME` }}", + "build_timestamp": "{{timestamp}}", + "client_id": null, + "client_secret": null, + "cloudbase_init_url": "https://github.com/cloudbase/cloudbase-init/releases/download/{{user `cloudbase_init_version`}}/CloudbaseInitSetup_{{user `cloudbase_init_version` | replace_all `.` `_` }}_x64.msi", + "cloudbase_logging_serial_port": "COM2,115200,N,8", + "cloudbase_metadata_services": "cloudbaseinit.metadata.services.azureservice.AzureService", + "cloudbase_metadata_services_unattend": "cloudbaseinit.metadata.services.base.EmptyMetadataService", + "cloudbase_plugins": "cloudbaseinit.plugins.windows.createuser.CreateUserPlugin, cloudbaseinit.plugins.common.setuserpassword.SetUserPasswordPlugin, cloudbaseinit.plugins.windows.extendvolumes.ExtendVolumesPlugin, cloudbaseinit.plugins.common.ephemeraldisk.EphemeralDiskPlugin, cloudbaseinit.plugins.windows.azureguestagent.AzureGuestAgentPlugin, cloudbaseinit.plugins.common.mtu.MTUPlugin, cloudbaseinit.plugins.common.sethostname.SetHostNamePlugin", + "cloudbase_plugins_unattend": "cloudbaseinit.plugins.common.mtu.MTUPlugin", + "community_gallery_image_id": "", + "direct_shared_gallery_image_id": "", + "exclude_from_latest": "false", + "image_offer": "", + "image_publisher": "", + "image_sku": "", + "image_version": "latest", + "manifest_output": "manifest.json", + "netbios_host_name_compatibility": "true", + "os_disk_size_gb": "", + "prepull": null, + "private_virtual_network_with_public_ip": "", + "storage_account_type": "", + "subscription_id": null, + "virtual_network_name": "", + "virtual_network_resource_group_name": "", + "virtual_network_subnet_name": "", + "vm_size": "", + "windows_service_manager": null, + "windows_updates_kbs": null, + "wire_server_users": "" + } +} diff --git a/image-builder/windows/packer/azure/scripts/sysprep.ps1 b/image-builder/windows/packer/azure/scripts/sysprep.ps1 new file mode 100644 index 000000000..a540be4c7 --- /dev/null +++ b/image-builder/windows/packer/azure/scripts/sysprep.ps1 @@ -0,0 +1,46 @@ +# Copyright 2020 The Kubernetes Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Modified from https://docs.microsoft.com/en-us/azure/virtual-machines/linux/image-builder-troubleshoot#sysprep-command-windows +# The Windows Azure Guest Agent is required for sysprep: https://www.packer.io/docs/builders/azure/arm#windows +Write-Output '>>> Waiting for GA Service (RdAgent) to start ...' +while ((Get-Service RdAgent).Status -ne 'Running') { Start-Sleep -s 5 } +Write-Output '>>> Waiting for GA Service (WindowsAzureTelemetryService) to start ...' +while ((Get-Service WindowsAzureTelemetryService) -and ((Get-Service WindowsAzureTelemetryService).Status -ne 'Running')) { Start-Sleep -s 5 } +Write-Output '>>> Waiting for GA Service (WindowsAzureGuestAgent) to start ...' +while ((Get-Service WindowsAzureGuestAgent).Status -ne 'Running') { Start-Sleep -s 5 } +Write-Output '>>> Sysprepping VM ...' +if( Test-Path $Env:SystemRoot\system32\Sysprep\unattend.xml ) { + Remove-Item $Env:SystemRoot\system32\Sysprep\unattend.xml -Force +} + +$unattendedXml = "$ENV:ProgramFiles\Cloudbase Solutions\Cloudbase-Init\conf\Unattend.xml" +$FileExists = Test-Path $unattendedXml +If ($FileExists -eq $True) { + # Use the Cloudbase-init provided unattend file during install + Write-Output "Using cloudbase-init unattend file for sysprep: $unattendedXml" + & $Env:SystemRoot\System32\Sysprep\Sysprep.exe /oobe /generalize /mode:vm /quit /quiet /unattend:$unattendedXml +}else { + & $Env:SystemRoot\System32\Sysprep\Sysprep.exe /oobe /generalize /mode:vm /quit /quiet +} + +# Wait for the image to be reset +while($true) { + $imageState = (Get-ItemProperty HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Setup\State).ImageState + Write-Output $imageState + if ($imageState -eq 'IMAGE_STATE_GENERALIZE_RESEAL_TO_OOBE') { break } + Start-Sleep -s 5 +} + +Write-Output '>>> Sysprep complete ...' diff --git a/image-builder/windows/packer/azure/windows-2019.json b/image-builder/windows/packer/azure/windows-2019.json new file mode 100644 index 000000000..f52d64a4d --- /dev/null +++ b/image-builder/windows/packer/azure/windows-2019.json @@ -0,0 +1,11 @@ +{ + "build_name": "windows-2019", + "distribution": "windows", + "distribution_version": "2019", + "image_name": "{{user `build_name`}}", + "image_offer": "WindowsServer", + "image_publisher": "MicrosoftWindowsServer", + "image_sku": "2019-Datacenter-Core-smalldisk", + "vm_size": "Standard_D4s_v3", + "windows_updates_kbs": "" +} diff --git a/image-builder/windows/packer/azure/windows-2022.json b/image-builder/windows/packer/azure/windows-2022.json new file mode 100644 index 000000000..8f404ee79 --- /dev/null +++ b/image-builder/windows/packer/azure/windows-2022.json @@ -0,0 +1,12 @@ +{ + "build_name": "windows-2022", + "distribution": "windows", + "distribution_version": "2022", + "image_name": "{{user `build_name`}}", + "image_offer": "WindowsServer", + "image_publisher": "MicrosoftWindowsServer", + "image_sku": "2022-Datacenter-Core-smalldisk", + "image_version": "latest", + "vm_size": "Standard_D4s_v3", + "windows_updates_kbs": "" +} diff --git a/image-builder/windows/packer/common/ansible-args-windows.json b/image-builder/windows/packer/common/ansible-args-windows.json new file mode 100644 index 000000000..1ea4a2a45 --- /dev/null +++ b/image-builder/windows/packer/common/ansible-args-windows.json @@ -0,0 +1,3 @@ +{ + "ansible_common_vars": "runtime={{user `runtime`}} docker_ee_version={{user `docker_ee_version`}} containerd_url={{user `containerd_url`}} containerd_sha256={{user `containerd_sha256_windows`}} pause_image={{user `pause_image`}} additional_debug_files=\"{{user `additional_debug_files`}}\" containerd_additional_settings={{user `containerd_additional_settings`}} custom_role_names=\"{{user `custom_role_names`}}\" http_proxy={{user `http_proxy`}} https_proxy={{user `https_proxy`}} no_proxy={{user `no_proxy`}} kubernetes_base_url={{user `kubernetes_base_url`}} kubernetes_semver={{user `kubernetes_semver`}} kubernetes_install_path={{user `kubernetes_install_path`}} cloudbase_init_url=\"{{user `cloudbase_init_url`}}\" cloudbase_plugins=\"{{user `cloudbase_plugins`}}\" cloudbase_metadata_services=\"{{user `cloudbase_metadata_services`}}\" cloudbase_plugins_unattend=\"{{user `cloudbase_plugins_unattend`}}\" cloudbase_metadata_services_unattend=\"{{user `cloudbase_metadata_services_unattend`}}\" prepull={{user `prepull`}} wins_url={{user `wins_url`}} windows_updates_kbs=\"{{user `windows_updates_kbs`}}\" windows_updates_categories=\"{{user `windows_updates_categories`}}\" windows_service_manager={{user `windows_service_manager`}} nssm_url={{user `nssm_url`}} distribution_version={{user `distribution_version`}} netbios_host_name_compatibility={{user `netbios_host_name_compatibility`}} disable_hypervisor={{ user `disable_hypervisor` }} cloudbase_logging_serial_port={{ user `cloudbase_logging_serial_port` }} load_additional_components={{ user `load_additional_components`}} additional_registry_images={{ user `additional_registry_images`}} additional_registry_images_list={{ user `additional_registry_images_list`}} additional_url_images={{ user `additional_url_images`}} additional_url_images_list={{ user `additional_url_images_list`}} additional_executables={{ user `additional_executables`}} additional_executables_list={{ user `additional_executables_list`}} additional_executables_destination_path={{ user `additional_executables_destination_path`}} ssh_source_url={{user `ssh_source_url` }} debug_tools={{user `debug_tools`}}" +} diff --git a/image-builder/windows/packer/common/cloudbase-init.json b/image-builder/windows/packer/common/cloudbase-init.json new file mode 100644 index 000000000..81402494e --- /dev/null +++ b/image-builder/windows/packer/common/cloudbase-init.json @@ -0,0 +1,3 @@ +{ + "cloudbase_init_version": "1.1.4" +} diff --git a/image-builder/windows/packer/common/common.json b/image-builder/windows/packer/common/common.json new file mode 100644 index 000000000..fa985b217 --- /dev/null +++ b/image-builder/windows/packer/common/common.json @@ -0,0 +1,17 @@ +{ + "additional_debug_files": "", + "debug_tools": "true", + "disable_hypervisor": "false", + "http_proxy": "", + "https_proxy": "", + "netbios_host_name_compatibility": "true", + "no_proxy": "", + "nssm_url": "https://upstreamartifacts.azureedge.net/nssm/nssm.exe", + "prepull": "true", + "runtime": "docker-ee", + "ssh_source_url": "", + "windows_service_manager": "nssm", + "windows_updates_categories": "", + "windows_updates_kbs": "", + "wins_version": "0.0.4" +}