From 37406ff774cdaad944d5083e84eceb159536d2ba Mon Sep 17 00:00:00 2001
From: Alexandre Iooss <erdnaxe@crans.org>
Date: Sat, 2 May 2020 10:18:10 +0200
Subject: [PATCH] [nginx-reverseproxy] Initial role

---
 network.yml                                   | 72 ++++++++++++++++
 roles/nginx-reverseproxy/handlers/main.yml    |  5 ++
 roles/nginx-reverseproxy/tasks/main.yml       | 40 +++++++++
 .../templates/nginx/redirect.j2               | 83 +++++++++++++++++++
 .../templates/nginx/reverseproxy.j2           | 62 ++++++++++++++
 .../nginx/reverseproxy_redirect_dname.j2      | 44 ++++++++++
 .../templates/update-motd.d/05-service.j2     |  3 +
 .../templates/www/html/50x.html.j2            | 63 ++++++++++++++
 8 files changed, 372 insertions(+)
 create mode 100644 roles/nginx-reverseproxy/handlers/main.yml
 create mode 100644 roles/nginx-reverseproxy/tasks/main.yml
 create mode 100644 roles/nginx-reverseproxy/templates/nginx/redirect.j2
 create mode 100644 roles/nginx-reverseproxy/templates/nginx/reverseproxy.j2
 create mode 100644 roles/nginx-reverseproxy/templates/nginx/reverseproxy_redirect_dname.j2
 create mode 100755 roles/nginx-reverseproxy/templates/update-motd.d/05-service.j2
 create mode 100644 roles/nginx-reverseproxy/templates/www/html/50x.html.j2

diff --git a/network.yml b/network.yml
index 97cc9737..daf70236 100755
--- a/network.yml
+++ b/network.yml
@@ -60,8 +60,80 @@
       domains: "crans.org, *.crans.org, crans.fr, *.crans.fr, crans.eu, *.crans.eu"
     bind:
       masters: "{{ lookup('re2oapi', 'get_role', 'dns-authoritary-master')[0] }}"
+    nginx:
+      ssl:
+        cert: /etc/letsencrypt/live/crans.org/fullchain.pem
+        cert_key: /etc/letsencrypt/live/crans.org/privkey.pem
+        trusted_cert: /etc/letsencrypt/live/crans.org/chain.pem
+ 
+      redirect_dnames:
+        - crans.eu
+        - crans.fr
+
+      reverseproxy_sites:
+        # Services web Crans
+        - {from: lutim.crans.org, to: 10.231.136.69}
+        - {from: zero.crans.org, to: 10.231.136.76}
+        - {from: pad.crans.org, to: 10.231.136.76}
+        - {from: ethercalc.crans.org, to: 10.231.136.203}
+        - {from: mediadrop.crans.org, to: 10.231.136.106}
+        - {from: videos.crans.org, to: 10.231.136.106}
+        - {from: video.crans.org, to: 10.231.136.106}
+        - {from: roundcube.crans.org, to: 10.231.136.105}
+        - {from: phabricator.crans.org, to: 10.231.136.123}
+        - {from: trackerusercontent.crans.org, to: 10.231.136.123}
+        - {from: cas.crans.org, to: 10.231.136.18}
+        - {from: auth.crans.org, to: 10.231.136.18}
+        - {from: login.crans.org, to: 10.231.136.18}
+        - {from: webmail.crans.org, to: 10.231.136.107}
+        - {from: horde.crans.org, to: 10.231.136.107}
+        - {from: owncloud.crans.org, to: 10.231.136.26}
+        - {from: ftps.crans.org, to: 10.231.136.98}
+        - {from: wiki.crans.org, to: 10.231.136.204}
+        - {from: www.crans.org, to: 10.231.136.46}
+        - {from: doc.crans.org, to: 10.231.136.46}
+        - {from: limesurvey.crans.org, to: 10.231.136.253}
+        - {from: lutim.crans.org, to: 10.231.136.69}
+        - {from: perso.crans.org, to: 10.231.136.1}
+        - {from: webnews.crans.org, to: 10.231.136.63}
+        - {from: re2o.crans.org, to: 10.231.136.9}
+        - {from: intranet.crans.org, to: 10.231.136.9}
+        - {from: autoconfig.crans.org, to: 10.231.136.46}
+        - {from: grafana.crans.org, to: 10.231.136.102}
+        - {from: webirc.crans.org, to: "10.231.136.1:9000"}
+
+        # Zamok
+        - {from: install-party.crans.org, to: 10.231.136.1}
+        - {from: med.crans.org, to: 10.231.136.1}
+        - {from: med-cartons.crans.org, to: 10.231.136.1}
+        - {from: amap.crans.org, to: 10.231.136.1}
+        - {from: pot-vieux.crans.org, to: 10.231.136.1}
+        - {from: bonvivens.crans.org, to: 10.231.136.1}
+
+      redirect_sites:
+        - {from: crans.org, to: www.crans.org}
+
+        # Aliases or legacy support
+        - {from: factures.crans.org, to: intranet.crans.org}
+        - {from: accounts.crans.org, to: intranet.crans.org}
+        - {from: intranet2.crans.org, to: intranet.crans.org}
+        - {from: clubs.crans.org, to: perso.crans.org}
+        - {from: task.crans.org, to: phabricator.crans.org}
+        - {from: adopteunpingouin.crans.org, to: install-party.crans.org}
+        - {from: i-p.crans.org, to: install-party.crans.org}
+
+        # To the wiki
+        - {from: wikipedia.crans.org, to: wiki.crans.org}
+        - {from: wifi.crans.org, to: wiki.crans.org/CransD%C3%A9marrage}
+        - {from: television.crans.org, to: wiki.crans.org/CransTv}
+        - {from: tv.crans.org, to: wiki.crans.org/CransTv}
+
+        # ENS Cachan
+        - {from: crans.ens-cachan.fr, to: www.crans.org}
+        - {from: install-party.ens-cachan.fr, to: install-party.crans.org}
   roles:
     - certbot
+    - nginx-reverseproxy
 
 - hosts: gitzly.adm.crans.org
   vars:
diff --git a/roles/nginx-reverseproxy/handlers/main.yml b/roles/nginx-reverseproxy/handlers/main.yml
new file mode 100644
index 00000000..6dfcdd76
--- /dev/null
+++ b/roles/nginx-reverseproxy/handlers/main.yml
@@ -0,0 +1,5 @@
+---
+- name: Reload nginx
+  systemd:
+    name: nginx
+    state: reloaded
diff --git a/roles/nginx-reverseproxy/tasks/main.yml b/roles/nginx-reverseproxy/tasks/main.yml
new file mode 100644
index 00000000..3c95a8f7
--- /dev/null
+++ b/roles/nginx-reverseproxy/tasks/main.yml
@@ -0,0 +1,40 @@
+---
+- name: Install NGINX
+  apt:
+    update_cache: true
+    name: nginx
+  register: apt_result
+  retries: 3
+  until: apt_result is succeeded
+
+- name: Copy reverse proxy sites
+  template:
+    src: "nginx/{{ item }}.j2"
+    dest: "/etc/nginx/sites-available/{{ item }}"
+  loop:
+    - reverseproxy
+    - reverseproxy_redirect_dname
+    - redirect
+  notify: Reload nginx
+
+- name: Activate sites
+  file:
+    src: "/etc/nginx/sites-available/{{ item }}"
+    dest: "/etc/nginx/sites-enabled/{{ item }}"
+    state: link
+  loop:
+    - reverseproxy
+    - reverseproxy_redirect_dname
+    - redirect
+  notify: Reload nginx
+
+- name: Copy 50x error page
+  template:
+    src: www/html/50x.html.j2
+    dest: /var/www/html/50x.html
+
+- name: Indicate role in motd
+  template:
+    src: update-motd.d/05-service.j2
+    dest: /etc/update-motd.d/05-nginx
+    mode: 0755
diff --git a/roles/nginx-reverseproxy/templates/nginx/redirect.j2 b/roles/nginx-reverseproxy/templates/nginx/redirect.j2
new file mode 100644
index 00000000..fb177b9a
--- /dev/null
+++ b/roles/nginx-reverseproxy/templates/nginx/redirect.j2
@@ -0,0 +1,83 @@
+{{ ansible_header | comment }}
+
+{% for site in nginx.redirect_sites %}
+# Redirect http://{{ site.from }} to http://{{ site.to }}
+server {
+    listen 80;
+    listen [::]:80;
+
+    server_name {{ site.from }};
+
+    location / {
+        return 302 http://{{ site.to }}$request_uri;
+    }
+}
+
+# Redirect https://{{ site.from }} to https://{{ site.to }}
+server {
+    listen 443;
+    listen [::]:443;
+
+    server_name {{ site.from }};
+
+    ssl on;
+    ssl_certificate {{ nginx.ssl.cert }};
+    ssl_certificate_key {{ nginx.ssl.cert_key }};
+
+    # SSL ciphers updated by Debian
+    include "/etc/letsencrypt/options-ssl-nginx.conf";
+
+    # Enable OCSP Stapling, point to certificate chain
+    ssl_stapling on;
+    ssl_stapling_verify on;
+    ssl_trusted_certificate {{ nginx.ssl.trusted_cert }};
+
+    location / {
+        return 302 https://{{ site.to }}$request_uri;
+    }
+}
+
+{% endfor %}
+
+{# Also redirect for DNAMEs #}
+{% for dname in nginx.redirect_dnames %}
+{% for site in nginx.redirect_sites %}
+{% set from = site.from | regex_replace('crans.org', dname) %}
+# Redirect http://{{ from }} to http://{{ site.to }}
+server {
+    listen 80;
+    listen [::]:80;
+
+    server_name {{ from }};
+
+    location / {
+        return 302 http://{{ site.to }}$request_uri;
+    }
+}
+
+# Redirect https://{{ from }} to https://{{ site.to }}
+server {
+    listen 443;
+    listen [::]:443;
+
+    server_name {{ from }};
+
+    ssl on;
+    ssl_certificate {{ nginx.ssl.cert }};
+    ssl_certificate_key {{ nginx.ssl.cert_key }};
+
+    # SSL ciphers updated by Debian
+    include "/etc/letsencrypt/options-ssl-nginx.conf";
+
+    # Enable OCSP Stapling, point to certificate chain
+    ssl_stapling on;
+    ssl_stapling_verify on;
+    ssl_trusted_certificate {{ nginx.ssl.trusted_cert }};
+
+    location / {
+        return 302 https://{{ site.to }}$request_uri;
+    }
+}
+
+{% endfor %}
+{% endfor %}
diff --git a/roles/nginx-reverseproxy/templates/nginx/reverseproxy.j2 b/roles/nginx-reverseproxy/templates/nginx/reverseproxy.j2
new file mode 100644
index 00000000..eab44a49
--- /dev/null
+++ b/roles/nginx-reverseproxy/templates/nginx/reverseproxy.j2
@@ -0,0 +1,62 @@
+{{ ansible_header | comment }}
+
+{% for site in nginx.reverseproxy_sites %}
+# Redirect http://{{ site.from }} to https://{{ site.from }}
+server {
+    listen 80;
+    listen [::]:80
+
+    server_name {{ site.from }};
+
+    location / {
+        return 302 https://$host$request_uri;
+    }
+}
+
+# Reverse proxify https://{{ site.from }} to http://{{ site.to }}
+server {
+    listen 443;
+    listen [::]:443;
+
+    server_name {{ site.from }};
+
+    ssl on;
+    ssl_certificate {{ nginx.ssl.cert }};
+    ssl_certificate_key {{ nginx.ssl.cert_key }};
+
+    # SSL ciphers updated by Debian
+    include "/etc/letsencrypt/options-ssl-nginx.conf";
+
+    # Enable OCSP Stapling, point to certificate chain
+    ssl_stapling on;
+    ssl_stapling_verify on;
+    ssl_trusted_certificate {{ nginx.ssl.trusted_cert }};
+
+    # Log into separate log files
+    access_log      /var/log/nginx/{{ site.from }}.log;
+    error_log       /var/log/nginx/{{ site.from }}_error.log;
+
+    # Keep the TCP connection open a bit for faster browsing
+    keepalive_timeout 70;
+ 
+    # Custom error page
+    error_page  500 502 503 504  /50x.html;
+    location = /50x.html {
+        root /var/www/html;
+    }
+
+    set_real_ip_from 10.231.136.0/24;
+    set_real_ip_from 2a0c:700:0:2::/64;
+    real_ip_header P-Real-Ip;
+
+    location / {
+        proxy_set_header Host {{ site.from }};
+        proxy_set_header P-Real-IP $remote_addr;
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_set_header X-Forwarded-Proto https;
+        proxy_redirect off;
+        proxy_pass http://{{ site.to }};
+    }
+}
+
+{% endfor %}
diff --git a/roles/nginx-reverseproxy/templates/nginx/reverseproxy_redirect_dname.j2 b/roles/nginx-reverseproxy/templates/nginx/reverseproxy_redirect_dname.j2
new file mode 100644
index 00000000..1affe511
--- /dev/null
+++ b/roles/nginx-reverseproxy/templates/nginx/reverseproxy_redirect_dname.j2
@@ -0,0 +1,44 @@
+{{ ansible_header | comment }}
+
+{% for dname in nginx.redirect_dnames %}
+{% for site in nginx.reverseproxy_sites %}
+{% set from = site.from | regex_replace('crans.org', dname) %}
+{% set to = site.from %}
+# Redirect http://{{ from }} to http://{{ to }}
+server {
+    listen 80;
+    listen [::]:80;
+
+    server_name {{ from }};
+
+    location / {
+        return 302 http://{{ to }}$request_uri;
+    }
+}
+
+# Redirect https://{{ from }} to https://{{ to }}
+server {
+    listen 443;
+    listen [::]:443;
+
+    server_name {{ from }};
+
+    ssl on;
+    ssl_certificate {{ nginx.ssl.cert }};
+    ssl_certificate_key {{ nginx.ssl.cert_key }};
+
+    # SSL ciphers updated by Debian
+    include "/etc/letsencrypt/options-ssl-nginx.conf";
+
+    # Enable OCSP Stapling, point to certificate chain
+    ssl_stapling on;
+    ssl_stapling_verify on;
+    ssl_trusted_certificate {{ nginx.ssl.trusted_cert }};
+
+    location / {
+        return 302 https://{{ to }}$request_uri;
+    }
+}
+
+{% endfor %}
+{% endfor %}
diff --git a/roles/nginx-reverseproxy/templates/update-motd.d/05-service.j2 b/roles/nginx-reverseproxy/templates/update-motd.d/05-service.j2
new file mode 100755
index 00000000..82373d0b
--- /dev/null
+++ b/roles/nginx-reverseproxy/templates/update-motd.d/05-service.j2
@@ -0,0 +1,3 @@
+#!/usr/bin/tail +14
+{{ ansible_header | comment }}
+> NGINX a été déployé sur cette machine. Voir /etc/nginx/.
diff --git a/roles/nginx-reverseproxy/templates/www/html/50x.html.j2 b/roles/nginx-reverseproxy/templates/www/html/50x.html.j2
new file mode 100644
index 00000000..b4bde1f9
--- /dev/null
+++ b/roles/nginx-reverseproxy/templates/www/html/50x.html.j2
@@ -0,0 +1,63 @@
+<!doctype html>
+<html lang="fr">
+<head>
+    <meta charset="utf-8">
+    <title>502</title>
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <style>
+        * {
+            line-height: 1.2;
+            margin: 0;
+        }
+
+        html {
+            color: #888;
+            display: table;
+            font-family: sans-serif;
+            height: 100%;
+            text-align: center;
+            width: 100%;
+        }
+
+        body {
+            display: table-cell;
+            vertical-align: middle;
+            margin: 2em auto;
+        }
+
+	a {
+	    color: #888;
+            text-decoration: underline dotted;
+	}
+
+        h1 {
+            color: #555;
+            font-size: 2em;
+            font-weight: 400;
+        }
+
+        p {
+            margin: 1em auto;
+            max-width: 480px;
+        }
+
+        @media only screen and (max-width: 280px) {
+            body, p {
+                width: 95%;
+            }
+
+            h1 {
+                font-size: 1.5em;
+                margin: 0 0 0.3em;
+            }
+        }
+    </style>
+</head>
+<body>
+    <h1>502</h1>
+    <p>Whoops, le service prend trop de temps à répondre…</p>
+    <p>Essayez de rafraîchir la page. Si le problème persiste, pensez
+    à contacter <a href="mailto:contact@crans.org">l'équipe technique du Cr@ns</a>.</p>
+</body>
+</html>
+