From 708cea1dad60d80629cfbe48073161959ca96ee0 Mon Sep 17 00:00:00 2001 From: Marc Olivier Bergeron Date: Sun, 24 May 2026 23:42:59 -0400 Subject: [PATCH] Added Windows common pre/post Ansible roles. Modified a little bit for Linux as well. --- ctf/deploy.py | 42 +++++- ctf/redeploy.py | 16 +++ .../init/.deploy/{ => ansible}/common.yaml | 12 +- .../init/.deploy/ansible/group_vars/all.yml | 3 + .../.deploy/ansible/group_vars/windows.yml | 3 + .../ansible/roles/cleanup/tasks/linux.yml | 131 +++++++++++++++++ .../ansible/roles/cleanup/tasks/main.yml | 12 ++ .../ansible/roles/cleanup/tasks/windows.yml | 37 +++++ .../ansible/roles/nsec/defaults/main.yml | 2 + .../ansible/roles/nsec/tasks/linux.yml | 1 + .../.deploy/ansible/roles/nsec/tasks/main.yml | 16 +++ .../ansible/roles/nsec/tasks/windows.yml | 48 +++++++ .../roles/windows_optimize/defaults/main.yml | 46 ++++++ .../roles/windows_optimize/tasks/iis.yml | 14 ++ .../roles/windows_optimize/tasks/main.yml | 54 +++++++ .../tasks/scheduled_tasks.yml | 33 +++++ .../roles/windows_optimize/tasks/services.yml | 12 ++ .../windows_optimize/tasks/sql_server.yml | 29 ++++ .../windows_optimize/tasks/telemetry.yml | 7 + .../roles/windows_optimize/tasks/updates.yml | 43 ++++++ .../roles/windows_rpc/files/block_rpc.txt | 9 ++ .../ansible/roles/windows_rpc/tasks/main.yml | 13 ++ .../init/.deploy/ansible/vars/windows.yml | 3 + ctf/templates/init/.deploy/cleanup.yaml | 135 ------------------ ctf/templates/new/common/inventory.j2 | 2 + pyproject.toml | 2 +- 26 files changed, 582 insertions(+), 143 deletions(-) rename ctf/templates/init/.deploy/{ => ansible}/common.yaml (52%) create mode 100644 ctf/templates/init/.deploy/ansible/group_vars/all.yml create mode 100644 ctf/templates/init/.deploy/ansible/group_vars/windows.yml create mode 100644 ctf/templates/init/.deploy/ansible/roles/cleanup/tasks/linux.yml create mode 100644 ctf/templates/init/.deploy/ansible/roles/cleanup/tasks/main.yml create mode 100644 ctf/templates/init/.deploy/ansible/roles/cleanup/tasks/windows.yml create mode 100644 ctf/templates/init/.deploy/ansible/roles/nsec/defaults/main.yml create mode 100644 ctf/templates/init/.deploy/ansible/roles/nsec/tasks/linux.yml create mode 100644 ctf/templates/init/.deploy/ansible/roles/nsec/tasks/main.yml create mode 100644 ctf/templates/init/.deploy/ansible/roles/nsec/tasks/windows.yml create mode 100644 ctf/templates/init/.deploy/ansible/roles/windows_optimize/defaults/main.yml create mode 100644 ctf/templates/init/.deploy/ansible/roles/windows_optimize/tasks/iis.yml create mode 100644 ctf/templates/init/.deploy/ansible/roles/windows_optimize/tasks/main.yml create mode 100644 ctf/templates/init/.deploy/ansible/roles/windows_optimize/tasks/scheduled_tasks.yml create mode 100644 ctf/templates/init/.deploy/ansible/roles/windows_optimize/tasks/services.yml create mode 100644 ctf/templates/init/.deploy/ansible/roles/windows_optimize/tasks/sql_server.yml create mode 100644 ctf/templates/init/.deploy/ansible/roles/windows_optimize/tasks/telemetry.yml create mode 100644 ctf/templates/init/.deploy/ansible/roles/windows_optimize/tasks/updates.yml create mode 100644 ctf/templates/init/.deploy/ansible/roles/windows_rpc/files/block_rpc.txt create mode 100644 ctf/templates/init/.deploy/ansible/roles/windows_rpc/tasks/main.yml create mode 100644 ctf/templates/init/.deploy/ansible/vars/windows.yml delete mode 100644 ctf/templates/init/.deploy/cleanup.yaml diff --git a/ctf/deploy.py b/ctf/deploy.py index 58edc97..92cc031 100644 --- a/ctf/deploy.py +++ b/ctf/deploy.py @@ -82,6 +82,20 @@ def deploy( help="Skip build container. (Use this only if you already have the necessary locally for the deploy.yaml to work!)", ), ] = False, + skip_pre_common: Annotated[ + bool, + typer.Option( + "--skip-pre-common", + help="Skip pre-common deployment Ansible script. Useful for Windows VM that crashes all the time. (Use this only if you already ran the pre-common once)", + ), + ] = False, + skip_post_common: Annotated[ + bool, + typer.Option( + "--skip-post-common", + help="Skip post-common deployment Ansible script. Useful for Windows VM. DO NOT USE IN PRODUCTION.", + ), + ] = False, exclude_tracks: Annotated[ list[str], typer.Option( @@ -240,6 +254,8 @@ def deploy( path=path, vm_remote=vm_remote, vm_project=vm_project, + skip_pre_common=skip_pre_common, + skip_post_common=skip_post_common, ) track.already_deployed = True @@ -467,6 +483,8 @@ def run_ansible_playbook( vm_remote: str | None = None, vm_project: str | None = None, execute_common: bool = True, + skip_pre_common: bool = False, + skip_post_common: bool = False, ) -> None: extra_args = [] if STATE["verbose"]: @@ -484,11 +502,11 @@ def run_ansible_playbook( if production: extra_args += ["-e", "nsec_production=true"] - if execute_common: - LOG.info(msg=f"Running common yaml with ansible for track {track}...") + if not skip_pre_common and execute_common: + LOG.info(msg=f"Running pre-common.yaml with ansible for track {track}...") ansible_args = [ "ansible-playbook", - os.path.join("..", "..", "..", ".deploy", "common.yaml"), + os.path.join("..", "..", "..", ".deploy", "ansible", "common.yaml"), "-i", "inventory", ] + extra_args @@ -507,6 +525,24 @@ def run_ansible_playbook( ] + extra_args subprocess.run(args=ansible_args, cwd=path, check=True) + if not skip_post_common and execute_common: + LOG.info(msg=f"Running post-common.yaml with ansible for track {track}...") + ansible_args = ( + [ + "ansible-playbook", + os.path.join("..", "..", "..", ".deploy", "ansible", "common.yaml"), + "-i", + "inventory", + ] + + extra_args + + ["-e", "nsec_post_deployment=true"] + ) + subprocess.run( + args=ansible_args, + cwd=path, + check=True, + ) + artifacts_path = os.path.join(path, "artifacts") if os.path.exists(path=artifacts_path): shutil.rmtree(artifacts_path) diff --git a/ctf/redeploy.py b/ctf/redeploy.py index 88dda96..3784641 100644 --- a/ctf/redeploy.py +++ b/ctf/redeploy.py @@ -64,6 +64,20 @@ def redeploy( help="Skip build container. (Use this only if you already have the necessary locally for the deploy.yaml to work!)", ), ] = False, + skip_pre_common: Annotated[ + bool, + typer.Option( + "--skip-pre-common", + help="Skip pre-common deployment Ansible script. Useful for Windows VM that crashes all the time. (Use this only if you already ran the pre-common once)", + ), + ] = False, + skip_post_common: Annotated[ + bool, + typer.Option( + "--skip-post-common", + help="Skip post-common deployment Ansible script. Useful for Windows VM. DO NOT USE IN PRODUCTION.", + ), + ] = False, exclude_tracks: Annotated[ list[str], typer.Option( @@ -90,5 +104,7 @@ def redeploy( keep_already_deployed=True, force=force, skip_build=skip_build, + skip_pre_common=skip_pre_common, + skip_post_common=skip_post_common, exclude_tracks=exclude_tracks, ) diff --git a/ctf/templates/init/.deploy/common.yaml b/ctf/templates/init/.deploy/ansible/common.yaml similarity index 52% rename from ctf/templates/init/.deploy/common.yaml rename to ctf/templates/init/.deploy/ansible/common.yaml index 26f231a..94693fa 100644 --- a/ctf/templates/init/.deploy/common.yaml +++ b/ctf/templates/init/.deploy/ansible/common.yaml @@ -1,14 +1,18 @@ -- name: Pre-deployment Common +- name: Pre-deployment - install Python on Linux OS hosts: all,!build,!windows order: shuffle gather_facts: false any_errors_fatal: true - tasks: - name: Distro update and Python3 install ansible.builtin.raw: | apt update && apt upgrade -y && apt install -y python3 changed_when: true + when: not (nsec_post_deployment | bool) -- name: Importing cleanup.yaml Playbook - ansible.builtin.import_playbook: cleanup.yaml +- name: Running common deployment roles for Linux and Windows + hosts: all,!build + order: shuffle + any_errors_fatal: true + roles: + - nsec diff --git a/ctf/templates/init/.deploy/ansible/group_vars/all.yml b/ctf/templates/init/.deploy/ansible/group_vars/all.yml new file mode 100644 index 0000000..14a7608 --- /dev/null +++ b/ctf/templates/init/.deploy/ansible/group_vars/all.yml @@ -0,0 +1,3 @@ +--- +nsec_post_deployment: false +nsec_production: false diff --git a/ctf/templates/init/.deploy/ansible/group_vars/windows.yml b/ctf/templates/init/.deploy/ansible/group_vars/windows.yml new file mode 100644 index 0000000..a087a65 --- /dev/null +++ b/ctf/templates/init/.deploy/ansible/group_vars/windows.yml @@ -0,0 +1,3 @@ +--- +allow_coercion: false +local_admin_password: CHANGE_ME diff --git a/ctf/templates/init/.deploy/ansible/roles/cleanup/tasks/linux.yml b/ctf/templates/init/.deploy/ansible/roles/cleanup/tasks/linux.yml new file mode 100644 index 0000000..5a9c092 --- /dev/null +++ b/ctf/templates/init/.deploy/ansible/roles/cleanup/tasks/linux.yml @@ -0,0 +1,131 @@ +--- +- name: Make network configuration static + ansible.builtin.shell: | + [ ! -e /run/systemd/resolve/resolv.conf ] && exit 0 + rm -f /etc/resolv.conf || true + cat /run/systemd/resolve/resolv.conf > /etc/resolv.conf + when: (nsec_production | bool) and not (nsec_post_deployment | bool) + changed_when: true + +- name: Mask most systemd units + ansible.builtin.shell: | + for i in \ + apt-daily-upgrade.service \ + apt-daily-upgrade.timer \ + apt-daily.service \ + apt-daily.timer \ + console-getty.service \ + console-setup.service \ + dmesg.service \ + dpkg-db-backup.service \ + dpkg-db-backup.timer \ + e2scrub_all.service \ + e2scrub_all.timer \ + e2scrub_reap.service \ + emergency.service \ + fstrim.service \ + fstrim.timer \ + getty-static.service \ + getty@tty1.service \ + initrd-cleanup.service \ + initrd-parse-etc.service \ + initrd-switch-root.service \ + initrd-udevadm-cleanup-db.service \ + keyboard-setup.service \ + kmod-static-nodes.service \ + ldconfig.service \ + logrotate.service \ + logrotate.timer \ + modprobe@configfs.service \ + modprobe@dm_mod.service \ + modprobe@drm.service \ + modprobe@fuse.service \ + modprobe@loop.service \ + motd-news.service \ + motd-news.timer \ + netplan-ovs-cleanup.service \ + rescue.service \ + rsyslog.service \ + setvtrgb.service \ + syslog.socket \ + systemd-ask-password-console.service \ + systemd-ask-password-wall.service \ + systemd-battery-check.service \ + systemd-bsod.service \ + systemd-confext.service \ + systemd-fsck-root.service \ + systemd-fsckd.service \ + systemd-fsckd.socket \ + systemd-hibernate-resume.service \ + systemd-initctl.service \ + systemd-initctl.socket \ + systemd-pcrextend.socket \ + systemd-pcrlock-file-system.service \ + systemd-pcrlock-firmware-code.service \ + systemd-pcrlock-firmware-config.service \ + systemd-pcrlock-machine-id.service \ + systemd-pcrlock-make-policy.service \ + systemd-pcrlock-secureboot-authority.service \ + systemd-pcrlock-secureboot-policy.service \ + systemd-pcrmachine.service \ + systemd-pcrphase-initrd.service \ + systemd-pcrphase-sysinit.service \ + systemd-pcrphase.service \ + systemd-random-seed.service \ + systemd-repart.service \ + systemd-soft-reboot.service \ + systemd-sysctl.service \ + systemd-sysext.service \ + systemd-sysext.socket \ + systemd-sysupdate-reboot.service \ + systemd-sysupdate-reboot.timer \ + systemd-sysupdate.service \ + systemd-sysupdate.timer \ + systemd-sysusers.service \ + systemd-timesyncd.service \ + systemd-tpm2-setup-early.service \ + systemd-tpm2-setup.service \ + systemd-update-done.service \ + systemd-update-utmp-runlevel.service \ + systemd-update-utmp.service \ + ua-reboot-cmds.service \ + ua-timer.service \ + ua-timer.timer \ + ubuntu-advantage.service; do + ln -s /dev/null /etc/systemd/system/${i} || true + done + changed_when: true + when: not (nsec_post_deployment | bool) + +- name: Mask network systemd units + ansible.builtin.shell: | + for i in \ + systemd-journal-catalog-update.service \ + systemd-journal-flush.service \ + systemd-journald-dev-log.socket \ + systemd-journald.service \ + systemd-journald.socket \ + networkd-dispatcher.service \ + systemd-network-generator.service \ + systemd-networkd-wait-online.service \ + systemd-networkd.service \ + systemd-networkd.socket \ + systemd-resolved.service \ + systemd-udev-settle.service \ + systemd-udev-trigger.service \ + systemd-udevd-control.socket \ + systemd-udevd-kernel.socket \ + systemd-udevd.service; do + ln -s /dev/null /etc/systemd/system/${i} || true + done + when: (nsec_production | bool) and not (nsec_post_deployment | bool) + changed_when: true + +- name: Remove all cron jobs + ansible.builtin.shell: | + rm -f /etc/cron.*/* || true + changed_when: true + when: not (nsec_post_deployment | bool) + +- name: Reboot the instance + ansible.builtin.reboot: diff --git a/ctf/templates/init/.deploy/ansible/roles/cleanup/tasks/main.yml b/ctf/templates/init/.deploy/ansible/roles/cleanup/tasks/main.yml new file mode 100644 index 0000000..3018dfa --- /dev/null +++ b/ctf/templates/init/.deploy/ansible/roles/cleanup/tasks/main.yml @@ -0,0 +1,12 @@ +--- +- name: Include cleanup Linux role + ansible.builtin.include_role: + name: cleanup + tasks_from: linux.yml + when: '"windows" not in group_names and "linux-incus-vm" not in group_names' + +- name: Include cleanup Windows role + ansible.builtin.include_role: + name: cleanup + tasks_from: windows.yml + when: '"windows" in group_names' diff --git a/ctf/templates/init/.deploy/ansible/roles/cleanup/tasks/windows.yml b/ctf/templates/init/.deploy/ansible/roles/cleanup/tasks/windows.yml new file mode 100644 index 0000000..26f84dd --- /dev/null +++ b/ctf/templates/init/.deploy/ansible/roles/cleanup/tasks/windows.yml @@ -0,0 +1,37 @@ +--- +- name: Delete the unattend file + ansible.windows.win_file: + name: C:\windows\system32\sysprep\unattend.xml + state: absent + when: not (nsec_post_deployment | bool) + +- name: Delete the unattend panther file + ansible.windows.win_file: + name: C:\windows\panther\unattend.xml + state: absent + when: not (nsec_post_deployment | bool) + +- name: Disable windows defender sending sample + ansible.windows.win_shell: Set-MpPreference -SubmitSamplesConsent 2 + when: not (nsec_post_deployment | bool) + +- name: Disable Defender via registry + ansible.windows.win_regedit: + path: HKLM:\SOFTWARE\Policies\Microsoft\Windows Defender + name: DisableAntiSpyware + data: 1 + type: dword + when: not (nsec_post_deployment | bool) + +- name: Change the local Administrator password + ansible.windows.win_command: | + powershell -Command + net user Administrator {{ local_admin_password }}; + Set-LocalUser -Name 'Administrator' -PasswordNeverExpires $true; + when: nsec_post_deployment | bool + +# If using incus-windows, there is a default `admin` user that is created. +- name: Remove the local admin user + ansible.windows.win_command: powershell -Command "net user /delete admin" + ignore_errors: true + when: nsec_post_deployment | bool diff --git a/ctf/templates/init/.deploy/ansible/roles/nsec/defaults/main.yml b/ctf/templates/init/.deploy/ansible/roles/nsec/defaults/main.yml new file mode 100644 index 0000000..c8633b3 --- /dev/null +++ b/ctf/templates/init/.deploy/ansible/roles/nsec/defaults/main.yml @@ -0,0 +1,2 @@ +--- +nsec_post_deployment: false diff --git a/ctf/templates/init/.deploy/ansible/roles/nsec/tasks/linux.yml b/ctf/templates/init/.deploy/ansible/roles/nsec/tasks/linux.yml new file mode 100644 index 0000000..ed97d53 --- /dev/null +++ b/ctf/templates/init/.deploy/ansible/roles/nsec/tasks/linux.yml @@ -0,0 +1 @@ +--- diff --git a/ctf/templates/init/.deploy/ansible/roles/nsec/tasks/main.yml b/ctf/templates/init/.deploy/ansible/roles/nsec/tasks/main.yml new file mode 100644 index 0000000..d5c1e26 --- /dev/null +++ b/ctf/templates/init/.deploy/ansible/roles/nsec/tasks/main.yml @@ -0,0 +1,16 @@ +--- +- name: Include NSEC Linux role + ansible.builtin.include_role: + name: nsec + tasks_from: linux.yml + when: '"windows" not in group_names' + +- name: Include NSEC Windows role + ansible.builtin.include_role: + name: nsec + tasks_from: windows.yml + when: '"windows" in group_names' + +- name: Importing cleanup role + ansible.builtin.include_role: + name: cleanup diff --git a/ctf/templates/init/.deploy/ansible/roles/nsec/tasks/windows.yml b/ctf/templates/init/.deploy/ansible/roles/nsec/tasks/windows.yml new file mode 100644 index 0000000..712902c --- /dev/null +++ b/ctf/templates/init/.deploy/ansible/roles/nsec/tasks/windows.yml @@ -0,0 +1,48 @@ +--- +# Incus exec/WebSocket to Windows VMs can flake; retry transient API errors. +- name: Disable IPv6 temporary addresses + ansible.windows.win_powershell: + script: Set-NetIPv6Protocol -UseTemporaryAddresses Disabled + register: nsec_ipv6_temp + until: nsec_ipv6_temp is success + retries: 8 + delay: 15 + when: not (nsec_post_deployment | bool) + +- name: Disable IPv6 randomize identifiers + ansible.windows.win_powershell: + script: Set-NetIPv6Protocol -RandomizeIdentifiers Disabled + register: nsec_ipv6_rand + until: nsec_ipv6_rand is success + retries: 8 + delay: 15 + when: not (nsec_post_deployment | bool) + +- name: Disable IPv4 protocol binding + ansible.windows.win_powershell: + script: | + $adapters = @(Get-NetAdapter | Where-Object { $_.Status -eq 'Up' } | Sort-Object InterfaceIndex) + if ($adapters.Count -eq 0) { throw 'No Up network adapter found for IPv4 binding.' } + $alias = ($adapters | Where-Object { $_.HardwareInterface } | Select-Object -First 1).Name + if (-not $alias) { $alias = $adapters[0].Name } + Disable-NetAdapterBinding -Name $alias -ComponentID ms_tcpip + register: ipv4_disabled + until: ipv4_disabled is success + retries: 8 + delay: 15 + when: not (nsec_post_deployment | bool) + +- name: Display result + ansible.builtin.debug: + var: ipv4_disabled.output + when: not (nsec_post_deployment | bool) + +- name: Include Windows optimization role + ansible.builtin.include_role: + name: windows_optimize + when: not (nsec_post_deployment | bool) + +- name: Include Windows RPC hardening role + ansible.builtin.include_role: + name: windows_rpc + when: not (nsec_post_deployment | bool) and not allow_coercion diff --git a/ctf/templates/init/.deploy/ansible/roles/windows_optimize/defaults/main.yml b/ctf/templates/init/.deploy/ansible/roles/windows_optimize/defaults/main.yml new file mode 100644 index 0000000..c21b0cc --- /dev/null +++ b/ctf/templates/init/.deploy/ansible/roles/windows_optimize/defaults/main.yml @@ -0,0 +1,46 @@ +--- +# Disable Services +windows_optimize_disable_services: + - "diagnosticshub.standardcollector.service" # Microsoft (R) Diagnostics Hub Standard Collector Service + - "DiagTrack" # Diagnostics Tracking Service + - "dmwappushservice" # WAP Push Message Routing Service + - "HomeGroupListener" # HomeGroup Listener + - "HomeGroupProvider" # HomeGroup Provider + - "lfsvc" # Geolocation Service + - "MapsBroker" # Downloaded Maps Manager + - "SharedAccess" # Internet Connection Sharing (ICS) + - "spooler" # Print Spooler + - "TrkWks" # Distributed Link Tracking Client + - "WbioSrvc" # Windows Biometric Service + - "WSearch" # Windows Indexing + - "WMPNetworkSvc" # Windows Media Player Network Sharing Service + - "XblAuthManager" # Xbox Live Auth Manager + - "XblGameSave" # Xbox Live Game Save Service + - "XboxNetApiSvc" # Xbox Live Networking Service + - "XboxGipSvc" + - "SysMain" # Superfetch + - "WerSvc" # Error reporting + - "BITS" # Background transfers (OK for AD/DC/CA) + - "wlidsvc" # Live ID auth + - "RemoteRegistry" + - "RetailDemo" + - "TabletInputService" + - "CDPSvc" # Connected device platform + - "CDPUserSvc" + - "DusmSvc" # Data usage + - "Fax" + - "PrintNotify" + - "WalletService" + - "wisvc" # Windows Insider + - "Themes" + - "SSDPSRV" + - "upnphost" + - "WpnService" # Push notifications + - "WpnUserService" + - "SEMgrSvc" + - "PimIndexMaintenanceSvc" + - "WdiServiceHost" + - "WdiSystemHost" + - "DPS" # Diagnostic policy + - "Wecsvc" # Event collector + - "PerfHost" diff --git a/ctf/templates/init/.deploy/ansible/roles/windows_optimize/tasks/iis.yml b/ctf/templates/init/.deploy/ansible/roles/windows_optimize/tasks/iis.yml new file mode 100644 index 0000000..5171a58 --- /dev/null +++ b/ctf/templates/init/.deploy/ansible/roles/windows_optimize/tasks/iis.yml @@ -0,0 +1,14 @@ +--- +- name: Remove unnecessary IIS modules only if IIS is installed + ansible.windows.win_feature: + name: "{{ item }}" + state: absent + loop: + - Web-DAV-Publishing + - Web-WebSockets + - Web-Windows-Auth + - Web-Digest-Auth + - Web-IP-Security + - Web-Url-Auth + - Web-Filtering + - Web-Mgmt-Tools diff --git a/ctf/templates/init/.deploy/ansible/roles/windows_optimize/tasks/main.yml b/ctf/templates/init/.deploy/ansible/roles/windows_optimize/tasks/main.yml new file mode 100644 index 0000000..4395d55 --- /dev/null +++ b/ctf/templates/init/.deploy/ansible/roles/windows_optimize/tasks/main.yml @@ -0,0 +1,54 @@ +--- +- name: Disable telemetry + ansible.builtin.include_tasks: telemetry.yml + +- name: Disable auto updates + ansible.builtin.include_tasks: updates.yml + +- name: Disable useless scheduled tasks + ansible.builtin.include_tasks: scheduled_tasks.yml + +- name: Optimize IIS + ansible.builtin.include_tasks: iis.yml + +- name: Optimize SQL Server + ansible.builtin.include_tasks: sql_server.yml + +# We need to call this role in a loop externally as otherwise it will fail for +# any services that happen not to exist on the target system +- name: Disable unnecessary services + ansible.builtin.include_tasks: services.yml + vars: + service: "{{ item }}" + with_items: "{{ windows_optimize_disable_services }}" + +# Configure registry to disable more things +- name: Disable Windows Tips / Suggestions / Consumer Features + ansible.windows.win_regedit: + path: HKLM:\SOFTWARE\Policies\Microsoft\Windows\CloudContent + name: "{{ item.name }}" + data: "{{ item.data }}" + type: dword + loop: + - { name: DisableWindowsConsumerFeatures, data: 1 } + - { name: DisableSoftLanding, data: 1 } + +- name: Disable Superfetch/Page Combining/Memory Compression + ansible.windows.win_regedit: + path: HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management + name: "{{ item.name }}" + data: "{{ item.data }}" + type: dword + loop: + - { name: DisablePagingExecutive, data: 1 } + - { name: DisablePrefetcher, data: 1 } + - { name: DisableSuperfetch, data: 1 } + - { name: FeatureSettingsOverride, data: 3 } + - { name: FeatureSettingsOverrideMask, data: 3 } + +- name: Disable indexing + ansible.windows.win_regedit: + path: HKLM:\SOFTWARE\Microsoft\Windows Search + name: SetupCompletedSuccessfully + data: 0 + type: dword diff --git a/ctf/templates/init/.deploy/ansible/roles/windows_optimize/tasks/scheduled_tasks.yml b/ctf/templates/init/.deploy/ansible/roles/windows_optimize/tasks/scheduled_tasks.yml new file mode 100644 index 0000000..d807317 --- /dev/null +++ b/ctf/templates/init/.deploy/ansible/roles/windows_optimize/tasks/scheduled_tasks.yml @@ -0,0 +1,33 @@ +--- +- name: Disable scheduled tasks + community.windows.win_scheduled_task: + name: "{{ item.name }}" + path: "{{ item.path }}" + state: absent + # Prevents failure if schedule task is unable to stop + error_action: ignore + loop: + - { + path: '\Microsoft\Windows\Application Experience', + name: "ProgramDataUpdater", + } + - { + path: '\Microsoft\Windows\Application Experience', + name: "StartupAppTask", + } + - { + path: '\Microsoft\Windows\Customer Experience Improvement Program', + name: "KernelCeipTask", + } + - { + path: '\Microsoft\Windows\DiskDiagnostic', + name: "Microsoft-Windows-DiskDiagnosticDataCollector", + } + - { path: '\Microsoft\Windows\Maintenance', name: "WinSAT" } + - { path: '\Microsoft\Windows\NetTrace', name: "GatherNetworkInfo" } + - { + path: '\Microsoft\Windows\Windows Error Reporting', + name: "QueueReporting", + } + - { path: '\Microsoft\Windows\UpdateOrchestrator', name: "Schedule Scan" } + - { path: '\Microsoft\Windows\UpdateOrchestrator', name: "USO_UxBroker" } diff --git a/ctf/templates/init/.deploy/ansible/roles/windows_optimize/tasks/services.yml b/ctf/templates/init/.deploy/ansible/roles/windows_optimize/tasks/services.yml new file mode 100644 index 0000000..0849726 --- /dev/null +++ b/ctf/templates/init/.deploy/ansible/roles/windows_optimize/tasks/services.yml @@ -0,0 +1,12 @@ +--- +- name: Check if {{ service }} is installed + ansible.windows.win_service_info: + name: "{{ service }}" + register: service_info + +- name: Disable {{ service }} service + ansible.windows.win_service: + name: "{{ service }}" + state: stopped + start_mode: disabled + when: service_info.exists diff --git a/ctf/templates/init/.deploy/ansible/roles/windows_optimize/tasks/sql_server.yml b/ctf/templates/init/.deploy/ansible/roles/windows_optimize/tasks/sql_server.yml new file mode 100644 index 0000000..287b64f --- /dev/null +++ b/ctf/templates/init/.deploy/ansible/roles/windows_optimize/tasks/sql_server.yml @@ -0,0 +1,29 @@ +--- +- name: Check if sqlcmd.exe exists + ansible.windows.win_shell: | + if (Get-Command sqlcmd.exe -ErrorAction SilentlyContinue) { Write-Output "yes" } else { Write-Output "no" } + register: sqlcmd_exists + +- name: Get list of SQL Server services + ansible.windows.win_shell: | + Get-Service | Where-Object { $_.Name -like "MSSQL*" } | Select-Object Name | ConvertTo-Json + register: sql_services_raw + when: sqlcmd_exists.stdout == "yes" + +- name: Parse SQL service list + ansible.builtin.set_fact: + sql_services: "{{ sql_services_raw.stdout | from_json }}" + when: sqlcmd_exists.stdout == "yes" + +- name: Cap SQL Server RAM (2048 MB) for all detected instances + ansible.windows.win_shell: | + sqlcmd -S localhost -Q "EXEC sys.sp_configure N'max server memory (MB)', N'2048'; RECONFIGURE WITH OVERRIDE;" + when: > + sqlcmd_exists.stdout == "yes" and + sql_services is defined and + sql_services | length > 0 + +- name: Debug message if SQL Server is not present + ansible.builtin.debug: + msg: "SQL Server not installed — skipping memory cap." + when: sqlcmd_exists.stdout != "yes" or (sql_services is defined and sql_services | length == 0) diff --git a/ctf/templates/init/.deploy/ansible/roles/windows_optimize/tasks/telemetry.yml b/ctf/templates/init/.deploy/ansible/roles/windows_optimize/tasks/telemetry.yml new file mode 100644 index 0000000..8629688 --- /dev/null +++ b/ctf/templates/init/.deploy/ansible/roles/windows_optimize/tasks/telemetry.yml @@ -0,0 +1,7 @@ +--- +- name: Disable Telemetry via group policy + ansible.windows.win_regedit: + path: HKLM:\SOFTWARE\Policies\Microsoft\Windows\DataCollection + name: AllowTelemetry + data: 0 + type: dword diff --git a/ctf/templates/init/.deploy/ansible/roles/windows_optimize/tasks/updates.yml b/ctf/templates/init/.deploy/ansible/roles/windows_optimize/tasks/updates.yml new file mode 100644 index 0000000..0724b7f --- /dev/null +++ b/ctf/templates/init/.deploy/ansible/roles/windows_optimize/tasks/updates.yml @@ -0,0 +1,43 @@ +--- +- name: Disable automatic download and installation of Windows updates + ansible.windows.win_regedit: + path: HKLM:\SOFTWARE\Wow6432Node\Policies\Microsoft\Windows\WindowsUpdate\AU + name: "{{ feature.name }}" + data: "{{ feature.value }}" + type: dword + loop: + - name: NoAutoUpdate + value: 1 + - name: AUOptions + value: 2 + - name: ScheduledInstallDay + value: 0 + - name: ScheduledInstallTime + value: 3 + loop_control: + loop_var: feature + +- name: Disable seeding of updates to other computers + ansible.windows.win_regedit: + path: HKLM:\SOFTWARE\Policies\Microsoft\Windows\DeliveryOptimization + name: DODownloadMode + data: 0 + type: dword + +- name: Disable automatic driver updates + ansible.windows.win_regedit: + path: HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\DriverSearching + name: SearchOrderConfig + data: 0 + type: dword + +- name: Disable Windows Update service + ansible.windows.win_service: + name: wuauserv + state: stopped + start_mode: disabled + +- name: Remove WSUS server settings + ansible.windows.win_regedit: + path: HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate + state: absent diff --git a/ctf/templates/init/.deploy/ansible/roles/windows_rpc/files/block_rpc.txt b/ctf/templates/init/.deploy/ansible/roles/windows_rpc/files/block_rpc.txt new file mode 100644 index 0000000..a5e0b75 --- /dev/null +++ b/ctf/templates/init/.deploy/ansible/roles/windows_rpc/files/block_rpc.txt @@ -0,0 +1,9 @@ +rpc +filter +add rule layer=um actiontype=block +add condition field=if_uuid matchtype=equal data=c681d488-d850-11d0-8c52-00c04fd90f7e +add filter +add rule layer=um actiontype=block +add condition field=if_uuid matchtype=equal data=df1941c5-fe89-4e79-bf10-463657acf44d +add filter +quit diff --git a/ctf/templates/init/.deploy/ansible/roles/windows_rpc/tasks/main.yml b/ctf/templates/init/.deploy/ansible/roles/windows_rpc/tasks/main.yml new file mode 100644 index 0000000..3003135 --- /dev/null +++ b/ctf/templates/init/.deploy/ansible/roles/windows_rpc/tasks/main.yml @@ -0,0 +1,13 @@ +--- +- name: Copy block_rpc.txt to Windows host + ansible.windows.win_copy: + src: "{{ role_path }}/files/block_rpc.txt" + dest: 'C:\block_rpc.txt' + +- name: Execute the PowerShell script using win_shell + ansible.windows.win_shell: netsh -f C:\block_rpc.txt + +- name: Delete the temporary script file + ansible.windows.win_file: + path: C:\block_rpc.txt + state: absent diff --git a/ctf/templates/init/.deploy/ansible/vars/windows.yml b/ctf/templates/init/.deploy/ansible/vars/windows.yml new file mode 100644 index 0000000..a087a65 --- /dev/null +++ b/ctf/templates/init/.deploy/ansible/vars/windows.yml @@ -0,0 +1,3 @@ +--- +allow_coercion: false +local_admin_password: CHANGE_ME diff --git a/ctf/templates/init/.deploy/cleanup.yaml b/ctf/templates/init/.deploy/cleanup.yaml deleted file mode 100644 index c5cf244..0000000 --- a/ctf/templates/init/.deploy/cleanup.yaml +++ /dev/null @@ -1,135 +0,0 @@ -- name: Pre-deployment system cleanup - hosts: all,!build,!windows,!linux-incus-vm - order: shuffle - gather_facts: false - any_errors_fatal: true - - tasks: - - name: Make network configuration static - ansible.builtin.shell: | - [ ! -e /run/systemd/resolve/resolv.conf ] && exit 0 - rm -f /etc/resolv.conf || true - cat /run/systemd/resolve/resolv.conf > /etc/resolv.conf - when: nsec_production | default(False) | bool - changed_when: true - - - name: Mask most systemd units - ansible.builtin.shell: | - for i in \ - apt-daily-upgrade.service \ - apt-daily-upgrade.timer \ - apt-daily.service \ - apt-daily.timer \ - console-getty.service \ - console-setup.service \ - dmesg.service \ - dpkg-db-backup.service \ - dpkg-db-backup.timer \ - e2scrub_all.service \ - e2scrub_all.timer \ - e2scrub_reap.service \ - emergency.service \ - fstrim.service \ - fstrim.timer \ - getty-static.service \ - getty@tty1.service \ - initrd-cleanup.service \ - initrd-parse-etc.service \ - initrd-switch-root.service \ - initrd-udevadm-cleanup-db.service \ - keyboard-setup.service \ - kmod-static-nodes.service \ - ldconfig.service \ - logrotate.service \ - logrotate.timer \ - modprobe@configfs.service \ - modprobe@dm_mod.service \ - modprobe@drm.service \ - modprobe@fuse.service \ - modprobe@loop.service \ - motd-news.service \ - motd-news.timer \ - netplan-ovs-cleanup.service \ - rescue.service \ - rsyslog.service \ - setvtrgb.service \ - syslog.socket \ - systemd-ask-password-console.service \ - systemd-ask-password-wall.service \ - systemd-battery-check.service \ - systemd-bsod.service \ - systemd-confext.service \ - systemd-fsck-root.service \ - systemd-fsckd.service \ - systemd-fsckd.socket \ - systemd-hibernate-resume.service \ - systemd-initctl.service \ - systemd-initctl.socket \ - systemd-pcrextend.socket \ - systemd-pcrlock-file-system.service \ - systemd-pcrlock-firmware-code.service \ - systemd-pcrlock-firmware-config.service \ - systemd-pcrlock-machine-id.service \ - systemd-pcrlock-make-policy.service \ - systemd-pcrlock-secureboot-authority.service \ - systemd-pcrlock-secureboot-policy.service \ - systemd-pcrmachine.service \ - systemd-pcrphase-initrd.service \ - systemd-pcrphase-sysinit.service \ - systemd-pcrphase.service \ - systemd-random-seed.service \ - systemd-repart.service \ - systemd-soft-reboot.service \ - systemd-sysctl.service \ - systemd-sysext.service \ - systemd-sysext.socket \ - systemd-sysupdate-reboot.service \ - systemd-sysupdate-reboot.timer \ - systemd-sysupdate.service \ - systemd-sysupdate.timer \ - systemd-sysusers.service \ - systemd-timesyncd.service \ - systemd-tpm2-setup-early.service \ - systemd-tpm2-setup.service \ - systemd-update-done.service \ - systemd-update-utmp-runlevel.service \ - systemd-update-utmp.service \ - ua-reboot-cmds.service \ - ua-timer.service \ - ua-timer.timer \ - ubuntu-advantage.service; do - ln -s /dev/null /etc/systemd/system/${i} || true - done - changed_when: true - - - name: Mask network systemd units - ansible.builtin.shell: | - for i in \ - systemd-journal-catalog-update.service \ - systemd-journal-flush.service \ - systemd-journald-dev-log.socket \ - systemd-journald.service \ - systemd-journald.socket \ - networkd-dispatcher.service \ - systemd-network-generator.service \ - systemd-networkd-wait-online.service \ - systemd-networkd.service \ - systemd-networkd.socket \ - systemd-resolved.service \ - systemd-udev-settle.service \ - systemd-udev-trigger.service \ - systemd-udevd-control.socket \ - systemd-udevd-kernel.socket \ - systemd-udevd.service; do - ln -s /dev/null /etc/systemd/system/${i} || true - done - when: nsec_production | default(False) | bool - changed_when: true - - - name: Remove all cron jobs - ansible.builtin.shell: | - rm -f /etc/cron.*/* || true - changed_when: true - - - name: Reboot the instance - ansible.builtin.reboot: diff --git a/ctf/templates/new/common/inventory.j2 b/ctf/templates/new/common/inventory.j2 index 07ff8cf..506b5fd 100644 --- a/ctf/templates/new/common/inventory.j2 +++ b/ctf/templates/new/common/inventory.j2 @@ -65,4 +65,6 @@ windows: # For virtual machines, the remote and project changes in production. ansible_incus_remote: "{{ '{{' }} ansible_incus_container_remote if ansible_incus_vm_remote else ansible_incus_container_remote {{ '}}' }}" ansible_incus_project: "{{ '{{' }} ansible_incus_vm_project if ansible_incus_vm_project else ansible_incus_project {{ '}}' }}" + local_admin_password: CHANGE_ME + allow_coercion: false # If the virtual machine requires coercion to work, set this to true or set it to true for the specific virtual machine only. {% endif %} diff --git a/pyproject.toml b/pyproject.toml index ba98238..6049e06 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ dependencies = [ "typer==0.24.1", "pydantic", ] -version = "5.2.0" +version = "5.3.0" classifiers = [ "Programming Language :: Python :: 3", "Operating System :: OS Independent",