diff --git a/group_vars/ext_nginx.yml b/group_vars/ext_nginx.yml new file mode 100644 index 0000000..6ce4859 --- /dev/null +++ b/group_vars/ext_nginx.yml @@ -0,0 +1,75 @@ +cert_domains: + - files.ezri.dev + - git.ezri.dev + - idm.ezri.dev + - jellyfin.ezri.dev + - mail.ezri.dev + - navidrome.ezri.dev + - vtt.ezri.dev + +sites_available: + - fqdn: files.ezri.dev + enabled: yes + upstream: http://10.242.202.90:9001 + max_upload: 0 + + - fqdn: git.ezri.dev + enabled: yes + upstream: http://10.242.202.90:30008 + + - fqdn: jellyfin.ezri.dev + enabled: yes + upstream: http://10.242.202.90:30013 + restricted: yes + allowed_ips: + - 10.242.0.0/23 + - 10.242.3.0/24 + - 10.242.4.0/24 + + - fqdn: navidrome.ezri.dev + enabled: yes + upstream: http://10.242.202.90:30043 + restricted: yes + allowed_ips: + - 10.242.0.0/23 + - 10.242.3.0/24 + - 10.242.4.0/24 + + - fqdn: mail.ezri.dev + enabled: yes + upstream: http://10.242.203.13:8000 + restricted: yes + allowed_ips: + - 10.242.0.0/23 + - 10.242.3.0/24 + + - fqdn: vtt.ezri.dev + enabled: no + upstream: http://10.242.203.13:3000 + max_upload: 512M + + - fqdn: idm.ezri.dev + enabled: yes + upstream: https://10.242.203.13:8443 + +streams_available: + - fqdn: git.ezri.dev + enabled: yes + listen_port: 22 + ssl: no + upstream: 10.242.202.90:30009 + + - fqdn: idm.ezri.dev + enabled: yes + listen_port: 636 + ssl: yes + cert_domain: idm.ezri.dev + upstream: 10.242.203.13:3636 + upstream_ssl: yes + restricted: yes + allowed_ips: + - 10.242.200.0/24 + - 10.242.0.1 + - 10.242.203.1 + - 10.242.203.13 + - 10.242.3.0/24 diff --git a/group_vars/int_nginx.yml b/group_vars/int_nginx.yml new file mode 100644 index 0000000..e69c34e --- /dev/null +++ b/group_vars/int_nginx.yml @@ -0,0 +1,59 @@ +cert_domains: + - vw.ezri.dev + +wildcard_domains: + - internal.ezri.dev + +sites_available: + - fqdn: lidarr.internal.ezri.dev + enabled: yes + cert_domain: internal.ezri.dev + upstream: http://10.242.201.3:8686 + restricted: yes + allowed_ips: + - 10.242.0.0/23 + - 10.242.3.0/24 + + - fqdn: radarr.internal.ezri.dev + enabled: yes + cert_domain: internal.ezri.dev + upstream: http://10.242.201.3:7878 + restricted: yes + allowed_ips: + - 10.242.0.0/23 + - 10.242.3.0/24 + + - fqdn: sonarr.internal.ezri.dev + enabled: yes + cert_domain: internal.ezri.dev + upstream: https://10.242.201.3:8989 + restricted: yes + allowed_ips: + - 10.242.0.0/23 + - 10.242.3.0/24 + + - fqdn: prowlarr.internal.ezri.dev + enabled: yes + cert_domain: internal.ezri.dev + upstream: http://10.242.201.3:9696 + restricted: yes + allowed_ips: + - 10.242.0.0/23 + - 10.242.3.0/24 + + - fqdn: qbittorrent.internal.ezri.dev + enabled: yes + cert_domain: internal.ezri.dev + upstream: http://10.242.201.2:8080 + restricted: yes + allowed_ips: + - 10.242.0.0/23 + - 10.242.3.0/24 + + - fqdn: vw.ezri.dev + enabled: yes + upstream: http://10.242.202.90:30032 + restricted: yes + allowed_ips: + - 10.242.0.0/23 + - 10.242.3.0/24 diff --git a/group_vars/nginx.yml b/group_vars/nginx.yml index c11492f..d6bed39 100644 --- a/group_vars/nginx.yml +++ b/group_vars/nginx.yml @@ -29,6 +29,9 @@ sites_available: cert_domain: ezri.dev upstream: http://10.242.2.2:30013 restricted: yes + allowed_ips: + - 10.242.0.0/16 + - 68.5.180.44/32 - fqdn: lidarr.internal.ezri.dev enabled: yes @@ -125,6 +128,7 @@ sites_available: allowed_ips: - 10.242.0.0/16 - 129.123.107.0/24 + - 50.39.97.200/32 streams_available: - fqdn: git.ezri.dev diff --git a/inventory b/inventory index ba86225..44a962b 100644 --- a/inventory +++ b/inventory @@ -50,4 +50,16 @@ sysadmin_exercise: nginx: hosts: peoples-home.servers.ezri.dev: - + +ext_nginx: + hosts: + assurance-of-peace.ext.ezri.dev: + ansible_port: 2231 + ansible_user: localadmin + ansible_become: yes + +int_nginx: + hosts: + peoples-home.int.ezri.dev: + ansible_user: localadmin + ansible_become: yes diff --git a/playbooks/deploy-nginx.yml b/playbooks/deploy-nginx.yml index d7f0736..6147ca3 100644 --- a/playbooks/deploy-nginx.yml +++ b/playbooks/deploy-nginx.yml @@ -1,11 +1,13 @@ --- - name: Configure Nginx proxy - hosts: nginx + hosts: + - ext_nginx + - int_nginx vars_prompt: - - name: admin_password - prompt: Enter the administrator password that will be used for this system - + - name: cloudflare_api_token + prompt: Enter the cloudflare API token to manage DNS records for certs + roles: - common - nginx diff --git a/playbooks/roles/common/tasks/main.yml b/playbooks/roles/common/tasks/main.yml index 69940e5..aeefc74 100644 --- a/playbooks/roles/common/tasks/main.yml +++ b/playbooks/roles/common/tasks/main.yml @@ -1,21 +1,21 @@ --- -- name: Create wheel group - ansible.builtin.group: - name: wheel - state: present - system: yes +# - name: Create wheel group +# ansible.builtin.group: +# name: wheel +# state: present +# system: yes -- name: Create local administrator - ansible.builtin.user: - state: present - name: localadmin - uid: 1000 - groups: - - wheel - create_home: yes - # Salt the password with the inventory name, this should be static between runs - password: '{{ admin_password|password_hash("sha512", "thisisabadsalt") }}' +# - name: Create local administrator +# ansible.builtin.user: +# state: present +# name: localadmin +# uid: 1000 +# groups: +# - wheel +# create_home: yes +# # Salt the password with the inventory name, this should be static between runs +# password: '{{ admin_password|password_hash("sha512", "thisisabadsalt") }}' - name: Load Arch tasks import_tasks: arch.yml @@ -46,12 +46,12 @@ owner: root group: root -- name: Configure SSH server - ansible.builtin.template: - src: sshd_config.j2 - dest: /etc/ssh/sshd_config - owner: root - group: root - mode: "0644" - notify: Restart SSH +# - name: Configure SSH server +# ansible.builtin.template: +# src: sshd_config.j2 +# dest: /etc/ssh/sshd_config +# owner: root +# group: root +# mode: "0644" +# notify: Restart SSH diff --git a/playbooks/roles/nginx/tasks/main.yml b/playbooks/roles/nginx/tasks/main.yml index 8978847..5cb800a 100644 --- a/playbooks/roles/nginx/tasks/main.yml +++ b/playbooks/roles/nginx/tasks/main.yml @@ -1,8 +1,13 @@ --- -- name: Install Nginx +- name: Install Nginx and certbot ansible.builtin.apt: - name: nginx + name: + - nginx + - libnginx-mod-stream + - certbot + - python3-dnspython + - python3-certbot-dns-cloudflare state: present - name: Allow ports 80 and 443 @@ -13,6 +18,72 @@ rule: allow to_port: '{{ item }}' +- name: Check for existence of certificates + ansible.builtin.stat: + path: /etc/letsencrypt/live/{{ item }}/fullchain.pem + loop: '{{ [cert_domains | default([]), wildcard_domains | default([]) ] | flatten }}' + register: cert_check + +- name: Deploy cloudflare secret + ansible.builtin.template: + src: certbot_secrets.j2 + dest: /etc/letsencrypt/secrets + owner: root + group: root + mode: "0600" + # Only run this when it is defined + when: cloudflare_api_token is defined and not cloudflare_api_token == "" + +- name: Verify cloudflare secret exists + # This is so the above conditional works; we only need to specify the API token on first run or when it changes; that said, if the variable + # is undefined and we haven't deployed the secret before, that's a problem. + ansible.builtin.stat: + path: /etc/letsencrypt/secrets + register: check_secret + failed_when: not check_secret.stat.exists + +- name: Request acmedns challenges for uncertified domains + ansible.builtin.command: + argv: + - certbot + - certonly + - '--dns-cloudflare' + - '--non-interactive' + - '--agree-tos' + - '-m' + - 'sysadmins@ezri.dev' + - '--dns-cloudflare-propagation-seconds' + - '30' + - '--dns-cloudflare-credentials' + - '/etc/letsencrypt/secrets' + - '-d' + - '{{ item }}' + loop: '{{ cert_domains | default([]) }}' + loop_control: + index_var: idx + when: not cert_check.results[idx].stat.exists + +- name: Request acmedns challenges for uncertified wildcard domains + ansible.builtin.command: + argv: + - certbot + - certonly + - '--dns-cloudflare' + - '--non-interactive' + - '--agree-tos' + - '-m' + - 'sysadmins@ezri.dev' + - '--dns-cloudflare-propagation-seconds' + - '30' + - '--dns-cloudflare-credentials' + - '/etc/letsencrypt/secrets' + - '-d' + - '*.{{ item }}' + loop: '{{ wildcard_domains | default([]) }}' + loop_control: + index_var: idx + when: not cert_check.results[idx + ((cert_domains | default([])) | length)].stat.exists + - name: Create config directories loop: - /etc/nginx @@ -48,16 +119,26 @@ - name: Enable site configurations loop: '{{ sites_available }}' + when: item.enabled ansible.builtin.file: src: ../sites-available/{{ item.fqdn }}.conf dest: /etc/nginx/sites-enabled/{{ item.fqdn }}.conf - state: '{{ item.enabled|ternary("link", "absent") }}' + state: link owner: root group: root notify: Reload nginx +- name: Disable site configurations + loop: '{{ sites_available }}' + when: not item.enabled + ansible.builtin.file: + dest: /etc/nginx/sites-enabled/{{ item.fqdn }}.conf + state: absent + notify: Reload nginx + - name: Deploy stream configurations loop: '{{ streams_available }}' + when: streams_available is defined ansible.builtin.template: src: stream.j2 dest: /etc/nginx/streams-available/{{ item.fqdn }}.conf @@ -67,17 +148,27 @@ notify: Reload nginx - name: Enable stream configurations - loop: '{{ streams_available }}' + loop: '{{ streams_available | default([]) }}' + when: item.enabled ansible.builtin.file: src: ../streams-available/{{ item.fqdn }}.conf dest: /etc/nginx/streams-enabled/{{ item.fqdn }}.conf - state: '{{ item.enabled|ternary("link", "absent") }}' + state: link owner: root group: root notify: Reload nginx +- name: Disable stream configurations + loop: '{{ streams_available | default([]) }}' + when: not item.enabled + ansible.builtin.file: + dest: /etc/nginx/streams-enabled/{{ item.fqdn }}.conf + state: absent + notify: Reload nginx + - name: Allow connections to enabled streams loop: '{{ streams_available }}' + when: streams_available is defined community.general.ufw: rule: allow to_port: '{{ item.listen_port }}' diff --git a/playbooks/roles/nginx/templates/certbot_secrets.j2 b/playbooks/roles/nginx/templates/certbot_secrets.j2 new file mode 100644 index 0000000..4e7d9ac --- /dev/null +++ b/playbooks/roles/nginx/templates/certbot_secrets.j2 @@ -0,0 +1 @@ +dns_cloudflare_api_token = {{ cloudflare_api_token }} diff --git a/playbooks/roles/nginx/templates/site.j2 b/playbooks/roles/nginx/templates/site.j2 index e288f49..a765c42 100644 --- a/playbooks/roles/nginx/templates/site.j2 +++ b/playbooks/roles/nginx/templates/site.j2 @@ -10,8 +10,8 @@ server { server_name {{ item.fqdn }}; - ssl_certificate /etc/letsencrypt/live/{{ item.cert_domain }}/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/{{ item.cert_domain }}/privkey.pem; + ssl_certificate /etc/letsencrypt/live/{{ item.cert_domain|default(item.fqdn) }}/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/{{ item.cert_domain|default(item.fqdn) }}/privkey.pem; if ($scheme = "http") { return 301 https://$host$request_uri;