Complete Ansible playbook for initial server setup and security hardening on Oracle Linux 9.
✅ System Updates
- Update all system packages to latest stable versions
- Install essential utilities (vim, git, curl, wget, net-tools, htop, tmux, etc.)
- Enable EPEL repository for additional packages
✅ Service & Local Exposure Hardening
- Remove insecure legacy packages (telnet, rsh, tftp, xinetd)
- Disable and mask unused services (avahi-daemon, cups, rpcbind, autofs, nfs-server)
- Disable USB mass storage and blacklist uncommon filesystems
- Configure login warning banners for console and SSH sessions
- Enforce package signature verification in DNF
✅ Kernel Hardening
- Enable ASLR (Address Space Layout Randomization)
- Enable DEP/NX (Data Execution Prevention)
- SYN cookies for DoS protection
- Restrict kernel pointer exposure
- Disable magic SysRq key
- Restrict namespace usage
- Configure ptrace restrictions
✅ SELinux Hardening
- Configure SELinux in enforcing mode
- Use targeted policy for optimal compatibility
- Enable SELinux-aware logging
✅ SSH Hardening
- Disable root SSH login (
PermitRootLogin no) - Disable password authentication (enforce SSH keys)
- Disable empty passwords
- Disable X11 forwarding
- Configure strong ciphers (ChaCha20-Poly1305, AES-GCM)
- Strong key exchange algorithms (Curve25519)
- Limit authentication attempts and login grace time
- Configure client alive intervals
- Disable port forwarding
✅ Firewall Configuration
- Enable firewalld (native to Oracle Linux 9)
- Set public zone as default
- Allow SSH service
- Configure additional service rules
✅ Sudo Hardening
- Require TTY for sudo commands
- Enable sudo input/output logging
- Configure session timeout (15 minutes)
- Enforce secure PATH
- Disable visudo functionality
- Log all sudo commands to
/var/log/sudo.log
✅ Audit Logging
- Enable auditd for comprehensive system auditing
- Monitor sudoers file changes
- Monitor SSH configuration changes
- Track user/group/shadow file modifications
- Log command execution
- Monitor file deletion
- Forward logs for retention and analysis
✅ Password Policy Hardening
- Enforce minimum 14-character passwords
- Require uppercase, lowercase, digits, special characters
- Password expiry: 90 days
- Prevent password reuse (remember 5 previous)
- Check for username in password
- Block sequences and repeated characters
✅ Account Lockout Protection
- Fail2ban SSH intrusion detection
- Automatic IP banning after 3 failed attempts
- PAM account lockout (5 failures = 15 min lockout)
- Configurable ban duration (default: 1 hour)
✅ File Integrity Monitoring (AIDE)
- Automated file integrity checks
- Daily monitoring schedule (3:05 AM)
- Monitor critical system files (SSH, sudo, PAM, sysctl, firewall, audit)
- Email notifications to root
- Detect unauthorized modifications
✅ Log Management
- Centralized logging with rsyslog
- High-precision timestamps (ISO8601 format)
- Separate logs for auth, kernel, cron, sudo
- Automatic log rotation (90 days, 180 for audit)
- Compressed log archives
- Secure log file permissions (0640)
✅ System Resource Limits
- Prevent core dumps (security)
- Limit maximum open files (65536)
- Restrict processes per user (4096)
- Fork bomb protection
- Prevent process/PID exhaustion
- Memory and stack limits
✅ GRUB Bootloader Hardening
- Disable single-user/recovery mode
- Hide GRUB menu (prevent unauthorized access)
- Set boot timeout (5 seconds)
- Optional password protection for boot
- Prevent unauthorized kernel parameters
✅ Optional User Management
- Create non-root admin user
- Add to sudoers group (wheel)
- Configure SSH key-based authentication
- Set password expiry (90 days)
oracle9-hardening/
├── hardening-only.yml # Dedicated hardening-only playbook
├── playbook.yml # Main playbook entry point
├── ansible.cfg # Ansible configuration
├── inventory/
│ └── hosts.yml # Target hosts definition
├── group_vars/
│ └── all.yml # Global variables and configuration
├── roles/
│ ├── system-update/
│ │ ├── tasks/main.yml # System update tasks
│ │ └── handlers/main.yml # Handlers
│ ├── service-hardening/
│ │ └── tasks/main.yml # Service exposure reduction and banners
│ ├── kernel-hardening/
│ │ ├── tasks/main.yml # Kernel security parameters
│ │ └── handlers/main.yml # Handlers
│ ├── selinux-hardening/
│ │ ├── tasks/main.yml # SELinux enforcement
│ │ └── handlers/main.yml # Handlers
│ ├── ssh-hardening/
│ │ ├── tasks/main.yml # SSH hardening
│ │ ├── templates/sshd_config.j2 # SSH config template
│ │ └── handlers/main.yml # Handlers
│ ├── firewall-config/
│ │ ├── tasks/main.yml # Firewall rules
│ │ └── handlers/main.yml # Handlers
│ ├── password-policy/
│ │ ├── tasks/main.yml # Password requirements (pwquality)
│ │ └── handlers/main.yml # Handlers
│ ├── account-lockout/
│ │ ├── tasks/main.yml # Fail2ban configuration
│ │ └── handlers/main.yml # Handlers
│ ├── sudo-hardening/
│ │ ├── tasks/main.yml # Sudo security settings
│ │ └── handlers/main.yml # Handlers
│ ├── audit-logging/
│ │ ├── tasks/main.yml # Auditd configuration
│ │ └── handlers/main.yml # Handlers
│ ├── file-integrity/
│ │ ├── tasks/main.yml # AIDE monitoring setup
│ │ └── handlers/main.yml # Handlers
│ ├── log-management/
│ │ ├── tasks/main.yml # Rsyslog & logrotate config
│ │ └── handlers/main.yml # Handlers
│ ├── system-limits/
│ │ ├── tasks/main.yml # Resource limits (ulimit, sysctl)
│ │ └── handlers/main.yml # Handlers
│ ├── grub-hardening/
│ │ ├── tasks/main.yml # GRUB bootloader security
│ │ └── handlers/main.yml # Handlers
│ └── user-management/
│ └── tasks/main.yml # User creation tasks
└── README.md # This file
- Control Node: Ansible 2.10+
- Target Nodes: Oracle Linux 9
- SSH Access: Public key authentication to root
- Privilege: root access (or sudo without password)
Edit inventory/hosts.yml and add your target servers:
production_servers:
hosts:
oracle01:
ansible_host: 192.168.1.10
ansible_user: root
oracle02:
ansible_host: 192.168.1.11
ansible_user: root
vars:
ansible_ssh_private_key_file: ~/.ssh/oracle_keyEdit group_vars/all.yml to customize:
- SSH port (default: 22)
- Packages to install
- Firewall zones and services
- Enable admin user creation
# Test run (dry-run, no changes)
ansible-playbook playbook.yml -i inventory/hosts.yml --check
# Apply to all hosts
ansible-playbook playbook.yml -i inventory/hosts.yml
# Apply only the hardening baseline (without package updates or user creation)
ansible-playbook hardening-only.yml -i inventory/hosts.yml
# Apply with specific tags
ansible-playbook playbook.yml -i inventory/hosts.yml --tags ssh,firewall
# Apply only to specific hosts
ansible-playbook playbook.yml -i inventory/hosts.yml -l oracle01
# Verbose output
ansible-playbook playbook.yml -i inventory/hosts.yml -vEdit group_vars/all.yml to customize the playbook behavior:
system_update_packages:
- vim
- git
- curl
- wget
- net-tools
# Add more packages as neededremove_legacy_insecure_packages: true
disable_unused_services: true
disable_usb_storage: true
blacklist_unused_filesystems: true
enforce_package_signature_check: true
service_hardening_disable_services:
- avahi-daemon
- cups
- rpcbindssh_port: 22
ssh_permit_root_login: "no"
ssh_password_authentication: "no"
ssh_x11_forwarding: "no"
ssh_max_auth_tries: 3
ssh_login_grace_time: 30
ssh_banner_enabled: trueenable_firewall: true
firewall_zone: public
firewall_services:
- ssh
# Add more services (http, https, etc.) if neededcreate_admin_user: false # Set to true to enable
admin_username: "oracle_admin" # Change as needed
admin_ssh_pubkey: "ssh-rsa ..." # Your public key (optional)Run specific roles using tags:
# Update system only
ansible-playbook playbook.yml --tags update,system
# Hardening tags
ansible-playbook playbook.yml --tags services,security # Service exposure hardening
ansible-playbook playbook.yml --tags kernel,security # Kernel hardening
ansible-playbook playbook.yml --tags selinux,security # SELinux enforcement
ansible-playbook playbook.yml --tags ssh,security # SSH hardening
ansible-playbook playbook.yml --tags firewall,security # Firewall rules
ansible-playbook playbook.yml --tags password,security # Password policy
ansible-playbook playbook.yml --tags account,security # Account lockout (Fail2ban)
ansible-playbook playbook.yml --tags sudo,security # Sudo hardening
ansible-playbook playbook.yml --tags audit,security # Audit logging
ansible-playbook playbook.yml --tags aide,integrity # File integrity (AIDE)
ansible-playbook playbook.yml --tags logging,security # Log management
ansible-playbook playbook.yml --tags limits,security # System limits
ansible-playbook playbook.yml --tags grub,bootloader # GRUB security
# Apply complete hardening
ansible-playbook playbook.yml --tags security,hardening
# Create admin user only
ansible-playbook playbook.yml --tags usersTo test the playbook on your local machine:
# Use the localhost inventory group
ansible-playbook playbook.yml -i inventory/hosts.yml -l local --check
# Or run with local connection (requires sudo)
ansible-playbook playbook.yml -i inventory/hosts.yml -l localhost -kTo create an admin user during playbook execution:
# Method 1: Edit group_vars/all.yml and set create_admin_user: true
# Method 2: Pass via command line
ansible-playbook playbook.yml \
-e "create_admin_user=true" \
-e "admin_username=myuser" \
-e "admin_ssh_pubkey='ssh-rsa AAAA...'"
# Method 3: Use limit to only create user on specific hosts
ansible-playbook playbook.yml \
-e "create_admin_user=true" \
-l oracle01-
SSH Keys Required: Password authentication is disabled after hardening. Ensure your SSH public key is configured before running if you don't have key-based access.
-
Firewall Configuration: The playbook enables firewalld. Ensure SSH port (default 22) is open in your network firewall before starting.
-
Root Access: After hardening, root SSH login is disabled. Use the admin user or existing non-root accounts for access.
-
Backup SSH Config: The original
/etc/ssh/sshd_configis backed up with timestamp. Original:/etc/ssh/sshd_config.backup.* -
Test on Non-Production: Run with
--checkflag first or test on non-production systems before production deployment.
- SSH Ciphers: ChaCha20-Poly1305, AES-GCM (no weak algorithms)
- Key Exchange: Curve25519, Diffie-Hellman Group Exchange SHA256 (no weak DH)
- MAC Algorithms: HMAC-SHA2 with ETM
- Root Login: Disabled
- Password Auth: Disabled (keys only)
- X11 Forwarding: Disabled
- Port Forwarding: Disabled
- Tunneling: Disabled
# General system verification
ssh -v <host> # Check SSH connection
sudo systemctl status firewalld # Firewall status
rpm -q vim git curl wget net-tools # Installed packages
# Kernel & SELinux hardening
sudo sysctl net.ipv4.ip_forward # Should be 0
sudo sysctl kernel.randomize_va_space # Should be 2
sudo sysctl kernel.sysrq # Should be 0
getenforce # Should be "Enforcing"
sudo sestatus # SELinux status
# SSH hardening
ssh -v root@<host> # Should be rejected (root login disabled)
sudo sshd -t # SSH config syntax check
# Password policy
sudo grep "minlen" /etc/security/pwquality.conf
sudo cat /etc/login.defs | grep PASS_
# Account lockout (Fail2ban)
sudo systemctl status fail2ban
sudo fail2ban-client status sshd # Show banned IPs
sudo fail2ban-client status fail2ban # Show all jails
# Audit logging
sudo systemctl status auditd
sudo aureport # Summary report
sudo ausearch -k sudoers # Search sudoers changes
# File integrity (AIDE)
sudo aide --check # Manual integrity check
sudo crontab -l | grep aide # Check cron schedule
# Log management
sudo tail -f /var/log/auth.log # Authentication logs
sudo tail -f /var/log/sudo.log # Sudo command logs
sudo tail -f /var/log/audit/audit.log # Audit logs
cat /etc/logrotate.d/90-hardening # Rotation config
# System resource limits
ulimit -a # Current limits
cat /proc/sys/fs/file-max # Max file descriptors
cat /etc/security/limits.d/99-hardening.conf
# GRUB hardening
cat /etc/default/grub # GRUB configuration
# Note: Reboot required to see GRUB changes- Check SSH config for syntax errors:
sshd -t - Verify public key is in
~/.ssh/authorized_keys(default user home) - Restore from backup:
cp /etc/ssh/sshd_config.backup.* /etc/ssh/sshd_config - Restart SSH:
systemctl restart sshd
# If users cannot change passwords
sudo pwmake 128 | wc -c # Generate valid password
sudo passwd <username> # Test password change
# Reset password complexity if needed
sudo chage -m 0 -M 99999 <username> # Disable expiry temporarily# Check Fail2ban status
sudo fail2ban-client status
# Unban an IP
sudo fail2ban-client set sshd unbanip <IP>
# Reset Fail2ban
sudo systemctl restart fail2ban
# View Fail2ban logs
sudo tail -f /var/log/fail2ban.log
sudo journalctl -u fail2ban -f# AIDE database corruption
sudo aide --init -c /etc/aide.conf
sudo mv /var/lib/aide/aide.db.new /var/lib/aide/aide.db
# Manual integrity check
sudo aide --check
# Update after acknowledged changes
sudo aide --update
sudo mv /var/lib/aide/aide.db.new /var/lib/aide/aide.db# Check failed login attempts
sudo faillog -u <username>
# Reset failed attempts
sudo faillog -r -u <username>
# Or using pam_faillock
sudo pam-auth-update --enable faillock# List all open ports
sudo firewall-cmd --list-ports
# Add a port (e.g., HTTP 80)
sudo firewall-cmd --permanent --add-port=80/tcp
sudo firewall-cmd --reload
# Debug firewall issue
sudo firewall-cmd --list-all
sudo firewall-cmd --get-active-zones# Test connectivity
ansible all -i inventory/hosts.yml -m ping
# With verbose output
ansible all -i inventory/hosts.yml -m ping -vvv
# Check specific host
ansible <hostname> -i inventory/hosts.yml -m setup | head -50If you need to revert changes:
# Restore SSH config from backup
sudo cp /etc/ssh/sshd_config.backup.* /etc/ssh/sshd_config
sudo systemctl restart sshd
# Disable firewall (temporary)
sudo systemctl stop firewalld
# Disable firewall (permanent)
sudo systemctl disable firewalldReduces the local attack surface before the rest of the hardening roles run:
remove_legacy_insecure_packages: true
disable_unused_services: true
disable_usb_storage: true
blacklist_unused_filesystems: true
configure_login_banners: trueVerification:
rpm -q telnet telnet-server rsh-server xinetd
sudo systemctl status avahi-daemon rpcbind
grep Banner /etc/ssh/sshd_config
cat /etc/issueEnforces strong password requirements using pwquality (PAM):
password_min_length: 14 # Minimum characters
password_require_uppercase: true # Uppercase required
password_require_lowercase: true # Lowercase required
password_require_digits: true # Digits required
password_require_special: true # Special characters required
password_max_days: 90 # Password expiry (90 days)
password_history: 5 # Remember 5 previous passwordsVerification:
grep "minlen" /etc/security/pwquality.conf
cat /etc/login.defs | grep PASS_Fail2ban monitors repeated login failures and blocks attackers:
enable_account_lockout: true
fail2ban_bantime: 3600 # Ban for 1 hour
fail2ban_maxretry: 3 # After 3 failures
fail2ban_findtime: 600 # In 10-minute windowVerification:
sudo systemctl status fail2ban
sudo fail2ban-client status sshd
sudo fail2ban-client set sshd unbanip <IP> # Unban an IPAutomatically detects unauthorized changes to critical system files:
Scheduled Check:
- Daily at 03:05 AM
- Monitored: SSH config, sudoers, PAM, sysctl, firewall, audit configs
Verification:
# Manual AIDE check
sudo aide --check
# View AIDE database
sudo aide --compare
# Check cron job
sudo crontab -l | grep aideCentralized logging with rsyslog and automatic rotation:
- Separate logs: auth, kernel, cron, sudo, debug
- ISO8601 format timestamps
- Automatic rotation: 90 days (audit logs: 180 days)
- Secure permissions: 0640 (root:adm)
Verification:
# Check rsyslog configuration
cat /etc/rsyslog.d/99-hardening.conf
# View recent system logs
tail -f /var/log/messages
tail -f /var/log/auth.log
# Check log rotation schedule
cat /etc/logrotate.d/90-hardeningPrevents resource exhaustion and DoS attacks:
- Core dumps disabled
- Max open files: 65536
- Max processes: 4096
- Fork bomb protection
- PID max: 4194303
Verification:
# Check current limits
ulimit -a
# Check file descriptor limits
cat /proc/sys/fs/file-max
cat /proc/sys/fs/inode-max
# View configured limits
cat /etc/security/limits.d/99-hardening.confSecures boot process and prevents unauthorized access:
- Single-user mode disabled
- Recovery mode disabled
- Boot timeout: 5 seconds
- Hidden GRUB menu
- Optional password protection
Verification:
# Check GRUB configuration
cat /etc/default/grub
# View GRUB config
sudo grub2-mkconfig -o /boot/grub2/grub.cfg
# Reboot to test (changes appear on next boot)Edit group_vars/all.yml to customize kernel parameters:
kernel_hardening_params:
kernel.randomize_va_space: "2" # ASLR (0=disabled, 2=full)
kernel.sysrq: "0" # Magic SysRq key
kernel.kptr_restrict: "2" # Kernel pointer exposure
kernel.unprivileged_userns_clone: "0" # Namespace restrictions
network_hardening_params:
net.ipv4.ip_forward: "0" # Disable IP forwarding
net.ipv4.tcp_syncookies: "1" # SYN flood protection
net.ipv4.conf.all.log_martians: "1" # Log suspicious packets
net.ipv6.conf.all.disable_ipv6: "1" # Disable IPv6 if not neededThe playbook configures SELinux in enforcing mode with targeted policy:
# Check SELinux status
getenforce
sudo sestatus -v
# View SELinux denials
sudo ausearch -m avc --ts recent
# Disable SELinux (not recommended for production)
sudo setenforce 0- TTY Required: Sudo commands must be run from a real terminal
- Input/Output Logging: All sudo session input/output is logged to
/var/log/sudo.log - Session Timeout: Sudo session expires after 15 minutes of inactivity
- Secure PATH: Sudo uses a hardened PATH to prevent path hijacking
- Command Logging: All sudo commands are logged to syslog
View sudo logs:
sudo journalctl SYSLOG_IDENTIFIER=sudo
# or
sudo tail -f /var/log/sudo.logThe audit daemon monitors:
- System Files:
/etc/sudoers,/etc/ssh/sshd_config,/etc/sysctl.conf - User/Group Changes:
/etc/passwd,/etc/group,/etc/shadow - Command Execution: All executed commands
- File Deletions: Track deleted files by unprivileged users
- SELinux Denials: Policy violations
View audit logs:
# Generate audit report
sudo aureport
# Search for specific events
sudo ausearch -k sudoers
sudo ausearch -k sshd_config
sudo ausearch -m exec
# Monitor in real-time
sudo tail -f /var/log/audit/audit.logThe playbook applies network stack hardening:
# Verify network parameters
sudo sysctl net.ipv4.ip_forward
sudo sysctl net.ipv4.tcp_syncookies
sudo sysctl net.ipv4.conf.all.send_redirects
# View all applied sysctl settings
sudo sysctl -a | grep -E "ipv4|ipv6"This comprehensive hardening playbook aligns with:
-
CIS Oracle Linux 9 Benchmarks - Complete coverage of Level 1 & Level 2 controls
- Account policy requirements (password complexity, aging)
- Login/Access controls (fail2ban, account lockout)
- File integrity monitoring (AIDE)
- Logging and auditing
-
PCI-DSS v3.2.1 Security Standards
- 2.2: Configuration standards for system components
- 2.4: Document security configuration
- 8.2: Password management policies
- 10: Logging and monitoring requirements
-
NIST Cybersecurity Framework
- PR.AC (Access Control)
- PR.PT (Protective Technology)
- DE.AE (Anomalies and Events)
- DE.AY (Analysis)
-
ANSSI Hardening Guidelines
- Bootloader security (GRUB hardening)
- System limits and ulimit restrictions
- Service hardening (SSH, sudo)
| Category | Standard | Covered |
|---|---|---|
| Access Control | CIS 5.2.1 - 5.3 | Password policy, account lockout |
| Authentication | CIS 5.2 | SSH key-based auth, sudo hardening |
| Audit Logging | CIS 4 | Auditd rules, file integrity (AIDE) |
| System Hardening | CIS 1.1 - 1.8 | Kernel params, SELinux, boot security |
| Account Management | CIS 5.1 - 5.5 | Password aging, expiry, complexity |
For issues, suggestions, or contributions:
- Review playbook logs for errors
- Check configuration in
group_vars/all.yml - Verify target system meets Prerequisites
- Test with
--checkflag in non-production first
This playbook is provided as-is for system hardening purposes. Test thoroughly in your environment before production deployment.
- Ansible Documentation
- Oracle Linux 9 Documentation
- SSH Hardening Best Practices
- CIS Oracle Linux 9 Benchmark
Use freely for your infrastructure automation.
For issues or improvements:
- Check the playbook syntax:
ansible-playbook --syntax-check playbook.yml - Run with verbose flags:
ansible-playbook playbook.yml -vvv - Check Ansible logs in
ansible.log