From 670c1f7561f2fb758866b9382adb3247106f3586 Mon Sep 17 00:00:00 2001 From: "Dustin C. Hatch" Date: Wed, 29 Mar 2023 11:20:45 -0500 Subject: [PATCH] yellow: Install/configure nginx We're going to use *nginx* as the reverse proxy in front of Home Assistant, as well as the web consoles for Zigbee2MQTT and ZWaveJS2MQTT. It will provide TLS termination for all of these applications. Since *nginx* will not start without a certificate and private key file for HTTPS, the *gen-nginx-cert.service* systemd unit generates a self-signed certificate if one does not already exist. This ensures that *nginx* can start by default, but still allows the administrator to replace the certificate with a trusted one later. The *nginx* container image has symlinks at `/var/log/nginx/error.log` and `/var/log/nginx/access.log`, pointing to `/dev/stderr` and `/dev/stdout`, respectively. The intent here is to send all log messages to the container runtime. Unfortunately, when the the container is managed by Podman from a systemd unit, the standard output and standard error streams are connected to the systemd journal via a UNIX socket. As a result, the `/dev/stdout` and `/dev/stderr` pseudo-files cannot be "opened" like normal files or pipes. Thus, to forward nginx's logs to the systemd journal correctly, we have to do a bit of trickery. For the error log at least, setting `error_log stderr` works well; nginx simply writes messages to the existing file descriptor. Unfortunately, the access log has no such mechanism. For that, we use nginx's syslog capabilities. The `/dev/log` socket is bind-mounted into the container, and nginx is configured to connect to it. --- yellow/install.packages | 2 + yellow/overlay/etc/aimee-os/writable-etc | 1 + .../etc/containers/systemd/nginx.container | 26 +++++++ yellow/overlay/etc/nginx/conf.d/default.conf | 70 +++++++++++++++++++ yellow/overlay/etc/nginx/nginx.conf | 36 ++++++++++ .../etc/systemd/system/gen-nginx-cert.service | 40 +++++++++++ yellow/overlay/usr/libexec/gen-nginx-cert | 38 ++++++++++ yellow/semanage.mods | 1 + 8 files changed, 214 insertions(+) create mode 100644 yellow/overlay/etc/containers/systemd/nginx.container create mode 100644 yellow/overlay/etc/nginx/conf.d/default.conf create mode 100644 yellow/overlay/etc/nginx/nginx.conf create mode 100644 yellow/overlay/etc/systemd/system/gen-nginx-cert.service create mode 100755 yellow/overlay/usr/libexec/gen-nginx-cert create mode 100644 yellow/semanage.mods diff --git a/yellow/install.packages b/yellow/install.packages index d00011d..eb4a49e 100644 --- a/yellow/install.packages +++ b/yellow/install.packages @@ -1,2 +1,4 @@ +acct-group/nginx +acct-user/nginx app-backup/burp app-containers/podman diff --git a/yellow/overlay/etc/aimee-os/writable-etc b/yellow/overlay/etc/aimee-os/writable-etc index cf39d96..cc481d9 100644 --- a/yellow/overlay/etc/aimee-os/writable-etc +++ b/yellow/overlay/etc/aimee-os/writable-etc @@ -1,2 +1,3 @@ d burp d mosquitto +d nginx/ssl diff --git a/yellow/overlay/etc/containers/systemd/nginx.container b/yellow/overlay/etc/containers/systemd/nginx.container new file mode 100644 index 0000000..422fabd --- /dev/null +++ b/yellow/overlay/etc/containers/systemd/nginx.container @@ -0,0 +1,26 @@ +# vim: set ft=systemd : +[Unit] +After=network-online.target +Wants=network-online.target +Requires=gen-nginx-cert.service + +[Container] +Image=docker.io/library/nginx +Network=host +Volume=/etc/nginx/nginx.conf:/etc/nginx/nginx.conf:ro +Volume=/etc/nginx/conf.d:/etc/nginx/conf.d:ro +Volume=/etc/nginx/ssl:/etc/nginx/ssl:ro +Volume=/dev/log:/dev/log +User=82 +Group=82 +AddCapability=CAP_NET_BIND_SERVICE +ReadOnly=true +VolatileTmp=yes + +[Service] +ProtectSystem=full +UMask=0077 +ExecReload=/usr/bin/podman exec systemd-%N nginx -s reload + +[Install] +WantedBy=multi-user.target diff --git a/yellow/overlay/etc/nginx/conf.d/default.conf b/yellow/overlay/etc/nginx/conf.d/default.conf new file mode 100644 index 0000000..8ebe600 --- /dev/null +++ b/yellow/overlay/etc/nginx/conf.d/default.conf @@ -0,0 +1,70 @@ +# vim: set sw=4 ts=4 sts=4 et : +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} + +server { + listen 80 default_server; + listen [::]:80 default_server; + server_name _; + root /usr/share/nginx/html; + + return 301 https://$host$request_uri; + + error_page 500 502 503 504 /50x.html; + + location = /50x.html { } +} + +server { + listen 443 ssl http2 default_server; + listen [::]:443 ssl http2 default_server; + server_name _; + root /usr/share/nginx/html; + + ssl_certificate "/etc/nginx/ssl/server.crt"; + ssl_certificate_key "/etc/nginx/ssl/server.key"; + ssl_session_cache shared:SSL:1m; + ssl_prefer_server_ciphers on; + + error_page 500 502 503 504 /50x.html; + + add_header + Strict-Transport-Security + "max-age=63072000; includeSubDomains" + always; + + location = /50x.html { } + + location = /zwave { + return 301 https://$host/zwave/; + } + + location = /zigbee { + return 301 https://$host/zigbee/; + } + + location /zwave/ { + proxy_pass http://127.0.0.1:8091/; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header X-External-Path /zwave; + } + + location /zigbee/ { + proxy_pass http://127.0.0.1:8080/; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header X-External-Path /zigbee; + } + + location / { + proxy_pass http://[::1]:8123/; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + } +} diff --git a/yellow/overlay/etc/nginx/nginx.conf b/yellow/overlay/etc/nginx/nginx.conf new file mode 100644 index 0000000..a7496cf --- /dev/null +++ b/yellow/overlay/etc/nginx/nginx.conf @@ -0,0 +1,36 @@ +worker_processes auto; + +error_log stderr notice; +pid /tmp/nginx.pid; + + +events { + worker_connections 1024; +} + + +http { + client_body_temp_path /tmp/client_temp; + proxy_temp_path /tmp/proxy_temp; + fastcgi_temp_path /tmp/fastcgi_temp; + uwsgi_temp_path /tmp/uwsgi_temp; + scgi_temp_path /tmp/scgi_temp; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log syslog:server=unix:/dev/log main; + + sendfile on; + #tcp_nopush on; + + keepalive_timeout 65; + + #gzip on; + + include /etc/nginx/conf.d/*.conf; +} diff --git a/yellow/overlay/etc/systemd/system/gen-nginx-cert.service b/yellow/overlay/etc/systemd/system/gen-nginx-cert.service new file mode 100644 index 0000000..f5f867b --- /dev/null +++ b/yellow/overlay/etc/systemd/system/gen-nginx-cert.service @@ -0,0 +1,40 @@ +# vim: set ft=systemd : +[Unit] +Description=Generate self-signed certificate for nginx +Before=nginx.service +ConditionPathExists=!/etc/nginx/ssl/server.crt + +[Service] +Type=oneshot +ExecStart=/usr/libexec/gen-nginx-cert +User=root +Group=nginx +CapabilityBoundingSet= +DeviceAllow= +DevicePolicy=closed +IPAddressDeny=any +LockPersonality=yes +MemoryDenyWriteExecute=yes +NoNewPrivileges=yes +PrivateDevices=yes +PrivateNetwork=yes +PrivateTmp=yes +PrivateUsers=yes +ProcSubset=pid +ProtectClock=yes +ProtectControlGroups=yes +ProtectHome=yes +ProtectHostname=yes +ProtectKernelLogs=yes +ProtectKernelModules=yes +ProtectKernelTunables=yes +ProtectProc=invisible +ProtectSystem=yes +RestrictAddressFamilies= +RestrictNamespaces=yes +RestrictRealtime=yes +RestrictSUIDSGID=yes +SystemCallArchitectures=native +SystemCallFilter=@system-service +SystemCallFilter=~@privileged +UMask=0027 diff --git a/yellow/overlay/usr/libexec/gen-nginx-cert b/yellow/overlay/usr/libexec/gen-nginx-cert new file mode 100755 index 0000000..eeab88a --- /dev/null +++ b/yellow/overlay/usr/libexec/gen-nginx-cert @@ -0,0 +1,38 @@ +#!/bin/sh +# vim: set sw=4 ts=4 sts=4 et : + +DAYS=90 +SUBJ=/CN=localhost +ALG=EC +CURVE=secp384r1 + +if [ -f /etc/default/gen-nginx-cert ]; then + . /etc/default/gen-nginx-cert +fi + +set -- \ + -out /etc/nginx/ssl/server.key \ + -algorithm "${ALG}" + +case "${ALG}" in +EC) + set -- "$@" \ + -pkeyopt ec_paramgen_curve:${CURVE} \ + -pkeyopt ec_param_enc:named_curve + ;; +RSA) + set -- "$@" \ + -pkeyopt rsa_keygen_bits:${BITS:+4096} + ;; +esac + +rm -f /etc/nginx/ssl/server.crt /etc/nginx/ssl/server.key +: > /etc/nginx/ssl/server.key +openssl genpkey "$@" +openssl \ + req -x509 \ + -subj "${SUBJ}" \ + -key /etc/nginx/ssl/server.key \ + -out /etc/nginx/ssl/server.crt \ + -sha256 \ + -days "${DAYS}" diff --git a/yellow/semanage.mods b/yellow/semanage.mods new file mode 100644 index 0000000..900c0d1 --- /dev/null +++ b/yellow/semanage.mods @@ -0,0 +1 @@ +boolean -m -1 container_mounton_non_security