From 34e97939f2b5466276724f4dc178b2083cca17e9 Mon Sep 17 00:00:00 2001
From: Adar Nimrod <nimrod@shore.co.il>
Date: Sat, 6 Nov 2021 18:45:03 +0200
Subject: [PATCH] Nginx base image.

---
 .gitlab-ci.yml                          | 16 +++++++++++++
 nginx/.dockerignore                     |  4 ++++
 nginx/Dockerfile                        | 26 +++++++++++++++++++++
 nginx/README.md                         |  3 +++
 nginx/conf.d/default.conf               | 13 +++++++++++
 nginx/conf.d/global.conf                | 13 +++++++++++
 nginx/conf.d/status.conf                |  7 ++++++
 nginx/snippets/ads-txt.conf             |  9 ++++++++
 nginx/snippets/allow-ns1.conf           |  1 +
 nginx/snippets/allow-ns4.conf           |  1 +
 nginx/snippets/allow-private-ips.conf   |  5 +++++
 nginx/snippets/allow-shore-ips.conf     |  3 +++
 nginx/snippets/common-headers.conf      |  6 +++++
 nginx/snippets/ldap-auth.conf           | 10 +++++++++
 nginx/snippets/proxy-headers.conf       |  8 +++++++
 nginx/snippets/proxy-ssl.conf           |  5 +++++
 nginx/snippets/redirect-https.conf      |  1 +
 nginx/snippets/redirect-www.conf        |  1 +
 nginx/snippets/robots-allow-all.conf    |  4 ++++
 nginx/snippets/robots-disallow-all.conf |  4 ++++
 nginx/snippets/security-txt.conf        |  9 ++++++++
 nginx/snippets/ssl.conf                 | 14 ++++++++++++
 nginx/snippets/upgrade-secure.conf      |  1 +
 nginx/snippets/vouch.conf               | 30 +++++++++++++++++++++++++
 nginx/snippets/websockets.conf          |  3 +++
 nginx/snippets/www-acme-challenge.conf  |  1 +
 26 files changed, 198 insertions(+)
 create mode 100644 nginx/.dockerignore
 create mode 100644 nginx/Dockerfile
 create mode 100644 nginx/README.md
 create mode 100644 nginx/conf.d/default.conf
 create mode 100644 nginx/conf.d/global.conf
 create mode 100644 nginx/conf.d/status.conf
 create mode 100644 nginx/snippets/ads-txt.conf
 create mode 100644 nginx/snippets/allow-ns1.conf
 create mode 100644 nginx/snippets/allow-ns4.conf
 create mode 100644 nginx/snippets/allow-private-ips.conf
 create mode 100644 nginx/snippets/allow-shore-ips.conf
 create mode 100644 nginx/snippets/common-headers.conf
 create mode 100644 nginx/snippets/ldap-auth.conf
 create mode 100644 nginx/snippets/proxy-headers.conf
 create mode 100644 nginx/snippets/proxy-ssl.conf
 create mode 100644 nginx/snippets/redirect-https.conf
 create mode 100644 nginx/snippets/redirect-www.conf
 create mode 100644 nginx/snippets/robots-allow-all.conf
 create mode 100644 nginx/snippets/robots-disallow-all.conf
 create mode 100644 nginx/snippets/security-txt.conf
 create mode 100644 nginx/snippets/ssl.conf
 create mode 100644 nginx/snippets/upgrade-secure.conf
 create mode 100644 nginx/snippets/vouch.conf
 create mode 100644 nginx/snippets/websockets.conf
 create mode 100644 nginx/snippets/www-acme-challenge.conf

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index a72788f..62ca6ed 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -151,3 +151,19 @@ push-webdav:
   needs:
     - job: build-webdav
       artifacts: true
+
+# nginx image:
+
+build-nginx:
+  extends: .container-build-base
+  variables:
+    CONTEXT: nginx
+
+push-nginx:
+  extends: .container-push-base
+  variables:
+    CONTEXT: nginx
+    IMAGE: nginx
+  needs:
+    - job: build-nginx
+      artifacts: true
diff --git a/nginx/.dockerignore b/nginx/.dockerignore
new file mode 100644
index 0000000..380e2e6
--- /dev/null
+++ b/nginx/.dockerignore
@@ -0,0 +1,4 @@
+*
+!conf.d/
+!www/
+!snippets/
diff --git a/nginx/Dockerfile b/nginx/Dockerfile
new file mode 100644
index 0000000..41618e0
--- /dev/null
+++ b/nginx/Dockerfile
@@ -0,0 +1,26 @@
+FROM nginx:1.21.3-alpine
+# hadolint ignore=DL3018
+RUN rm -rf /etc/nginx/conf./* && \
+    chmod 777 /run && \
+    apk add --no-cache --update libcap openssl && \
+    curl https://letsencrypt.org/certs/isrg-root-ocsp-x1.pem.txt > /etc/ssl/ocsp.pem && \
+    mkdir /var/ssl &&\
+    curl https://ssl-config.mozilla.org/ffdhe2048.txt > /var/ssl/dhparams &&\
+    chmod 644 /var/ssl/dhparams && \
+    install -d -m 755 -o root -g root /etc/nginx/snippets && \
+    install -d -m 755 -o root -g root /var/ssl && \
+    install -d -m 755 -o root -g root /var/www && \
+    install -d -m 700 -o nginx -g nginx /var/cache/nginx && \
+    openssl req -x509 \
+                -newkey rsa:4096 \
+                -keyout /var/ssl/site.key \
+                -nodes \
+                -out /var/ssl/site.crt \
+                -batch && \
+    setcap CAP_NET_BIND_SERVICE=+ep "$(command -v nginx)" && \
+    chown nginx /var/ssl/site.*
+COPY conf.d/ /etc/nginx/conf.d/
+COPY snippets/ /etc/nginx/snippets/
+USER nginx
+RUN nginx -t
+HEALTHCHECK CMD curl --fail --verbose --user-agent 'Docker health check' --header "Host: status" http://localhost/ || exit 1
diff --git a/nginx/README.md b/nginx/README.md
new file mode 100644
index 0000000..630b37a
--- /dev/null
+++ b/nginx/README.md
@@ -0,0 +1,3 @@
+# Nginx
+
+My tweaked version of the Nginx image.
diff --git a/nginx/conf.d/default.conf b/nginx/conf.d/default.conf
new file mode 100644
index 0000000..f428ba9
--- /dev/null
+++ b/nginx/conf.d/default.conf
@@ -0,0 +1,13 @@
+server {
+    listen      80 default_server;
+    listen      [::]:80 default_server;
+    include     snippets/www-acme-challenge.conf;
+    location    / { return 301 https://www.shore.co.il$request_uri; }
+}
+
+server {
+    listen      443 ssl http2 default_server;
+    listen      [::]:443 ssl http2 default_server;
+    include     snippets/ssl.conf;
+    location    / { return 301 https://www.shore.co.il$request_uri; }
+}
diff --git a/nginx/conf.d/global.conf b/nginx/conf.d/global.conf
new file mode 100644
index 0000000..608fe8d
--- /dev/null
+++ b/nginx/conf.d/global.conf
@@ -0,0 +1,13 @@
+# The resolver for the Docker network.
+resolver                        127.0.0.11 valid=30s;
+gzip                            on;
+tcp_nopush                      on;
+tcp_nodelay                     on;
+server_tokens                   off;
+include                         snippets/common-headers.conf;
+# Validate proxied SSL connections.
+proxy_ssl_trusted_certificate   /etc/ssl/certs/ca-certificates.crt;
+proxy_ssl_verify                on;
+proxy_ssl_verify_depth          4;
+# For proxying /validate on different hosts to Vouch.
+map $host $vouch { default vouch; }
diff --git a/nginx/conf.d/status.conf b/nginx/conf.d/status.conf
new file mode 100644
index 0000000..6ecb7d8
--- /dev/null
+++ b/nginx/conf.d/status.conf
@@ -0,0 +1,7 @@
+server {
+    listen      80;
+    listen      [::]:80;
+    server_name status;
+    location = /        { stub_status; }
+    include     snippets/allow-private-ips.conf;
+}
diff --git a/nginx/snippets/ads-txt.conf b/nginx/snippets/ads-txt.conf
new file mode 100644
index 0000000..b074c08
--- /dev/null
+++ b/nginx/snippets/ads-txt.conf
@@ -0,0 +1,9 @@
+location = /ads.txt {
+    if ($scheme = http) {
+        return 301 https://$host$request_uri;
+    }
+    if ($scheme = https) {
+        add_header Content-Type "text/plain; charset=utf-8";
+        return 200 "contact=webmaster@shore.co.il\n";
+    }
+}
diff --git a/nginx/snippets/allow-ns1.conf b/nginx/snippets/allow-ns1.conf
new file mode 100644
index 0000000..bdadb24
--- /dev/null
+++ b/nginx/snippets/allow-ns1.conf
@@ -0,0 +1 @@
+allow   62.219.131.121;  # ns1.shore.co.il
diff --git a/nginx/snippets/allow-ns4.conf b/nginx/snippets/allow-ns4.conf
new file mode 100644
index 0000000..5e39f40
--- /dev/null
+++ b/nginx/snippets/allow-ns4.conf
@@ -0,0 +1 @@
+allow   163.172.74.36;  # ns4.shore.co.il
diff --git a/nginx/snippets/allow-private-ips.conf b/nginx/snippets/allow-private-ips.conf
new file mode 100644
index 0000000..154262a
--- /dev/null
+++ b/nginx/snippets/allow-private-ips.conf
@@ -0,0 +1,5 @@
+allow 127.0.0.0/8;
+allow 10.0.0.0/8;
+allow 192.168.0.0/16;
+allow 172.16.0.0/12;
+deny all;
diff --git a/nginx/snippets/allow-shore-ips.conf b/nginx/snippets/allow-shore-ips.conf
new file mode 100644
index 0000000..709b549
--- /dev/null
+++ b/nginx/snippets/allow-shore-ips.conf
@@ -0,0 +1,3 @@
+include snippets/allow-ns1.conf;
+include snippets/allow-ns4.conf;
+include snippets/allow-private-ips.conf;
diff --git a/nginx/snippets/common-headers.conf b/nginx/snippets/common-headers.conf
new file mode 100644
index 0000000..e97cb68
--- /dev/null
+++ b/nginx/snippets/common-headers.conf
@@ -0,0 +1,6 @@
+# add_headers are inherited from previous level if and only if there are no
+# add_header directives defined on the current level. So any time there's an
+# add_header directive there should be an `include snippets/common-headers.conf`
+# directive as well.
+add_header      X-Frame-Options SAMEORIGIN always;
+add_header      Permissions-Policy interest-cohort=();
diff --git a/nginx/snippets/ldap-auth.conf b/nginx/snippets/ldap-auth.conf
new file mode 100644
index 0000000..822c440
--- /dev/null
+++ b/nginx/snippets/ldap-auth.conf
@@ -0,0 +1,10 @@
+auth_request    /validate;
+
+location = /validate {
+  proxy_pass                        https://auth.shore.co.il/validate;
+  proxy_http_version                1.1;
+  include                           snippets/proxy-ssl.conf;
+  internal;
+  proxy_pass_request_body           off;
+  proxy_set_header Content-Length   "";
+}
diff --git a/nginx/snippets/proxy-headers.conf b/nginx/snippets/proxy-headers.conf
new file mode 100644
index 0000000..e142036
--- /dev/null
+++ b/nginx/snippets/proxy-headers.conf
@@ -0,0 +1,8 @@
+proxy_set_header    X-Forwarded-Host $host;
+proxy_set_header    X-Forwarded-Proto $scheme;
+proxy_set_header    X-Forwarded-For $proxy_add_x_forwarded_for;
+proxy_set_header    Host $host;
+proxy_set_header    X-Real-IP $remote_addr;
+proxy_hide_header   Strict-Transport-Security;
+proxy_hide_header   Public-Key-Pins;
+proxy_hide_header   Public-Key-Pins-Report-Only;
diff --git a/nginx/snippets/proxy-ssl.conf b/nginx/snippets/proxy-ssl.conf
new file mode 100644
index 0000000..b83886a
--- /dev/null
+++ b/nginx/snippets/proxy-ssl.conf
@@ -0,0 +1,5 @@
+proxy_ssl_verify                  on;
+proxy_ssl_verify_depth            3;
+proxy_ssl_name                    auth.shore.co.il;
+proxy_ssl_server_name             on;
+proxy_ssl_trusted_certificate     /etc/ssl/certs/ca-certificates.crt;
diff --git a/nginx/snippets/redirect-https.conf b/nginx/snippets/redirect-https.conf
new file mode 100644
index 0000000..991d593
--- /dev/null
+++ b/nginx/snippets/redirect-https.conf
@@ -0,0 +1 @@
+location    / { return 301 https://$host$request_uri; }
diff --git a/nginx/snippets/redirect-www.conf b/nginx/snippets/redirect-www.conf
new file mode 100644
index 0000000..2d89d75
--- /dev/null
+++ b/nginx/snippets/redirect-www.conf
@@ -0,0 +1 @@
+location    / { return 301 https://www.$host$request_uri; }
diff --git a/nginx/snippets/robots-allow-all.conf b/nginx/snippets/robots-allow-all.conf
new file mode 100644
index 0000000..627aee5
--- /dev/null
+++ b/nginx/snippets/robots-allow-all.conf
@@ -0,0 +1,4 @@
+location = /robots.txt {
+    add_header Content-Type "text/plain; charset=utf-8";
+    return 200 "User-agent: *\nDisallow:\n";
+}
diff --git a/nginx/snippets/robots-disallow-all.conf b/nginx/snippets/robots-disallow-all.conf
new file mode 100644
index 0000000..03d5031
--- /dev/null
+++ b/nginx/snippets/robots-disallow-all.conf
@@ -0,0 +1,4 @@
+location = /robots.txt {
+    add_header Content-Type "text/plain; charset=utf-8";
+    return 200 "User-agent: *\nDisallow: *\n";
+}
diff --git a/nginx/snippets/security-txt.conf b/nginx/snippets/security-txt.conf
new file mode 100644
index 0000000..c1f0d21
--- /dev/null
+++ b/nginx/snippets/security-txt.conf
@@ -0,0 +1,9 @@
+location = /.well-known/security.txt {
+    if ($scheme = http) {
+        return 301 https://$host$request_uri;
+    }
+    if ($scheme = https) {
+        add_header Content-Type "text/plain; charset=utf-8";
+        return 200 "Contact: mailto:security@shore.co.il\nEncryption: https://www.shore.co.il/blog/static/nimrod.asc";
+    }
+}
diff --git a/nginx/snippets/ssl.conf b/nginx/snippets/ssl.conf
new file mode 100644
index 0000000..cb1f77f
--- /dev/null
+++ b/nginx/snippets/ssl.conf
@@ -0,0 +1,14 @@
+add_header                  Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
+add_header                  Expect-CT "max-age=86400, enforce, report-uri=\"https://www.shore.co.il/about\"";
+include                     snippets/common-headers.conf;
+ssl_certificate             /var/ssl/site.crt;
+ssl_certificate_key         /var/ssl/site.key;
+ssl_dhparam                 /var/ssl/dhparams;
+ssl_protocols               TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
+ssl_ciphers                 !AESCCM:!kRSA:!3DES:!RC4:!DES:!MD5:!aNULL:!NULL:AESGCM+ECDH:ECDH+CHACHA20:AES256+ECDH:AES128:CHACHA20:+SHA1;
+ssl_prefer_server_ciphers   on;
+ssl_session_cache           shared:SSL:50m;
+ssl_session_timeout         5m;
+ssl_stapling                on;
+ssl_stapling_verify         on;
+ssl_trusted_certificate     /etc/ssl/ocsp.pem;
diff --git a/nginx/snippets/upgrade-secure.conf b/nginx/snippets/upgrade-secure.conf
new file mode 100644
index 0000000..2abc805
--- /dev/null
+++ b/nginx/snippets/upgrade-secure.conf
@@ -0,0 +1 @@
+if ($http_Upgrade-Insecure-Requests = 1) { return 301 https://$host$request_uri; }
diff --git a/nginx/snippets/vouch.conf b/nginx/snippets/vouch.conf
new file mode 100644
index 0000000..9571b80
--- /dev/null
+++ b/nginx/snippets/vouch.conf
@@ -0,0 +1,30 @@
+# send all requests to the `/validate` endpoint for authorization
+auth_request    /validate;
+
+location = /validate {
+  # forward the /validate request to Vouch Proxy
+  proxy_pass                        http://$vouch:9090/validate;
+  proxy_http_version                1.1;
+  internal;
+  include                           snippets/proxy-headers.conf;
+
+  # Vouch Proxy only acts on the request headers
+  proxy_pass_request_body           off;
+  proxy_set_header Content-Length   "";
+
+  # optionally add X-Vouch-User as returned by Vouch Proxy along with the request
+  auth_request_set $auth_resp_x_vouch_user $upstream_http_x_vouch_user;
+
+  # these return values are used by the @error401 call
+  auth_request_set                  $auth_resp_jwt $upstream_http_x_vouch_jwt;
+  auth_request_set                  $auth_resp_err $upstream_http_x_vouch_err;
+  auth_request_set                  $auth_resp_failcount $upstream_http_x_vouch_failcount;
+}
+
+# if validate returns `401 not authorized` then forward the request to the error401block
+error_page 401 = @error401;
+
+location @error401 {
+    # redirect to Vouch Proxy for login
+    return 302 https://vouch.shore.co.il/login?url=$scheme://$http_host$request_uri&vouch-failcount=$auth_resp_failcount&X-Vouch-Token=$auth_resp_jwt&error=$auth_resp_err;
+}
diff --git a/nginx/snippets/websockets.conf b/nginx/snippets/websockets.conf
new file mode 100644
index 0000000..64b7e37
--- /dev/null
+++ b/nginx/snippets/websockets.conf
@@ -0,0 +1,3 @@
+proxy_set_header    Upgrade $http_upgrade;
+proxy_set_header    Connection "Upgrade";
+proxy_read_timeout  36000s;
diff --git a/nginx/snippets/www-acme-challenge.conf b/nginx/snippets/www-acme-challenge.conf
new file mode 100644
index 0000000..ba3c0b7
--- /dev/null
+++ b/nginx/snippets/www-acme-challenge.conf
@@ -0,0 +1 @@
+location /.well-known/acme-challenge/ { root /var/www/www.shore.co.il; }
-- 
GitLab