feat(nginx): add security headers with per-domain CSP #1
4 changed files with 35 additions and 1 deletions
|
|
@ -104,6 +104,8 @@
|
||||||
domains = {
|
domains = {
|
||||||
"search.ashisgreat.xyz" = {
|
"search.ashisgreat.xyz" = {
|
||||||
port = 8888;
|
port = 8888;
|
||||||
|
# SearXNG sets its own CSP in settings.yml — omit at Nginx level to avoid conflicts
|
||||||
|
contentSecurityPolicy = null;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,8 @@ in
|
||||||
extraConfig = ''
|
extraConfig = ''
|
||||||
client_max_body_size 512M;
|
client_max_body_size 512M;
|
||||||
'';
|
'';
|
||||||
|
# Relaxed CSP for Forgejo — needs inline styles for code highlighting
|
||||||
|
contentSecurityPolicy = "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self' wss://${cfg.domain}; frame-ancestors 'self'";
|
||||||
};
|
};
|
||||||
|
|
||||||
# Open SSH port for Git
|
# Open SSH port for Git
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# Nginx Reverse Proxy Module
|
# Nginx Reverse Proxy Module
|
||||||
# Provides: Nginx with automatic Let's Encrypt certificates
|
# Provides: Nginx with automatic Let's Encrypt certificates and security headers
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# myModules.nginx = {
|
# myModules.nginx = {
|
||||||
|
|
@ -39,11 +39,19 @@ in
|
||||||
type = lib.types.port;
|
type = lib.types.port;
|
||||||
description = "Local port to proxy to";
|
description = "Local port to proxy to";
|
||||||
};
|
};
|
||||||
|
|
||||||
extraConfig = lib.mkOption {
|
extraConfig = lib.mkOption {
|
||||||
type = lib.types.lines;
|
type = lib.types.lines;
|
||||||
default = "";
|
default = "";
|
||||||
description = "Extra Nginx config for this location";
|
description = "Extra Nginx config for this location";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
contentSecurityPolicy = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'self'";
|
||||||
|
description = "Content-Security-Policy header value. Set to null to omit.";
|
||||||
|
};
|
||||||
|
|
||||||
extraLocations = lib.mkOption {
|
extraLocations = lib.mkOption {
|
||||||
type = lib.types.attrsOf (lib.types.submodule {
|
type = lib.types.attrsOf (lib.types.submodule {
|
||||||
options = {
|
options = {
|
||||||
|
|
@ -96,6 +104,26 @@ in
|
||||||
enableACME = true;
|
enableACME = true;
|
||||||
forceSSL = true;
|
forceSSL = true;
|
||||||
|
|
||||||
|
# Security headers applied per-vhost
|
||||||
|
extraConfig = ''
|
||||||
|
# Strict Transport Security — 6 months, include subdomains, preload-ready
|
||||||
|
add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload" always;
|
||||||
|
|
||||||
|
# Prevent MIME type sniffing
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
|
||||||
|
# Restrict browser features
|
||||||
|
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
|
||||||
|
|
||||||
|
# Cross-origin isolation
|
||||||
|
add_header Cross-Origin-Resource-Policy "same-origin" always;
|
||||||
|
add_header Cross-Origin-Opener-Policy "same-origin" always;
|
||||||
|
|
||||||
|
# Content Security Policy (configurable per-domain)
|
||||||
|
'' + lib.optionalString (opts.contentSecurityPolicy != null) ''
|
||||||
|
add_header Content-Security-Policy "${opts.contentSecurityPolicy}" always;
|
||||||
|
'';
|
||||||
|
|
||||||
locations = {
|
locations = {
|
||||||
"/" = {
|
"/" = {
|
||||||
proxyPass = "http://127.0.0.1:${toString opts.port}";
|
proxyPass = "http://127.0.0.1:${toString opts.port}";
|
||||||
|
|
|
||||||
|
|
@ -114,6 +114,8 @@ in
|
||||||
extraConfig = ''
|
extraConfig = ''
|
||||||
client_max_body_size 128M;
|
client_max_body_size 128M;
|
||||||
'';
|
'';
|
||||||
|
# Relaxed CSP for Vaultwarden — needs unsafe-eval for WebCrypto vault
|
||||||
|
contentSecurityPolicy = "default-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://haveibeenpwned.com; font-src 'self'; connect-src 'self' wss://${cfg.domain} https://api.bitwarden.com https://haveibeenpwned.com; frame-ancestors 'self'";
|
||||||
extraLocations."/notifications/hub" = {
|
extraLocations."/notifications/hub" = {
|
||||||
proxyPass = "http://127.0.0.1:${toString cfg.websocketPort}";
|
proxyPass = "http://127.0.0.1:${toString cfg.websocketPort}";
|
||||||
extraConfig = ''
|
extraConfig = ''
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue