205 lines
9.8 KiB
YAML
205 lines
9.8 KiB
YAML
---
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
# IEC 62443-3-3 SL2 — Windows Server Compliance
|
|
#
|
|
# Target type : Windows Server 2016 / 2019 / 2022
|
|
# Connection : WinRM (HTTP :5985 or HTTPS :5986)
|
|
# Collections : ansible.windows (ships with Ansible)
|
|
# Python pkg : pywinrm (installed in ansible-node image)
|
|
# Privilege : No become required — WinRM user needs local admin rights
|
|
#
|
|
# Inventory group : windows_servers (see assets.yml)
|
|
#
|
|
# Run:
|
|
# ansible-playbook -i assets.yml methodologies/ansible/playbooks/examples/windows_server.yml
|
|
#
|
|
# WinRM quick-enable on target (run as Administrator):
|
|
# winrm quickconfig -q
|
|
# winrm set winrm/config/service/auth '@{Basic="true"}'
|
|
# winrm set winrm/config/service '@{AllowUnencrypted="true"}'
|
|
# # For production: use HTTPS and Kerberos transport instead
|
|
#
|
|
# Vault usage (recommended for passwords):
|
|
# ansible-vault encrypt_string 'MyPassword' --name ansible_password
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
|
|
- name: "IEC 62443-3-3 SL2 — Windows Server Compliance"
|
|
hosts: windows_servers
|
|
gather_facts: yes
|
|
vars:
|
|
report_dir: "../../reports"
|
|
|
|
pre_tasks:
|
|
- name: "Ensure report directory exists"
|
|
ansible.builtin.file:
|
|
path: "{{ report_dir }}"
|
|
state: directory
|
|
mode: "0755"
|
|
delegate_to: localhost
|
|
run_once: true
|
|
|
|
tasks:
|
|
|
|
# ── FR1 · SR 1.5: Minimum password length ─────────────────────────────────
|
|
# Uses win_security_policy — no PowerShell shell-out needed.
|
|
|
|
- block:
|
|
- name: "Gather: Minimum password length (security policy)"
|
|
ansible.windows.win_security_policy:
|
|
section: System Access
|
|
key: MinimumPasswordLength
|
|
register: _min_pw_len
|
|
|
|
- name: "Evaluate: WIN-IAC-01 — Password minimum length ≥ 14"
|
|
ansible.builtin.set_fact:
|
|
test_results: "{{ test_results | default([]) + [{
|
|
'test_id': 'WIN-IAC-01',
|
|
'category': 'FR1 — Identification and Authentication Control',
|
|
'requirement': 'SR 1.5 — Authenticator Strength',
|
|
'description': 'Windows local password policy minimum length shall be ≥ 14 characters',
|
|
'passed': (_min_pw_len.value | int >= 14),
|
|
'expected': 'MinimumPasswordLength ≥ 14',
|
|
'actual': 'MinimumPasswordLength = ' + (_min_pw_len.value | string),
|
|
'severity': 'high',
|
|
'remediation': 'Computer Configuration → Windows Settings → Security Settings → Account Policies → Password Policy → Minimum password length: 14'
|
|
}] }}"
|
|
ignore_errors: yes
|
|
|
|
# ── FR1 · SR 1.11: Account lockout threshold ──────────────────────────────
|
|
|
|
- block:
|
|
- name: "Gather: Account lockout threshold"
|
|
ansible.windows.win_security_policy:
|
|
section: System Access
|
|
key: LockoutBadCount
|
|
register: _lockout_count
|
|
|
|
- name: "Evaluate: WIN-IAC-02 — Account lockout ≤ 5 attempts"
|
|
ansible.builtin.set_fact:
|
|
test_results: "{{ test_results + [{
|
|
'test_id': 'WIN-IAC-02',
|
|
'category': 'FR1 — Identification and Authentication Control',
|
|
'requirement': 'SR 1.11 — Unsuccessful Login Attempts',
|
|
'description': 'Account lockout shall trigger after ≤ 5 failed login attempts',
|
|
'passed': (
|
|
(_lockout_count.value | int > 0) and
|
|
(_lockout_count.value | int <= 5)
|
|
),
|
|
'expected': 'LockoutBadCount between 1 and 5',
|
|
'actual': 'LockoutBadCount = ' + (_lockout_count.value | string),
|
|
'severity': 'high',
|
|
'remediation': 'Set Account lockout threshold to 5 in Local Security Policy'
|
|
}] }}"
|
|
ignore_errors: yes
|
|
|
|
# ── FR2 · SR 2.8: Audit policy — logon events ─────────────────────────────
|
|
# Uses win_audit_policy_system — no auditpol.exe shell-out needed.
|
|
|
|
- block:
|
|
- name: "Gather: Audit policy — Logon subcategory"
|
|
ansible.windows.win_audit_policy_system:
|
|
subcategory: Logon
|
|
register: _audit_logon
|
|
|
|
- name: "Evaluate: WIN-UC-01 — Logon events audited"
|
|
ansible.builtin.set_fact:
|
|
test_results: "{{ test_results + [{
|
|
'test_id': 'WIN-UC-01',
|
|
'category': 'FR2 — Use Control',
|
|
'requirement': 'SR 2.8 — Auditable Events',
|
|
'description': 'Logon/logoff events shall be audited (success and failure)',
|
|
'passed': (_audit_logon.auditing_mode == 'success and failure'),
|
|
'expected': 'Logon: success and failure',
|
|
'actual': 'Logon: ' + _audit_logon.auditing_mode,
|
|
'severity': 'high',
|
|
'remediation': 'auditpol /set /subcategory:"Logon" /success:enable /failure:enable'
|
|
}] }}"
|
|
ignore_errors: yes
|
|
|
|
# ── FR2 · SR 2.8: Audit policy — privilege use ────────────────────────────
|
|
|
|
- block:
|
|
- name: "Gather: Audit policy — Sensitive Privilege Use"
|
|
ansible.windows.win_audit_policy_system:
|
|
subcategory: Sensitive Privilege Use
|
|
register: _audit_privuse
|
|
|
|
- name: "Evaluate: WIN-UC-02 — Privilege use audited"
|
|
ansible.builtin.set_fact:
|
|
test_results: "{{ test_results + [{
|
|
'test_id': 'WIN-UC-02',
|
|
'category': 'FR2 — Use Control',
|
|
'requirement': 'SR 2.8 — Auditable Events',
|
|
'description': 'Sensitive privilege use (SeDebugPrivilege, SeTcbPrivilege, etc.) shall be audited',
|
|
'passed': (_audit_privuse.auditing_mode in ['success and failure', 'failure']),
|
|
'expected': 'Sensitive Privilege Use: failure (at minimum)',
|
|
'actual': 'Sensitive Privilege Use: ' + _audit_privuse.auditing_mode,
|
|
'severity': 'medium',
|
|
'remediation': 'auditpol /set /subcategory:"Sensitive Privilege Use" /failure:enable'
|
|
}] }}"
|
|
ignore_errors: yes
|
|
|
|
# ── FR5 · SR 5.1: Windows Firewall enabled on all profiles ────────────────
|
|
# PowerShell ConvertTo-Json + Ansible from_json filter avoids regex parsing.
|
|
|
|
- block:
|
|
- name: "Gather: Windows Firewall profile states"
|
|
ansible.windows.win_shell: |
|
|
@(Get-NetFirewallProfile | Select-Object Name, Enabled, DefaultInboundAction) |
|
|
ConvertTo-Json -Compress
|
|
register: _fw_profiles
|
|
|
|
- name: "Evaluate: WIN-RDF-01 — Firewall enabled on all profiles"
|
|
ansible.builtin.set_fact:
|
|
_fw_json: "{{ _fw_profiles.stdout | from_json }}"
|
|
|
|
- name: "Evaluate: WIN-RDF-01 — record result"
|
|
ansible.builtin.set_fact:
|
|
test_results: "{{ test_results + [{
|
|
'test_id': 'WIN-RDF-01',
|
|
'category': 'FR5 — Restricted Data Flow',
|
|
'requirement': 'SR 5.1 — Network Segmentation',
|
|
'description': 'Windows Firewall shall be enabled on Domain, Private, and Public profiles',
|
|
'passed': (_fw_json | selectattr('Enabled', 'equalto', true) | list | length == 3),
|
|
'expected': 'All 3 firewall profiles Enabled = True',
|
|
'actual': _fw_json | map(attribute='Name') | zip(_fw_json | map(attribute='Enabled')) | list | string,
|
|
'severity': 'critical',
|
|
'remediation': 'Set-NetFirewallProfile -All -Enabled True'
|
|
}] }}"
|
|
ignore_errors: yes
|
|
|
|
# ── FR5 · SR 5.3: Telnet / FTP / RDP services ─────────────────────────────
|
|
# Uses win_service_info — typed return dict, no regex needed.
|
|
|
|
- block:
|
|
- name: "Gather: Telnet service state"
|
|
ansible.windows.win_service_info:
|
|
name: TlntSvr
|
|
register: _telnet_svc
|
|
|
|
- name: "Evaluate: WIN-RDF-02 — Telnet service disabled"
|
|
ansible.builtin.set_fact:
|
|
test_results: "{{ test_results + [{
|
|
'test_id': 'WIN-RDF-02',
|
|
'category': 'FR5 — Restricted Data Flow',
|
|
'requirement': 'SR 5.3 — Communication Constraints',
|
|
'description': 'The Telnet Server service (TlntSvr) shall be absent or disabled',
|
|
'passed': (
|
|
_telnet_svc.services | length == 0 or
|
|
_telnet_svc.services[0].start_mode == 'disabled'
|
|
),
|
|
'expected': 'TlntSvr: absent or start_mode=disabled',
|
|
'actual': (
|
|
'TlntSvr: state=' + _telnet_svc.services[0].state
|
|
+ ', start_mode=' + _telnet_svc.services[0].start_mode
|
|
) if _telnet_svc.services | length > 0 else 'TlntSvr: not installed',
|
|
'severity': 'critical',
|
|
'remediation': 'Stop-Service TlntSvr; Set-Service TlntSvr -StartupType Disabled'
|
|
}] }}"
|
|
ignore_errors: yes
|
|
|
|
# ── Generate report ────────────────────────────────────────────────────────
|
|
|
|
- name: "Generate compliance report"
|
|
ansible.builtin.include_tasks: ../library/report.yml
|