From 1bf7d1835f2e9be510fcff2f9f0a3467e0e4792c Mon Sep 17 00:00:00 2001 From: Tommy Date: Mon, 24 Jun 2024 10:21:29 -0700 Subject: [PATCH] Initial file upload Signed-off-by: Tommy --- README.md | 18 ++++ certbot/uptime-kuma | 5 ++ etc/nginx/conf.d/http2.conf | 3 + etc/nginx/conf.d/sites_default.conf | 10 +++ etc/nginx/conf.d/sites_uptime-kuma.conf | 27 ++++++ etc/nginx/conf.d/tls.conf | 31 +++++++ etc/nginx/snippets/hsts.conf | 5 ++ etc/nginx/snippets/proxy.conf | 27 ++++++ etc/nginx/snippets/quic.conf | 2 + etc/nginx/snippets/security.conf | 25 ++++++ etc/nginx/snippets/universal_paths.conf | 3 + etc/sysctl.d/99-nonlocal-bind.conf | 2 + .../certbot-renew.service.d/override.conf | 30 +++++++ .../system/nginx.service.d/override.conf | 30 +++++++ setup.sh | 85 +++++++++++++++++++ 15 files changed, 303 insertions(+) create mode 100644 README.md create mode 100644 certbot/uptime-kuma create mode 100644 etc/nginx/conf.d/http2.conf create mode 100644 etc/nginx/conf.d/sites_default.conf create mode 100644 etc/nginx/conf.d/sites_uptime-kuma.conf create mode 100644 etc/nginx/conf.d/tls.conf create mode 100644 etc/nginx/snippets/hsts.conf create mode 100644 etc/nginx/snippets/proxy.conf create mode 100644 etc/nginx/snippets/quic.conf create mode 100644 etc/nginx/snippets/security.conf create mode 100644 etc/nginx/snippets/universal_paths.conf create mode 100644 etc/sysctl.d/99-nonlocal-bind.conf create mode 100644 etc/systemd/system/certbot-renew.service.d/override.conf create mode 100644 etc/systemd/system/nginx.service.d/override.conf create mode 100644 setup.sh diff --git a/README.md b/README.md new file mode 100644 index 0000000..15be3c3 --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# NGINX Configs + +These are my NGINX configurations. They are written for Fedora CoreOS's NGINX build with `nginx-mod-stream`. + +## Getting Started + +1. Install `nginx`, `nginx-mod-stream`, and `policycoreutils-python-utils` on Fedora. Makesure `rsync` is available on the OS. +2. Comment out the default server block in `/etc/nginx/nginx.conf`. +3. Copy all configuration files in `/etc/nginx` except the ones named `/etc/nginx/conf.d/sites_.*` to the corresponding location onto the server. +4. Run `setup.sh` +5. Make a dummy vhost listening on port `80` with the server_name you want. +6. Generate certificates with the example in the certbot directory. +7. Copy `/etc/nginx/conf.d/sites_default.conf` to `/etc/nginx/conf.d` for https redirection. +8. Make your actual vhost config based on the `sites_.*` samples in `/etc/nginx/conf.d/sites_default.conf`. + +## Notes + +This is used on my tunnel servers with multiple IP addresses. Hence, you may see addresses like `ipv4_1` and `ipv4_2`. Just replace them with your own ip addresses. diff --git a/certbot/uptime-kuma b/certbot/uptime-kuma new file mode 100644 index 0000000..1622001 --- /dev/null +++ b/certbot/uptime-kuma @@ -0,0 +1,5 @@ +certbot certonly --webroot --webroot-path /srv/nginx --no-eff-email \ + --key-type ecdsa --reuse-key --must-staple \ + --deploy-hook "certbot-ocsp-fetcher -o /var/cache/certbot-ocsp-fetcher" \ + --cert-name uptime.yourdomain.tld \ + -d uptime.yourdomain.tld diff --git a/etc/nginx/conf.d/http2.conf b/etc/nginx/conf.d/http2.conf new file mode 100644 index 0000000..96587ce --- /dev/null +++ b/etc/nginx/conf.d/http2.conf @@ -0,0 +1,3 @@ +# This is all it takes to enable http2 globally + +http2 on; \ No newline at end of file diff --git a/etc/nginx/conf.d/sites_default.conf b/etc/nginx/conf.d/sites_default.conf new file mode 100644 index 0000000..88e03b1 --- /dev/null +++ b/etc/nginx/conf.d/sites_default.conf @@ -0,0 +1,10 @@ +server { + listen ipv4_1:80 default_server; + listen [ipv6_1]:80 default_server; + + include snippets/universal_paths.conf; + + location / { + return 308 https://$host$request_uri; + } +} \ No newline at end of file diff --git a/etc/nginx/conf.d/sites_uptime-kuma.conf b/etc/nginx/conf.d/sites_uptime-kuma.conf new file mode 100644 index 0000000..d33fd47 --- /dev/null +++ b/etc/nginx/conf.d/sites_uptime-kuma.conf @@ -0,0 +1,27 @@ +# This file assumes you have an uptime kuma instance running on the server + +server { + listen ipv4_1:443 quic reuseport; + listen ipv4_1:443 ssl; + listen ipv6_1:443 quic reuseport; + listen ipv6_1:443 ssl; + + server_name uptime.yourdomain.tld; + + ssl_certificate /etc/letsencrypt/live/uptime.yourdomain.tld/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/uptime.yourdomain.tld/privkey.pem; + ssl_trusted_certificate /etc/letsencrypt/live/uptime.yourdomain.tld/chain.pem; + ssl_stapling_file /var/cache/certbot-ocsp-fetcher/uptime.yourdomain.tld.der; + + include snippets/universal_paths.conf; + include snippets/hsts.conf; + include snippets/security.conf; + include snippets/quic.conf; + include snippets/proxy.conf; + proxy_hide_header Content-Security-Policy; + add_header Content-Security-Policy "default-src 'none'; connect-src 'self'; img-src 'self' data:; frame-src 'self'; manifest-src 'self'; object-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; base-uri 'none'; block-all-mixed-content; form-action 'none'; frame-ancestors 'self'; upgrade-insecure-requests"; + + location / { + proxy_pass http://127.0.0.1:3001; + } +} diff --git a/etc/nginx/conf.d/tls.conf b/etc/nginx/conf.d/tls.conf new file mode 100644 index 0000000..8a7a9d5 --- /dev/null +++ b/etc/nginx/conf.d/tls.conf @@ -0,0 +1,31 @@ +# Shared TLS configuration + +## Use strong ciphers +ssl_protocols TLSv1.2 TLSv1.3; +ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256; +ssl_prefer_server_ciphers on; +ssl_conf_command Options PrioritizeChaCha; + +## Configure ssl session cache +## Improves performance but we don't wanna keep this forever +## Session ticket creation and rotation is handled by GrapheneOS's scripts: +## https://github.com/GrapheneOS/infrastructure/blob/main/nginx-create-session-ticket-keys +## https://github.com/GrapheneOS/infrastructure/blob/main/nginx-rotate-session-ticket-keys + +ssl_session_cache shared:SSL:10m; # About 40000 sessions +ssl_session_timeout 1d; +ssl_session_ticket_key session-ticket-keys/4.key; +ssl_session_ticket_key session-ticket-keys/3.key; +ssl_session_ticket_key session-ticket-keys/2.key; +ssl_session_ticket_key session-ticket-keys/1.key; + +## Enable OCSP Stapling +## We will use GrapheneOS's OCSP Fetcher to get the stapling file: https://github.com/GrapheneOS/infrastructure/blob/main/certbot-ocsp-fetcher +ssl_stapling on; +ssl_stapling_verify on; + +## The following settings need to be declared manually per vhost: +# ssl_certificate +# ssl_certificate_key +# ssl_trusted_certificate +# ssl_stapling_file \ No newline at end of file diff --git a/etc/nginx/snippets/hsts.conf b/etc/nginx/snippets/hsts.conf new file mode 100644 index 0000000..14cca7f --- /dev/null +++ b/etc/nginx/snippets/hsts.conf @@ -0,0 +1,5 @@ +# Enable HSTS header +# Only add this to server blocks with TLS + +proxy_hide_header Strict-Transport-Security; +add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; \ No newline at end of file diff --git a/etc/nginx/snippets/proxy.conf b/etc/nginx/snippets/proxy.conf new file mode 100644 index 0000000..40e8006 --- /dev/null +++ b/etc/nginx/snippets/proxy.conf @@ -0,0 +1,27 @@ +# Proxy Header Settings +# Use this with all reverse proxy vhosts + +# Force http 1.1, anything not supporting it shouldn't be used +proxy_http_version 1.1; + +# Replay attack mitigation for early data +proxy_set_header Early-Data $ssl_early_data; + +# Restore visitor IP +proxy_set_header X-Real-IP $remote_addr; + +# Restore original method & URL +proxy_set_header X-Original-Method $request_method; +proxy_set_header X-Original-URL $scheme://$http_host$request_uri; + +# Forward host header +proxy_set_header Host $host; + +# Upgrade connection +proxy_set_header Upgrade $http_upgrade; +proxy_set_header Connection "upgrade"; + +# Enable X-Forwarded headers +proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; +proxy_set_header X-Forwarded-Host $host; +proxy_set_header X-Forwarded-Proto $scheme; \ No newline at end of file diff --git a/etc/nginx/snippets/quic.conf b/etc/nginx/snippets/quic.conf new file mode 100644 index 0000000..0f6c17b --- /dev/null +++ b/etc/nginx/snippets/quic.conf @@ -0,0 +1,2 @@ +quic_retry on; +add_header Alt-Svc 'h3=":443"; ma=86400'; \ No newline at end of file diff --git a/etc/nginx/snippets/security.conf b/etc/nginx/snippets/security.conf new file mode 100644 index 0000000..61bc3ce --- /dev/null +++ b/etc/nginx/snippets/security.conf @@ -0,0 +1,25 @@ +# Global security headers - apply everywhere + +# We do not set clipboard-write() here, because it is very commonly used +proxy_hide_header Strict-Transport-Security; +add_header Permissions-Policy "accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), bluetooth=(), browsing-topics=(), camera=(), clipboard-read=(), display-capture=(), document-domain=(), encrypted-media=(), fullscreen=(), gamepad=(), geolocation=(), gyroscope=(), hid=(), idle-detection=(), interest-cohort=(), keyboard-map=(), local-fonts=(), magnetometer=(), microphone=(), midi=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), serial=(), speaker-selection=(), sync-xhr=(), usb=(), xr-spatial-tracking=()" always; + +proxy_hide_header Permissions-Policy; +add_header Referrer-Policy "same-origin" always; + +proxy_hide_header X-Content-Type-Options; +add_header X-Content-Type-Options "nosniff" always; + +proxy_hide_header X-Frame-Options; +add_header X-Frame-Options "SAMEORIGIN" always; + +proxy_hide_header Cross-Origin-Resource-Policy; +add_header Cross-Origin-Resource-Policy cross-origin; + +proxy_hide_header Cross-Origin-Opener-Policy; +add_header Cross-Origin-Opener-Policy same-origin; + +# Obsolete and replaced by Content-Security-Policy +# Only here to pass Hardenize checks +proxy_hide_header X-XSS-Protection; +add_header X-XSS-Protection "0" always; \ No newline at end of file diff --git a/etc/nginx/snippets/universal_paths.conf b/etc/nginx/snippets/universal_paths.conf new file mode 100644 index 0000000..86c2d3e --- /dev/null +++ b/etc/nginx/snippets/universal_paths.conf @@ -0,0 +1,3 @@ +location /.well-known/acme-challenge/ { + root /srv/nginx; +} \ No newline at end of file diff --git a/etc/sysctl.d/99-nonlocal-bind.conf b/etc/sysctl.d/99-nonlocal-bind.conf new file mode 100644 index 0000000..b450637 --- /dev/null +++ b/etc/sysctl.d/99-nonlocal-bind.conf @@ -0,0 +1,2 @@ +net.ipv4.ip_nonlocal_bind = 1 +net.ipv6.ip_nonlocal_bind = 1 \ No newline at end of file diff --git a/etc/systemd/system/certbot-renew.service.d/override.conf b/etc/systemd/system/certbot-renew.service.d/override.conf new file mode 100644 index 0000000..800ee1a --- /dev/null +++ b/etc/systemd/system/certbot-renew.service.d/override.conf @@ -0,0 +1,30 @@ +# Based on https://github.com/GrapheneOS/infrastructure/blob/main/systemd/system/certbot-renew.service.d/local.conf + +[Service] +CapabilityBoundingSet= +CPUSchedulingPolicy=batch +LockPersonality=true +MemoryDenyWriteExecute=true +NoNewPrivileges=true +PrivateDevices=true +PrivateIPC=true +PrivateUsers=true +PrivateTmp=true +ProcSubset=pid +ProtectClock=true +ProtectControlGroups=true +ProtectHome=read-only +ProtectHostname=true +ProtectKernelLogs=true +ProtectKernelModules=true +ProtectKernelTunables=true +ProtectProc=invisible +ProtectSystem=strict +ReadWritePaths=/etc/letsencrypt /var/lib/letsencrypt /var/log/letsencrypt -/srv/nginx -/var/cache/certbot-ocsp-fetcher +RestrictAddressFamilies=AF_INET AF_INET6 +RestrictNamespaces=true +RestrictRealtime=true +RestrictSUIDSGID=true +SystemCallArchitectures=native +SystemCallFilter=@system-service +SystemCallFilter=~@resources @obsolete \ No newline at end of file diff --git a/etc/systemd/system/nginx.service.d/override.conf b/etc/systemd/system/nginx.service.d/override.conf new file mode 100644 index 0000000..7346ff9 --- /dev/null +++ b/etc/systemd/system/nginx.service.d/override.conf @@ -0,0 +1,30 @@ +# Based on https://github.com/GrapheneOS/infrastructure/blob/main/systemd/system/nginx.service.d/local.conf + +[Service] +CapabilityBoundingSet=CAP_CHOWN CAP_DAC_OVERRIDE CAP_NET_BIND_SERVICE CAP_SETUID CAP_SETGID +LockPersonality=true +MemoryDenyWriteExecute=true +NoNewPrivileges=true +PrivateDevices=true +PrivateIPC=true +PrivateTmp=true +ProcSubset=pid +ProtectClock=true +ProtectControlGroups=true +ProtectHome=true +ProtectHostname=true +ProtectKernelLogs=true +ProtectKernelModules=true +ProtectKernelTunables=true +ProtectProc=invisible +ProtectSystem=strict +ReadWritePaths=/var/lib/nginx /var/log/nginx -/var/cache/nginx +RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX +RestrictNamespaces=true +RestrictRealtime=true +RestrictSUIDSGID=true +RuntimeDirectory=nginx +RuntimeDirectoryMode=700 +SystemCallArchitectures=native +SystemCallFilter=@system-service +SystemCallFilter=~@obsolete \ No newline at end of file diff --git a/setup.sh b/setup.sh new file mode 100644 index 0000000..9d72924 --- /dev/null +++ b/setup.sh @@ -0,0 +1,85 @@ +#!/bin/bash + +# Copyright (C) 2024 Thien Tran +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + + +# Allow reverse proxy +sudo setsebool -P httpd_can_network_connect 1 + +# Allow QUIC +sudp semanage port -a -t http_port_t -p udp 443 + +# Open ports for NGINX +sudo firewall-cmd --permanent --add-service=http +sudo firewall-cmd --permanent --add-service=https +sudo firewall-cmd --permanent --add-port=443/udp +sudo firewall-cmd --reload + +# Add 99-nonlocal-bind.conf +# This fixes a long standing bug where network-online.target is reached before IPv6 is obtained, which breaks IPv6 pinning. +# Also, if you are using floating IPs for NGINX stream like I do, you need it anyways +curl https://raw.githubusercontent.com/TommyTran732/NGINX-Configs/main/etc/sysctl.d/99-nonlocal-bind.conf | sudo tee /etc/sysctl.d/99-nonlocal-bind.conf + +# Setup webroot for NGINX +sudo mkdir /srv/nginx +## Explicitly using /var/srv here because SELinux does not follow symlinks +sudo semanage fcontext -a -t httpd_sys_content_t "/var/srv/nginx(/.*)?" +sudo restorecon -Rv /var/srv/nginx +sudo mkdir -p /srv/nginx/.well-known/acme-challenge + +# NGINX hardening +sudo mkdir -p /etc/systemd/system/nginx.service.d +curl https://raw.githubusercontent.com/GrapheneOS/infrastructure/main/systemd/system/nginx.service.d/local.conf | sudo tee /etc/systemd/system/nginx.service.d/override.conf +sudo systemctl daemon-reload + +# Setup certbot-ocsp-fetcher +curl https://raw.githubusercontent.com/GrapheneOS/infrastructure/main/certbot-ocsp-fetcher | sudo tee /usr/local/bin/certbot-ocsp-fetcher +## Explicitly using /var/usrlocal/bin here because SELinux does not follow symlinks +sudo semanage fcontext -a -t bin_t /var/usrlocal/bin/certbot-ocsp-fetcher +sudo restorecon -Rv /var/usrlocal/bin/certbot-ocsp-fetcher +sudo mkdir /var/cache/certbot-ocsp-fetcher/ +sudo semanage fcontext -a -t httpd_config_t "/var/cache/certbot-ocsp-fetcher(/.*)?" + +# Setup nginx-create-session-ticket-keys +curl https://raw.githubusercontent.com/GrapheneOS/infrastructure/main/nginx-create-session-ticket-keys | sudo tee /usr/local/bin/nginx-create-session-ticket-keys +## Explicitly using /var/usrlocal/bin here because SELinux does not follow symlinks +sudo semanage fcontext -a -t bin_t /var/usrlocal/bin/nginx-create-session-ticket-keys +sudo restorecon /var/usrlocal/bin/nginx-create-session-ticket-keys +echo 'restorecon -Rv /etc/nginx/session-ticket-keys' | sudo tee -a /usr/local/bin/nginx-create-session-ticket-keys + +# Setup nginx-rotate-session-ticket-keys +curl https://raw.githubusercontent.com/GrapheneOS/infrastructure/main/nginx-rotate-session-ticket-keys | sudo tee /usr/local/bin/nginx-rotate-session-ticket-keys +## Explicitly using /var/usrlocal/bin here because SELinux does not follow symlinks +sudo semanage fcontext -a -t bin_t /var/usrlocal/bin/nginx-rotate-session-ticket-keys +sudo restorecon -Rv /var/usrlocal/bin/nginx-rotate-session-ticket-keys +sudo sed -i '$i restorecon -Rv /etc/nginx/session-ticket-keys' /var/usrlocal/bin/nginx-rotate-session-ticket-keys + +# Download the units +curl https://raw.githubusercontent.com/GrapheneOS/infrastructure/main/systemd/system/certbot-ocsp-fetcher.service | sudo tee /etc/systemd/system/certbot-ocsp-fetcher.service +curl https://raw.githubusercontent.com/GrapheneOS/infrastructure/main/systemd/system/certbot-ocsp-fetcher.timer | sudo tee /etc/systemd/system/certbot-ocsp-fetcher.timer +curl https://raw.githubusercontent.com/GrapheneOS/infrastructure/main/systemd/system/nginx-create-session-ticket-keys.service | sudo tee /etc/systemd/system/nginx-create-session-ticket-keys.service +curl https://raw.githubusercontent.com/GrapheneOS/infrastructure/main/systemd/system/nginx-rotate-session-ticket-keys.service | sudo tee /etc/systemd/system/nginx-rotate-session-ticket-keys.service +curl https://raw.githubusercontent.com/GrapheneOS/infrastructure/main/systemd/system/nginx-rotate-session-ticket-keys.timer | sudo tee /etc/systemd/system/nginx-rotate-session-ticket-keys.timer + +# Systemd Hardening +sudo mkdir -p /etc/systemd/system/nginx.service.d /etc/systemd/system/certbot-renew.service.d +curl https://raw.githubusercontent.com/TommyTran732/NGINX-Configs/main/etc/systemd/system/nginx.service.d/override.conf | sudo tee /etc/systemd/system/nginx.service.d/override.conf +curl https://raw.githubusercontent.com/TommyTran732/NGINX-Configs/main/etc/systemd/system/certbot-renew.service.d/override.conf | sudo tee /etc/systemd/system/certbot-renew.service.d/override.conf +sudo systemctl daemon-reload + +# Enable the units +sudo systemctl enable certbot-ocsp-fetcher.timer +sudo systemctl enable --now nginx-create-session-ticket-keys.service +sudo systemctl enable --now nginx-rotate-session-ticket-keys.timer