2017-02-01 08:38:03 +00:00
|
|
|
# -*- awk -*-
|
|
|
|
# eotk (c) 2017 Alec Muffett
|
|
|
|
|
|
|
|
# EMACS awk mode works quite well for nginx configs
|
|
|
|
|
|
|
|
# logs and pids
|
|
|
|
pid %PROJECT_DIR%/nginx.pid;
|
2017-02-22 09:54:06 +00:00
|
|
|
error_log %LOG_DIR%/nginx-error.log %NGINX_SYSLOG%;
|
2017-02-01 08:38:03 +00:00
|
|
|
|
2017-02-28 17:20:32 +00:00
|
|
|
# TODO: notes for custom 403 error-handling pages:
|
|
|
|
# https://www.cyberciti.biz/faq/unix-linux-nginx-custom-error-403-page-configuration/
|
|
|
|
# https://nginx.org/en/docs/http/ngx_http_core_module.html#error_page
|
|
|
|
|
2017-02-01 08:38:03 +00:00
|
|
|
# performance
|
|
|
|
%%IF %IS_SOFTMAP%
|
|
|
|
worker_processes %SOFTMAP_NGINX_WORKERS%; # softmap
|
|
|
|
%%ELSE
|
|
|
|
worker_processes %NGINX_WORKERS%; # hardmap
|
|
|
|
%%ENDIF
|
|
|
|
worker_rlimit_nofile %NGINX_RLIM%;
|
|
|
|
events {
|
|
|
|
worker_connections %NGINX_RLIM%;
|
|
|
|
}
|
|
|
|
|
|
|
|
http {
|
|
|
|
# dns for proxy (sigh)
|
2017-02-28 08:44:17 +00:00
|
|
|
# we should do `ipv6=off` here, but compat issues, hence NGINX_RESOLVER_FLAGS
|
|
|
|
resolver %NGINX_RESOLVER% valid=%NGINX_TIMEOUT%s %NGINX_RESOLVER_FLAGS%;
|
2017-02-01 08:38:03 +00:00
|
|
|
resolver_timeout %NGINX_TIMEOUT%s;
|
|
|
|
|
2017-02-28 08:44:17 +00:00
|
|
|
# internal connection buffers; these are quite large, need space to
|
|
|
|
# swallow entire SSL headers because we're being a MITM...
|
2017-02-07 00:54:55 +00:00
|
|
|
proxy_buffering on;
|
|
|
|
proxy_buffers 16 64k;
|
|
|
|
proxy_buffer_size 64k;
|
|
|
|
proxy_busy_buffers_size 512k;
|
2017-02-07 11:35:33 +00:00
|
|
|
proxy_max_temp_file_size 2048k;
|
2017-02-07 00:54:55 +00:00
|
|
|
proxy_temp_file_write_size 64k;
|
|
|
|
proxy_temp_path "/tmp";
|
2017-02-01 09:56:44 +00:00
|
|
|
|
2017-02-28 10:52:08 +00:00
|
|
|
%%IF %NGINX_CACHE_SECONDS%
|
|
|
|
# nginx caching static responses for %NGINX_CACHE_SECONDS% seconds
|
2017-02-28 09:54:33 +00:00
|
|
|
# - this is a lightweight cache to reduce "storms", hence the global
|
|
|
|
# approch of "cache everything for a small number of seconds"
|
|
|
|
# https://nginx.org/en/docs/http/ngx_http_proxy_module.html
|
|
|
|
proxy_cache_path /tmp/nginx-cache-%PROJECT% levels=1:2 keys_zone=%PROJECT%:%NGINX_CACHE_SIZE%;
|
|
|
|
proxy_cache %PROJECT%;
|
|
|
|
proxy_cache_revalidate on;
|
|
|
|
proxy_cache_use_stale timeout updating;
|
|
|
|
# "proxy_cache_valid any" includes things like 404s
|
2017-02-28 10:52:08 +00:00
|
|
|
proxy_cache_valid any %NGINX_CACHE_SECONDS%s;
|
2017-02-28 09:54:33 +00:00
|
|
|
%%ELSE
|
|
|
|
# nginx caching disabled
|
|
|
|
%%ENDIF
|
|
|
|
|
2017-02-01 08:38:03 +00:00
|
|
|
# logs
|
|
|
|
access_log %LOG_DIR%/nginx-access.log;
|
|
|
|
|
|
|
|
# global settings
|
|
|
|
server_tokens off;
|
|
|
|
|
|
|
|
# allow/deny (first wins)
|
|
|
|
allow "unix:";
|
|
|
|
deny all;
|
|
|
|
|
|
|
|
# rewrite these content types; text/html is implicit
|
|
|
|
subs_filter_types
|
|
|
|
application/javascript
|
|
|
|
application/json
|
|
|
|
application/x-javascript
|
|
|
|
text/css
|
|
|
|
text/javascript
|
|
|
|
text/xml
|
|
|
|
;
|
|
|
|
|
2017-02-27 22:45:38 +00:00
|
|
|
# subs_filter: these patterns bear some explanation; the goal is to
|
|
|
|
# work regular expressions really hard in order to minimise the
|
|
|
|
# number of expressions which are used in the basic config, so the
|
|
|
|
# basic pattern is to capture zero/more "sub." in "//sub.foo.com"
|
|
|
|
# and interpolate that into "//sub.xxxxxxxx.onion"; so far?
|
|
|
|
|
|
|
|
# but it turns out that some JSON libraries like to "escape" the
|
|
|
|
# forward slashes in JSON content, leading to input like (literal)
|
|
|
|
# "http:\/\/sub.foo.com\/foo.html" - so you need to add the
|
|
|
|
# backslashes, but then you need to escape the backslashes, except
|
|
|
|
# they need double-escaping in the regexp because of string
|
|
|
|
# interpolation; hence 4x backslash -> 1x matched character
|
|
|
|
|
|
|
|
# likewise we use the "_RE2" form of the re-escaped domain name in
|
|
|
|
# order to coerce the regexp to match literal dots, not wildcards.
|
|
|
|
|
|
|
|
# there seems to be some sort of shortcut at play here; the trailing
|
|
|
|
# "\\b" also seems to work as "\b" however that would apparently
|
|
|
|
# break the double-escaping that is necessary/works everywhere else
|
|
|
|
# in subs_filter.
|
|
|
|
|
|
|
|
# also, regrettably, named capture groups appear not to work, we're
|
|
|
|
# fortunate that there appear not to be more than 9 capture groups
|
|
|
|
# by default, lest "$1" bleed into the subsequent digits of an onion
|
|
|
|
# address: $1234567abcdefghij.onion
|
|
|
|
|
2017-02-11 22:14:18 +00:00
|
|
|
%%BEGIN
|
2017-02-27 02:10:54 +00:00
|
|
|
subs_filter
|
2017-02-27 22:45:38 +00:00
|
|
|
//(([-0-9a-z]+\\.)+)?%DNS_DOMAIN_RE2%\\b
|
|
|
|
//$1%ONION_ADDRESS%
|
2017-02-27 02:10:54 +00:00
|
|
|
gir;
|
2017-02-27 22:45:38 +00:00
|
|
|
|
|
|
|
subs_filter
|
|
|
|
\\\\/\\\\/(([-0-9a-z]+\\.)+)?%DNS_DOMAIN_RE2%\\b
|
|
|
|
\\/\\/$1%ONION_ADDRESS%
|
|
|
|
gir;
|
|
|
|
|
2017-02-11 22:14:18 +00:00
|
|
|
%%END
|
|
|
|
|
|
|
|
# fix the cookies
|
|
|
|
%%BEGIN
|
2017-02-27 02:10:54 +00:00
|
|
|
proxy_cookie_domain
|
|
|
|
%DNS_DOMAIN%
|
|
|
|
%ONION_ADDRESS%
|
|
|
|
;
|
2017-02-11 22:14:18 +00:00
|
|
|
%%END
|
|
|
|
|
2017-02-28 09:00:23 +00:00
|
|
|
# fix the header-redirects
|
2017-02-11 22:14:18 +00:00
|
|
|
%%BEGIN
|
2017-02-27 02:10:54 +00:00
|
|
|
proxy_redirect
|
2017-02-27 22:45:38 +00:00
|
|
|
~*^(.*?)\\b%DNS_DOMAIN_RE2%\\b(.*)$
|
2017-02-27 02:10:54 +00:00
|
|
|
$1%ONION_ADDRESS%$2
|
|
|
|
;
|
2017-02-11 22:14:18 +00:00
|
|
|
%%END
|
|
|
|
|
2017-02-26 20:44:39 +00:00
|
|
|
# o2d_lookup -> if cannot remap, return input. note: old versions
|
|
|
|
# of lua-plugin cannot cope with code like o2d_mappings[o[1]]
|
|
|
|
# because of `long bracket syntax`; the `[o[` freaks it out.
|
|
|
|
# See: https://github.com/openresty/lua-nginx-module/issues/748
|
2017-02-07 10:05:53 +00:00
|
|
|
init_by_lua_block {
|
2017-02-11 20:22:04 +00:00
|
|
|
slog = function (s) -- in case of manual debugging
|
|
|
|
ngx.log(ngx.ERR, "\n<<", s, ">>\n")
|
2017-02-07 15:27:41 +00:00
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2017-02-11 20:22:04 +00:00
|
|
|
o2d_mappings = {}
|
2017-02-07 10:05:53 +00:00
|
|
|
%%BEGIN
|
2017-02-11 20:22:04 +00:00
|
|
|
o2d_mappings["%ONION_ADDRESS%"] = "%DNS_DOMAIN%"
|
2017-02-07 10:05:53 +00:00
|
|
|
%%END
|
|
|
|
|
2017-02-11 20:22:04 +00:00
|
|
|
o2d_lookup = function (o)
|
2017-02-26 20:44:39 +00:00
|
|
|
local k = o[1]
|
|
|
|
return ( o2d_mappings[k] or k )
|
2017-02-07 10:05:53 +00:00
|
|
|
end
|
|
|
|
|
2017-02-11 20:22:04 +00:00
|
|
|
onion2dns = function (i)
|
2017-02-07 10:05:53 +00:00
|
|
|
if i == nil then
|
|
|
|
return nil
|
|
|
|
end
|
2017-02-11 20:22:04 +00:00
|
|
|
local o, num, errs = ngx.re.gsub(i, "\\b([a-z2-7]{16}\\.onion)\\b", o2d_lookup, "io")
|
2017-02-07 10:05:53 +00:00
|
|
|
return o
|
|
|
|
end
|
2017-02-11 20:22:04 +00:00
|
|
|
|
|
|
|
dns2onion = function (i) -- inherently a bit flaky because ordering, boundaries; avoid
|
|
|
|
local num, errs
|
|
|
|
%%BEGIN
|
|
|
|
i, num, errs = ngx.re.gsub(i, "\\b(%DNS_DOMAIN_RE2%)\\b", "%ONION_ADDRESS%", "io")
|
|
|
|
%%END
|
|
|
|
return i
|
|
|
|
end
|
2017-02-07 10:05:53 +00:00
|
|
|
}
|
|
|
|
|
2017-02-28 08:50:15 +00:00
|
|
|
%%IF %SUPPRESS_HEADER_CSP%
|
2017-02-07 11:00:56 +00:00
|
|
|
# csp suppression
|
2017-02-06 13:42:48 +00:00
|
|
|
proxy_hide_header "Content-Security-Policy";
|
2017-02-07 11:00:56 +00:00
|
|
|
proxy_hide_header "Content-Security-Policy-Report-Only";
|
2017-02-11 19:38:13 +00:00
|
|
|
%%ELSE
|
|
|
|
# csp not suppressed
|
2017-02-06 13:42:48 +00:00
|
|
|
%%ENDIF
|
2017-02-11 19:38:13 +00:00
|
|
|
|
2017-02-28 08:50:15 +00:00
|
|
|
%%IF %SUPPRESS_HEADER_HSTS%
|
2017-02-07 11:00:56 +00:00
|
|
|
# hsts suppression
|
|
|
|
proxy_hide_header "Strict-Transport-Security";
|
2017-02-11 19:38:13 +00:00
|
|
|
%%ELSE
|
|
|
|
# hsts not suppressed
|
|
|
|
%%ENDIF
|
2017-02-01 08:38:03 +00:00
|
|
|
|
2017-02-28 08:50:15 +00:00
|
|
|
%%IF %SUPPRESS_HEADER_HPKP%
|
2017-02-11 19:38:13 +00:00
|
|
|
# hpkp suppression
|
|
|
|
proxy_hide_header "Public-Key-Pins";
|
|
|
|
proxy_hide_header "Public-Key-Pins-Report-Only";
|
|
|
|
%%ELSE
|
|
|
|
# hpkp not suppressed
|
2017-02-07 11:00:56 +00:00
|
|
|
%%ENDIF
|
2017-02-11 19:38:13 +00:00
|
|
|
|
2017-02-01 08:38:03 +00:00
|
|
|
# global proxy settings
|
|
|
|
proxy_read_timeout %NGINX_TIMEOUT%;
|
|
|
|
proxy_connect_timeout %NGINX_TIMEOUT%;
|
|
|
|
|
|
|
|
# SSL config
|
|
|
|
ssl_certificate %SSL_DIR%/%CERT_PREFIX%.cert;
|
|
|
|
ssl_certificate_key %SSL_DIR%/%CERT_PREFIX%.pem;
|
2017-02-28 08:44:17 +00:00
|
|
|
ssl_buffer_size 4k;
|
2017-02-01 13:23:46 +00:00
|
|
|
#ssl_ciphers 'EECDH+CHACHA20:EECDH+AESGCM:EECDH+AES256'; ## LibreSSL, OpenSSL 1.1.0+
|
|
|
|
ssl_ciphers 'EECDH+AESGCM:EECDH+AES256'; ## OpenSSL 1.0.1% to 1.0.2%
|
2017-02-28 08:44:17 +00:00
|
|
|
ssl_ecdh_curve prime256v1;
|
|
|
|
#ssl_ecdh_curve secp384r1:prime256v1; ## NGINX nginx 1.11.0 and later
|
|
|
|
ssl_prefer_server_ciphers on;
|
2017-02-01 13:03:17 +00:00
|
|
|
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
|
2017-02-01 13:23:46 +00:00
|
|
|
ssl_session_cache shared:SSL:10m;
|
2017-02-01 13:03:17 +00:00
|
|
|
ssl_session_timeout 10m;
|
2017-02-01 13:23:46 +00:00
|
|
|
|
2017-02-28 08:44:17 +00:00
|
|
|
# websockets: on the basis of http_upgrade, set connection_upgrade:
|
|
|
|
# empty -> empty
|
|
|
|
# default -> "upgrade"
|
2017-02-01 08:38:03 +00:00
|
|
|
map $http_upgrade $connection_upgrade {
|
|
|
|
default "upgrade";
|
|
|
|
"" "";
|
|
|
|
}
|
|
|
|
|
|
|
|
%%BEGIN
|
|
|
|
# for %ONION_ADDRESS% -> %DNS_DOMAIN%
|
|
|
|
server {
|
|
|
|
%%IF %IS_SOFTMAP%
|
|
|
|
%%RANGE I 1 %SOFTMAP_TOR_WORKERS%
|
|
|
|
# softmap onion %I%
|
|
|
|
listen unix:%PROJECT_DIR%/%TOR_WORKER_PREFIX%-%I%.d/port-80.sock;
|
2017-02-01 09:00:51 +00:00
|
|
|
listen unix:%PROJECT_DIR%/%TOR_WORKER_PREFIX%-%I%.d/port-443.sock ssl;
|
2017-02-01 08:38:03 +00:00
|
|
|
%%ENDRANGE
|
|
|
|
%%ELSE
|
|
|
|
# hardmap
|
|
|
|
# unix sockets; use <ONION_ADDRESS>.d as a naming convention
|
|
|
|
listen unix:%PROJECT_DIR%/%ONION_ADDRESS%.d/port-80.sock;
|
2017-02-01 09:00:51 +00:00
|
|
|
listen unix:%PROJECT_DIR%/%ONION_ADDRESS%.d/port-443.sock ssl;
|
2017-02-01 08:38:03 +00:00
|
|
|
%%ENDIF
|
|
|
|
|
2017-02-27 02:10:54 +00:00
|
|
|
# subdomain regexp captures trailing dot, use carefully; does not need "~*"
|
2017-02-01 08:38:03 +00:00
|
|
|
server_name
|
|
|
|
%ONION_ADDRESS%
|
2017-02-27 22:49:39 +00:00
|
|
|
~^(?<snsd>([-0-9a-z]+\\.)+)%ONION_ADDRESS_RE2%$
|
2017-02-01 08:38:03 +00:00
|
|
|
;
|
|
|
|
|
|
|
|
%%IF %NGINX_HELLO_ONION%
|
|
|
|
# for test & to help SSL certificate acceptance
|
2017-02-28 12:43:39 +00:00
|
|
|
location ~* ^/hello[-_]onion/?$ {
|
2017-02-01 08:38:03 +00:00
|
|
|
return 200 "Hello, Onion User!";
|
|
|
|
}
|
2017-02-28 09:00:23 +00:00
|
|
|
%%ELSE
|
|
|
|
# no "hello-onion" endpoint
|
2017-02-01 08:38:03 +00:00
|
|
|
%%ENDIF
|
|
|
|
|
2017-02-28 12:43:39 +00:00
|
|
|
%%IF %BLOCK_LOCATION%
|
|
|
|
# block locations by name
|
|
|
|
location %BLOCK_LOCATION% {
|
|
|
|
return 403 "URI not supported over Onion. Sorry.";
|
|
|
|
}
|
|
|
|
%%ELSE
|
|
|
|
# no named location blocking
|
|
|
|
%%ENDIF
|
|
|
|
|
|
|
|
%%IF %BLOCK_LOCATION_RE%
|
|
|
|
# block locations matching this regular expression
|
|
|
|
location ~* %BLOCK_LOCATION_RE% {
|
|
|
|
return 403 "URI not supported over Onion. Sorry.";
|
|
|
|
}
|
|
|
|
%%ELSE
|
|
|
|
# no location regular expression blocking
|
|
|
|
%%ENDIF
|
|
|
|
|
2017-02-01 08:38:03 +00:00
|
|
|
# for traffic
|
|
|
|
location / {
|
2017-02-27 02:10:54 +00:00
|
|
|
proxy_pass "$scheme://${snsd}%DNS_DOMAIN%"; # note $scheme
|
2017-02-01 08:38:03 +00:00
|
|
|
proxy_http_version 1.1;
|
2017-02-27 02:10:54 +00:00
|
|
|
proxy_set_header Host "${snsd}%DNS_DOMAIN%";
|
2017-02-01 08:38:03 +00:00
|
|
|
proxy_set_header Accept-Encoding ""; # but putting this in `http` fails?
|
|
|
|
proxy_set_header Connection $connection_upgrade; # SSL
|
|
|
|
proxy_set_header Upgrade $http_upgrade; # SSL
|
|
|
|
proxy_ssl_server_name on; # SSL
|
2017-02-07 10:05:53 +00:00
|
|
|
|
|
|
|
set_by_lua_block $referer2 {
|
2017-02-11 20:22:04 +00:00
|
|
|
return onion2dns(ngx.var.http_referer)
|
2017-02-07 10:05:53 +00:00
|
|
|
}
|
|
|
|
proxy_set_header Referer $referer2;
|
|
|
|
|
|
|
|
set_by_lua_block $origin2 {
|
2017-02-11 20:22:04 +00:00
|
|
|
return onion2dns(ngx.var.http_origin)
|
2017-02-07 10:05:53 +00:00
|
|
|
}
|
|
|
|
proxy_set_header Origin $origin2;
|
2017-02-28 09:00:23 +00:00
|
|
|
|
|
|
|
%%IF %SUPPRESS_METHODS_EXCEPT_GET%
|
|
|
|
# suppress non-GET methods (e.g.: POST)
|
|
|
|
limit_except GET {
|
|
|
|
deny all;
|
|
|
|
}
|
|
|
|
%%ELSE
|
|
|
|
# non-GET methods (e.g.: POST) are not suppressed
|
|
|
|
%%ENDIF
|
2017-02-28 12:43:39 +00:00
|
|
|
|
|
|
|
%%IF %BLOCK_HOST%
|
|
|
|
# block hosts matching this name
|
|
|
|
if ( $host = %BLOCK_HOST% ) {
|
|
|
|
# https://www.nginx.com/resources/wiki/start/topics/depth/ifisevil/
|
|
|
|
return 403 "Host not supported over Onion. Sorry.";
|
|
|
|
}
|
|
|
|
%%ELSE
|
|
|
|
# no named host blocking
|
|
|
|
%%ENDIF
|
|
|
|
|
|
|
|
%%IF %BLOCK_HOST_RE%
|
|
|
|
# block hosts matching this regular expression
|
|
|
|
if ( $host ~* %BLOCK_HOST_RE% ) {
|
|
|
|
# https://www.nginx.com/resources/wiki/start/topics/depth/ifisevil/
|
|
|
|
return 403 "Host not supported over Onion. Sorry.";
|
|
|
|
}
|
|
|
|
%%ELSE
|
|
|
|
# no host regular expression blocking
|
|
|
|
%%ENDIF
|
2017-02-01 08:38:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
%%END
|
|
|
|
|
|
|
|
# header purge
|
|
|
|
more_clear_headers "Age";
|
|
|
|
more_clear_headers "Server";
|
|
|
|
more_clear_headers "Via";
|
|
|
|
more_clear_headers "X-From-Nginx";
|
|
|
|
more_clear_headers "X-NA";
|
|
|
|
more_clear_headers "X-Powered-By";
|
|
|
|
more_clear_headers "X-Request-Id";
|
|
|
|
more_clear_headers "X-Runtime";
|
|
|
|
more_clear_headers "X-Varnish";
|
|
|
|
}
|