From bacfc66f7c028e2787cc9b553a3967f2d50765f2 Mon Sep 17 00:00:00 2001 From: Timotej Lazar Date: Thu, 4 Jul 2024 14:55:09 +0200 Subject: [PATCH 1/3] alpine: flush some handlers --- roles/alpine/tasks/main.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/roles/alpine/tasks/main.yml b/roles/alpine/tasks/main.yml index 3c8656e..f91e21f 100644 --- a/roles/alpine/tasks/main.yml +++ b/roles/alpine/tasks/main.yml @@ -4,6 +4,8 @@ src: interfaces.j2 notify: restart networking +- meta: flush_handlers + - name: Set hostname hostname: name: '{{ dns_name }}' @@ -60,6 +62,8 @@ enabled: yes state: started +- meta: flush_handlers + - name: Enable QEMU guest agent when: is_virtual block: From 973522c373553930ebf08546007c555924d44474 Mon Sep 17 00:00:00 2001 From: Timotej Lazar Date: Thu, 4 Jul 2024 15:01:47 +0200 Subject: [PATCH 2/3] Import friwall role from network ansible scripts To reuse alpine and nginx roles. Probably going to merge repos at some point. --- inventory.yml | 3 +- roles/friwall/files/friwall.ini | 16 ++++ roles/friwall/files/motd | 1 + roles/friwall/files/pusher.initd | 18 ++++ roles/friwall/files/uwsgi.ini | 2 + roles/friwall/handlers/main.yml | 23 +++++ roles/friwall/tasks/main.yml | 113 +++++++++++++++++++++++ roles/friwall/templates/interfaces.j2 | 14 +++ roles/friwall/templates/networks.json.j2 | 10 ++ roles/friwall/templates/nginx.conf.j2 | 13 +++ roles/friwall/templates/nodes.json.j2 | 11 +++ roles/friwall/templates/settings.json.j2 | 10 ++ roles/opensmtpd/tasks/main.yml | 9 ++ setup.yml | 7 ++ 14 files changed, 249 insertions(+), 1 deletion(-) create mode 100644 roles/friwall/files/friwall.ini create mode 100644 roles/friwall/files/motd create mode 100755 roles/friwall/files/pusher.initd create mode 100644 roles/friwall/files/uwsgi.ini create mode 100644 roles/friwall/handlers/main.yml create mode 100644 roles/friwall/tasks/main.yml create mode 100644 roles/friwall/templates/interfaces.j2 create mode 100644 roles/friwall/templates/networks.json.j2 create mode 100644 roles/friwall/templates/nginx.conf.j2 create mode 100644 roles/friwall/templates/nodes.json.j2 create mode 100644 roles/friwall/templates/settings.json.j2 create mode 100644 roles/opensmtpd/tasks/main.yml diff --git a/inventory.yml b/inventory.yml index 0b952f4..326d7e4 100644 --- a/inventory.yml +++ b/inventory.yml @@ -11,7 +11,8 @@ device_query_filters: query_filters: - tenant: 'fri-it' - role: 'compute-node' - - role: 'storage-node' + - role: 'firewall' - role: 'server' + - role: 'storage-node' group_by: - cluster diff --git a/roles/friwall/files/friwall.ini b/roles/friwall/files/friwall.ini new file mode 100644 index 0000000..c6050c0 --- /dev/null +++ b/roles/friwall/files/friwall.ini @@ -0,0 +1,16 @@ +[uwsgi] +uid = friwall +gid = friwall + +socket = /run/friwall.socket +chown-socket = friwall:nginx +chmod-socket = 660 + +plugin = python3 +chdir = /srv/friwall/app +mount = /=wsgi:app +env = PYTHONUSERBASE=/srv/friwall/.local +env = HOME=/srv/friwall + +# Microsoft OIDC endpoint sends some fat‐ass headers. +buffer-size = 16384 diff --git a/roles/friwall/files/motd b/roles/friwall/files/motd new file mode 100644 index 0000000..a1b5626 --- /dev/null +++ b/roles/friwall/files/motd @@ -0,0 +1 @@ +Welcome to the wall. Trespassers will be shot. Survivors will be shot again. \ No newline at end of file diff --git a/roles/friwall/files/pusher.initd b/roles/friwall/files/pusher.initd new file mode 100755 index 0000000..4b85867 --- /dev/null +++ b/roles/friwall/files/pusher.initd @@ -0,0 +1,18 @@ +#!/sbin/openrc-run + +command="/srv/friwall/app/$RC_SVCNAME" +command_background="yes" +command_user="friwall" +command_group="nogroup" +directory="/srv/friwall" +pidfile="/run/$RC_SVCNAME.pid" + +depend() { + need net +} + +stop() { + ebegin "Stopping $RC_SVCNAME" + pkill -INT -g $(cat "$pidfile") && rm -f "$pidfile" + eend $? +} diff --git a/roles/friwall/files/uwsgi.ini b/roles/friwall/files/uwsgi.ini new file mode 100644 index 0000000..275e85e --- /dev/null +++ b/roles/friwall/files/uwsgi.ini @@ -0,0 +1,2 @@ +[uwsgi] +emperor = /etc/uwsgi/conf.d diff --git a/roles/friwall/handlers/main.yml b/roles/friwall/handlers/main.yml new file mode 100644 index 0000000..7d1fb48 --- /dev/null +++ b/roles/friwall/handlers/main.yml @@ -0,0 +1,23 @@ +- name: reload nginx + service: + name: nginx + state: reloaded + when: "'handler' not in ansible_skip_tags" + +- name: restart pusher + service: + name: pusher + state: restarted + when: "'handler' not in ansible_skip_tags" + +- name: reload uwsgi + service: + name: uwsgi + state: reloaded + when: "'handler' not in ansible_skip_tags" + +- name: restart uwsgi + service: + name: uwsgi + state: restarted + when: "'handler' not in ansible_skip_tags" diff --git a/roles/friwall/tasks/main.yml b/roles/friwall/tasks/main.yml new file mode 100644 index 0000000..f8ef199 --- /dev/null +++ b/roles/friwall/tasks/main.yml @@ -0,0 +1,113 @@ +- name: Create friwall group + group: + name: friwall + system: yes + +- name: Create friwall user + user: + name: friwall + system: yes + home: /srv/friwall + shell: /sbin/nologin + generate_ssh_key: yes + ssh_key_comment: "{{ inventory_hostname }}" + ssh_key_type: ed25519 + +- name: Install packages + package: + name: git,inotify-tools,py3-pip,procps-ng,rsync,uwsgi,uwsgi-python3,wireguard-tools + +- name: Clone web files + become: yes + become_user: friwall + become_method: su + become_flags: "-s /bin/sh" + git: + repo: '{{ lookup("passwordstore", "vm/"~inventory_hostname, subkey="friwall_repo") }}' + dest: /srv/friwall/app + force: yes + notify: reload uwsgi + +- name: Install requirements + become: yes + become_user: friwall + become_method: su + become_flags: '-s /bin/sh' + pip: + requirements: /srv/friwall/app/requirements.txt + extra_args: --user --break-system-packages --no-warn-script-location + register: result + +- name: Configure base settings + template: + dest: "/srv/friwall/{{ item }}" + src: "{{ item }}.j2" + owner: friwall + group: friwall + mode: 0600 + force: no + loop: + - nodes.json + - settings.json + notify: restart uwsgi + +- name: Configure list of networks + template: + dest: "/srv/friwall/networks.json" + src: "networks.json.j2" + owner: friwall + group: friwall + mode: 0600 + +- name: Configure uwsgi + copy: + dest: /etc/uwsgi/ + src: uwsgi.ini + notify: restart uwsgi + +- name: Configure uwsgi instance + copy: + dest: /etc/uwsgi/conf.d/ + src: friwall.ini + owner: friwall + group: friwall + +- name: Enable uwsgi + service: + name: uwsgi + enabled: yes + state: started + +- name: Configure nginx instance + template: + dest: /etc/nginx/http.d/friwall.conf + src: nginx.conf.j2 + notify: reload nginx + +- name: Install config pusher initscript + copy: + dest: /etc/init.d/pusher + src: pusher.initd + mode: 0755 + notify: restart pusher + +- name: Enable config pusher service + service: + name: pusher + enabled: true + state: started + +- name: Regenerate config daily + cron: + name: "regenerate config" + job: "cd ~/app ; FLASK_APP=web python3 -m flask generate" + user: friwall + hour: "3" + minute: "33" + +- name: Try (re-)pushing config periodically + cron: + name: "push config" + job: "cd ~/app ; FLASK_APP=web python3 -m flask push" + user: friwall + minute: "*/15" diff --git a/roles/friwall/templates/interfaces.j2 b/roles/friwall/templates/interfaces.j2 new file mode 100644 index 0000000..d738c99 --- /dev/null +++ b/roles/friwall/templates/interfaces.j2 @@ -0,0 +1,14 @@ +auto lo +iface lo inet loopback + +{% for iface in interfaces %} +auto {{ iface.name }} +iface {{ iface.name }} inet static +{% for address in iface.ip_addresses %} + address {{ address.address }} +{% endfor %} +{% if iface.custom_fields.gateway %} + gateway {{ iface.custom_fields.gateway.address | ipaddr('address') }} +{% endif %} + +{% endfor %} diff --git a/roles/friwall/templates/networks.json.j2 b/roles/friwall/templates/networks.json.j2 new file mode 100644 index 0000000..e9333ab --- /dev/null +++ b/roles/friwall/templates/networks.json.j2 @@ -0,0 +1,10 @@ +{ +{% for vlan, addrs in prefixes | selectattr('vrf') + | selectattr('vlan') | selectattr('vlan.id', 'in', vlans|map(attribute='id')) + | sort(attribute='vlan.vid') | groupby('vlan.vid') %} + "{{ addrs[0].vlan.name }}": { + "ip": {{ addrs | selectattr('family.value', '==', 4) | map(attribute='prefix') | to_json }}, + "ip6": {{ addrs | selectattr('family.value', '==', 6) | map(attribute='prefix') | to_json }} + }{% if not loop.last %},{% endif +%} +{% endfor %} +} diff --git a/roles/friwall/templates/nginx.conf.j2 b/roles/friwall/templates/nginx.conf.j2 new file mode 100644 index 0000000..6f7a3e2 --- /dev/null +++ b/roles/friwall/templates/nginx.conf.j2 @@ -0,0 +1,13 @@ +server { + listen 443 ssl; + listen [::]:443 ssl; + server_name {{ dns_name }}; + + ssl_certificate /etc/letsencrypt/live/{{ dns_name }}/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/{{ dns_name }}/privkey.pem; + + location / { + uwsgi_pass unix:/run/friwall.socket; + include uwsgi_params; + } +} diff --git a/roles/friwall/templates/nodes.json.j2 b/roles/friwall/templates/nodes.json.j2 new file mode 100644 index 0000000..c4ba065 --- /dev/null +++ b/roles/friwall/templates/nodes.json.j2 @@ -0,0 +1,11 @@ +{% set nodes = query('netbox.netbox.nb_lookup', 'devices', api_filter='role=firewall', raw_data=true) + | selectattr('config_context') | selectattr('config_context', 'contains', 'master') + | selectattr('config_context.master', '==', inventory_hostname) + | map(attribute='name') -%} + +{ +{% for node in nodes %} + "{{ hostvars[node] | device_address | selectattr('family.value', '==', 4) + | map(attribute='address') | ipaddr('address') | first }}": -1{{ '' if loop.last else ',' }} +{% endfor %} +} diff --git a/roles/friwall/templates/settings.json.j2 b/roles/friwall/templates/settings.json.j2 new file mode 100644 index 0000000..8d1be7b --- /dev/null +++ b/roles/friwall/templates/settings.json.j2 @@ -0,0 +1,10 @@ +{ + "ldap_host": "{{ domain }}", + "ldap_user": "{{ lookup("passwordstore", "vm/"~inventory_hostname, subkey="ldap_user") }}", + "ldap_pass": "{{ lookup("passwordstore", "vm/"~inventory_hostname, subkey="ldap_pass") }}", + "ldap_base_dn": "{{ ldap_base_dn }}", + "oidc_server": "{{ lookup("passwordstore", "vm/"~inventory_hostname, subkey="oidc_server") }}", + "oidc_client_id": "{{ lookup("passwordstore", "vm/"~inventory_hostname, subkey="oidc_client_id") }}", + "oidc_client_secret": "{{ lookup("passwordstore", "vm/"~inventory_hostname, subkey="oidc_client_secret") }}", + "wg_net": "{{ wg_net }}" +} diff --git a/roles/opensmtpd/tasks/main.yml b/roles/opensmtpd/tasks/main.yml new file mode 100644 index 0000000..651c33a --- /dev/null +++ b/roles/opensmtpd/tasks/main.yml @@ -0,0 +1,9 @@ +- name: Install mail server + package: + name: opensmtpd + +- name: Enable mail server + service: + name: smtpd + enabled: yes + state: started diff --git a/setup.yml b/setup.yml index 94bdd8d..b4c17ad 100644 --- a/setup.yml +++ b/setup.yml @@ -3,6 +3,13 @@ roles: - facts +- hosts: zid + roles: + - alpine + - opensmtpd + - nginx + - friwall + - hosts: ceph-* roles: - debian From f10d94612f8635e0f61c835058e398c392d26914 Mon Sep 17 00:00:00 2001 From: Timotej Lazar Date: Thu, 4 Jul 2024 15:31:25 +0200 Subject: [PATCH 3/3] Factor out password store retrieval --- roles/facts/tasks/main.yml | 4 ++++ roles/forgejo/tasks/main.yml | 4 ---- roles/friwall/tasks/main.yml | 2 +- roles/friwall/templates/settings.json.j2 | 10 +++++----- roles/netbox/tasks/main.yml | 10 +++++----- roles/proxmox/templates/sync-ldap.py.j2 | 6 +++--- roles/synapse/tasks/main.yml | 3 --- 7 files changed, 18 insertions(+), 21 deletions(-) diff --git a/roles/facts/tasks/main.yml b/roles/facts/tasks/main.yml index 89fece7..914e128 100644 --- a/roles/facts/tasks/main.yml +++ b/roles/facts/tasks/main.yml @@ -19,3 +19,7 @@ set_fact: cluster_services: '{{ (cluster_services|default([])) + query("netbox.netbox.nb_lookup", "services", raw_data=true, api_filter="id="+item) }}' loop: '{{ cluster.custom_fields.services | map(attribute="id") | map("string") }}' + +- name: Fetch passwords + set_fact: + password: '{{ lookup("passwordstore", ("vm/" if is_virtual else "host/")~inventory_hostname, returnall=true) | from_yaml }}' diff --git a/roles/forgejo/tasks/main.yml b/roles/forgejo/tasks/main.yml index 77f23c5..f5c4fd9 100644 --- a/roles/forgejo/tasks/main.yml +++ b/roles/forgejo/tasks/main.yml @@ -61,10 +61,6 @@ become: yes become_user: forgejo block: - - name: Get passwords - set_fact: - password: '{{ lookup("passwordstore", "vm/"~inventory_hostname, returnall=true) | from_yaml }}' - - name: Create admin user command: | forgejo admin user create --admin diff --git a/roles/friwall/tasks/main.yml b/roles/friwall/tasks/main.yml index f8ef199..8ae3f8a 100644 --- a/roles/friwall/tasks/main.yml +++ b/roles/friwall/tasks/main.yml @@ -23,7 +23,7 @@ become_method: su become_flags: "-s /bin/sh" git: - repo: '{{ lookup("passwordstore", "vm/"~inventory_hostname, subkey="friwall_repo") }}' + repo: '{{ password.friwall_repo }}' dest: /srv/friwall/app force: yes notify: reload uwsgi diff --git a/roles/friwall/templates/settings.json.j2 b/roles/friwall/templates/settings.json.j2 index 8d1be7b..3086ff3 100644 --- a/roles/friwall/templates/settings.json.j2 +++ b/roles/friwall/templates/settings.json.j2 @@ -1,10 +1,10 @@ { "ldap_host": "{{ domain }}", - "ldap_user": "{{ lookup("passwordstore", "vm/"~inventory_hostname, subkey="ldap_user") }}", - "ldap_pass": "{{ lookup("passwordstore", "vm/"~inventory_hostname, subkey="ldap_pass") }}", + "ldap_user": "{{ password.ldap_user }}", + "ldap_pass": "{{ password.ldap_pass }}", "ldap_base_dn": "{{ ldap_base_dn }}", - "oidc_server": "{{ lookup("passwordstore", "vm/"~inventory_hostname, subkey="oidc_server") }}", - "oidc_client_id": "{{ lookup("passwordstore", "vm/"~inventory_hostname, subkey="oidc_client_id") }}", - "oidc_client_secret": "{{ lookup("passwordstore", "vm/"~inventory_hostname, subkey="oidc_client_secret") }}", + "oidc_server": "{{ password.oidc_server }}", + "oidc_client_id": "{{ password.oidc_client_id }}", + "oidc_client_secret": "{{ password.oidc_client_secret }}", "wg_net": "{{ wg_net }}" } diff --git a/roles/netbox/tasks/main.yml b/roles/netbox/tasks/main.yml index 275a77d..cc8e41a 100644 --- a/roles/netbox/tasks/main.yml +++ b/roles/netbox/tasks/main.yml @@ -84,11 +84,11 @@ - key: "^REMOTE_AUTH_BACKEND =" line: "REMOTE_AUTH_BACKEND = 'social_core.backends.open_id_connect.OpenIdConnectAuth'" - key: "^SOCIAL_AUTH_OIDC_OIDC_ENDPOINT =" - line: "SOCIAL_AUTH_OIDC_OIDC_ENDPOINT = '{{ lookup('passwordstore', 'vm/'~inventory_hostname, subkey='oidc_endpoint') }}'" + line: "SOCIAL_AUTH_OIDC_OIDC_ENDPOINT = '{{ password.oidc_endpoint }}'" - key: "^SOCIAL_AUTH_OIDC_KEY =" - line: "SOCIAL_AUTH_OIDC_KEY = '{{ lookup('passwordstore', 'vm/'~inventory_hostname, subkey='oidc_client_id') }}'" + line: "SOCIAL_AUTH_OIDC_KEY = '{{ password.oidc_client_id }}'" - key: "^SOCIAL_AUTH_OIDC_SECRET =" - line: "SOCIAL_AUTH_OIDC_SECRET = '{{ lookup('passwordstore', 'vm/'~inventory_hostname, subkey='oidc_client_secret') }}'" + line: "SOCIAL_AUTH_OIDC_SECRET = '{{ password.oidc_client_secret }}'" # TODO the key should really be upn but it doesn’t seem to work - key: "^SOCIAL_AUTH_OIDC_USERNAME_KEY =" line: "SOCIAL_AUTH_OIDC_USERNAME_KEY = 'email'" @@ -113,10 +113,10 @@ import sys from users.models import User #from django.contrib.auth.models import User - username = '{{ lookup('passwordstore', 'vm/'~inventory_hostname, subkey='admin_user') }}' + username = '{{ password.admin_user }}' if not User.objects.filter(username=username): User.objects.create_superuser(username, '', # TODO email - '{{ lookup('passwordstore', 'vm/'~inventory_hostname, subkey='admin_pass') }}') + '{{ password.admin_pass }}') sys.exit(1) register: result changed_when: result.rc != 0 diff --git a/roles/proxmox/templates/sync-ldap.py.j2 b/roles/proxmox/templates/sync-ldap.py.j2 index c653a8f..4448d90 100644 --- a/roles/proxmox/templates/sync-ldap.py.j2 +++ b/roles/proxmox/templates/sync-ldap.py.j2 @@ -6,11 +6,11 @@ import re import ldap3 -{% set password = lookup('passwordstore', "cluster/"+cluster.name, returnall=true) | from_yaml %} +{% set cluster_password = lookup('passwordstore', "cluster/"+cluster.name, returnall=true) | from_yaml %} realm = '{{ hostvars[inventory_hostname]["sync-ldap"] }}' ldap_host = '{{ domain }}' -ldap_user = '{{ password.ldap_user }}' -ldap_pass = '{{ password.ldap_pass }}' +ldap_user = '{{ cluster_password.ldap_user }}' +ldap_pass = '{{ cluster_password.ldap_pass }}' ldap_base = '{{ domain | split(".") | map("regex_replace", "^", "dc=") | join(",") }}' # build LDAP query for users diff --git a/roles/synapse/tasks/main.yml b/roles/synapse/tasks/main.yml index bda2a97..b96add5 100644 --- a/roles/synapse/tasks/main.yml +++ b/roles/synapse/tasks/main.yml @@ -1,6 +1,3 @@ -- set_fact: - password: '{{ lookup("passwordstore", "vm/"~inventory_hostname, returnall=true) | from_yaml }}' - - name: Install packages package: name: synapse