diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a72788fb508c319c6594c169b617c92fc9a31bb5..62ca6edd9b78ba115ebb35736d5f472130db5e5c 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 0000000000000000000000000000000000000000..380e2e62d48d3718eee6fb713bc578042f0ab6fd --- /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 0000000000000000000000000000000000000000..41618e0a01a69691c02e87e6dc8fa0ba4f07b8a2 --- /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 0000000000000000000000000000000000000000..630b37a377e6609d2bf5eb1bcffd937041601eee --- /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 0000000000000000000000000000000000000000..f428ba9fdb62cac0a123ea3c6832ab5d29d1c4e3 --- /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 0000000000000000000000000000000000000000..608fe8de67213f080cf2736ceceab71c26117be1 --- /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 0000000000000000000000000000000000000000..6ecb7d85dd156109d327ee8d4b3e0ba39ce1681d --- /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 0000000000000000000000000000000000000000..b074c08328eef2bc8a18f41937ff787ceacbe6ba --- /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 0000000000000000000000000000000000000000..bdadb248d461af214acec72962ed6f45b9ac4651 --- /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 0000000000000000000000000000000000000000..5e39f4028d30aa2529179de757b07a19d4039ff6 --- /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 0000000000000000000000000000000000000000..154262aa4070edf80c878a8fba8cdf6a9f03030a --- /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 0000000000000000000000000000000000000000..709b549d2e1c5e15fa5cd4c8d671a509181f6a0f --- /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 0000000000000000000000000000000000000000..e97cb6890f107423095a68af45db2c4662ecc482 --- /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 0000000000000000000000000000000000000000..822c4407093249d3d77133c9e93b86374b2afae9 --- /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 0000000000000000000000000000000000000000..e1420368822afd1ec4d574b95a2403909fe2a439 --- /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 0000000000000000000000000000000000000000..b83886af06e69be66442d924d1ca1c2f58c88125 --- /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 0000000000000000000000000000000000000000..991d5934ea26fdf1596be7731bca044be57e1a21 --- /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 0000000000000000000000000000000000000000..2d89d75e34296121d630cd90330793dff97ff19d --- /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 0000000000000000000000000000000000000000..627aee5db300861870c3dc70c82016a4abd676c2 --- /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 0000000000000000000000000000000000000000..03d50312dfd4108ef9758ff8d65f4d090d0a4c1c --- /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 0000000000000000000000000000000000000000..c1f0d219b5dc1f4fe2537633a0c2ed58f05f10b3 --- /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 0000000000000000000000000000000000000000..cb1f77f67c32f78cacdde5ed8f5f0d74b346ac2c --- /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 0000000000000000000000000000000000000000..2abc805d48d6d33d67fa70967ac85fc7075dc65c --- /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 0000000000000000000000000000000000000000..9571b80c28f366b99b57096ab7c23afacf61b46d --- /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 0000000000000000000000000000000000000000..64b7e3736a33c2d1e6621b4b0d64076030759251 --- /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 0000000000000000000000000000000000000000..ba3c0b7117cdc522b64ab5593b5d888e72e8a7df --- /dev/null +++ b/nginx/snippets/www-acme-challenge.conf @@ -0,0 +1 @@ +location /.well-known/acme-challenge/ { root /var/www/www.shore.co.il; }