From de3373e97d133e0ac76fb44deb5dea27c18d8815 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Romain=20Gon=C3=A7alves?= Date: Sat, 11 Dec 2021 18:50:33 +0000 Subject: roles: Add pf and relayd roles for domain controller --- group_vars/all.yml | 10 ++++ host_vars/dc0.yml | 17 +++++++ host_vars/stack0-dev0.yml | 10 ++++ playbooks/site.yml | 9 ++++ roles/acme/defaults/main.yml | 2 + roles/acme/tasks/main.yml | 31 ++++++++++++ roles/acme/templates/acme-client.conf.j2 | 26 ++++++++++ roles/cgit/defaults/main.yml | 8 ++-- roles/pf/defaults/main.yml | 1 + roles/pf/handlers/main.yml | 2 + roles/pf/tasks/main.yml | 25 ++++++++++ roles/pf/templates/pf.conf.j2 | 24 ++++++++++ roles/relayd/defaults/main.yml | 4 ++ roles/relayd/tasks/main.yml | 13 +++++ roles/relayd/templates/relayd.conf.j2 | 82 ++++++++++++++++++++++++++++++++ 15 files changed, 260 insertions(+), 4 deletions(-) create mode 100644 roles/acme/defaults/main.yml create mode 100644 roles/acme/tasks/main.yml create mode 100644 roles/acme/templates/acme-client.conf.j2 create mode 100644 roles/pf/defaults/main.yml create mode 100644 roles/pf/handlers/main.yml create mode 100644 roles/pf/tasks/main.yml create mode 100644 roles/pf/templates/pf.conf.j2 create mode 100644 roles/relayd/defaults/main.yml create mode 100644 roles/relayd/tasks/main.yml create mode 100644 roles/relayd/templates/relayd.conf.j2 diff --git a/group_vars/all.yml b/group_vars/all.yml index 380aa39..91af459 100644 --- a/group_vars/all.yml +++ b/group_vars/all.yml @@ -2,6 +2,8 @@ ansible_hostname: "{{ ansible_host }}" ansible_become_method: su wireguard_domain_controller: "{{ __global_domain_controller }}" +relayd_domain_name: "{{ __global_domain_name }}" +acme_domain_name: "{{ __global_domain_name }}" nfsclient_server: stack0 httpd_use_nfs: true @@ -19,3 +21,11 @@ __global_domain_name_hosts: owo __global_domain_name_servers: - 8.8.8.8 - 1.1.1.1 + +# __services: +# - domain: status.test +# is_public: true +# port: 120 +# protocols: +# - tcp +# - udp diff --git a/host_vars/dc0.yml b/host_vars/dc0.yml index d145b81..6bc7c96 100644 --- a/host_vars/dc0.yml +++ b/host_vars/dc0.yml @@ -5,3 +5,20 @@ __is_vm: true __ip: external: 185.203.114.234 internal: 10.10.0.1 + +__services: + - name: ssh + protocol: tcp + port: 22 + + - name: wireguard + protocol: udp + port: 53 + + - name: http + protocol: tcp + port: 80 + + - name: https + protocol: tcp + port: 443 diff --git a/host_vars/stack0-dev0.yml b/host_vars/stack0-dev0.yml index aa571d4..98db313 100644 --- a/host_vars/stack0-dev0.yml +++ b/host_vars/stack0-dev0.yml @@ -3,3 +3,13 @@ __is_vm: true __ip: external: 192.168.5.61 internal: 10.10.0.61 + +__services: + - name: ssh + protocol: tcp + port: 22 + + - name: cgit + domain: git + protocol: tcp + port: 1235 diff --git a/playbooks/site.yml b/playbooks/site.yml index eef50b8..af002a6 100644 --- a/playbooks/site.yml +++ b/playbooks/site.yml @@ -3,6 +3,15 @@ - role: wireguard tags: role_wireguard +- hosts: dc0 + roles: + - role: pf + tags: role_pf + - role: relayd + tags: role_relayd + - role: acme + tags: role_acme + - hosts: servers roles: - role: sshd diff --git a/roles/acme/defaults/main.yml b/roles/acme/defaults/main.yml new file mode 100644 index 0000000..80c091a --- /dev/null +++ b/roles/acme/defaults/main.yml @@ -0,0 +1,2 @@ +acme_configuration_file: /etc/acme-client.conf +acme_domain_name: null diff --git a/roles/acme/tasks/main.yml b/roles/acme/tasks/main.yml new file mode 100644 index 0000000..aad4342 --- /dev/null +++ b/roles/acme/tasks/main.yml @@ -0,0 +1,31 @@ +- name: generate acme-client configuration + template: + src: acme-client.conf.j2 + dest: "{{ acme_configuration_file }}" + owner: 0 + group: 0 + mode: 0644 + +- name: retrieve enabled domains + shell: grep "^domain" /etc/acme-client.conf | cut -d " " -f 2 + register: subdomains + +- name: generate acme certificates + command: acme-client -v {{ item }} + loop: "{{ subdomains.stdout_lines }}" + register: result + failed_when: + - result.rc != 0 + - "'certificate valid' not in result.stderr" + +- name: display registered certificates + debug: + var: result + +- name: enable automatic acme certificates update + cron: + name: "automatic acme certificates update for subdomain : {{ item }}" + minute: 0 + hour: 6,18 + job: "acme-client -v {{ item }} && rcctl reload relayd" + loop: "{{ subdomains.stdout_lines }}" diff --git a/roles/acme/templates/acme-client.conf.j2 b/roles/acme/templates/acme-client.conf.j2 new file mode 100644 index 0000000..3792009 --- /dev/null +++ b/roles/acme/templates/acme-client.conf.j2 @@ -0,0 +1,26 @@ +# managed by Ansible +{% import 'macros.j2' as macros with context %} + +authority letsencrypt { + api url "https://acme-v02.api.letsencrypt.org/directory" + account key "/etc/acme/letsencrypt-privkey.pem" +} + +domain {{ acme_domain_name }} { + alternative names { www.{{ acme_domain_name }} } + domain key "/etc/ssl/private/{{ acme_domain_name }}.key" + domain full chain certificate "/etc/ssl/{{ acme_domain_name }}.crt" + sign with letsencrypt +} + +{% call(h) macros.loop_valid_hosts("servers") -%} +{% for service in h.__services if service.domain is defined %} +domain {{ service.domain }}.{{ acme_domain_name }} { + {% set domain = service.domain ~ "." ~ acme_domain_name %} + alternative names { www.{{ domain }} } + domain key "/etc/ssl/private/{{ domain }}.key" + domain full chain certificate "/etc/ssl/{{ domain }}.crt" + sign with letsencrypt +} +{% endfor %} +{%- endcall %} diff --git a/roles/cgit/defaults/main.yml b/roles/cgit/defaults/main.yml index 0c60bd5..715c258 100644 --- a/roles/cgit/defaults/main.yml +++ b/roles/cgit/defaults/main.yml @@ -6,11 +6,11 @@ cgit_ip: 0.0.0.0 cgit_port: 1235 cgit_authenticate: false -cgit__favicon: http://rgoncalves.se/logo.png -cgit__logo: http://rgoncalves.se/logo.png -cgit__css: http://rgoncalves.se/style/cgit.css +cgit__favicon: https://rgoncalves.se/logo.png +cgit__logo: https://rgoncalves.se/logo.png +cgit__css: https://rgoncalves.se/style/cgit.css cgit__root_desc: development hub -cgit__root_readme: http://rgoncalves.se +cgit__root_readme: https://rgoncalves.se cgit__footer: /conf/footer.html cgit__clone_urls: - git://git.{{ __global_domain_name }}/$CGIT_REPO_URL diff --git a/roles/pf/defaults/main.yml b/roles/pf/defaults/main.yml new file mode 100644 index 0000000..777717d --- /dev/null +++ b/roles/pf/defaults/main.yml @@ -0,0 +1 @@ +pf_configuration_file: /etc/pf.conf diff --git a/roles/pf/handlers/main.yml b/roles/pf/handlers/main.yml new file mode 100644 index 0000000..187e769 --- /dev/null +++ b/roles/pf/handlers/main.yml @@ -0,0 +1,2 @@ +- name: lint pf configuration + command: "pfctl -nf {{ pf_configuration_file }}" diff --git a/roles/pf/tasks/main.yml b/roles/pf/tasks/main.yml new file mode 100644 index 0000000..4fafb77 --- /dev/null +++ b/roles/pf/tasks/main.yml @@ -0,0 +1,25 @@ +- name: generate pf configuration + template: + src: pf.conf.j2 + dest: "{{ pf_configuration_file }}" + owner: 0 + group: 0 + mode: 0600 + notify: + - lint pf configuration + +- name: enable pf + command: pfctl -e + register: result + failed_when: + - result.rc != 0 + - "'already enabled' not in result.stderr" + +- name: restart pf + command: pfctl -f "{{ pf_configuration_file }}" + +- name: test ssh connection on new pf rule + wait_for: + port: 22 + delay: 2 + state: started diff --git a/roles/pf/templates/pf.conf.j2 b/roles/pf/templates/pf.conf.j2 new file mode 100644 index 0000000..6bc936a --- /dev/null +++ b/roles/pf/templates/pf.conf.j2 @@ -0,0 +1,24 @@ +# managed by Ansible +{% import 'macros.j2' as macros with context %} + +# common configuration +set block-policy drop +set loginterface egress +set skip on { lo wg0 } +block all + +# force ssh if not present below +pass in quick on egress proto tcp to port 22 + +# host services +{% for service in __services %} +pass in quick on egress proto {{ service["protocol"] }} to port {{ service["port"] }} +{% endfor %} + +# wireguard +pass in on egress inet proto udp from any to any port 50000 +pass out quick on egress inet from (wg0:network) nat-to (egress:0) + +# output network +pass out quick inet +pass in proto { icmp, icmp6 } all diff --git a/roles/relayd/defaults/main.yml b/roles/relayd/defaults/main.yml new file mode 100644 index 0000000..174a889 --- /dev/null +++ b/roles/relayd/defaults/main.yml @@ -0,0 +1,4 @@ +relayd_configuration_file: /etc/relayd.conf +relayd_domain_name: example.com +relayd_transparent: true +relayd_block_msg: aah! diff --git a/roles/relayd/tasks/main.yml b/roles/relayd/tasks/main.yml new file mode 100644 index 0000000..14df192 --- /dev/null +++ b/roles/relayd/tasks/main.yml @@ -0,0 +1,13 @@ +- name: generate relayd configuration + template: + src: relayd.conf.j2 + dest: "{{ relayd_configuration_file }}" + +- name: verify relayd configuration + command: "relayd -nf {{ relayd_configuration_file }}" + +- name: enable and restart relayd + service: + name: relayd + state: restarted + enabled: true diff --git a/roles/relayd/templates/relayd.conf.j2 b/roles/relayd/templates/relayd.conf.j2 new file mode 100644 index 0000000..c97e9da --- /dev/null +++ b/roles/relayd/templates/relayd.conf.j2 @@ -0,0 +1,82 @@ +# managed by Ansible +{% import 'macros.j2' as macros with context %} + +# general +log connection errors + +# hosts +table { 127.0.0.1 } +{% call(h) macros.loop_valid_hosts("servers") -%} +table <{{ h.inventory_hostname }}> { {{ h.__ip.internal }} } +{% for service in h.__services if service.domain is defined %} +table <{{ h.inventory_hostname }}_{{ service.domain }}> { {{ h.__ip.internal }} } +{% endfor %} +{%- endcall %} + +# protocols + +http protocol "https" { + + tls ciphers "HIGH:!AES128:!kRSA:!aNULL" + tls ecdhe "P-384,P-256,X25519" + + tcp { sack, backlog 128 } + + match request header append "X-Forwarded-For" value "$REMOTE_ADDR" + match request header append "X-Forwarded-By" value "$SERVER_ADDR:$SERVER_PORT" + match request header set "Connection" value "close" + match request header set "X-Forwarded-Proto" value "https" + match request header set "X-Forwarded-Port" value "443" + match response header set "Content-Security-Policy" value "upgrade-insecure-requests" + match response header set "Referrer-Policy" value "no-referrer" + match response header set "X-XSS-Protection" value "1; mode=block" + + tls keypair "{{ relayd_domain_name }}" + pass request quick header "Host" value "{{ relayd_domain_name }}" forward to +{% call(h) macros.loop_valid_hosts("servers") -%} +{% for service in h.__services if service.domain is defined %} + {% set domain_name = service.domain ~ "." ~ relayd_domain_name -%} + tls keypair "{{ domain_name }}" + pass request quick header "Host" value "{{ domain_name }}" forward to <{{ h.inventory_hostname }}_{{ service.domain }}> +{% endfor %} +{%- endcall %} + + block label "{{ relayd_block_msg }}" + return error +} + +http protocol "http" { + + # acme + pass request quick path "/.well-known/acme-challenge/*" forward to + + pass request quick header "Host" value "{{ relayd_domain_name }}" forward to +{% call(h) macros.loop_valid_hosts("servers") -%} +{% for service in h.__services if service.domain is defined %} + {% set domain_name = service.domain ~ "." ~ relayd_domain_name -%} + pass request quick header "Host" value "{{ domain_name }}" forward to <{{ h.inventory_hostname }}_{{ service.domain }}> +{% endfor %} +{%- endcall %} + + return error +} + +# relays + +relay "www" { + listen on egress port 80 + protocol "http" + # assume httpd reverse proxy is running for https redirection + forward to port 8888 check icmp +} + +relay "wwwtls" { + listen on egress port 443 tls + protocol "https" + forward to port 80 check http "/" code 200 +{% call(h) macros.loop_valid_hosts("servers") -%} +{% for service in h.__services if service.domain is defined %} + forward to <{{ h.inventory_hostname }}_{{ service.domain }}> port {{ service.port }} check tcp +{% endfor %} +{%- endcall %} +} -- cgit v1.2.3