---
- name: "Play 1: Provisioning droplets & firewall"
hosts: localhost
vars:
digitalocean_token: "{{ lookup('ansible.builtin.file', 'digiocean_accesstoken') }}"
public_key: "{{ lookup('ansible.builtin.file', 'ssh_key.pub') }}"
tasks:
- name: "Copy SSH key to DigitalOcean team"
digitalocean.cloud.ssh_key:
state: present
token: "{{ digitalocean_token }}"
public_key: "{{ public_key }}"
name: ssh_key
register: ssh_key
- name: "Create droplets"
digitalocean.cloud.droplet:
state: present
token: "{{ digitalocean_token }}"
name: "{{ item.name }}"
region: nyc3
size: s-1vcpu-1gb
image: rockylinux-9-x64
ssh_keys: ["{{ ssh_key.ssh_key.fingerprint }}", "{{ ssh_key.ssh_key.id }}"]
unique_name: true
loop:
- { name: "GarseeR-2509-STIG1" }
- { name: "GarseeR-2509-STIG2" }
- name: "Wait for droplets' provisioning"
ansible.builtin.pause:
seconds: 15
- name: "Get info for droplets"
block:
- name: "Get info for droplet 1"
digitalocean.cloud.droplets_info:
name: "GarseeR-2509-STIG1"
token: "{{ digitalocean_token }}"
register: drop1
- name: "Get info for droplet 2"
digitalocean.cloud.droplets_info:
name: "GarseeR-2509-STIG2"
token: "{{ digitalocean_token }}"
register: drop2
- name: "Add drer && tin.add_host:
name: "{{ item.ip }}"
groupname: "{{ item.group }}"
loop:
- { ip: "{{ drop1.droplets[0].networks.v4[0].ip_address }}", group: "digitalocean" }
- { ip: "{{ drop2.droplets[0].networks.v4[0].ip_address }}", group: "digitalocean" }
- name: "Play 2: Updating & securing droplets"
hosts: digitalocean
vars:
ansible_ssh_public_key_file: "./ssh_key.pub"
ansible_ssh_private_key_file: "./ssh_key"
handlers:
- name: Systemd daemon reload
ansible.builtin.systemd:
daemon_reload: yes
tasks:
- name: "RHEL-09-211045 | The systemd Ctrl-Alt-Delete burst key sequence in RHEL 9 must be disabled"
notify: Systemd daemon reload
block:
- name: "Patch systemd config"
ansible.builtin.lineinfile:
path: /etc/systemd/system.conf
regexp: ^CtrlAltDelBurstAction=none
line: CtrlAltDelBurstAction=none
create: true
mode: 'go-wx'
- name: "Create symlink to /dev/null"
ansible.builtin.file:
src: /dev/null
dest: /etc/systemd/system/ctrl-alt-del.target
state: link
- name: "RHEL-09-211050 | The systemd Ctrl-Alt-Delete burst key sequence in RHEL 9 must be disabled | systemctl stop, disable, mask"
ansible.builtin.systemd:
name: ctrl-alt-del.target
masked: true
state: stopped
- name: "RHEL-09-214015 | RHEL 9 must check the GPG signature of software packages originating from external software repositories before installation."
ansible.builtin.lineinfile:
path: /etc/dnf/dnf.conf
regexp: ^gpgcheck
line: gpgcheck=1
- name: "RHEL-09-215015 | RHEL 9 must not have a File Transfer Protocol (FTP) server package installed."
ansible.builtin.package:
name: vsftpd
state: absent
- name: "RHEL-09-211040 | RHEL 9 systemd-journald service must be enabled."
ansible.builtin.systemd:
name: systemd-journald
state: started
enabled: yes
- name: "RHEL-09-{231110,231115,231120} | RHEL 9 must mount /dev/shm with the {nodev,noexec,nosuid} option."
block:
- name: "Patch /etc/fstab for mount options"
ansible.builtin.lineinfile:
path: /etc/fstab
backup: yes
backrefs: yes
regexp: '^(\S+\s+{{ item.point }}\s+\S+\s+)(?!(?:\S*,)?{{ item.opt }}(?:,\S*)?\s+)(\S+)(\s+.+)$'
line: '\1{{ item.opt }},\2\3'
loop:
- { point: "/dev/shm", opt: "nodev" }
- { point: "/dev/shm", opt: "noexec" }
- { point: "/dev/shm", opt: "nosuid" }
- name: 'Remount /dev/shm'
command: 'mount /dev/shm -o remount'
- name: "RHEL-09-232030 | RHEL 9 /var/log/messages file must have mode 0640 or less permissive."
ansible.builtin.file:
path: /var/log/messages
state: file
mode: '0640'
- name: "RHEL-09-252070 | HIGH | There must be no shosts.equiv files on RHEL 9."
block:
- name: "Find files"
ansible.builtin.find:
path: /
pattern: shosts.equiv
register: discovered_shosts_equiv
- name: "Remove files"
when:
- discovered_shosts_equiv.files | length > 0
ansible.builtin.file:
path: "{{ item }}"
state: absent
loop: "{{ discovered_shosts_equiv.files }}"
- name: "RHEL-09-252075 | There must be no .shosts files on RHEL 9."
block:
- name: "Find files"
ansible.builtin.find:
path: /
hidden: true
pattern: .shosts
register: discovered_dot_shosts
- name: "Remove files"
ansible.builtin.file:
path: "{{ item }}"
state: absent
loop: "{{ discovered_dot_shosts.files }}"
- name: "RHEL-09-215060 | RHEL 9 must not have a Trivial File Transfer Protocol (TFTP) server package installed."
ansible.builtin.package:
name: tftp-server
state: absent
- name: "RHEL-09-411100 | The root account must be the only account having unrestricted access to RHEL 9 system."
ansible.builtin.shell: "cat /etc/passwd | awk -F: '($3 == 0 && $1 != \"root\") {i++;print $1 } END {exit i}'"
- name: "RHEL-09-672020 | RHEL 9 crypto policy must not be overridden."
block:
- name: "Check for crypto policies FIPS presence."
ansible.builtin.shell: "update-crypto-policies --check && echo PASS"
register: discovered_crypto_policies_fips
- name: "Install crypto-policies if not installed."
when:
- discovered_crypto_policies_fips is defined
- "'PASS' not in discovered_crypto_policies_fips.stdout"
ansible.builtin.command: dnf -y reinstall crypto-policies
register: crypto_policies_installed
- name: "Enable FIPS in crypto-policies."
ansible.builtin.command: update-crypto-policies --set FIPS
- name: "RHEL-09-611025 | RHEL 9 must not allow blank or null passwords."
ansible.builtin.replace:
path: "{{ item }}"
regexp: (.*) nullok(.*)
replace: \1\2
loop:
- /etc/pam.d/system-auth
- /etc/pam.d/password-auth
- name: "RHEL-09-412035 | RHEL 9 must automatically exit interactive command shell user sessions after 15 minutes of inactivity."
block:
- name: "Check that /etc/profile.d/tmout.sh exists."
ansible.builtin.stat:
path: /etc/profile.d/tmout.sh
register: discovered_timeout_script
- name: "Create tmout.sh if it doesn't exist."
when: not discovered_timeout_script.stat.exists
ansible.builtin.copy:
dest: "/etc/profile.d/tmout.sh"
content: |
declare -xr TMOUT=600
- name: "Modify tmout.sh if it exists."
when: discovered_timeout_script.stat.exists
ansible.builtin.lineinfile:
path: /etc/profile.d/tmout.sh
regexp: ^\s*declare -xr TMOUT=600
line: declare -xr TMOUT=600
mode: 'u+x,go-w,go+x'
- name: "RHEL-09-412050 | RHEL 9 must enforce a delay of at least four seconds between logon prompts following a failed logon attempt."
block:
- name: "Enforce 4-second delay locally"
ansible.builtin.lineinfile:
path: /etc/login.defs
regexp: \s*FAIL_DELAY
line: "FAIL_DELAY 4"
- name: "RHEL-09-412065 | RHEL 9 must define default permissions for all authenticated users in such a way that the user can only read and modify their own files."
block:
- name: "Ensure the umask setting exists"
ansible.builtin.lineinfile:
path: /etc/login.defs
regexp: UMASK \d\d\d
line: UMASK 077
- name: "Amend all instances"
ansible.builtin.replace:
path: /etc/login.defs
regexp: UMASK\s+\d\d\d
replace: UMASK 077
- name: "RHEL-09-412055 | RHEL 9 must define default permissions for the bash shell."
block:
- name: "Ensure /etc/bashrc exists"
ansible.builtin.lineinfile:
path: /etc/bashrc
regexp: umask \d\d\d
line: umask 077
- name: "Amend all instances of /etc/bashrc"
ansible.builtin.replace:
path: /etc/bashrc
regexp: umask \d\d\d
replace: umask 077
- name: "RHEL-09-412080 | RHEL 9 must terminate idle user sessions."
ansible.builtin.lineinfile:
path: /etc/systemd/logind.conf
regexp: ^(#|)StopIdleSessionSec\s*=\s*\d*
line: "StopIdleSessionSec=900"
- name: "Play 3: Install WordPress on droplet hosts"
hosts: digitalocean
vars:
ansible_ssh_public_key_file: "./ssh_key.pub"
ansible_ssh_private_key_file: "./ssh_key"
tasks:
- name: "Install WordPress dependencies (Apache, MariaDB, Python, PHP)"
ansible.builtin.dnf:
name:
- httpd
- mariadb
- mariadb-server
- php
- git
- python3
- python3-pip
- php
- php-common
- php-cli
- php-gd
- php-curl
- php-mysqlnd
- php-fpm
- php-mysqli
- php-json
state: present
- name: "Install PyMySQL"
ansible.builtin.pip:
name: pymysql
- name: "Start MariaDB"
ansible.builtin.service:
name: mariadb
state: started
enabled: yes
- name: "Make superuser on MariaDB"
ansible.builtin.mysql_user:
name: "admin"
password: "Fullsail1!"
priv: "*.*:ALL,GRANT"
login_unix_socket: "/var/lib/mysql/mysql.sock"
- name: "Create WordPress database"
ansible.builtin.mysql_db:
name: wordpress
state: present
login_user: "admin"
login_password: "Fullsail1!"
login_unix_socket: "/var/lib/mysql/mysql.sock"
- name: "Create WordPress user on database"
ansible.builtin.mysql_user:
name: "wordpress_user"
password: "Fullsail1!"
priv: "*.*:ALL,GRANT"
login_user: "admin"
login_password: "Fullsail1!"
login_unix_socket: "/var/lib/mysql/mysql.sock"
- name: "Flush privileges on MariaDB"
ansible.builtin.mysql_query:
query: "FLUSH PRIVILEGES"
login_user: "admin"
login_password: "Fullsail1!"
login_unix_socket: "/var/lib/mysql/mysql.sock"
- name: "Restart MariaDB"
ansible.builtin.service:
name: mariadb
state: restarted
- name: "Clone WordPress repository to site directory"
ansible.builtin.git:
repo: https://github.com/WordPress/WordPress.git
dest: /var/www/html
clone: yes
- name: "Change database settings in wp-config-sample.php"
ansible.builtin.replace:
path: /var/www/html/wp-config-sample.php
regexp: "{{ item.match }}"
replace: "{{ item.replace }}"
loop:
- { match: "database_name_here", replace: "wordpress" }
- { match: "username_here", replace: "wordpress_user" }
- { match: "password_here", replace: "Fullsail1!" }
- name: "Generate secret keys"
ansible.builtin.set_fact:
key1: "{{ query('community.general.random_string', length=48) }}"
key2: "{{ query('community.general.random_string', length=48) }}"
key3: "{{ query('community.general.random_string', length=48) }}"
key4: "{{ query('community.general.random_string', length=48) }}"
key5: "{{ query('community.general.random_string', length=48) }}"
key6: "{{ query('community.general.random_string', length=48) }}"
key7: "{{ query('community.general.random_string', length=48) }}"
key8: "{{ query('community.general.random_string', length=48) }}"
- name: "Add secret keys to wp-config-sample.php"
ansible.builtin.replace:
path: /var/www/html/wp-config-sample.php
regexp: "{{ item.key }}"
replace: "{{ item.value }}"
loop:
- { key: "define( 'AUTH_KEY', 'put your unique phrase here' );", value: "{{ key1 }}" }
- { key: "define( 'SECURE_AUTH_KEY', 'put your unique phrase here' );", value: "{{ key2 }}" }
- { key: "define( 'LOGGED_IN_KEY', 'put your unique phrase here' );", value: "{{ key3 }}" }
- { key: "define( 'NONCE_KEY', 'put your unique phrase here' );", value: "{{ key4 }}" }
- { key: "define( 'AUTH_SALT', 'put your unique phrase here' );", value: "{{ key5 }}" }
- { key: "define( 'SECURE_AUTH_SALT', 'put your unique phrase here' );", value: "{{ key6 }}" }
- { key: "define( 'LOGGED_IN_SALT', 'put your unique phrase here' );", value: "{{ key7 }}" }
- { key: "define( 'NONCE_SALT', 'put your unique phrase here' );", value: "{{ key8 }}" }
- name: Rename wp-config-sample.yml to wp-config.yml"
ansible.builtin.command:
cmd: "mv /var/www/html/wp-config-sample.php /var/www/html/wp-config.php"
- name: "Fix permissions on WordPress site directory"
ansible.builtin.file:
dest: /var/www
owner: apache
mode: '0755'
recurse: yes
- name: "Start & enable Apache"
ansible.builtin.service:
name: httpd
state: started
enabled: yes