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/tasks/main.yml b/roles/podman/tasks/main.yml new file mode 100644 index 0000000..18b8cc0 --- /dev/null +++ b/roles/podman/tasks/main.yml @@ -0,0 +1,14 @@ +- name: Install Podman + ansible.builtin.dnf: + name: ["podman", "podman-docker", "podman-compose"] + state: present + +- name: Check if linger file exists + ansible.builtin.stat: + path: "/var/lib/systemd/linger/{{ target_user }}" + register: linger_file + changed_when: false + +- name: Enable lingering if file doesn't exist + ansible.builtin.command: loginctl enable-linger {{ target_user }} + when: not linger_file.stat.exists diff --git a/roles/webserver/tasks/debian.yml b/roles/webserver/tasks/debian.yml new file mode 100644 index 0000000..492aad6 --- /dev/null +++ b/roles/webserver/tasks/debian.yml @@ -0,0 +1,16 @@ +- 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 \ No newline at end of file diff --git a/roles/webserver/tasks/main.yml b/roles/webserver/tasks/main.yml index 1760726..3270880 100644 --- a/roles/webserver/tasks/main.yml +++ b/roles/webserver/tasks/main.yml @@ -1,19 +1,10 @@ -- 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: Include Rocky Linux-specific tasks + ansible.builtin.include_tasks: rocky.yml + when: ansible_os_family == "RedHat" - name: Create MariaDB databases community.mysql.mysql_db: @@ -27,7 +18,7 @@ community.mysql.mysql_user: name: "{{ item.name }}" password: "{{ item.pass }}" - host: '%' + host: "%" state: present priv: "{{ item.name }}.*:ALL" login_unix_socket: /var/run/mysqld/mysqld.sock diff --git a/roles/webserver/tasks/rocky.yml b/roles/webserver/tasks/rocky.yml new file mode 100644 index 0000000..96b7a8e --- /dev/null +++ b/roles/webserver/tasks/rocky.yml @@ -0,0 +1,16 @@ +- 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: Install MySQL Support for Python 3 + ansible.builtin.apt: + name: python3-pymysql + state: present \ 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