Hi there! I'm Maneshwar. Right now, I’m building LiveAPI, a first-of-its-kind tool that helps you automatically index API endpoints across all your repositories. LiveAPI makes it easier to discover, understand, and interact with APIs in large infrastructures.
DevOps often emphasizes CI/CD, observability, and uptime — but what about proactive security hardening?
This post walks you through how to set up automated, cron-based security auditing with ClamAV and Lynis, powered by Ansible, and how to post alerts directly to Discord.
You'll go from this:
ansible-playbook -i hosts.ini cron-all.yml -v
To an infrastructure where every server audits itself and sends alerts every night, with zero manual effort.
Tools Used
- Ansible — to orchestrate setup across all servers.
- ClamAV — antivirus for Linux.
- Lynis — a comprehensive Linux security auditing tool.
- Cron — for scheduled execution.
- Discord Webhooks — to notify you in real time.
- Ansible Galaxy — for clean project structure.
Project Layout
Let’s generate a reusable role using Galaxy:
ansible-galaxy init roles/cron-all
Here’s the final structure:
ansible/ ├─ ansible.cfg ├─ cron-all.yml ├─ hosts.ini ├─ requirements.yml ├─ roles/ │ └─ cron-all/ │ ├─ tasks/ │ │ ├─ main.yml │ │ ├─ crons.yml │ │ └─ security_audit_issues.yml │ ├─ templates/ │ │ ├─ crons.j2 │ │ └─ security_audit_issues.sh.j2 ...
The Ansible Playbook
# cron-all.yml --- - name: Install crons on all hosts hosts: all become: yes gather_facts: true remote_user: ubuntu become_user: ubuntu roles: - cron-all
This runs the cron-all
role as ubuntu
, while elevating when needed for root-level tasks (e.g. installing packages or accessing /var/log
).
Installing ClamAV & Lynis with Ansible
# roles/cron-all/tasks/security_audit_issues.yml - name: Install ClamAV and Lynis ansible.builtin.apt: name: - clamav - lynis state: present update_cache: true become: true
Also ensure fresh virus definitions and set up directories for logs and quarantine:
- name: Ensure ClamAV virus definitions are up to date ansible.builtin.command: freshclam register: clamav_update changed_when: "'updated' in clamav_update.stdout" failed_when: false become: true - name: Create log directory for security audit ansible.builtin.file: path: /home/ubuntu/crons/log state: directory owner: ubuntu group: ubuntu mode: "0755"
Scheduling the Cron Job
A smart cron loop pulls jobs from a template:
# roles/cron-all/tasks/crons.yml - name: Add custom cron jobs to ubuntu user crontab vars: lines: "{{ lookup('template', 'crons.j2').splitlines() }}" ansible.builtin.cron: name: "{{ (idx > 0 and lines[idx-1].strip().startswith('#')) | ternary(lines[idx-1] | trim | regex_replace('^#\\s*', ''), ' '.join(item.split()[5:])) }}" minute: "{{ item.split()[0] }}" hour: "{{ item.split()[1] }}" day: "{{ item.split()[2] }}" month: "{{ item.split()[3] }}" weekday: "{{ item.split()[4] }}" job: "{{ ' '.join(item.split()[5:]) }}" user: ubuntu loop: "{{ lines }}" loop_control: index_var: idx when: item | trim | length > 0 and not item.strip().startswith('#')
The actual cron job comes from a template:
# roles/cron-all/templates/crons.j2 # 11:00 PM IST (converted to 17:30 UTC) 30 17 * * * sudo /bin/bash -lc "/home/ubuntu/crons/scan_and_audit.sh > /home/ubuntu/crons/log/scan_and_audit.log 2>&1"
The Audit Script
This Bash script:
- Logs to
/var/log/security_audit_<timestamp>.log
- Runs
clamscan
across/
- Runs
lynis audit system
- Sends the results to Discord via webhook
# roles/cron-all/templates/security_audit_issues.sh.j2 #!/bin/bash export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" TIMESTAMP=$(date '+%Y-%m-%d_%H-%M') LOG_FILE="/var/log/security_audit_$TIMESTAMP.log" WEBHOOK_URL="https://discord.com/api/webhooks/..." # Replace this echo "🔐 Security Audit - $TIMESTAMP" | tee "$LOG_FILE" echo "Running ClamAV scan..." | tee -a "$LOG_FILE" mkdir -p /var/quarantine clamscan -r / --infected --move=/var/quarantine | tee -a "$LOG_FILE" CLAM_EXIT=${PIPESTATUS[0]} echo -e "\nRunning Lynis audit..." | tee -a "$LOG_FILE" lynis audit system | tee -a "$LOG_FILE" LYNIS_EXIT=${PIPESTATUS[0]} # Prepare status if [[ $CLAM_EXIT -eq 0 && $LYNIS_EXIT -eq 0 ]]; then STATUS="✅ Security audit completed successfully." else STATUS="⚠️ Security audit completed with issues." fi read -r -d '' PAYLOAD << EOF { "content": "**$STATUS**" } EOF curl -X POST "$WEBHOOK_URL" \ -F "payload_json=$PAYLOAD" \ -F "file=@$LOG_FILE"
Running It
Make sure your hosts.ini
points to your servers:
[all] your-server-ip ansible_user=
ansible-playbook -i hosts.ini cron-all.yml -v
LiveAPI helps you get all your backend APIs documented in a few minutes.
With LiveAPI, you can generate interactive API docs that allow users to search and execute endpoints directly from the browser.
If you're tired of updating Swagger manually or syncing Postman collections, give it a shot.
Top comments (0)