Compare commits

...

3 commits

Author SHA1 Message Date
Timotej Lazar f10d94612f Factor out password store retrieval 2024-07-04 15:31:57 +02:00
Timotej Lazar 973522c373 Import friwall role from network ansible scripts
To reuse alpine and nginx roles. Probably going to merge repos at some point.
2024-07-04 15:31:53 +02:00
Timotej Lazar bacfc66f7c alpine: flush some handlers 2024-07-04 14:55:09 +02:00
20 changed files with 265 additions and 16 deletions

View file

@ -11,7 +11,8 @@ device_query_filters:
query_filters: query_filters:
- tenant: 'fri-it' - tenant: 'fri-it'
- role: 'compute-node' - role: 'compute-node'
- role: 'storage-node' - role: 'firewall'
- role: 'server' - role: 'server'
- role: 'storage-node'
group_by: group_by:
- cluster - cluster

View file

@ -4,6 +4,8 @@
src: interfaces.j2 src: interfaces.j2
notify: restart networking notify: restart networking
- meta: flush_handlers
- name: Set hostname - name: Set hostname
hostname: hostname:
name: '{{ dns_name }}' name: '{{ dns_name }}'
@ -60,6 +62,8 @@
enabled: yes enabled: yes
state: started state: started
- meta: flush_handlers
- name: Enable QEMU guest agent - name: Enable QEMU guest agent
when: is_virtual when: is_virtual
block: block:

View file

@ -19,3 +19,7 @@
set_fact: set_fact:
cluster_services: '{{ (cluster_services|default([])) + query("netbox.netbox.nb_lookup", "services", raw_data=true, api_filter="id="+item) }}' 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") }}' 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 }}'

View file

@ -61,10 +61,6 @@
become: yes become: yes
become_user: forgejo become_user: forgejo
block: block:
- name: Get passwords
set_fact:
password: '{{ lookup("passwordstore", "vm/"~inventory_hostname, returnall=true) | from_yaml }}'
- name: Create admin user - name: Create admin user
command: | command: |
forgejo admin user create --admin forgejo admin user create --admin

View file

@ -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

1
roles/friwall/files/motd Normal file
View file

@ -0,0 +1 @@
Welcome to the wall. Trespassers will be shot. Survivors will be shot again.

View file

@ -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 $?
}

View file

@ -0,0 +1,2 @@
[uwsgi]
emperor = /etc/uwsgi/conf.d

View file

@ -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"

View file

@ -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: '{{ password.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"

View file

@ -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 %}

View file

@ -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 %}
}

View file

@ -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;
}
}

View file

@ -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 %}
}

View file

@ -0,0 +1,10 @@
{
"ldap_host": "{{ domain }}",
"ldap_user": "{{ password.ldap_user }}",
"ldap_pass": "{{ password.ldap_pass }}",
"ldap_base_dn": "{{ ldap_base_dn }}",
"oidc_server": "{{ password.oidc_server }}",
"oidc_client_id": "{{ password.oidc_client_id }}",
"oidc_client_secret": "{{ password.oidc_client_secret }}",
"wg_net": "{{ wg_net }}"
}

View file

@ -84,11 +84,11 @@
- key: "^REMOTE_AUTH_BACKEND =" - key: "^REMOTE_AUTH_BACKEND ="
line: "REMOTE_AUTH_BACKEND = 'social_core.backends.open_id_connect.OpenIdConnectAuth'" line: "REMOTE_AUTH_BACKEND = 'social_core.backends.open_id_connect.OpenIdConnectAuth'"
- key: "^SOCIAL_AUTH_OIDC_OIDC_ENDPOINT =" - 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 =" - 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 =" - 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 # TODO the key should really be upn but it doesn’t seem to work
- key: "^SOCIAL_AUTH_OIDC_USERNAME_KEY =" - key: "^SOCIAL_AUTH_OIDC_USERNAME_KEY ="
line: "SOCIAL_AUTH_OIDC_USERNAME_KEY = 'email'" line: "SOCIAL_AUTH_OIDC_USERNAME_KEY = 'email'"
@ -113,10 +113,10 @@
import sys import sys
from users.models import User from users.models import User
#from django.contrib.auth.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): if not User.objects.filter(username=username):
User.objects.create_superuser(username, '', # TODO email User.objects.create_superuser(username, '', # TODO email
'{{ lookup('passwordstore', 'vm/'~inventory_hostname, subkey='admin_pass') }}') '{{ password.admin_pass }}')
sys.exit(1) sys.exit(1)
register: result register: result
changed_when: result.rc != 0 changed_when: result.rc != 0

View file

@ -0,0 +1,9 @@
- name: Install mail server
package:
name: opensmtpd
- name: Enable mail server
service:
name: smtpd
enabled: yes
state: started

View file

@ -6,11 +6,11 @@ import re
import ldap3 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"] }}' realm = '{{ hostvars[inventory_hostname]["sync-ldap"] }}'
ldap_host = '{{ domain }}' ldap_host = '{{ domain }}'
ldap_user = '{{ password.ldap_user }}' ldap_user = '{{ cluster_password.ldap_user }}'
ldap_pass = '{{ password.ldap_pass }}' ldap_pass = '{{ cluster_password.ldap_pass }}'
ldap_base = '{{ domain | split(".") | map("regex_replace", "^", "dc=") | join(",") }}' ldap_base = '{{ domain | split(".") | map("regex_replace", "^", "dc=") | join(",") }}'
# build LDAP query for users # build LDAP query for users

View file

@ -1,6 +1,3 @@
- set_fact:
password: '{{ lookup("passwordstore", "vm/"~inventory_hostname, returnall=true) | from_yaml }}'
- name: Install packages - name: Install packages
package: package:
name: synapse name: synapse

View file

@ -3,6 +3,13 @@
roles: roles:
- facts - facts
- hosts: zid
roles:
- alpine
- opensmtpd
- nginx
- friwall
- hosts: ceph-* - hosts: ceph-*
roles: roles:
- debian - debian