From 5e4bf1268c266e63d0e92e845ad910a2103b86ff Mon Sep 17 00:00:00 2001 From: Roman Ilin Date: Mon, 15 Jun 2026 12:59:09 +0300 Subject: --- .../host/files/60-machinectl-fast-user-auth.rules | 6 + roles/host/handlers/main.yaml | 25 +++ roles/host/tasks/main.yaml | 195 +++++++++++++++++++++ roles/host/templates/nginx-reload.sh.j2 | 3 + roles/host/templates/nginx.conf.j2 | 67 +++++++ roles/host/templates/nspawn.j2 | 10 ++ 6 files changed, 306 insertions(+) create mode 100644 roles/host/files/60-machinectl-fast-user-auth.rules create mode 100644 roles/host/handlers/main.yaml create mode 100644 roles/host/tasks/main.yaml create mode 100644 roles/host/templates/nginx-reload.sh.j2 create mode 100644 roles/host/templates/nginx.conf.j2 create mode 100644 roles/host/templates/nspawn.j2 (limited to 'roles/host') diff --git a/roles/host/files/60-machinectl-fast-user-auth.rules b/roles/host/files/60-machinectl-fast-user-auth.rules new file mode 100644 index 0000000..eee39ea --- /dev/null +++ b/roles/host/files/60-machinectl-fast-user-auth.rules @@ -0,0 +1,6 @@ +polkit.addRule(function(action, subject) { + if(action.id == "org.freedesktop.machine1.host-shell" && + subject.isInGroup("wheel")) { + return polkit.Result.YES; + } +}); diff --git a/roles/host/handlers/main.yaml b/roles/host/handlers/main.yaml new file mode 100644 index 0000000..e643580 --- /dev/null +++ b/roles/host/handlers/main.yaml @@ -0,0 +1,25 @@ +- name: Reload Firewalld + ansible.builtin.systemd: + name: firewalld + state: reloaded + +- name: Restart Host systemd-networkd + ansible.builtin.systemd: + name: systemd-networkd + state: restarted + +- name: Restart Containers + ansible.builtin.systemd: + name: "systemd-nspawn@{{ item.key }}" + state: restarted + loop: "{{ containers | dict2items }}" + +- name: Restart Nginx + ansible.builtin.systemd: + name: nginx + state: restarted + +- name: Restart Polkit + ansible.builtin.systemd: + name: polkit + state: restarted diff --git a/roles/host/tasks/main.yaml b/roles/host/tasks/main.yaml new file mode 100644 index 0000000..007d44d --- /dev/null +++ b/roles/host/tasks/main.yaml @@ -0,0 +1,195 @@ +- name: Install firewalld and systemd-networkd packages + ansible.builtin.dnf: + name: + - firewalld + - systemd-container + - systemd-networkd + - openssl + - python3-libsemanage + - policycoreutils + state: present + +- name: Configure Polkit to allow machinectl fast user auth + ansible.builtin.copy: + src: 60-machinectl-fast-user-auth.rules + dest: /etc/polkit-1/rules.d/60-machinectl-fast-user-auth.rules + mode: "0644" + notify: Restart Polkit + +- name: Ensure firewalld and systemd-networkd are running + ansible.builtin.systemd: + name: "{{ item }}" + state: started + enabled: yes + loop: + - firewalld + - systemd-networkd + +- name: Enable masquerading for container internet access + ansible.posix.firewalld: + masquerade: yes + state: enabled + permanent: yes + zone: public + notify: Reload Firewalld + +- name: Open HTTP and HTTPS (TCP) for Nginx + ansible.posix.firewalld: + service: "{{ item }}" + state: enabled + permanent: yes + zone: public + loop: + - http + - https + notify: Reload Firewalld + +- name: Open HTTPS (UDP) for HTTP/3 QUIC support + ansible.posix.firewalld: + port: 443/udp + state: enabled + permanent: yes + zone: public + notify: Reload Firewalld + +- name: Configure Host Firewall Port Forwarding dynamically + ansible.posix.firewalld: + port_forward: + - port: "{{ item.1 }}" + proto: tcp + toaddr: "{{ item.0.value.ip }}" + toport: "{{ item.1 }}" + permanent: yes + state: enabled + loop: "{{ containers | dict2items | subelements('value.forward_ports', skip_missing=True) }}" + notify: Reload Firewalld + +- name: Create bridge netdev on host + ansible.builtin.copy: + dest: /etc/systemd/network/10-nspawn-br0.netdev + content: | + [NetDev] + Name=nspawn-br0 + Kind=bridge + notify: Restart Host systemd-networkd + +- name: Assign IP to the host bridge + ansible.builtin.copy: + dest: /etc/systemd/network/10-nspawn-br0.network + content: | + [Match] + Name=nspawn-br0 + [Network] + Address={{ bridge_ip }}/24 + notify: Restart Host systemd-networkd + +- name: Apply networking changes immediately + ansible.builtin.meta: flush_handlers + +- name: Ensure containers are bootstrapped (AlmaLinux 10) + ansible.builtin.dnf: + installroot: "/var/lib/machines/{{ item.key }}" + releasever: "10" + name: + - almalinux-release + - systemd + - dbus + - systemd-networkd + - passwd + - dnf + - iproute + state: present + loop: "{{ containers | dict2items }}" + +- name: Initialize machine-id for containers + ansible.builtin.command: systemd-machine-id-setup --root=/var/lib/machines/{{ item.key }} + args: + creates: "/var/lib/machines/{{ item.key }}/etc/machine-id" + loop: "{{ containers | dict2items }}" + +- name: Fix SELinux contexts in container rootfs + ansible.builtin.command: restorecon -R /var/lib/machines/{{ item.key }} + register: restorecon_result + changed_when: "'Relabeled' in restorecon_result.stdout" + loop: "{{ containers | dict2items }}" + +- name: Ensure systemd-nspawn directory exists + ansible.builtin.file: + path: /etc/systemd/nspawn + state: directory + mode: "0755" + +- name: Ensure Let's Encrypt mock directories exist + ansible.builtin.file: + path: "{{ item }}" + state: directory + mode: '0755' + loop: + - "/etc/letsencrypt/archive/{{ vault_public_domain }}" + - "/etc/letsencrypt/live/{{ vault_public_domain }}" + +- name: Check if Let's Encrypt certificate exists + ansible.builtin.stat: + path: "/etc/letsencrypt/live/{{ vault_public_domain }}/fullchain.pem" + register: le_cert + +- name: Generate self-signed fallback cert in Let's Encrypt paths if missing + ansible.builtin.command: > + openssl req -x509 -nodes -days 365 + -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 + -keyout /etc/letsencrypt/live/{{ vault_public_domain }}/privkey.pem + -out /etc/letsencrypt/live/{{ vault_public_domain }}/fullchain.pem + -subj "/CN={{ vault_public_domain }}" + args: + creates: "/etc/letsencrypt/live/{{ vault_public_domain }}/fullchain.pem" + when: not le_cert.stat.exists + +- name: Create systemd-nspawn configuration + ansible.builtin.template: + src: nspawn.j2 + dest: "/etc/systemd/nspawn/{{ item.key }}.nspawn" + loop: "{{ containers | dict2items }}" + notify: Restart Containers + +- name: Enable and Start Containers + ansible.builtin.systemd: + name: "systemd-nspawn@{{ item.key }}" + state: started + enabled: yes + loop: "{{ containers | dict2items }}" + +- name: Install Nginx + ansible.builtin.dnf: + name: nginx + state: present + +- name: Allow Nginx to connect to upstream servers (SELinux) + ansible.posix.seboolean: + name: httpd_can_network_connect + state: yes + persistent: yes + +- name: Configure Nginx dynamically + ansible.builtin.template: + src: nginx.conf.j2 + dest: /etc/nginx/nginx.conf + validate: nginx -t -c %s + notify: Restart Nginx + +- name: Ensure Nginx is enabled and running + ansible.builtin.systemd: + name: nginx + state: started + enabled: yes + +- name: Ensure Let's Encrypt renewal hook directory exists + ansible.builtin.file: + path: /etc/letsencrypt/renewal-hooks/post + state: directory + mode: '0755' + +- name: Deploy Let's Encrypt post-renewal hook for Nginx + ansible.builtin.template: + src: nginx-reload.sh.j2 + dest: /etc/letsencrypt/renewal-hooks/post/nginx-reload.sh + mode: '0755' diff --git a/roles/host/templates/nginx-reload.sh.j2 b/roles/host/templates/nginx-reload.sh.j2 new file mode 100644 index 0000000..f248776 --- /dev/null +++ b/roles/host/templates/nginx-reload.sh.j2 @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +systemctl reload nginx diff --git a/roles/host/templates/nginx.conf.j2 b/roles/host/templates/nginx.conf.j2 new file mode 100644 index 0000000..7360cae --- /dev/null +++ b/roles/host/templates/nginx.conf.j2 @@ -0,0 +1,67 @@ +user nginx; +worker_processes auto; +worker_rlimit_nofile 8192; +error_log /var/log/nginx/error.log notice; +pid /run/nginx.pid; + +events { + worker_connections 4096; +} + +http { + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + tcp_nopush on; + keepalive_timeout 65; + types_hash_max_size 4096; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Modern SSL configuration + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; + ssl_prefer_server_ciphers on; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 1d; + ssl_session_tickets off; + + # Redirect all HTTP traffic to HTTPS + server { + listen 80 default_server; + server_name _; + return 301 https://$host$request_uri; + } + +{% for name, config in containers.items() %} +{% if config.web_subdomain is defined %} + server { + listen 443 ssl; # TCP for HTTP/1.1 & HTTP/2 + listen 443 quic; # UDP for HTTP/3 QUIC + http2 on; # Enable HTTP/2 over TCP + + server_name {{ config.web_subdomain }}.{{ vault_public_domain }}; + + # Nginx reads them natively, no combining needed + ssl_certificate /etc/letsencrypt/live/{{ vault_public_domain }}/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/{{ vault_public_domain }}/privkey.pem; + + # Advertise HTTP/3 availability to browsers + add_header Alt-Svc 'h3=":443"; ma=2592000' always; + + location / { + proxy_pass http://{{ config.ip }}:80; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto https; + } + } +{% endif %} +{% endfor %} +} diff --git a/roles/host/templates/nspawn.j2 b/roles/host/templates/nspawn.j2 new file mode 100644 index 0000000..15faf53 --- /dev/null +++ b/roles/host/templates/nspawn.j2 @@ -0,0 +1,10 @@ +[Exec] +Boot=yes +Hostname={{ item.key }}.{{ vault_public_domain }} + +[Files] +BindReadOnly=/etc/letsencrypt/live/{{ vault_public_domain }}:/etc/letsencrypt/live/{{ vault_public_domain }} +BindReadOnly=/etc/letsencrypt/archive/{{ vault_public_domain }}:/etc/letsencrypt/archive/{{ vault_public_domain }} + +[Network] +Bridge=nspawn-br0 -- cgit