From eac2af3b6f7fb511274fa6040ec3b92b8f163e97 Mon Sep 17 00:00:00 2001
From: Adar Nimrod <nimrod@shore.co.il>
Date: Thu, 28 Jan 2021 01:45:17 +0200
Subject: [PATCH] SSL on Kodi.

Both host01 and kodi are behind the ns1 router. For completely secure
connections, SSL termination should be done on each host. So use HAProxy
on ns1 to route HTTPS traffic using the SNI extension and HTTP traffic
using the Host header (default to host01). Add tasks to the renew-hosts
playbook to issue (or renew) Let's Encrypt certificates on kodi.
---
 .gitignore                         |   7 +-
 renew-certs.yaml                   | 139 +++++++++++++++++++++++++++++
 roles/router/files/haproxy.cfg     |  62 +++++++++++++
 roles/router/files/nsd/shore.co.il |  13 +--
 roles/router/files/pf.conf         |   8 +-
 roles/router/handlers/main.yaml    |   5 ++
 roles/router/tasks/main.yaml       |  25 ++++++
 7 files changed, 245 insertions(+), 14 deletions(-)
 create mode 100644 roles/router/files/haproxy.cfg

diff --git a/.gitignore b/.gitignore
index ed3e466..69692d4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -47,8 +47,7 @@ dist/
 *.env
 .bundle/
 !Pipfile.lock
-site.*
-mail.*
-account.*
-host.*
+*.crt
+*.csr
+*.key
 .vault-password
diff --git a/renew-certs.yaml b/renew-certs.yaml
index 23043fe..937e353 100644
--- a/renew-certs.yaml
+++ b/renew-certs.yaml
@@ -20,6 +20,10 @@
       delegate_to: ns4
       command: docker restart web-proxy_proxy_1
 
+    - name: Restart Nginx on kodi
+      delegate_to: kodi
+      command: docker restart web-proxy_proxy_1
+
     - name: Reload Exim
       delegate_to: host01
       command: docker kill --signal SIGHUP mail_smtp_1
@@ -425,6 +429,126 @@
       tags:
         - mail
 
+    - name: Generate kodi key
+      community.crypto.openssl_privatekey:
+        mode: *mode
+        path: &kodi_key_src |-
+            {{ playbook_dir }}/kodi.key
+        size: *size
+        state: present
+        type: *type
+      tags:
+        - kodi
+
+    - name: Generate kodi certificate signing request
+      community.crypto.openssl_csr:
+        common_name: kodi.shore.co.il
+        country_name: *country_name
+        digest: *digest
+        email_address: |-
+            {{ email }}
+        locality_name: *locality_name
+        organization_name: *organization_name
+        path: &kodi_csr_src kodi.csr
+        privatekey_path: *kodi_key_src
+        state: present
+        subject_alt_name: |-
+            DNS:kodi.shore.co.il,DNS:library.shore.co.il,DNS:jellyfin.shore.co.il
+      register: acme_kodi_csr
+      tags:
+        - kodi
+
+    - name: Create kodi challenge
+      community.crypto.acme_certificate:
+        account_email: |-
+            {{ email }}
+        account_key_src: *account_key_src
+        acme_directory: |-
+            {{ acme_directory }}
+        acme_version: |
+            {{ acme_version }}
+        csr: *kodi_csr_src
+        fullchain_dest: &kodi_cert_src |-
+            {{ playbook_dir }}/kodi.crt
+        modify_account: false
+        remaining_days: 35
+        select_crypto_backend: *crypto_backend
+      register: acme_kodi_challenge
+      tags:
+        - kodi
+
+    - name: Debug kodi challenge
+      debug:
+        var: acme_kodi_challenge
+        verbosity: 1
+      tags:
+        - kodi
+
+    - name: Renew kodi cert
+      when: acme_kodi_challenge is changed
+      tags:
+        - kodi
+      block:
+
+        - name: Create ACME challenge directory
+          delegate_to: kodi
+          file:
+            path: /var/www/www.shore.co.il/.well-known/acme-challenge
+            state: directory
+
+        - name: Copy http-01 kodi challenge
+          delegate_to: kodi
+          with_dict: |
+              {{ acme_kodi_challenge['challenge_data'] }}
+          copy:
+            content: |-
+                {{ item.value['http-01']['resource_value'] }}
+            # yamllint disable-line rule:line-length
+            dest: /var/www/www.shore.co.il/{{ item.value['http-01']['resource'] }}
+            group: www-data
+            mode: 0o0644
+            owner: root
+
+        - name: Validate kodi challenge
+          community.crypto.acme_certificate:
+            account_email: |-
+                {{ email }}
+            account_key_src: *account_key_src
+            acme_directory: |-
+                {{ acme_directory }}
+            acme_version: |
+                {{ acme_version }}
+            challenge: http-01
+            csr: *kodi_csr_src
+            data: "{{ acme_kodi_challenge }}"
+            fullchain_dest: *kodi_cert_src
+            modify_account: false
+            remaining_days: 35
+            select_crypto_backend: *crypto_backend
+
+    - name: Copy kodi key, certificate to server
+      delegate_to: kodi
+      with_items:
+        - src: *kodi_key_src
+          dest: /var/ssl/site.key
+          mode: 0o0444
+        - src: *kodi_cert_src
+          dest: /var/ssl/site.crt
+          mode: 0o0444
+      copy:
+        src: |-
+            {{ item.src }}
+        dest: |-
+            {{ item.dest }}
+        mode: |-
+            {{ item.mode }}
+        owner: root
+        group: root
+      notify:
+        - Restart Nginx on kodi
+      tags:
+        - kodi
+
     - name: Generate Diffie-Hellman parameters on host01
       become: true
       delegate_to: host01
@@ -456,3 +580,18 @@
       tags:
         - ns4
         - dhparams
+
+    - name: Generate Diffie-Hellman parameters on kodi
+      become: true
+      delegate_to: kodi
+      community.crypto.openssl_dhparam:
+        force: true
+        mode: 0o0644
+        path: /var/ssl/dhparams
+        size: 4096
+        state: present
+      notify:
+        - Restart Nginx on kodi
+      tags:
+        - kodi
+        - dhparams
diff --git a/roles/router/files/haproxy.cfg b/roles/router/files/haproxy.cfg
new file mode 100644
index 0000000..89bed2b
--- /dev/null
+++ b/roles/router/files/haproxy.cfg
@@ -0,0 +1,62 @@
+global
+        log 127.0.0.1   local0 debug
+        log-send-hostname
+        maxconn 1024
+        chroot /var/haproxy
+        uid 604
+        gid 604
+        daemon
+        pidfile /var/run/haproxy.pid
+
+defaults
+        log     global
+        mode    http
+        option  httplog
+        option  dontlognull
+        option  redispatch
+        retries 3
+        maxconn 2000
+        timeout client 30s
+        timeout server 30s
+        timeout connect 5s
+
+frontend http
+        bind 62.219.131.121:80
+        mode http
+        acl kodi hdr(host) -i kodi.shore.co.il
+        acl kodi hdr(host) -i library.shore.co.il
+        acl kodi hdr(host) -i jellyfin.shore.co.il
+        use_backend kodi_http if kodi
+        default_backend host01_http
+
+frontend https
+        bind 62.219.131.121:443
+        mode tcp
+        option tcplog
+        tcp-request inspect-delay 5s
+        tcp-request content accept if { req_ssl_hello_type 1 }
+        acl kodi req_ssl_sni -i kodi.shore.co.il
+        acl kodi req_ssl_sni -i library.shore.co.il
+        acl kodi req_ssl_sni -i jellyfin.shore.co.il
+        use_backend kodi_https if kodi
+        default_backend host01_https
+
+backend host01_http
+        mode http
+        option forwardfor
+        server host01 host01.shore.co.il:80 check
+
+backend host01_https
+        mode tcp
+        option ssl-hello-chk
+        server host01 host01.shore.co.il:443 check
+
+backend kodi_http
+        mode http
+        option forwardfor
+        server kodi kodi.shore.co.il:80 check
+
+backend kodi_https
+        mode tcp
+        option ssl-hello-chk
+        server kodi kodi.shore.co.il:443 check
diff --git a/roles/router/files/nsd/shore.co.il b/roles/router/files/nsd/shore.co.il
index e84bfa0..897139b 100644
--- a/roles/router/files/nsd/shore.co.il
+++ b/roles/router/files/nsd/shore.co.il
@@ -1,7 +1,7 @@
 $TTL 1h
 $ORIGIN shore.co.il.
 @               IN      SOA     ns1     hostmaster (
-        2021012601
+        2021012701
         1h
         5m
         4w
@@ -51,15 +51,18 @@ _caldavs._tcp    IN  SRV  0 1 443 nextcloud
                     IN  TXT "v=spf1 -all"
                     IN  SPF "v=spf1 -all"
 
-www         IN  CNAME   ns4
 autoconfig  IN  CNAME   ns4
-nextcloud   IN  CNAME   ns1
 code        IN  CNAME   ns1
 git         IN  CNAME   ns1
-lam         IN  CNAME   ns1
-registry    IN  CNAME   ns4
 imap        IN  CNAME   smtp
+jellyfin    IN  CNAME   ns1
+kodi        IN  CNAME   ns1
+lam         IN  CNAME   ns1
+library     IN  CNAME   ns1
 mta-sts     IN  CNAME   smtp
+nextcloud   IN  CNAME   ns1
+registry    IN  CNAME   ns4
+www         IN  CNAME   ns4
 
 host01._domainkey IN    TXT     ("v=DKIM1\; k=rsa\;"
 "p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw9EM6TzCofz004vL+aBV"
diff --git a/roles/router/files/pf.conf b/roles/router/files/pf.conf
index b88d537..ebbc869 100644
--- a/roles/router/files/pf.conf
+++ b/roles/router/files/pf.conf
@@ -39,13 +39,11 @@ pass quick inet proto icmp icmp-type { echoreq, unreach }
 # Allowed local services
 pass in quick on ingress proto { tcp, udp } to (ingress:0) port { bootps, bootpc } set prio ( 4, 6 )
 pass in quick proto { tcp, udp } to port domain set queue services set prio ( 4, 6 )
-#pass in quick proto tcp to (egress:0) port { www, https } set prio ( 4, 6 )
+pass in quick proto tcp to (egress:0) port { www, https } set prio ( 4, 6 )
 
 # Port redirection
-pass in quick proto tcp to (egress:0) port { smtp, submission, imaps, www, https } rdr-to host01.shore.co.il set queue critical set prio ( 4, 6 )
-pass out quick proto tcp to host01.shore.co.il port { submission, smtp, imaps, www, https } received-on ingress nat-to ingress set prio ( 4, 6 )
-#pass in quick proto tcp to (egress:0) port { smtp, submission, imaps } rdr-to host01.shore.co.il set queue critical set prio ( 4, 6 )
-#pass out quick proto tcp to host01.shore.co.il port { submission, smtp, imaps } received-on ingress nat-to ingress set prio ( 4, 6 )
+pass in quick proto tcp to (egress:0) port { smtp, submission, imaps } rdr-to host01.shore.co.il set queue critical set prio ( 4, 6 )
+pass out quick proto tcp to host01.shore.co.il port { submission, smtp, imaps } received-on ingress nat-to ingress set prio ( 4, 6 )
 pass in quick proto { tcp, udp } to (egress:0) port bittorrent rdr-to kodi.shore.co.il set queue bulk set prio 1
 
 # Allowd NAT and proxying
diff --git a/roles/router/handlers/main.yaml b/roles/router/handlers/main.yaml
index cf1a557..f5409e7 100644
--- a/roles/router/handlers/main.yaml
+++ b/roles/router/handlers/main.yaml
@@ -11,6 +11,11 @@
   command:
     cmd: newaliases
 
+- name: Restart HAProxy
+  service:
+    name: haproxy
+    state: restarted
+
 - name: Restart NSD
   service:
     name: nsd
diff --git a/roles/router/tasks/main.yaml b/roles/router/tasks/main.yaml
index e21666a..c7177c9 100644
--- a/roles/router/tasks/main.yaml
+++ b/roles/router/tasks/main.yaml
@@ -135,6 +135,30 @@
     - dns
     - network
 
+- name: Configure HAProxy
+  copy:
+    backup: true
+    dest: /etc/haproxy/haproxy.cfg
+    mode: preserve
+    src: haproxy.cfg
+    validate: haproxy -c -f %s
+  notify:
+    - Restart HAProxy
+  tags:
+    - haproxy
+    - web
+    - network
+
+- name: Enable HAProxy
+  service:
+    enabled: true
+    name: haproxy
+    state: started
+  tags:
+    - haproxy
+    - web
+    - network
+
 - name: Configure PF
   copy:
     dest: /etc/pf.conf
@@ -169,6 +193,7 @@
     - curl
     - git
     - go
+    - haproxy
   community.general.openbsd_pkg:
     name: '{{ item }}'
     state: present
-- 
GitLab