Ansible for SIEM and SOC: Automate Security Operations, Incident Response, and Compliance
By Luca Berton · Published 2024-01-01 · Category: installation
Automate SOC and SIEM operations with Ansible. Incident response playbooks, threat hunting automation, SOAR integration, compliance scanning, log collection.
Why Ansible for Security Operations?
Security Operations Centers (SOCs) face a fundamental scaling problem: alerts grow faster than headcount. The average SOC processes 10,000+ alerts per day while facing a chronic analyst shortage. Automation is not optional — it's survival.
Ansible fits SOC automation because: • Agentless — no software to install on endpoints or network devices • Human-readable — security analysts can read and write YAML playbooks without being developers • Cross-platform — orchestrate firewalls, endpoints, SIEM, ticketing, cloud, and network in a single workflow • Auditable — every action logged, every playbook version-controlled • Idempotent — safe to re-run during an incident without causing additional damage
See also: Ansible for SOC and SIEM: Automate Security Operations Complete Guide
SIEM Integration
Collect and Forward Logs
---
- name: Configure centralized logging
hosts: all
become: true
vars:
siem_server: "siem.company.com"
siem_port: 514
siem_protocol: tcp
tasks:
- name: Install rsyslog
ansible.builtin.package:
name: rsyslog
state: present
- name: Configure remote syslog forwarding
ansible.builtin.template:
src: rsyslog-forward.conf.j2
dest: /etc/rsyslog.d/50-siem-forward.conf
mode: '0644'
notify: restart rsyslog
- name: Ensure audit logging enabled
ansible.builtin.package:
name: auditd
state: present
- name: Deploy audit rules for security monitoring
ansible.builtin.copy:
content: |
# Monitor authentication files
-w /etc/passwd -p wa -k identity
-w /etc/shadow -p wa -k identity
-w /etc/group -p wa -k identity
-w /etc/gshadow -p wa -k identity
# Monitor sudo usage
-w /etc/sudoers -p wa -k sudo_changes
-w /etc/sudoers.d/ -p wa -k sudo_changes
# Monitor SSH configuration
-w /etc/ssh/sshd_config -p wa -k sshd_config
# Monitor cron
-w /etc/crontab -p wa -k cron
-w /etc/cron.d/ -p wa -k cron
# Monitor login/logout
-w /var/log/lastlog -p wa -k logins
-w /var/log/faillog -p wa -k logins
# Monitor privilege escalation
-a always,exit -F arch=b64 -S execve -C uid!=euid -k escalation
dest: /etc/audit/rules.d/security.rules
mode: '0640'
notify: restart auditd
handlers:
- name: restart rsyslog
ansible.builtin.systemd:
name: rsyslog
state: restarted
- name: restart auditd
ansible.builtin.service:
name: auditd
state: restarted
Splunk Integration
---
- name: Deploy Splunk Universal Forwarder
hosts: all
become: true
vars:
splunk_server: "splunk-indexer.company.com"
splunk_port: 9997
splunk_forwarder_url: "https://download.splunk.com/products/universalforwarder/releases/9.3.0/linux/splunkforwarder-9.3.0-linux-x86_64.tgz"
tasks:
- name: Download Splunk UF
ansible.builtin.get_url:
url: "{{ splunk_forwarder_url }}"
dest: /tmp/splunkforwarder.tgz
- name: Extract Splunk UF
ansible.builtin.unarchive:
src: /tmp/splunkforwarder.tgz
dest: /opt
remote_src: true
creates: /opt/splunkforwarder
- name: Configure forwarding
ansible.builtin.template:
src: splunk-outputs.conf.j2
dest: /opt/splunkforwarder/etc/system/local/outputs.conf
notify: restart splunk-forwarder
- name: Configure inputs
ansible.builtin.copy:
content: |
[monitor:///var/log/auth.log]
sourcetype = linux_secure
index = security
[monitor:///var/log/audit/audit.log]
sourcetype = linux_audit
index = security
[monitor:///var/log/syslog]
sourcetype = syslog
index = main
dest: /opt/splunkforwarder/etc/system/local/inputs.conf
notify: restart splunk-forwarder
- name: Start Splunk Forwarder
ansible.builtin.command:
cmd: /opt/splunkforwarder/bin/splunk start --accept-license --no-prompt
creates: /opt/splunkforwarder/var/run/splunk/splunkd.pid
handlers:
- name: restart splunk-forwarder
ansible.builtin.command:
cmd: /opt/splunkforwarder/bin/splunk restart
Elastic SIEM (ELK)
---
- name: Deploy Elastic Agent for SIEM
hosts: all
become: true
vars:
fleet_url: "https://fleet.company.com:8220"
enrollment_token: "{{ vault_elastic_token }}"
tasks:
- name: Download Elastic Agent
ansible.builtin.get_url:
url: "https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent-8.16.0-linux-x86_64.tar.gz"
dest: /tmp/elastic-agent.tar.gz
- name: Extract agent
ansible.builtin.unarchive:
src: /tmp/elastic-agent.tar.gz
dest: /opt
remote_src: true
- name: Enroll agent with Fleet
ansible.builtin.command:
cmd: >
/opt/elastic-agent-8.16.0-linux-x86_64/elastic-agent install
--url={{ fleet_url }}
--enrollment-token={{ enrollment_token }}
--non-interactive
creates: /opt/Elastic/Agent/elastic-agent
Incident Response Automation
Automated Host Isolation
When a compromise is detected, automatically isolate the host while preserving forensic evidence:
---
- name: Isolate compromised host
hosts: "{{ compromised_host }}"
become: true
vars:
soc_server: "10.0.0.5"
siem_server: "10.0.0.10"
ticket_id: "{{ incident_ticket }}"
tasks:
- name: Capture network connections before isolation
ansible.builtin.shell: |
ss -tunap > /root/forensics/connections_{{ ansible_date_time.iso8601 }}.txt
netstat -rn > /root/forensics/routes_{{ ansible_date_time.iso8601 }}.txt
ip addr show > /root/forensics/interfaces_{{ ansible_date_time.iso8601 }}.txt
args:
creates: /root/forensics
- name: Create forensics directory
ansible.builtin.file:
path: /root/forensics
state: directory
mode: '0700'
- name: Capture running processes
ansible.builtin.shell: |
ps auxww > /root/forensics/processes.txt
lsof -i > /root/forensics/open_files.txt
changed_when: false
- name: Capture memory-mapped files for suspicious processes
ansible.builtin.shell: |
for pid in $(ps aux | awk '/SUSPECT_PROCESS/ {print $2}'); do
cat /proc/$pid/maps > /root/forensics/maps_${pid}.txt 2>/dev/null
ls -la /proc/$pid/fd/ > /root/forensics/fd_${pid}.txt 2>/dev/null
done
changed_when: false
ignore_errors: true
- name: Block all traffic except SOC and SIEM
community.general.ufw:
rule: allow
from_ip: "{{ item }}"
comment: "Incident {{ ticket_id }} - allowed during isolation"
loop:
- "{{ soc_server }}"
- "{{ siem_server }}"
- name: Set default deny
community.general.ufw:
default: deny
direction: "{{ item }}"
loop:
- incoming
- outgoing
- name: Enable firewall
community.general.ufw:
state: enabled
- name: Disable cron to prevent lateral movement
ansible.builtin.systemd:
name: cron
state: stopped
enabled: false
- name: Lock all non-root user accounts
ansible.builtin.shell: |
for user in $(awk -F: '$3 >= 1000 && $3 != 65534 {print $1}' /etc/passwd); do
usermod -L "$user"
echo "Locked: $user" >> /root/forensics/locked_users.txt
done
changed_when: true
- name: Send isolation notification
ansible.builtin.uri:
url: "https://hooks.slack.com/services/{{ vault_slack_webhook }}"
method: POST
body_format: json
body:
text: "🔴 Host {{ inventory_hostname }} isolated for incident {{ ticket_id }}"
Automated Threat Hunting
---
- name: Hunt for indicators of compromise
hosts: all
become: true
vars:
ioc_hashes:
- "e99a18c428cb38d5f260853678922e03"
- "d41d8cd98f00b204e9800998ecf8427e"
ioc_ips:
- "185.220.101.0/24"
- "45.155.205.0/24"
suspicious_ports:
- 4444
- 5555
- 8888
- 1337
tasks:
- name: Check for known malicious file hashes
ansible.builtin.shell: |
find / -type f -executable -newer /etc/hostname -exec md5sum {} \; 2>/dev/null | head -1000
register: file_hashes
changed_when: false
timeout: 120
- name: Flag IOC hash matches
ansible.builtin.set_fact:
ioc_matches: "{{ file_hashes.stdout_lines | select('search', item) | list }}"
loop: "{{ ioc_hashes }}"
register: hash_results
- name: Check for connections to known bad IPs
ansible.builtin.shell: |
ss -tunap | grep -E "{{ ioc_ips | join('|') }}" || true
register: bad_connections
changed_when: false
- name: Check for suspicious listening ports
ansible.builtin.shell: |
ss -tlnp | grep -E ":{{ suspicious_ports | join('|:') }}" || true
register: suspicious_listeners
changed_when: false
- name: Check for unauthorized SSH keys
ansible.builtin.find:
paths: /home
patterns: "authorized_keys"
recurse: true
register: ssh_keys
- name: Check for suspicious cron jobs
ansible.builtin.shell: |
for user in $(cut -d: -f1 /etc/passwd); do
crontab -l -u "$user" 2>/dev/null | grep -v "^#" | grep -v "^$"
done
register: cron_jobs
changed_when: false
- name: Check for rootkits (basic)
ansible.builtin.shell: |
# Check for hidden processes
diff <(ps aux | awk '{print $2}' | sort -n) <(ls /proc | grep -E '^[0-9]+$' | sort -n) || true
# Check for modified system binaries
rpm -Va 2>/dev/null | grep -E '^..5' || dpkg --verify 2>/dev/null | grep -v "^$" || true
register: rootkit_check
changed_when: false
- name: Generate hunt report
ansible.builtin.template:
src: hunt-report.j2
dest: "/tmp/hunt_{{ inventory_hostname }}_{{ ansible_date_time.date }}.txt"
delegate_to: localhost
See also: Ansible for AI Security: Protect Models, APIs & Data Pipelines (2026 Guide)
Compliance Automation
CIS Benchmark Scanning
---
- name: CIS benchmark compliance check
hosts: all
become: true
tasks:
- name: Check SSH configuration
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: "{{ item.regexp }}"
line: "{{ item.line }}"
state: present
loop:
- { regexp: '^PermitRootLogin', line: 'PermitRootLogin no' }
- { regexp: '^PasswordAuthentication', line: 'PasswordAuthentication no' }
- { regexp: '^X11Forwarding', line: 'X11Forwarding no' }
- { regexp: '^MaxAuthTries', line: 'MaxAuthTries 4' }
- { regexp: '^ClientAliveInterval', line: 'ClientAliveInterval 300' }
- { regexp: '^ClientAliveCountMax', line: 'ClientAliveCountMax 0' }
- { regexp: '^LoginGraceTime', line: 'LoginGraceTime 60' }
- { regexp: '^Protocol', line: 'Protocol 2' }
notify: restart sshd
- name: Ensure password expiration
ansible.builtin.lineinfile:
path: /etc/login.defs
regexp: "{{ item.regexp }}"
line: "{{ item.line }}"
loop:
- { regexp: '^PASS_MAX_DAYS', line: 'PASS_MAX_DAYS 90' }
- { regexp: '^PASS_MIN_DAYS', line: 'PASS_MIN_DAYS 7' }
- { regexp: '^PASS_WARN_AGE', line: 'PASS_WARN_AGE 14' }
- name: Ensure core dumps restricted
ansible.builtin.lineinfile:
path: /etc/security/limits.conf
line: "* hard core 0"
state: present
- name: Disable USB storage
ansible.builtin.copy:
content: "install usb-storage /bin/true"
dest: /etc/modprobe.d/usb-storage.conf
mode: '0644'
- name: Set file permissions on sensitive files
ansible.builtin.file:
path: "{{ item.path }}"
mode: "{{ item.mode }}"
owner: root
group: "{{ item.group | default('root') }}"
loop:
- { path: /etc/passwd, mode: '0644' }
- { path: /etc/shadow, mode: '0640', group: shadow }
- { path: /etc/group, mode: '0644' }
- { path: /etc/gshadow, mode: '0640', group: shadow }
- { path: /etc/crontab, mode: '0600' }
handlers:
- name: restart sshd
ansible.builtin.systemd:
name: sshd
state: restarted
Vulnerability Scanning and Remediation
---
- name: Vulnerability management
hosts: all
become: true
tasks:
- name: Check for available security updates
ansible.builtin.apt:
update_cache: true
upgrade: "no"
register: apt_updates
check_mode: true
when: ansible_os_family == "Debian"
- name: Get list of security updates
ansible.builtin.shell: |
apt list --upgradable 2>/dev/null | grep -i security
register: security_updates
changed_when: false
when: ansible_os_family == "Debian"
- name: Apply security patches (maintenance window)
ansible.builtin.apt:
upgrade: safe
update_cache: true
when:
- ansible_os_family == "Debian"
- maintenance_window | default(false)
notify: check reboot required
- name: Scan for CVEs in installed packages
ansible.builtin.shell: |
if command -v grype &>/dev/null; then
grype dir:/ --only-fixed -o table 2>/dev/null | head -50
else
echo "grype not installed"
fi
register: cve_scan
changed_when: false
handlers:
- name: check reboot required
ansible.builtin.stat:
path: /var/run/reboot-required
register: reboot_file
- name: notify reboot needed
ansible.builtin.debug:
msg: "⚠️ {{ inventory_hostname }} requires reboot after security patches"
when: reboot_file.stat.exists | default(false)
Firewall Orchestration
---
- name: Block threat actor across all firewalls
hosts: firewalls
vars:
threat_ips:
- "185.220.101.42"
- "45.155.205.19"
ticket_id: "IR-2026-0142"
tasks:
- name: Block on Palo Alto
paloaltonetworks.panos.panos_address_object:
provider: "{{ panos_provider }}"
name: "block-{{ item | replace('.', '-') }}"
value: "{{ item }}"
description: "Blocked - {{ ticket_id }}"
loop: "{{ threat_ips }}"
when: "'paloalto' in group_names"
- name: Block on Linux firewalls
community.general.ufw:
rule: deny
from_ip: "{{ item }}"
comment: "Blocked - {{ ticket_id }}"
loop: "{{ threat_ips }}"
when: "'linux' in group_names"
- name: Block on Cisco ASA
cisco.asa.asa_acls:
config:
- afi: ipv4
acls:
- name: OUTSIDE_IN
aces:
- grant: deny
source:
address: "{{ item }}"
destination:
any: true
state: merged
loop: "{{ threat_ips }}"
when: "'cisco_asa' in group_names"
See also: Ansible for Data Sovereignty & Geopatriation: Manage Sovereign Cloud Infrastructure (2026 Guide)
Integration with SOAR Platforms
Trigger Ansible from SOAR
Configure AAP webhooks to receive SOAR triggers:
# AAP job template with survey for dynamic parameters
# SOAR calls AAP API to launch jobs:
# curl -X POST "https://aap.company.com/api/controller/v2/job_templates/42/launch/" \
# -H "Authorization: Bearer $TOKEN" \
# -H "Content-Type: application/json" \
# -d '{
# "extra_vars": {
# "compromised_host": "web-03.company.com",
# "incident_ticket": "IR-2026-0142",
# "action": "isolate"
# }
# }'
SOAR-Driven Response Playbook
---
- name: SOAR-triggered incident response
hosts: "{{ compromised_host }}"
become: true
tasks:
- name: Isolate host
ansible.builtin.include_role:
name: incident_response
tasks_from: isolate.yml
when: action == "isolate"
- name: Collect forensics
ansible.builtin.include_role:
name: incident_response
tasks_from: collect_evidence.yml
when: action == "forensics"
- name: Block IOCs
ansible.builtin.include_role:
name: incident_response
tasks_from: block_iocs.yml
when: action == "block"
delegate_to: "{{ item }}"
loop: "{{ groups['firewalls'] }}"
- name: Restore host
ansible.builtin.include_role:
name: incident_response
tasks_from: restore.yml
when: action == "restore"
FAQ
Can Ansible replace a SOAR platform?
Ansible can handle the orchestration and automation parts of SOAR (Security Orchestration, Automation, and Response) but lacks the case management, analyst workflow, and threat intelligence platform features. Use Ansible as the execution engine behind a SOAR platform like Splunk SOAR, Palo Alto XSOAR, or IBM QRadar SOAR.
Is Ansible secure enough for SOC operations?
Yes, with proper configuration. Use AAP for centralized credential management (no passwords in playbooks), RBAC (limit who can run what), audit logging (every job tracked), and encrypted variables (Ansible Vault). AAP integrates with CyberArk, HashiCorp Vault, and other enterprise secret managers.
How does Ansible compare to dedicated security automation tools?
Ansible is general-purpose automation that excels at cross-platform orchestration. Dedicated security tools (CrowdStrike Falcon, SentinelOne) have deeper endpoint visibility. The best approach: use Ansible to orchestrate responses across your full stack while dedicated tools handle detection and endpoint telemetry.
Conclusion
Ansible bridges the gap between security detection and response. Deploy SIEM agents across thousands of endpoints, automate incident response with host isolation playbooks, run compliance scans against CIS benchmarks, orchestrate firewall blocks across multi-vendor environments, and integrate with SOAR platforms — all with human-readable YAML that security analysts can understand and modify. Combined with AAP's RBAC, scheduling, and audit capabilities, Ansible becomes the execution layer for enterprise security operations.
Related Articles
• Ansible Hardening: CIS Security Benchmark Automation • AAP 2.6 RBAC Best Practices • AAP 2.6 Notifications and Webhooks • AAP 2.6 Event-Driven Ansible (EDA) • UFW Allow Port with AnsibleCategory: installation