Ryan Garsee


About me My portfolio Contact me

My projects

First, I installed Docker and Docker Compose, which runs Mayan EDMS:

dnf install docker docker-compose



I then downloaded the docker-compose.yml and .env files for Mayan EDMS:
curl "https://gitlab.com/mayan-edms/mayan-edms/-/raw/master/docker/{docker-compose.yml,.env}" -O



Then I started Docker and ran Mayan EDMS using Docker Compose:
systemctl start docker && docker-compose up --detach



Next, I set up a network share on Mayan EDMS, so that Windows machines can create/read/update/delete documents on the EDMS I just installed. To start, I installed the Samba file server:
dnf install samba



After the install, I set the path of my documents in Samba (modifying /etc/samba/smb.conf) so the Samba file server can broadcast it on the network.

For this project, I made a playbook file containing 3 phases of tasks: creating 2 DigitalOcean droplets, applying—for security—20 DISA STIGs to the droplets, and installing a LAMP stack and installing WordPress.



Here's my playbook.
---
- 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
                        



After writing my playbook, I installed Ansible to run my playbook:
dnf install ansible
Then, running "ansible-playbook playbook.yml", which takes a long time from the sheer amount of tasks being run, WordPress is set up and ready for content/branding to be added to it.


I had access to 3 Cisco routers, and I made an IPsec S2S¹ VPN tunnel between them.
¹site-to-site

My first step in making the tunnel:


I opened a shell, or IOS, prompt on the two routers, giving them hostnames to identify them.
For Router 1:
hostname GarseeR-R1
For Router 2:
hostname GarseeR-R2
For Router 3:
hostname GarseeR-R3



My second step in making the tunnel:


I configured IP addresses on the three routers. To do this, I added an IP address to each of the routers' two interfaces. After adding their IP addresses, I entered "no shut" to allow the routers to contact each other. On the first router, specifically, I added a network route from Router 1 to Routers 2 and 3, and back.
interface g0/0
ip address 10.0.0.1 255.255.255.0
no shut
interface g0/1
ip address 192.168.0.1 255.255.255.0
no shut
ip route 0.0.0.0 0.0.0.0 10.0.0.2
The second and third routers were public, the first one you contact to access the tunnel. Therefore, I only added an IP address to their 2 interfaces, like Router 1 above, only without the "ip route" line.
After running these statements, I applied them to the 2 routers by entering "wr", which stands for "write record".


My third step in making the tunnel:


Lastly, I configured IPsec on 2 of the 3 routers. To do this, I enabled a license module, which is additional software/functionality, of which you need to buy a license to use. With the payment made, I ran these statements to enable IPsec:
license boot module c1900 technology-package securityk9

wr
After, I ran these statements, to configure IPsec and ISAKMP policies, on:
The first router:
crypto isakmp policy 10
encryption aes 256
authentication pre-share
group 5
crypto isakmp key ja!Nah5t@o#0hNoh address 10.0.1.1
crypto ipsec transform-set R3 esp-aes 256 esp-sha-hmac
crypto map IPSEC 10 ipsec-isakmp
set peer 10.0.1.1
set pfs group5
set security-association lifetime seconds 86400
set transform-set R3
match address 100
interface GigabitEthernet0/0
crypto map IPSEC
access-list 100 permit ip 192.168.0.0 0.0.0.255 192.168.1.0 0.0.0.255
The third router:
crypto isakmp policy 10
encryption aes 256
authentication pre-share
group 5
crypto isakmp key ja!Nah5t@o#0hNoh address 10.0.0.1
crypto ipsec transform-set R1 esp-aes 256 esp-sha-hmac
crypto map IPSEC 10 ipsec-isakmp
set peer 10.0.0.1
set pfs group5
set security-association lifetime seconds 86400
set transform-set R1
match address 100
interface GigabitEthernet0/0
crypto map IPSEC
access-list 100 permit ip 192.168.1.0 0.0.0.255 192.168.0.0 0.0.0.255
When finished with the above statements, I ran "wr" again to save my changes to Router 1 and Router 3's configuration.


My fourth, final, step in making the tunnel:


I tested the tunnel's connection between the three routers by opening a command prompt—IOS, but actions only—window, and running the "ping" command. For the "ping" command, I ran it with the IP address of the third router on the first, and the first on the third: Example (pinging first router from third):
ping 10.0.0.1
After both (first -> third, third -> first) pings returned successfully, I considered this project finished.