--- # ══════════════════════════════════════════════════════════════════════ # TEMPLATE: Service State Check # ══════════════════════════════════════════════════════════════════════ # # Uses ansible.builtin.service_facts — no shell command needed. # service_facts gathers all service states into ansible_facts.services # as a dict keyed by service name. Prefer this over shelling out to # systemctl for any service-related check. # # IMPORTANT — run service_facts ONCE per suite, not once per test. # Put this gather task at the TOP of your suite file: # # - name: "Gather: Load all service states" # ansible.builtin.service_facts: # # Then each test block below can query ansible_facts.services without # running additional commands. # # Service dict structure (ansible_facts.services['sshd.service']): # name: 'sshd.service' # state: 'running' | 'stopped' | 'failed' | 'inactive' # status: 'enabled' | 'disabled' | 'masked' | 'static' | 'unknown' # # Common 'passed' expression patterns: # # # Service must be running and enabled: # 'passed': ( # ansible_facts.services['sshd.service'] is defined and # ansible_facts.services['sshd.service'].state == 'running' and # ansible_facts.services['sshd.service'].status == 'enabled' # ), # # # Service must NOT be running (insecure service check): # 'passed': ( # ansible_facts.services['telnet.socket'] is not defined or # ansible_facts.services['telnet.socket'].state != 'running' # ), # # # Any of several insecure services must all be absent/inactive: # 'passed': ( # ['telnet.socket', 'rsh.socket', 'ftp.service'] # | map('extract', ansible_facts.services) # | select('defined') # | selectattr('state', 'equalto', 'running') # | list | length == 0 # ), # # ══════════════════════════════════════════════════════════════════════ # ── Put this ONCE at the top of the suite file ─────────────────────── # # - name: "Gather: Load all service states (suite-wide)" # ansible.builtin.service_facts: # # ── Per-test blocks below ──────────────────────────────────────────── # ── SUITE_ID: Service must be running ─────────────────────────────── - block: - name: "Evaluate: TEST_ID — SERVICE_NAME is running and enabled" ansible.builtin.set_fact: test_results: "{{ test_results + [{ 'test_id': 'TEST_ID', 'category': 'FR_NUMBER — CATEGORY_NAME', 'requirement': 'SR X.Y — REQUIREMENT_NAME', 'description': 'SERVICE_NAME shall be running and enabled at boot', 'passed': ( ansible_facts.services['SERVICE_NAME.service'] is defined and ansible_facts.services['SERVICE_NAME.service'].state == 'running' and ansible_facts.services['SERVICE_NAME.service'].status == 'enabled' ), 'expected': 'SERVICE_NAME: state=running, status=enabled', 'actual': ( 'state=' + ansible_facts.services['SERVICE_NAME.service'].state + ', status=' + ansible_facts.services['SERVICE_NAME.service'].status ) if ansible_facts.services['SERVICE_NAME.service'] is defined else 'SERVICE_NAME.service: not found in service facts', 'severity': 'high', 'remediation': 'systemctl enable --now SERVICE_NAME' }] }}" ignore_errors: yes # ── SUITE_ID: Insecure service must NOT be running ────────────────── - block: - name: "Evaluate: TEST_ID — INSECURE_SERVICE_NAME is not running" ansible.builtin.set_fact: test_results: "{{ test_results + [{ 'test_id': 'TEST_ID', 'category': 'FR_NUMBER — CATEGORY_NAME', 'requirement': 'SR X.Y — REQUIREMENT_NAME', 'description': 'INSECURE_SERVICE_NAME shall be disabled and not running', 'passed': ( ansible_facts.services['INSECURE_SERVICE_NAME.service'] is not defined or ansible_facts.services['INSECURE_SERVICE_NAME.service'].state != 'running' ), 'expected': 'INSECURE_SERVICE_NAME: absent or not running', 'actual': ( 'state=' + ansible_facts.services['INSECURE_SERVICE_NAME.service'].state ) if ansible_facts.services['INSECURE_SERVICE_NAME.service'] is defined else 'not installed', 'severity': 'critical', 'remediation': 'systemctl disable --now INSECURE_SERVICE_NAME' }] }}" ignore_errors: yes