Add playbooks/suites/fr2_use_control.yml

This commit is contained in:
2026-07-07 16:57:37 +00:00
parent bde8d5c91a
commit 0335cad80a
+165
View File
@@ -0,0 +1,165 @@
---
# suites/fr2_use_control.yml
#
# FR2 — Use Control (UC)
# IEC 62443-3-3 SL2 requirements:
# SR 2.1 Authorization enforcement (sudo/privilege separation)
# SR 2.4 Audit log integrity
# SR 2.5 Session lock (terminal idle timeout)
# SR 2.8 Auditable events (auditd active)
# SR 2.9 Audit storage capacity
# ── SR 2.1: Sudo privilege separation ───────────────────────────
- block:
- name: "Gather: Check for unrestricted sudo access (NOPASSWD ALL)"
ansible.builtin.shell: |
grep -r 'NOPASSWD.*ALL' /etc/sudoers /etc/sudoers.d/ 2>/dev/null |
grep -v '^\s*#' | grep -v '^$' || true
register: _nopasswd
changed_when: false
- name: "Evaluate: UC-01"
ansible.builtin.set_fact:
test_results: "{{ test_results + [{
'test_id': 'UC-01',
'category': 'FR2 — Use Control',
'requirement': 'SR 2.1 — Authorization Enforcement',
'description': 'No user shall have unrestricted NOPASSWD sudo access to all commands',
'passed': (_nopasswd.stdout | trim | length == 0),
'expected': 'No NOPASSWD ALL entries in sudoers',
'actual': (_nopasswd.stdout | trim | default('None found', true)),
'severity': 'high',
'remediation': 'Restrict sudo rules to specific commands and require authentication'
}] }}"
ignore_errors: yes
- block:
- name: "Gather: Check sudoers file permissions"
ansible.builtin.stat:
path: /etc/sudoers
register: _sudoers_stat
- name: "Evaluate: UC-02"
ansible.builtin.set_fact:
test_results: "{{ test_results + [{
'test_id': 'UC-02',
'category': 'FR2 — Use Control',
'requirement': 'SR 2.1 — Authorization Enforcement',
'description': '/etc/sudoers shall be owned by root:root with mode 0440',
'passed': (
_sudoers_stat.stat.exists and
_sudoers_stat.stat.pw_name == 'root' and
_sudoers_stat.stat.gr_name == 'root' and
_sudoers_stat.stat.mode == '0440'
),
'expected': 'root:root 0440',
'actual': (_sudoers_stat.stat.pw_name + ':' + _sudoers_stat.stat.gr_name + ' ' + _sudoers_stat.stat.mode) if _sudoers_stat.stat.exists else 'FILE NOT FOUND',
'severity': 'critical',
'remediation': 'chown root:root /etc/sudoers && chmod 0440 /etc/sudoers'
}] }}"
ignore_errors: yes
# ── SR 2.5: Session lock / terminal timeout ─────────────────────
- block:
- name: "Gather: Check TMOUT setting in /etc/profile or /etc/bash.bashrc"
ansible.builtin.shell: |
for f in /etc/profile /etc/bash.bashrc /etc/profile.d/*.sh; do
[ -f "$f" ] && grep -h 'TMOUT=' "$f" 2>/dev/null
done | tail -1 | grep -oP 'TMOUT=\K[0-9]+' || echo "NOT SET"
register: _tmout
changed_when: false
- name: "Evaluate: UC-03"
ansible.builtin.set_fact:
test_results: "{{ test_results + [{
'test_id': 'UC-03',
'category': 'FR2 — Use Control',
'requirement': 'SR 2.5 — Session Lock',
'description': 'Interactive shell sessions shall timeout after ≤ 900 seconds of inactivity',
'passed': (
_tmout.stdout | trim | regex_search('^[0-9]+$') and
(_tmout.stdout | trim | int > 0) and
(_tmout.stdout | trim | int <= 900)
),
'expected': 'TMOUT set between 1-900 seconds',
'actual': 'TMOUT=' + (_tmout.stdout | trim),
'severity': 'medium',
'remediation': 'Add "readonly TMOUT=900" to /etc/profile'
}] }}"
ignore_errors: yes
# ── SR 2.4: Audit log integrity (immutability) ──────────────────
- block:
- name: "Gather: Check if auditd immutable mode is configured"
ansible.builtin.shell: |
grep -c '^\s*-e\s*2' /etc/audit/rules.d/*.rules /etc/audit/audit.rules 2>/dev/null || echo "0"
register: _audit_immutable
changed_when: false
- name: "Evaluate: UC-04"
ansible.builtin.set_fact:
test_results: "{{ test_results + [{
'test_id': 'UC-04',
'category': 'FR2 — Use Control',
'requirement': 'SR 2.4 — Audit Log Integrity',
'description': 'Audit configuration shall be immutable (-e 2)',
'passed': (_audit_immutable.stdout | trim | int > 0),
'expected': 'audit.rules contains -e 2',
'actual': ('Immutable rules found: ' + _audit_immutable.stdout) if (_audit_immutable.stdout | trim | int > 0) else 'Immutable flag NOT set',
'severity': 'high',
'remediation': 'Add "-e 2" to /etc/audit/audit.rules (requires reboot)'
}] }}"
ignore_errors: yes
# ── SR 2.8: Auditable events (auditd running) ───────────────────
- block:
- name: "Gather: Check auditd service status"
ansible.builtin.shell: |
systemctl is-active auditd 2>/dev/null || echo "inactive"
register: _auditd_active
changed_when: false
- name: "Evaluate: UC-05"
ansible.builtin.set_fact:
test_results: "{{ test_results + [{
'test_id': 'UC-05',
'category': 'FR2 — Use Control',
'requirement': 'SR 2.8 — Auditable Events',
'description': 'The audit daemon (auditd) shall be running and enabled',
'passed': (_auditd_active.stdout | trim == 'active'),
'expected': 'auditd service is active',
'actual': 'auditd is ' + (_auditd_active.stdout | trim),
'severity': 'high',
'remediation': 'systemctl enable --now auditd'
}] }}"
ignore_errors: yes
- block:
- name: "Gather: Check if critical system calls are audited"
ansible.builtin.shell: |
rules=0
for call in execve execveat mount umount2 creat open openat truncate ftruncate; do
grep -rq "$call" /etc/audit/rules.d/ 2>/dev/null && rules=$((rules+1))
done
echo "$rules"
register: _audit_sc
changed_when: false
- name: "Evaluate: UC-06"
ansible.builtin.set_fact:
test_results: "{{ test_results + [{
'test_id': 'UC-06',
'category': 'FR2 — Use Control',
'requirement': 'SR 2.8 — Auditable Events',
'description': 'Critical system events (exec, mount, file modifications) shall be audited',
'passed': (_audit_sc.stdout | trim | int >= 4),
'expected': '≥ 4 critical syscall types audited',
'actual': (_audit_sc.stdout | trim) + ' of 9 critical syscall types audited',
'severity': 'medium',
'remediation': 'Add audit rules for execve, mount, creat/open/openat, truncate/ftruncate'
}] }}"
ignore_errors: yes