diff --git a/.ansible/.lock b/.ansible/.lock new file mode 100644 index 0000000..e69de29 diff --git a/README.md b/README.md index d747275..eaed5dc 100644 --- a/README.md +++ b/README.md @@ -1,58 +1,65 @@ # Free I.T. Athen's Infrastructure + This project is used to develop Ansible for deploying and maintaining websites and services operated by Free I.T. Athens (FRITA). - Requires GNU Make, Ansible, and Vagrant on the host ## Quick Start + 1. Clone this project -2. Run `make` to provision a Debian 11 base box +2. Run `make` to provision a Rocky 9 base box 3. Go to - - [Traefik Dashboard](https://traefik.local.freeitathens.org:8443/dashboard/#/) - - [WordPress](https://www.local.freeitathens.org) - - [Nextcloud](https://cloud.local.freeitathens.org) + - [Traefik Dashboard](https://traefik.local.freeitathens.org:8443/dashboard/#/) + - [WordPress](https://www.local.freeitathens.org) + - [Nextcloud](https://cloud.local.freeitathens.org) 4. Click through the HTTPS security warning ## Production + 1. Clone [production-env](https://github.com/freeitathens/production-env/) to `./environments` - ``` - mkdir -p environments - git clone git@github.com:freeitathens/production-env.git ./environments - ``` + ``` + mkdir -p environments + git clone git@github.com:freeitathens/production-env.git ./environments + ``` 2. Run `./scripts/vault-key.sh` from the root of the project to obtain the Ansible Vault password 3. Enter the Bitwarden Master Password 4. Run `ansible-playbook` against the production servers, e.g., - ``` - ansible-playbook -u root -i environments/production --vault-pass-file ./.ansible_vault webserver.yml --diff --check - ``` + ``` + ansible-playbook -u root -i environments/production --vault-pass-file ./.ansible_vault webserver.yml --diff --check + ``` 5. Delete the `.ansible_vault` file when you are done ### Using Ansible Vault to add or rotate values + Do not submit ciphertext into Ansible Vault with the indention formatting.
To submit, press `CTRL+d` twice. - Decrypt Ansible Vault values - ``` - ansible-vault decrypt --vault-pass-file .ansible_vault - ``` + ``` + ansible-vault decrypt --vault-pass-file .ansible_vault + ``` - Encrypt new Ansible Vault values - ``` - ansible-vault encrypt --vault-pass-file .ansible_vault - ``` - - e.g., `pwgen -s 100 1 | ansible-vault encrypt --vault-pass-file .ansible_vault` + ``` + ansible-vault encrypt --vault-pass-file .ansible_vault + ``` + + - e.g., `pwgen -s 100 1 | ansible-vault encrypt --vault-pass-file .ansible_vault` ## Authors -* **Kris Lamoureux** - *Project Founder* - [@krislamo](https://github.com/krislamo) + +- **Kris Lamoureux** - _Project Founder_ - [@krislamo](https://github.com/krislamo) ## Copyrights and Licenses -Copyright (C) 2019, 2020, 2022 Free I.T. Athens + +Copyright (C) 2019, 2020, 2022, 2023, 2025 Free I.T. Athens This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -60,7 +67,7 @@ Foundation, version 3 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . diff --git a/Vagrantfile b/Vagrantfile index 51925a0..4857a41 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -14,9 +14,8 @@ else File.write(".playbook", PLAYBOOK) end -# Debian 11 Vagrant.configure("2") do |config| - config.vm.box = "debian/bullseye64" + config.vm.box = "rockylinux/9" config.vm.synced_folder ".", "/vagrant", disabled: true config.vm.network "private_network", type: "dhcp" @@ -29,6 +28,8 @@ Vagrant.configure("2") do |config| libvirt.cpus = 2 libvirt.memory = 4096 libvirt.default_prefix = "" + # Doesn't boot without this on rockylinux/9 + libvirt.machine_virtual_size = 10 end # Set VirtualBox settings diff --git a/dev/vars/webserver.yml b/dev/vars/webserver.yml index 3c7f583..fced150 100644 --- a/dev/vars/webserver.yml +++ b/dev/vars/webserver.yml @@ -9,10 +9,13 @@ secret: NEXTCLOUD_ADMIN_PASSWORD: NCadm1npa55w0rd! ############## -### Docker ### +### Common ### ############## -docker_users: - - vagrant +users: + oci: + uid: 2000 + gid: 2000 + home: true ################ #### MariaDB ### diff --git a/dev/webserver.yml b/dev/webserver.yml index 35832e1..f93efc9 100644 --- a/dev/webserver.yml +++ b/dev/webserver.yml @@ -5,5 +5,5 @@ - vars/webserver.yml roles: - common - - docker + - podman - webserver diff --git a/roles/common/defaults/main.yml b/roles/common/defaults/main.yml index a891384..0972c48 100644 --- a/roles/common/defaults/main.yml +++ b/roles/common/defaults/main.yml @@ -1,4 +1,5 @@ -packages: +common_packages: - dnsutils - ncdu - tree + - vim diff --git a/roles/common/tasks/debian.yml b/roles/common/tasks/debian.yml new file mode 100644 index 0000000..bfff234 --- /dev/null +++ b/roles/common/tasks/debian.yml @@ -0,0 +1,30 @@ +- name: Install useful software + ansible.builtin.apt: + name: "{{ common_packages }}" + state: present + update_cache: true + +- name: Install the Uncomplicated Firewall + ansible.builtin.apt: + name: ufw + state: present + update_cache: true + +- name: Deny incoming traffic by default + community.general.ufw: + default: deny + direction: incoming + +- name: Allow outgoing traffic by default + community.general.ufw: + default: allow + direction: outgoing + +- name: Allow OpenSSH with rate limiting + community.general.ufw: + name: ssh + rule: limit + +- name: Enable firewall + community.general.ufw: + state: enabled diff --git a/roles/common/tasks/main.yml b/roles/common/tasks/main.yml index 8a6a01c..3a572fa 100644 --- a/roles/common/tasks/main.yml +++ b/roles/common/tasks/main.yml @@ -2,35 +2,38 @@ ansible.builtin.file: path: "~/.ansible/tmp" state: directory - mode: 0700 + mode: "700" -- name: Install useful software - ansible.builtin.apt: - name: "{{ packages }}" +- name: Create system user groups + ansible.builtin.group: + name: "{{ item.key }}" + gid: "{{ item.value.gid }}" state: present - update_cache: true + loop: "{{ users | dict2items }}" + loop_control: + label: "{{ item.key }}" + when: users is defined -- name: Install the Uncomplicated Firewall - ansible.builtin.apt: - name: ufw +- name: Create system users + ansible.builtin.user: + name: "{{ item.key }}" state: present - update_cache: true + uid: "{{ item.value.uid }}" + group: "{{ item.value.gid }}" + groups: "{{ item.value.groups | default([]) }}" + shell: "{{ item.value.shell | default('/bin/bash') }}" + create_home: "{{ item.value.home | default(false) }}" + home: "{{ item.value.homedir | default('/home/' + item.key) }}" + system: "{{ item.value.system | default(false) }}" + loop: "{{ users | dict2items }}" + loop_control: + label: "{{ item.key }}" + when: users is defined -- name: Deny incoming traffic by default - community.general.ufw: - default: deny - direction: incoming +- name: Include Debian-specific tasks + ansible.builtin.include_tasks: debian.yml + when: ansible_os_family == "Debian" -- name: Allow outgoing traffic by default - community.general.ufw: - default: allow - direction: outgoing - -- name: Allow OpenSSH with rate limiting - community.general.ufw: - name: ssh - rule: limit - -- name: Enable firewall - community.general.ufw: - state: enabled +- name: Include Rocky Linux-specific tasks + ansible.builtin.include_tasks: rocky.yml + when: ansible_os_family == "RedHat" diff --git a/roles/common/tasks/rocky.yml b/roles/common/tasks/rocky.yml new file mode 100644 index 0000000..ade3100 --- /dev/null +++ b/roles/common/tasks/rocky.yml @@ -0,0 +1,42 @@ +- name: Install EPEL repository + ansible.builtin.dnf: + name: epel-release + state: present + update_cache: true + +- name: Install useful software + ansible.builtin.dnf: + name: "{{ common_packages }}" + state: present + update_cache: true + +- name: Install firewalld + ansible.builtin.dnf: + name: firewalld + state: present + +- name: Start and enable firewalld service + ansible.builtin.systemd: + name: firewalld + state: started + enabled: true + +- name: Set default zone to drop (deny incoming by default) + ansible.posix.firewalld: + zone: drop + state: enabled + permanent: true + immediate: true + +- name: Allow SSH in drop zone with rate limiting via rich rule + ansible.posix.firewalld: + zone: drop + rich_rule: 'rule service name="ssh" accept limit value="10/m"' + permanent: true + immediate: true + state: enabled + +- name: Set drop as the default zone + ansible.builtin.command: + cmd: firewall-cmd --set-default-zone=drop + changed_when: false diff --git a/roles/podman/handlers/main.yml b/roles/podman/handlers/main.yml new file mode 100644 index 0000000..81d3d77 --- /dev/null +++ b/roles/podman/handlers/main.yml @@ -0,0 +1,4 @@ +- name: Restart systemd-logind + ansible.builtin.systemd: + name: systemd-logind + state: restarted diff --git a/roles/podman/tasks/main.yml b/roles/podman/tasks/main.yml new file mode 100644 index 0000000..f629126 --- /dev/null +++ b/roles/podman/tasks/main.yml @@ -0,0 +1,20 @@ +- name: Install Podman + ansible.builtin.dnf: + name: ["podman", "podman-docker", "podman-compose"] + state: present + +- name: Create logind.conf.d directory + ansible.builtin.file: + path: /etc/systemd/logind.conf.d + state: directory + mode: "755" + +- name: Create enable-linger.conf file + ansible.builtin.copy: + dest: /etc/systemd/logind.conf.d/enable-linger.conf + content: | + [Login] + KillUserProcesses=no + UserStopDelaySec=0 + mode: "644" + notify: Restart systemd-logind diff --git a/roles/webserver/handlers/main.yml b/roles/webserver/handlers/main.yml index 0bc6198..91f1c48 100644 --- a/roles/webserver/handlers/main.yml +++ b/roles/webserver/handlers/main.yml @@ -23,8 +23,9 @@ listen: composeup_webserver - name: Check Nextcloud status - ansible.builtin.command: "docker exec --user www-data {{ webserver_root | basename }}_nextcloud_1 - php occ status" + ansible.builtin.command: + "docker exec --user www-data {{ webserver_root | basename }}_nextcloud_1 + php occ status" listen: composeup_webserver register: nextcloud_status diff --git a/roles/webserver/tasks/debian.yml b/roles/webserver/tasks/debian.yml new file mode 100644 index 0000000..8e8fd3e --- /dev/null +++ b/roles/webserver/tasks/debian.yml @@ -0,0 +1,48 @@ +- name: Install MariaDB Server + ansible.builtin.apt: + name: mariadb-server + state: present + +- name: Change the bind-address to allow Docker + ansible.builtin.lineinfile: + path: /etc/mysql/mariadb.conf.d/50-server.cnf + regex: "^bind-address" + line: "bind-address = 0.0.0.0" + notify: restart_mariadb + +- name: Install MySQL Support for Python 3 + ansible.builtin.apt: + name: python3-pymysql + state: present + +- name: Create MariaDB databases + community.mysql.mysql_db: + name: "{{ item.name }}" + state: present + login_unix_socket: /var/run/mysqld/mysqld.sock + loop: "{{ databases }}" + no_log: true + +- name: Create MariaDB users + community.mysql.mysql_user: + name: "{{ item.name }}" + password: "{{ item.pass }}" + host: "%" + state: present + priv: "{{ item.name }}.*:ALL" + login_unix_socket: /var/run/mysqld/mysqld.sock + loop: "{{ databases }}" + no_log: true + +- name: Create webserver docker-compose directory + ansible.builtin.file: + path: "{{ webserver_root }}" + state: directory + mode: "600" + +- name: Install webserver docker-compose.yml + ansible.builtin.copy: + src: docker-compose.yml + dest: "{{ webserver_root }}/docker-compose.yml" + mode: "600" + notify: composeup_webserver diff --git a/roles/webserver/tasks/main.yml b/roles/webserver/tasks/main.yml index 1760726..1562b26 100644 --- a/roles/webserver/tasks/main.yml +++ b/roles/webserver/tasks/main.yml @@ -1,57 +1,16 @@ -- name: Install MariaDB Server - ansible.builtin.apt: - name: mariadb-server - state: present +- name: Include Debian-specific tasks + ansible.builtin.include_tasks: debian.yml + when: ansible_os_family == "Debian" -- name: Change the bind-address to allow Docker - ansible.builtin.lineinfile: - path: /etc/mysql/mariadb.conf.d/50-server.cnf - regex: "^bind-address" - line: "bind-address = 0.0.0.0" - notify: restart_mariadb - -- name: Install MySQL Support for Python 3 - ansible.builtin.apt: - name: python3-pymysql - state: present - -- name: Create MariaDB databases - community.mysql.mysql_db: - name: "{{ item.name }}" - state: present - login_unix_socket: /var/run/mysqld/mysqld.sock - loop: "{{ databases }}" - no_log: "{{ item.pass is defined }}" - -- name: Create MariaDB users - community.mysql.mysql_user: - name: "{{ item.name }}" - password: "{{ item.pass }}" - host: '%' - state: present - priv: "{{ item.name }}.*:ALL" - login_unix_socket: /var/run/mysqld/mysqld.sock - loop: "{{ databases }}" - no_log: "{{ item.pass is defined }}" - -- name: Create webserver docker-compose directory - ansible.builtin.file: - path: "{{ webserver_root }}" - state: directory - mode: 0600 - -- name: Install webserver docker-compose.yml - ansible.builtin.copy: - src: docker-compose.yml - dest: "{{ webserver_root }}/docker-compose.yml" - mode: 0600 - notify: composeup_webserver +- name: Include Rocky Linux-specific tasks + ansible.builtin.include_tasks: rocky.yml + when: ansible_os_family == "RedHat" - name: Install docker-compose .env ansible.builtin.template: src: compose-env.j2 dest: "{{ webserver_root }}/.env" - mode: 0600 + mode: "600" notify: composeup_webserver - name: Allow MariaDB database connections diff --git a/roles/webserver/tasks/rocky.yml b/roles/webserver/tasks/rocky.yml new file mode 100644 index 0000000..2d342ef --- /dev/null +++ b/roles/webserver/tasks/rocky.yml @@ -0,0 +1,58 @@ +- name: Install MariaDB Server + ansible.builtin.dnf: + name: mariadb-server + state: present + +- name: Change the bind-address to allow Docker + ansible.builtin.lineinfile: + path: /etc/my.cnf.d/mariadb-server.cnf + regex: "^bind-address" + line: "bind-address = 0.0.0.0" + notify: restart_mariadb + +- name: Start and enable MariaDB service + ansible.builtin.systemd: + name: mariadb + state: started + enabled: yes + +- name: Install MySQL Support for Python 3 + ansible.builtin.dnf: + name: python3-PyMySQL + state: present + +- name: Create MariaDB databases + community.mysql.mysql_db: + name: "{{ item.name }}" + state: present + login_unix_socket: /var/lib/mysql/mysql.sock + loop: "{{ databases }}" + # no_log: true + +- name: Create MariaDB users + community.mysql.mysql_user: + name: "{{ item.name }}" + password: "{{ item.pass }}" + host: "%" + state: present + priv: "{{ item.name }}.*:ALL" + login_unix_socket: /var/lib/mysql/mysql.sock + loop: "{{ databases }}" + # no_log: true + +- name: Create webserver docker-compose directory + ansible.builtin.file: + path: /home/oci/webserver + state: directory + mode: "600" + owner: oci + group: oci + +- name: Install webserver docker-compose.yml + ansible.builtin.copy: + src: docker-compose.yml + dest: /home/oci/webserver/compose.yml + mode: "600" + owner: oci + group: oci + notify: composeup_webserver \ No newline at end of file diff --git a/webserver.yml b/webserver.yml index 6878092..e660d63 100644 --- a/webserver.yml +++ b/webserver.yml @@ -3,5 +3,5 @@ become: true roles: - common - - docker + - podman - webserver