From fb0b3914086484d9284426985984e2c1699ba557 Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Fri, 5 Jan 2024 16:13:01 -0500 Subject: [PATCH] ldap, internal CA, internal webserver, dns, etc. --- .env.sample | 5 + README.md | 6 +- deploy-ca.yml | 31 ++++++ common.yml => deploy-common.yml | 0 deploy-lldap.yml | 4 + deploy-pihole.yml | 4 + deploy-private.yml | 4 + deploy-vpn-tags.yml => deploy-vpn-hosts.yml | 0 group_vars/all.yml | 8 ++ group_vars/ca.yml | 6 ++ group_vars/lldap.yml | 3 + group_vars/pihole.yml | 2 + inventory | 9 ++ .../authelia/templates/docker-compose.yml.j2 | 4 +- roles/ca/tasks/main.yml | 15 +++ roles/ca/templates/crt.j2 | 1 + roles/common/tasks/systemd-resolved.yml | 10 ++ roles/lldap/tasks/main.yml | 28 ++++++ roles/lldap/templates/docker-compose.yml.j2 | 18 ++++ .../nameservers/templates/db.simponic.xyz.j2 | 5 - roles/pihole/tasks/main.yml | 36 +++++++ roles/pihole/templates/docker-compose.yml.j2 | 18 ++++ .../johan/http.ca.internal.simponic.xyz.conf | 13 +++ .../http.lldap.internal.simponic.xyz.conf | 13 +++ .../http.pihole.internal.simponic.xyz.conf | 13 +++ .../johan/https.ca.internal.simponic.xyz.conf | 32 +++++++ .../https.lldap.internal.simponic.xyz.conf | 32 +++++++ .../https.pihole.internal.simponic.xyz.conf | 32 +++++++ roles/private/files/nginx.conf | 27 ++++++ roles/private/tasks/main.yml | 95 +++++++++++++++++++ roles/vpn/files/config/acl.json | 11 ++- roles/vpn/files/docker-compose.yml | 4 +- roles/vpn/templates/config.yml.j2 | 6 +- 33 files changed, 480 insertions(+), 15 deletions(-) create mode 100644 deploy-ca.yml rename common.yml => deploy-common.yml (100%) create mode 100644 deploy-lldap.yml create mode 100644 deploy-pihole.yml create mode 100644 deploy-private.yml rename deploy-vpn-tags.yml => deploy-vpn-hosts.yml (100%) create mode 100644 group_vars/ca.yml create mode 100644 group_vars/lldap.yml create mode 100644 group_vars/pihole.yml create mode 100644 roles/ca/tasks/main.yml create mode 100644 roles/ca/templates/crt.j2 create mode 100644 roles/lldap/tasks/main.yml create mode 100644 roles/lldap/templates/docker-compose.yml.j2 create mode 100644 roles/pihole/tasks/main.yml create mode 100644 roles/pihole/templates/docker-compose.yml.j2 create mode 100644 roles/private/files/johan/http.ca.internal.simponic.xyz.conf create mode 100644 roles/private/files/johan/http.lldap.internal.simponic.xyz.conf create mode 100644 roles/private/files/johan/http.pihole.internal.simponic.xyz.conf create mode 100644 roles/private/files/johan/https.ca.internal.simponic.xyz.conf create mode 100644 roles/private/files/johan/https.lldap.internal.simponic.xyz.conf create mode 100644 roles/private/files/johan/https.pihole.internal.simponic.xyz.conf create mode 100644 roles/private/files/nginx.conf create mode 100644 roles/private/tasks/main.yml diff --git a/.env.sample b/.env.sample index c3e0e81..120ed58 100644 --- a/.env.sample +++ b/.env.sample @@ -1,2 +1,7 @@ HEADSCALE_PREAUTH_KEY= HEADSCALE_OIDC_SECRET= +LLDAP_USER_PASS= +LLDAP_JWT_SECRET= +PIHOLE_WEBPWD= +STEP_CA_ROOT_PASSWORD= +STEP_CA_INTERMEDIATE_PASSWORD= diff --git a/README.md b/README.md index c91b725..e57f075 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ order: - - common.yml + - deploy-common.yml - deploy-nameservers.yml - deploy-webservers.yml + - deploy-lldap.yml - deploy-authelia.yml + - deploy-pihole.yml - deploy-vpn.yml - - deploy-vpn-tags.yml + - deploy-vpn-hosts.yml diff --git a/deploy-ca.yml b/deploy-ca.yml new file mode 100644 index 0000000..699fc5f --- /dev/null +++ b/deploy-ca.yml @@ -0,0 +1,31 @@ +- name: add acme CA + hosts: ca + become: yes + roles: + - role: maxhoesel.smallstep.step_ca + tasks: + - name: add an acme provisioner to the ca + maxhoesel.smallstep.step_ca_provisioner: + name: ACME + type: ACME + become_user: step-ca + - name: restart step-ca + ansible.builtin.systemd_service: + name: step-ca + state: restarted + enabled: true + - name: allow step-ca port traffic on vpn + ufw: + rule: allow + from: 100.64.0.0/10 + port: "{{ step_ca_port }}" + - name: restart ufw + ansible.builtin.systemd_service: + name: ufw + state: restarted + enabled: true + +- name: configure trust to internal ca on all hosts + hosts: all + roles: + - ca diff --git a/common.yml b/deploy-common.yml similarity index 100% rename from common.yml rename to deploy-common.yml diff --git a/deploy-lldap.yml b/deploy-lldap.yml new file mode 100644 index 0000000..d632d92 --- /dev/null +++ b/deploy-lldap.yml @@ -0,0 +1,4 @@ +- name: lldap setup + hosts: lldap + roles: + - lldap diff --git a/deploy-pihole.yml b/deploy-pihole.yml new file mode 100644 index 0000000..59644ae --- /dev/null +++ b/deploy-pihole.yml @@ -0,0 +1,4 @@ +- name: pihole setup + hosts: pihole + roles: + - pihole diff --git a/deploy-private.yml b/deploy-private.yml new file mode 100644 index 0000000..4274a21 --- /dev/null +++ b/deploy-private.yml @@ -0,0 +1,4 @@ +- name: private setup + hosts: private + roles: + - private diff --git a/deploy-vpn-tags.yml b/deploy-vpn-hosts.yml similarity index 100% rename from deploy-vpn-tags.yml rename to deploy-vpn-hosts.yml diff --git a/group_vars/all.yml b/group_vars/all.yml index ff86d8b..6a4a738 100644 --- a/group_vars/all.yml +++ b/group_vars/all.yml @@ -1,6 +1,14 @@ --- +johan_ip: '100.64.0.5' + dns_servers: - 1.1.1.1 - 1.0.0.1 dns_dnssec: true dns_domains: ["simponic.xyz"] +dns_stub_listener: false + +step_bootstrap_fingerprint: '2de0c420e3b6f9f8e47f325de908b2b2d395d3bc7e49ed9b672ce9be89bea1bf' +step_bootstrap_ca_url: 'ca.internal.simponic.xyz' +step_acme_cert_contact: 'elizabeth.hunt@simponic.xyz' +step_ca_port: 5239 diff --git a/group_vars/ca.yml b/group_vars/ca.yml new file mode 100644 index 0000000..9ef5c52 --- /dev/null +++ b/group_vars/ca.yml @@ -0,0 +1,6 @@ +--- +step_ca_root_password: "{{ lookup('env', 'STEP_CA_ROOT_PASSWORD') }}" +step_ca_intermediate_password: "{{ lookup('env', 'STEP_CA_INTERMEDIATE_PASSWORD') }}" +step_ca_dns: "{{ step_bootstrap_ca_url }}, {{ johan_ip }}" +step_ca_name: Simponic Internal CA +step_ca_address: ":{{ step_ca_port }}" diff --git a/group_vars/lldap.yml b/group_vars/lldap.yml new file mode 100644 index 0000000..2322e7f --- /dev/null +++ b/group_vars/lldap.yml @@ -0,0 +1,3 @@ +--- +lldap_jwt_secret: "{{ lookup('env', 'LLDAP_JWT_SECRET') }}" +lldap_user_pass: "{{ lookup('env', 'LLDAP_USER_PASS') }}" diff --git a/group_vars/pihole.yml b/group_vars/pihole.yml new file mode 100644 index 0000000..354d74c --- /dev/null +++ b/group_vars/pihole.yml @@ -0,0 +1,2 @@ +--- +pihole_webpwd: "{{ lookup('env', 'PIHOLE_WEBPWD') }}" diff --git a/inventory b/inventory index 1a1b4a1..b855b00 100644 --- a/inventory +++ b/inventory @@ -31,5 +31,14 @@ nijika ansible_user=root ansible_connection=ssh [dnsinternal] johan ansible_user=root ansible_connection=ssh +[pihole] +johan ansible_user=root ansible_connection=ssh + +[lldap] +johan ansible_user=root ansible_connection=ssh + +[ca] +johan ansible_user=root ansible_connection=ssh + [mail] #ash ansible_user=root ansible_connection=ssh diff --git a/roles/authelia/templates/docker-compose.yml.j2 b/roles/authelia/templates/docker-compose.yml.j2 index b60545f..aa53bb9 100644 --- a/roles/authelia/templates/docker-compose.yml.j2 +++ b/roles/authelia/templates/docker-compose.yml.j2 @@ -7,8 +7,10 @@ services: volumes: - ./authelia:/config ports: - - 9091:9091 + - 127.0.0.1:9091:9091 restart: unless-stopped + dns: + - {{ johan_ip }} redis: image: redis:alpine container_name: redis diff --git a/roles/ca/tasks/main.yml b/roles/ca/tasks/main.yml new file mode 100644 index 0000000..2649686 --- /dev/null +++ b/roles/ca/tasks/main.yml @@ -0,0 +1,15 @@ +--- + +- name: get root CA certificate + command: > + curl -k -X GET -H "Content-Type:application/json" \ + "https://{{ step_bootstrap_ca_url }}:{{ step_ca_port }}/root/{{ step_bootstrap_fingerprint }}" + register: root_ca_fp + +- name: copy to os certificates + template: + src: "../templates/crt.j2" + dest: "/usr/local/share/ca-certificates/{{ step_bootstrap_ca_url }}.crt" + +- name: update trusted certs + command: "update-ca-certificates" diff --git a/roles/ca/templates/crt.j2 b/roles/ca/templates/crt.j2 new file mode 100644 index 0000000..f775757 --- /dev/null +++ b/roles/ca/templates/crt.j2 @@ -0,0 +1 @@ +{{ (root_ca_fp.stdout | from_json).ca }} diff --git a/roles/common/tasks/systemd-resolved.yml b/roles/common/tasks/systemd-resolved.yml index dc40275..dbf9742 100644 --- a/roles/common/tasks/systemd-resolved.yml +++ b/roles/common/tasks/systemd-resolved.yml @@ -41,6 +41,16 @@ no_extra_spaces: true register: conf_domains +- name: stub listener + community.general.ini_file: + path: /etc/systemd/resolved.conf + section: Resolve + option: DNSStubListener + value: '{{ "yes" if dns_stub_listener else "no" }}' + mode: '0644' + no_extra_spaces: true + register: conf_domains + - name: Check if systemd-resolve runs ansible.builtin.shell: pgrep systemd-resolve failed_when: false diff --git a/roles/lldap/tasks/main.yml b/roles/lldap/tasks/main.yml new file mode 100644 index 0000000..79b9a86 --- /dev/null +++ b/roles/lldap/tasks/main.yml @@ -0,0 +1,28 @@ +--- +- name: ensure lldap docker/compose exist + file: + path: /etc/docker/compose/lldap + state: directory + owner: root + group: root + mode: 0700 + +- name: build lldap docker-compose.yml.j2 + template: + src: ../templates/docker-compose.yml.j2 + dest: /etc/docker/compose/lldap/docker-compose.yml + owner: root + group: root + mode: u=rw,g=r,o=r + +- name: daemon-reload and enable lldap + ansible.builtin.systemd_service: + state: restarted + enabled: true + name: docker-compose@lldap + +- name: allow ldap on vpn + ufw: + rule: allow + port: '3890' + from: '100.64.0.0/10' diff --git a/roles/lldap/templates/docker-compose.yml.j2 b/roles/lldap/templates/docker-compose.yml.j2 new file mode 100644 index 0000000..c4757b2 --- /dev/null +++ b/roles/lldap/templates/docker-compose.yml.j2 @@ -0,0 +1,18 @@ +version: "3" + +volumes: + lldap_data: + driver: local + +services: + lldap: + image: lldap/lldap:stable + ports: + - "{{ johan_ip }}:3890:3890" + - "127.0.0.1:17170:17170" + volumes: + - "lldap_data:/data" + environment: + - LLDAP_JWT_SECRET="{{ lldap_jwt_secret }}" + - LLDAP_LDAP_USER_PASS="{{ lldap_user_pass }}" + - LLDAP_LDAP_BASE_DN=dc=simponic,dc=xyz diff --git a/roles/nameservers/templates/db.simponic.xyz.j2 b/roles/nameservers/templates/db.simponic.xyz.j2 index 5861870..58db1a0 100644 --- a/roles/nameservers/templates/db.simponic.xyz.j2 +++ b/roles/nameservers/templates/db.simponic.xyz.j2 @@ -38,8 +38,3 @@ simponic.xyz. 1 IN MX 10 mail.simponic.xyz. ;; TXT Records dkim._domainkey.simponic.xyz. 1 IN TXT "v=DKIM1; p= MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoNWLcbrcGq0z8f0fSmxLbfK/Q/ZwmnPyJEfljS2VuDIm7DUXahHIFtB8hfZ/WAocoirb8kUHTvTAgmUOXPpNxTDve3tV9S+CBBYHH2c9XBsuaZn/Vi0TR5vbBDuISmlXT6k+2cdq0LO+PYRwJI65t/JWTR5fQlCmVgxbI5gwDYFRZC0Nl5gEwuKw7pdEJg4Pymyox" "i" "zcikaGk/plXj6BDvv9pK1q8Wa+QPIkuBPFvsEh3KSApMP1p5thzHFaeNyCn5PuYEvbgkal0722px6GvYfR2W/APNRztbmWVewXH6kEWCgOYMkmWiYYLgEwz62rq2SzszP1rrl3WjVi26916wIDAQAB" simponic.xyz. 1 IN TXT "v=spf1 mx ip4:192.3.248.205 ~all" - -_acme-challenge.simponic.xyz. 1 IN TXT "6GcrSuBAj8pNKqiCBWUIONRJjbDVtOizadBDGngW4-U" -_acme-challenge.simponic.xyz. 1 IN TXT "IErMxNBY3v1Wc3RV06khZXKDC1f9EObYHSXETK5ZKYE" -_acme-challenge.simponic.xyz. 1 IN TXT "3jWQSVbU-SdebeymX58bir5kEHG1dVHRXYE-P_4Qao8" -_acme-challenge.simponic.xyz. 1 IN TXT "LXaVBwQCanWKF4cNxzPHHPM9N-kkjhn8_4r6RsveBqs" diff --git a/roles/pihole/tasks/main.yml b/roles/pihole/tasks/main.yml new file mode 100644 index 0000000..0467b80 --- /dev/null +++ b/roles/pihole/tasks/main.yml @@ -0,0 +1,36 @@ +--- +- name: ensure pihole docker/compose exist + file: + path: /etc/docker/compose/pihole + state: directory + owner: root + group: root + mode: 0700 + +- name: build pihole docker-compose.yml.j2 + template: + src: ../templates/docker-compose.yml.j2 + dest: /etc/docker/compose/pihole/docker-compose.yml + owner: root + group: root + mode: u=rw,g=r,o=r + +- name: daemon-reload and enable pihole + ansible.builtin.systemd_service: + state: restarted + enabled: true + name: docker-compose@pihole + +- name: allow dns queries in vpn/tcp + ufw: + rule: allow + from: '100.64.0.0/10' + port: '53' + proto: 'tcp' + +- name: allow dns queries in vpn/udp + ufw: + rule: allow + from: '100.64.0.0/10' + port: '53' + proto: 'udp' diff --git a/roles/pihole/templates/docker-compose.yml.j2 b/roles/pihole/templates/docker-compose.yml.j2 new file mode 100644 index 0000000..ed98d52 --- /dev/null +++ b/roles/pihole/templates/docker-compose.yml.j2 @@ -0,0 +1,18 @@ +version: "3" + +services: + pihole: + container_name: pihole + image: pihole/pihole:latest + ports: + - "{{ johan_ip }}:53:53/tcp" + - "{{ johan_ip }}:53:53/udp" + - "127.0.0.1:53:53/tcp" + - "127.0.0.1:53:53/udp" + - "127.0.0.1:9135:80/tcp" + environment: + WEBPASSWORD: '{{ pihole_webpwd }}' + volumes: + - './etc-pihole:/etc/pihole' + - './etc-dnsmasq.d:/etc/dnsmasq.d' + restart: unless-stopped diff --git a/roles/private/files/johan/http.ca.internal.simponic.xyz.conf b/roles/private/files/johan/http.ca.internal.simponic.xyz.conf new file mode 100644 index 0000000..f1cea50 --- /dev/null +++ b/roles/private/files/johan/http.ca.internal.simponic.xyz.conf @@ -0,0 +1,13 @@ +server { + listen 80; + server_name ca.internal.simponic.xyz; + + location /.well-known/acme-challenge { + root /var/www/letsencrypt; + try_files $uri $uri/ =404; + } + + location / { + rewrite ^ https://ca.internal.simponic.xyz$request_uri? permanent; + } +} diff --git a/roles/private/files/johan/http.lldap.internal.simponic.xyz.conf b/roles/private/files/johan/http.lldap.internal.simponic.xyz.conf new file mode 100644 index 0000000..8b9efe5 --- /dev/null +++ b/roles/private/files/johan/http.lldap.internal.simponic.xyz.conf @@ -0,0 +1,13 @@ +server { + listen 80; + server_name lldap.internal.simponic.xyz; + + location /.well-known/acme-challenge { + root /var/www/letsencrypt; + try_files $uri $uri/ =404; + } + + location / { + rewrite ^ https://lldap.internal.simponic.xyz$request_uri? permanent; + } +} diff --git a/roles/private/files/johan/http.pihole.internal.simponic.xyz.conf b/roles/private/files/johan/http.pihole.internal.simponic.xyz.conf new file mode 100644 index 0000000..a30ffc3 --- /dev/null +++ b/roles/private/files/johan/http.pihole.internal.simponic.xyz.conf @@ -0,0 +1,13 @@ +server { + listen 80; + server_name pihole.internal.simponic.xyz; + + location /.well-known/acme-challenge { + root /var/www/letsencrypt; + try_files $uri $uri/ =404; + } + + location / { + rewrite ^ https://pihole.internal.simponic.xyz$request_uri? permanent; + } +} diff --git a/roles/private/files/johan/https.ca.internal.simponic.xyz.conf b/roles/private/files/johan/https.ca.internal.simponic.xyz.conf new file mode 100644 index 0000000..8d7d190 --- /dev/null +++ b/roles/private/files/johan/https.ca.internal.simponic.xyz.conf @@ -0,0 +1,32 @@ +server { + listen 443 ssl; + server_name ca.internal.simponic.xyz; + + ssl_certificate /etc/letsencrypt/live/ca.internal.simponic.xyz/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/ca.internal.simponic.xyz/privkey.pem; + ssl_trusted_certificate /etc/letsencrypt/live/ca.internal.simponic.xyz/fullchain.pem; + + ssl_session_cache shared:SSL:50m; + ssl_session_timeout 5m; + ssl_stapling on; + ssl_stapling_verify on; + + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4"; + + ssl_dhparam /etc/nginx/dhparams.pem; + ssl_prefer_server_ciphers on; + + location / { + proxy_pass https://ca.internal.simponic.xyz:5239; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $server_name; + proxy_buffering off; + 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 $http_x_forwarded_proto; + add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always; + } +} diff --git a/roles/private/files/johan/https.lldap.internal.simponic.xyz.conf b/roles/private/files/johan/https.lldap.internal.simponic.xyz.conf new file mode 100644 index 0000000..98306f5 --- /dev/null +++ b/roles/private/files/johan/https.lldap.internal.simponic.xyz.conf @@ -0,0 +1,32 @@ +server { + listen 443 ssl; + server_name lldap.internal.simponic.xyz; + + ssl_certificate /etc/letsencrypt/live/lldap.internal.simponic.xyz/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/lldap.internal.simponic.xyz/privkey.pem; + ssl_trusted_certificate /etc/letsencrypt/live/lldap.internal.simponic.xyz/fullchain.pem; + + ssl_session_cache shared:SSL:50m; + ssl_session_timeout 5m; + ssl_stapling on; + ssl_stapling_verify on; + + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4"; + + ssl_dhparam /etc/nginx/dhparams.pem; + ssl_prefer_server_ciphers on; + + location / { + proxy_pass http://127.0.0.1:17170; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $server_name; + proxy_buffering off; + 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 $http_x_forwarded_proto; + add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always; + } +} diff --git a/roles/private/files/johan/https.pihole.internal.simponic.xyz.conf b/roles/private/files/johan/https.pihole.internal.simponic.xyz.conf new file mode 100644 index 0000000..b231f3f --- /dev/null +++ b/roles/private/files/johan/https.pihole.internal.simponic.xyz.conf @@ -0,0 +1,32 @@ +server { + listen 443 ssl; + server_name pihole.internal.simponic.xyz; + + ssl_certificate /etc/letsencrypt/live/pihole.internal.simponic.xyz/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/pihole.internal.simponic.xyz/privkey.pem; + ssl_trusted_certificate /etc/letsencrypt/live/pihole.internal.simponic.xyz/fullchain.pem; + + ssl_session_cache shared:SSL:50m; + ssl_session_timeout 5m; + ssl_stapling on; + ssl_stapling_verify on; + + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4"; + + ssl_dhparam /etc/nginx/dhparams.pem; + ssl_prefer_server_ciphers on; + + location / { + proxy_pass http://127.0.0.1:9135; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $server_name; + proxy_buffering off; + 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 $http_x_forwarded_proto; + add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always; + } +} diff --git a/roles/private/files/nginx.conf b/roles/private/files/nginx.conf new file mode 100644 index 0000000..f978052 --- /dev/null +++ b/roles/private/files/nginx.conf @@ -0,0 +1,27 @@ +user www-data; +worker_processes 4; +pid /run/nginx.pid; +load_module modules/ndk_http_module.so; + +events { + worker_connections 768; +} + +http { + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + include /etc/nginx/mime.types; + default_type application/octet-stream; + + access_log /var/log/nginx/access.log; + error_log /var/log/nginx/error.log; + + gzip on; + gzip_disable "msie6"; + + include /etc/nginx/conf.d/*.conf; + include /etc/nginx/sites-enabled/*; +} diff --git a/roles/private/tasks/main.yml b/roles/private/tasks/main.yml new file mode 100644 index 0000000..5c4eb7e --- /dev/null +++ b/roles/private/tasks/main.yml @@ -0,0 +1,95 @@ +--- +- name: allow http from vpn + ufw: + rule: allow + port: '80' + proto: tcp + from: 100.64.0.0/10 + +- name: allow https from vpn + ufw: + rule: allow + port: '443' + proto: tcp + from: 100.64.0.0/10 + +- name: restart ufw + service: name=ufw state=restarted enabled=yes + +- name: install letsencrypt + apt: name=letsencrypt state=latest + +- name: create letsencrypt directory + file: name=/var/www/letsencrypt state=directory + +- name: install nginx + apt: name=nginx state=latest + +- name: remove default nginx + file: name=/etc/nginx/sites-enabled/default state=absent + +- name: generate dhparams + shell: openssl dhparam -out /etc/nginx/dhparams.pem 2048 + args: + creates: /etc/nginx/dhparams.pem + +- name: add system nginx config + template: + src: ../files/nginx.conf + dest: /etc/nginx/nginx.conf + +- name: copy http nginx configuration for each domain + copy: + src: "{{ item }}" + dest: "/etc/nginx/sites-enabled/" + with_fileglob: + - "files/{{ inventory_hostname }}/http.*.conf" + +- name: restart nginx to get letsencrypt certificate + service: name=nginx state=restarted enabled=yes + +- name: find deployed domains + ansible.builtin.find: + paths: "/etc/nginx/sites-enabled/" + patterns: "http.*.conf" + register: nginx_conf_files + delegate_to: "{{ inventory_hostname }}" + +- name: extract domains from deployed nginx configurations + shell: | + grep -oP 'server_name\s+\K[^;]+' "{{ item.path }}" + loop: "{{ nginx_conf_files.files }}" + register: extracted_domains + +# crt is given from the "ca" role to all hosts; that needs to run first +- name: request letsencrypt certificate + shell: > + REQUESTS_CA_BUNDLE="/usr/local/share/ca-certificates/{{ step_bootstrap_ca_url }}.crt" \ + letsencrypt certonly -n -d {{ item.stdout }} \ + --server https://{{ step_bootstrap_ca_url }}:{{ step_ca_port }}/acme/ACME/directory \ + --webroot -w /var/www/letsencrypt \ + --agree-tos --email {{ step_acme_cert_contact }} + args: + creates: "/etc/letsencrypt/live/{{ item.stdout }}" + loop: "{{ extracted_domains.results }}" + when: item.stdout != "" + +- name: copy https nginx configuration for each domain + copy: + src: "{{ item }}" + dest: "/etc/nginx/sites-enabled/" + with_fileglob: + - "files/{{ inventory_hostname }}/https.*.conf" + +- name: reload nginx to activate sites + service: name=nginx state=restarted + +- name: add monthly letsencrypt cronjob for cert renewal based on hash of domain name to prevent hitting LE rate limits + cron: + name: "letsencrypt_renewal_{{ item.stdout }}" + day: "{{ '%02d' | format(1 + (item.stdout | hash('md5') | int(0, 16) % 27)) }}" + hour: "{{ (item.stdout | hash('md5') | int(0, 16) % 24 ) }}" + minute: "{{ (item.stdout | hash('md5') | int(0, 16) % 60 ) }}" + job: "REQUESTS_CA_BUNDLE=/usr/local/share/ca-certificates/{{ step_bootstrap_ca_url }}.crt letsencrypt renew --server https://{{ step_bootstrap_ca_url }}:{{ step_ca_port }}/acme/ACME/directory --cert-name {{ item.stdout }} -n --webroot -w /var/www/letsencrypt --agree-tos --email {{ step_acme_cert_contact }} && service nginx reload" + loop: "{{ extracted_domains.results }}" + when: item.stdout != "" diff --git a/roles/vpn/files/config/acl.json b/roles/vpn/files/config/acl.json index 2dbb13a..7c28276 100644 --- a/roles/vpn/files/config/acl.json +++ b/roles/vpn/files/config/acl.json @@ -1,6 +1,7 @@ { "groups": { - "group:admin": ["elizabeth.hunt"] + "group:admin": ["elizabeth.hunt"], + "group:sys": ["sys"] }, "tagOwners": { "tag:prod": ["group:admin"], @@ -14,8 +15,14 @@ "dst": [ "tag:dev:*", "tag:private:*", - "tag:prod:*" + "tag:prod:*", + "group:sys:*" ] + }, + { + "action": "accept", + "src": ["group:sys"], + "dst": ["group:sys:*"] } ] } diff --git a/roles/vpn/files/docker-compose.yml b/roles/vpn/files/docker-compose.yml index dc5e961..38d58d3 100644 --- a/roles/vpn/files/docker-compose.yml +++ b/roles/vpn/files/docker-compose.yml @@ -7,7 +7,7 @@ services: - ./config:/etc/headscale - ./data:/var/lib/headscale ports: - - 27896:8080 + - 127.0.0.1:27896:8080 command: headscale serve restart: unless-stopped headscale-ui: @@ -15,4 +15,4 @@ services: restart: unless-stopped container_name: headscale-ui ports: - - 9443:443 + - 127.0.0.1:9443:443 diff --git a/roles/vpn/templates/config.yml.j2 b/roles/vpn/templates/config.yml.j2 index 4f815f4..d083c8f 100644 --- a/roles/vpn/templates/config.yml.j2 +++ b/roles/vpn/templates/config.yml.j2 @@ -177,7 +177,7 @@ dns_config: # List of DNS servers to expose to clients. nameservers: - - 1.1.1.1 + - {{ johan_ip }} # NextDNS (see https://tailscale.com/kb/1218/nextdns/). # "abc123" is example NextDNS ID, replace with yours. @@ -202,7 +202,7 @@ dns_config: # - 8.8.8.8 # Search domains to inject. - domains: ['simponic.xyz'] + domains: ['simponic.xyz', 'internal.simponic.xyz'] # Extra DNS records # so far only A-records are supported (on the tailscale side) @@ -223,7 +223,7 @@ dns_config: # `base_domain` must be a FQDNs, without the trailing dot. # The FQDN of the hosts will be # `hostname.user.base_domain` (e.g., _myhost.myuser.example.com_). - base_domain: headscale.simponic.xyz + base_domain: internal.simponic.xyz # Unix socket used for the CLI to connect without authentication # Note: for production you will want to set this to something like: