Add playbooks/suites/fr2_use_control.yml
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user