Add playbooks/suites/fr5_data_flow.yml

This commit is contained in:
2026-07-07 16:57:37 +00:00
parent 0335cad80a
commit 943a21ef12
+119
View File
@@ -0,0 +1,119 @@
---
# suites/fr5_data_flow.yml
#
# FR5 — Restricted Data Flow (RDF)
# IEC 62443-3-3 SL2 requirements:
# SR 5.1 Network segmentation (firewall active, boundary filtering)
# SR 5.2 Zone boundary protection
# SR 5.3 General-purpose communication constraints (unnecessary services)
# ── SR 5.1: Host-based firewall active ──────────────────────────
- block:
- name: "Gather: Check iptables/nftables rules exist"
ansible.builtin.shell: |
if command -v nft >/dev/null 2>&1; then
nft list ruleset 2>/dev/null | grep -c 'accept\|drop\|reject' || echo "0"
elif command -v iptables >/dev/null 2>&1; then
iptables -L -n 2>/dev/null | grep -c 'ACCEPT\|DROP\|REJECT' || echo "0"
else
echo "NO FIREWALL"
fi
register: _fw_rules
changed_when: false
- name: "Evaluate: RDF-01"
ansible.builtin.set_fact:
test_results: "{{ test_results + [{
'test_id': 'RDF-01',
'category': 'FR5 — Restricted Data Flow',
'requirement': 'SR 5.1 — Network Segmentation',
'description': 'A host-based firewall with active rules shall be present',
'passed': (
_fw_rules.stdout | trim != 'NO FIREWALL' and
(_fw_rules.stdout | trim | int > 0)
),
'expected': 'Firewall with active filter rules',
'actual': ('Rules found: ' + (_fw_rules.stdout | trim)) if (_fw_rules.stdout | trim != 'NO FIREWALL' and (_fw_rules.stdout | trim | int > 0)) else (_fw_rules.stdout | trim),
'severity': 'critical',
'remediation': 'Install and configure iptables/nftables with default-deny inbound policy'
}] }}"
ignore_errors: yes
- block:
- name: "Gather: Check default inbound policy"
ansible.builtin.shell: |
if command -v nft >/dev/null 2>&1; then
nft list chain inet filter INPUT 2>/dev/null | grep policy | awk '{print $NF}' || echo "UNKNOWN"
elif command -v iptables >/dev/null 2>&1; then
iptables -L INPUT -n 2>/dev/null | head -1 | awk '{print $4}' | tr -d ')'
else
echo "NO FIREWALL"
fi
register: _default_policy
changed_when: false
- name: "Evaluate: RDF-02"
ansible.builtin.set_fact:
test_results: "{{ test_results + [{
'test_id': 'RDF-02',
'category': 'FR5 — Restricted Data Flow',
'requirement': 'SR 5.1 — Network Segmentation',
'description': 'Default inbound firewall policy shall be DROP',
'passed': (_default_policy.stdout | trim | lower == 'drop'),
'expected': 'Default inbound policy is DROP',
'actual': 'Default policy: ' + (_default_policy.stdout | trim),
'severity': 'high',
'remediation': 'Set default INPUT policy to DROP: iptables -P INPUT DROP'
}] }}"
ignore_errors: yes
# ── SR 5.3: Unnecessary services ────────────────────────────────
- block:
- name: "Gather: Check for unnecessary network services"
ansible.builtin.shell: |
# Services commonly flagged as unnecessary on ICS/OT systems
for svc in telnet.socket rsh.socket rexec.socket rlogin.socket \
ftp.service vsftpd.service xinetd.service; do
systemctl is-active "$svc" 2>/dev/null | grep -q 'active' && echo "$svc"
done
register: _bad_services
changed_when: false
- name: "Evaluate: RDF-03"
ansible.builtin.set_fact:
test_results: "{{ test_results + [{
'test_id': 'RDF-03',
'category': 'FR5 — Restricted Data Flow',
'requirement': 'SR 5.3 — Communication Constraints',
'description': 'Insecure network services (telnet, rsh, ftp) shall be disabled',
'passed': (_bad_services.stdout | trim | length == 0),
'expected': 'No insecure legacy services active',
'actual': (_bad_services.stdout | trim | default('None', true)),
'severity': 'critical',
'remediation': 'Disable: systemctl disable --now <service>'
}] }}"
ignore_errors: yes
- block:
- name: "Gather: Check listening TCP ports"
ansible.builtin.shell: |
ss -tlnp 2>/dev/null | awk 'NR>1 {print $4}' | awk -F: '{print $NF}' | sort -n | uniq | tr '\n' ' '
register: _listening
changed_when: false
- name: "Evaluate: RDF-04"
ansible.builtin.set_fact:
test_results: "{{ test_results + [{
'test_id': 'RDF-04',
'category': 'FR5 — Restricted Data Flow',
'requirement': 'SR 5.3 — Communication Constraints',
'description': 'Only necessary TCP ports shall be listening (document in exception list)',
'passed': 'review',
'expected': 'Documented and approved port list',
'actual': 'Listening ports: ' + (_listening.stdout | trim | default('Unable to determine', true)),
'severity': 'low',
'remediation': 'Review and disable unnecessary listening services'
}] }}"
ignore_errors: yes