# -*- awk -*- # EMACS awk mode works quite well for nginx configs # eotk (c) 2017-2022 Alec Muffett # TODO LIST: # 1/ purge the TE request header being sent upstream; trailers and encodings are bad karma # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/TE # 2/ force the Transfer Encoding response header to `identity`? # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Transfer-Encoding # 3/ These... # X-Forwarded-Host request ? # X-XSS-Protection - response ? # X-DNS-Prefetch-Control - response security risk? # Via - request/response? # eotk (c) 2019-2021 Alec Muffett # SECURITY NOTE: the contents of this file, when actualised, should # not be made world-readable nor published without redaction; # password-like 128-bit "nonces" are used in the static regexps which # substitute hostnames. It a leak occurs: simply rebuild the # configurations (which will create new nonces) and redeploy. # logs and pids pid %PROJECT_DIR%/nginx.pid; error_log %LOG_DIR%/nginx-error.log %NGINX_SYSLOG%; %%IF %NGINX_MODULES_DIRS% %%CSV %NGINX_MODULES_DIRS% include %1%/*.conf; %%ENDCSV %%ELSE # no nginx_modules_dirs specified %%ENDIF # 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 # 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 { # nginx fails without large enough buckets (sigh) map_hash_bucket_size %NGINX_HASH_BUCKET_SIZE%; server_names_hash_bucket_size %NGINX_HASH_BUCKET_SIZE%; # dns for proxy (sigh) resolver %NGINX_RESOLVER% valid=%NGINX_TIMEOUT%s; resolver_timeout %NGINX_TIMEOUT%s; # we walk a line between keeping it small and flooding resources... proxy_buffering on; # for initial; impacts SSL header proxy_buffer_size %NGINX_BLOCK_SIZE%; # for rest of response proxy_buffers %NGINX_BLOCK_COUNT% %NGINX_BLOCK_SIZE%; # how much can be busy sending to client? proxy_busy_buffers_size %NGINX_BLOCK_BUSY_SIZE%; # where to stash oversize requests? client_body_temp_path /tmp/nginx-body-%PROJECT%; client_max_body_size 4m; # in case we want to start spooling responses locally proxy_temp_path /tmp/nginx-proxy-%PROJECT%; proxy_max_temp_file_size %NGINX_TMPFILE_SIZE%; proxy_temp_file_write_size %NGINX_BLOCK_SIZE%; %%IF %NGINX_CACHE_SECONDS% # nginx caching static responses for %NGINX_CACHE_SECONDS% seconds # - 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_min_uses %NGINX_CACHE_MIN_USES%; proxy_cache_revalidate on; proxy_cache_use_stale timeout updating; proxy_cache_valid any %NGINX_CACHE_SECONDS%s; # "any" includes 404s, etc # content-types to not cache map $http_content_type $no_cache_content_type { %%CSV %NO_CACHE_CONTENT_TYPE% %1% 1; %%ENDCSV default 0; } # hosts not to cache map $http_host $no_cache_host { hostnames; %%CSV %NO_CACHE_HOST% %1% 1; %%ENDCSV default 0; } # so, should we skip caching this stuff for some reason? proxy_no_cache $no_cache_content_type $no_cache_host; proxy_cache_bypass $no_cache_content_type $no_cache_host; %%ELSE # nginx caching disabled %%ENDIF # logs (default) 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 %%IF %EXTRA_SUBS_FILTER_TYPES% # extra_subs_filter_types %EXTRA_SUBS_FILTER_TYPES% %%ELSE # no extra_subs_filter_types %%ENDIF ; # Frankly, I am horrified at the use of "$1" to drive the # subs_filter replacement string for the preamble capture, in case # the onion address is "2bcdef..." and the resulting replacement # string is "$12bcdef..." - which you would expect to be capture # group "$12"; however it seems that subs_filter source code # hard-limits capture groups to 9, and attempts to use "${1}" and # "\\g{1}" so far have all failed, therefore we stick with it... %%IF %PRESERVE_CSV% # preserve subs (save-phase): 1=description,2=re,3=i_or_empty,4=replacement %%CSV %PRESERVE_CSV% # saving regexp '%2%' as '%1%' for replacement with '%4%' (%3%) subs_filter "(%PRESERVE_PREAMBLE_RE%)(%2%)\\b" "$1%PRESERVE_BEFORE%%1%%PRESERVE_AFTER%" g%3%r ; %%ENDCSV %%ELSE # no preserve subs (save-phase) %%ENDIF %%BEGIN # map: %DNS_DOMAIN% -> %ONION_ADDRESS% subs_filter "(%LEFT_TLD_RE%)%DNS_DOMAIN_RE2%\\b" "$1%ONION_ADDRESS%" gir ; # map: %DNS_DOMAIN_RE% -> %ONION_ADDRESS_RE% subs_filter "(%LEFT_TLD_RE%)%DNS_DOMAIN_RE4%\\b" "$1%ONION_ADDRESS_RE2%" gir ; %%IF %HARD_MODE% > 1 # hard2 map: %DNS_DOMAIN_RE2% -> %ONION_ADDRESS_RE2% subs_filter "(%LEFT_TLD_RE%)%DNS_DOMAIN_RE8%\\b" "$1%ONION_ADDRESS_RE4%" gir ; # hard2 map: %DNS_DOMAIN_RE3% -> %ONION_ADDRESS_RE3% subs_filter "(%LEFT_TLD_RE%)%DNS_DOMAIN_RE12%\\b" "$1%ONION_ADDRESS_RE6%" gir ; %%ENDIF %%END %%IF %FOREIGNMAP_CSV% # foreignmap subs: 1=onion,2=re,3=re2,4=dns,5=re,6=re2 %%CSV %FOREIGNMAP_CSV% # for %4% -> %1% subs_filter "(%LEFT_TLD_RE%)%6%\\b" "$1%1%" gir ; %%ENDCSV %%ELSE # no foreignmap subs %%ENDIF %%IF %PRESERVE_CSV% # preserve subs (restore-phase): 1=description,2=re,3=i_or_empty,4=replacement %%CSV %PRESERVE_CSV% # restoring '%1%' with '%4%' subs_filter "%PRESERVE_BEFORE%%1%%PRESERVE_AFTER%" "%4%" g ; %%ENDCSV %%ELSE # no preserve subs (restore-phase) %%ENDIF %%IF %KLUDGE_DISABLE_SRI% # disabling sri with a kludge subs_filter "i(ntegrity=\"?sha(?:256|384|512)-)" "_$1" gir ; %%ELSE # not disabling sri with a kludge %%ENDIF # o2d_re_helper -> if cannot remap, return input. NB: 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 init_by_lua_block { -- helper functions for elsewhere -- builds data structures -- http://www.lua.org/pil/11.5.html TrueMap = function (list) local set = {} for _, l in ipairs(list) do set[l] = true end return set end -- for compression sanity-testing is_compression = TrueMap{ "br", "compress", "deflate", "gzip", } -- for neutering entire uris to be filled with "" is_neutered_uri = TrueMap{ -- "/path/goes/here", -- ... } -- for debug messages Slog = function (s) -- in case of manual debugging ngx.log(ngx.ERR, s) return end -- for matching uri suffixes, etc HasSuffix = function (s, x) return string.sub(s, -string.len(x)) == x end -- useful shim for rewriting "scalar-or-table" values ApplyReplacement = function (i, f) if i == nil or i == "" then return i end if (type(i) == "table") then local k, v, result result = {} for k, v in ipairs(i) do table.insert(result, ApplyReplacement(v, f)) -- recurse end return result end return f(i) end -- MAPPING TABLES o2d_mappings = {} %%BEGIN o2d_mappings["%ONION_ADDRESS%"] = "%DNS_DOMAIN%" %%END -- d2o_mappings = {} %%BEGIN -- d2o_mappings["%DNS_DOMAIN%"] = "%ONION_ADDRESS%" %%END -- injected origins origin_replacement = {} %%IF %INJECT_ORIGIN% %%CSV %INJECT_ORIGIN% origin_replacement["%1%"] = "%2%" %%ENDCSV %%ELSE -- no origin replacements %%ENDIF -- injected referers referer_replacement = {} %%IF %INJECT_REFERER% %%CSV %INJECT_REFERER% referer_replacement["%1%"] = "%2%" %%ENDCSV %%ELSE -- no referer replacements %%ENDIF -- EDITING FUNCTIONS -- 1st element is the LEFT_TLD_RE boundary prefix, probably an empty string, maybe '2f' -- 2nd element is the onion address o2d_re_helper = function (m) local prefix = m[1] local k = m[2] local v = (o2d_mappings[k] or k) if (prefix == "") then -- fast happy-path response return v end return prefix .. v end o2d_search_and_replace = function (i) -- because onion addresses are matchable, this can be done in one pass... local o, num, errs = ngx.re.gsub(i, "(%LEFT_TLD_RE%)([a-z2-7]{56}\\.onion)\\b", o2d_re_helper, "io") if errs == nil and num == 0 then return i -- nothing was changed, so return the original end return o end d2o_search_and_replace = function (i) local num, errs -- do a brute-force list of substitutions, because dns has no systematic pattern to match. -- please excuse the apparent capture and interpolation of (\b) as a zero-width assertion, -- this is because LEFT_TLD_RE is configurable and may be redefined (due to "%2F" usage) -- but it defaults to "\b" which looks silly in the default case... %%BEGIN i, num, errs = ngx.re.gsub(i, "(%LEFT_TLD_RE%)%DNS_DOMAIN_RE2%\\b", "${1}%ONION_ADDRESS%", "io") %%END return i end -- SHIMS -- shim for origin rewrite, permitting injection rewrite_origin_o2d = function (i, ctx) return origin_replacement[ctx] or ApplyReplacement(i, o2d_search_and_replace) end -- shim for referer rewrite, permitting injection rewrite_referer_o2d = function (i, ctx) return referer_replacement[ctx] or ApplyReplacement(i, o2d_search_and_replace) end -- shim for cookie rewrite, permitting injection rewrite_cookie_o2d = function (i, ctx) return ApplyReplacement(i, o2d_search_and_replace) end -- shim for arbitrary rewrites rewrite_o2d = function (i) return ApplyReplacement(i, o2d_search_and_replace) end -- shim for scalar rewrites rewrite_header_d2o = function (i) if i == "*" then -- other special cases are in `ApplyReplacement` return i end return ApplyReplacement(i, d2o_search_and_replace) end } # filter the response headers en-route back to the user header_filter_by_lua_block { -- did upstream miss our request for 'identity'? local ce = ngx.var.upstream_http_content_encoding or "" if is_compression[ce] then Slog("compressed data returned from origin: "..ce) %%IF %DROP_UNREWRITABLE_CONTENT% -- I'd prefer to do something nice like this: -- ngx.status = 520 -- ngx.say("upstream content was compressed and therefore not rewritable") -- ngx.exit(ngx.OK) -- ...but say() needs an API that is not available in this phase: -- https://github.com/openresty/lua-nginx-module#header_filter_by_lua -- therefore: ngx.exit(520) -- en.wikipedia.org/wiki/List_of_HTTP_status_codes %%ELSE -- UNREWRITABLE CONTENT NOT DROPPED, COMPRESSION ONION LEAKS POSSIBLE %%ENDIF end local k, v local response_rewrites = { %%IF %SUPPRESS_HEADER_CSP% -- CSP headers are suppressed via SUPPRESS_HEADER_CSP %%ELSE "Content-Security-Policy", "Content-Security-Policy-Report-Only", %%ENDIF "Access-Control-Allow-Origin", "Content-Location", "Feature-Policy", "Link", "Location", "Set-Cookie", "Timing-Allow-Origin" } -- if REDIRECT_HOST (etc) is active we will set[1] $dont_onionify_response_headers -- to `1`, which Lua will import[2] as string-or-nil and which requires manual[3] -- type-conversion for comparison; simple not-nil tests are empirically prone to -- misbehave, so it seems safest to simply test for an explicit value. -- [1] see `generate-bw-code.pl` in two places -- [2] https://github.com/openresty/lua-nginx-module#ngxvarvariable -- [3] https://developer.roblox.com/en-us/articles/Type-Coercion-in-Lua#during-comparisons -- it would be nice to reduce indentation and do a fastpath-return on `... == 1` -- but that might break future work, below... if tonumber(ngx.var.dont_onionify_response_headers) ~= 1 then local i, k for i, k in ipairs(response_rewrites) do local v = ngx.header[k] if v then ngx.header[k] = rewrite_header_d2o(v) end end end %%IF %DEBUG_CSP_SANDBOX% local csp_sandbox = "sandbox allow-forms allow-modals allow-orientation-lock".. " allow-pointer-lock allow-popups allow-popups-to-escape-sandbox".. " allow-presentation allow-same-origin allow-scripts;" ngx.header["Content-Security-Policy"] = csp_sandbox %%ELSE -- no debug csp sandboxing %%ENDIF } %%IF %DEBUG_TRAP% # debug trap: filter the response body en-route back to the user # hello! you have found the debug_trap code, which is not # well-documented but adds repeated chunks of very slow code to # capture/log any server-supplied content matching the supplied # regular expressions, with up-to-64 chars of preceding and trailing # context; use with caution, and probably do not put this into # production, against ALL CONTENT INCLUDING (eg:) VIDEO; this code # exists to help debug situations where some HTML or JS content is # being passed through NGINX to the browser, but you don't know how # it got there. # config usage: set debug_trap foo\\.regex\\.tld [...] body_filter_by_lua_block { -- change `auxh` to "Content-Encoding" or "Set-Cookie" or whatever you want to log... local auxh = "Access-Control-Allow-Origin" local i = ngx.arg[1] local ct = ngx.header["Content-Type"] or "" local aux = ngx.header[auxh] or "" local uri = ngx.var.uri or "" local iterator, err %%CSV %DEBUG_TRAP% iterator, err = ngx.re.gmatch(i, ".{0,64}(%1%).{0,64}", "io") if not iterator then ngx.log(ngx.ERR, "gmatch error: ", err) else while true do local m, err = iterator() if err then ngx.log(ngx.ERR, "iterator error: ", err) break end if not m then break end local msg = string.format("\n\tTRAP %s\n".. "\tTYPE %s\n".. "\tAUXH %s: %s\n".. "\tCODE %s\n".. "\tURI %s\n", m[1], ct, auxh, aux, m[0], uri) Slog(msg) end -- while true end -- if iterator %%ENDCSV -- search and replace; this would be faster to do with a linear CSV unwind, -- as you could benefit from the regexp optimiser in-place + tune casei local edit_map = {} -- example config follows: -- edit_map["regular\\.expression"] = "replacement.string" -- edit_map["insertion.preamble"] = "${0}inserted.code" -- ... for regexp, replacement in pairs(edit_map) do local m, err = ngx.re.match(ngx.arg[1], regexp, "i") if m then -- could just gsub it blindly but i want the logs Slog("REPLACE: "..m[0].." BY "..replacement.."\n") local i, num, errs = ngx.re.gsub(ngx.arg[1], regexp, replacement, "i") ngx.arg[1] = i end end -- neutering, if any (return "safe" content) if is_neutered_uri[uri] then Slog("neutering: "..uri) ngx.arg[1] = "" end } %%ELSE # no debug traps %%ENDIF %%IF %SUPPRESS_HEADER_CSP% # csp suppression proxy_hide_header "Content-Security-Policy"; proxy_hide_header "Content-Security-Policy-Report-Only"; %%ELSE # csp not suppressed, will be rewritten instead, see below %%ENDIF %%IF %SUPPRESS_HEADER_HSTS% # hsts suppression proxy_hide_header "Strict-Transport-Security"; %%ELSE # hsts not suppressed %%ENDIF %%IF %SUPPRESS_HEADER_HPKP% # hpkp suppression proxy_hide_header "Public-Key-Pins"; proxy_hide_header "Public-Key-Pins-Report-Only"; %%ELSE # hpkp not suppressed %%ENDIF # global proxy settings proxy_read_timeout %NGINX_TIMEOUT%; proxy_connect_timeout %NGINX_TIMEOUT%; # SSL config ssl_buffer_size 4k; %%IF %SSL_CERT_EACH_ONION% # SSL_CERT_EACH_ONION is enabled, see individual onions for ssl_certificate directives %%ELSE # SSL_CERT_EACH_ONION is disabled, all SubjectAltNames MUST be defined this certificate file ssl_certificate %SSL_DIR%/%CERT_PREFIX%.cert; ssl_certificate_key %SSL_DIR%/%CERT_PREFIX%.pem; %%ENDIF #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% ssl_ecdh_curve prime256v1; #ssl_ecdh_curve secp384r1:prime256v1; ## NGINX nginx 1.11.0 and later ssl_prefer_server_ciphers on; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; # websockets: on the basis of http_upgrade, set connection_upgrade: # empty -> empty # default -> "upgrade" map $http_upgrade $connection_upgrade { default "upgrade"; "" ""; } %%BEGIN %%IF %FORCE_HTTPS% # FORCE_HTTPS is in use; set up separate server for port 80 & force redirects server { %%IF %IS_SOFTMAP% %%RANGE I 1 %SOFTMAP_TOR_WORKERS% listen unix:%PROJECT_DIR%/%TOR_WORKER_PREFIX%-%I%.d/port-80.sock; %%ENDRANGE %%ELSE listen unix:%PROJECT_DIR%/%ONION_DIRNAME%/port-80.sock; %%ENDIF %%IF %LOG_SEPARATE% access_log %LOG_DIR%/nginx-rdr443-%DNS_DOMAIN%.log; %%ELSE access_log %LOG_DIR%/nginx-rdr443.log; %%ENDIF # subdomain regexp captures trailing dot, use carefully; does not need "~*" # NB: this regexp should be kept in-sync with the other FORCE_HTTPS copy server_name %ONION_ADDRESS% ~^(?([-0-9a-z]+\\.)+)%ONION_ADDRESS_RE2%$ ; %%IF %SUPPRESS_TOR2WEB% # suppress tor2web traffic; "let them use clearnet" if ($http_x_tor2web) { return 403 "%BLOCK_ERR%"; } %%ELSE # tor2web not suppressed %%ENDIF location / { # tell the client to try again as HTTPS without ever leaving the onion # use 307 / temporary redirect because your URIs may change in future # use $host (not $server) to copy-over subdomains, etc, transparently # SEND BACK ORIGINAL PARAMS, FIX THEM ONLY UPON FORWARD TO THE PROXY. return 307 https://$host$request_uri; } %%IF %SSL_PROOF_CSV% # ssl_proof_csv: 1=fixed_path,2=response %%CSV %SSL_PROOF_CSV% location "%1%" { return 200 "%2%"; } %%ENDCSV %%ELSE # no ssl_proof_csv %%ENDIF } %%ELSE # FORCE_HTTPS is not in use, cleartext data may traverse the internet %%ENDIF # for %ONION_ADDRESS% -> %DNS_DOMAIN% server { %%IF %IS_SOFTMAP% %%RANGE I 1 %SOFTMAP_TOR_WORKERS% %%IF not %FORCE_HTTPS% # FORCE_HTTPS is not in use, cleartext data may traverse the internet listen unix:%PROJECT_DIR%/%TOR_WORKER_PREFIX%-%I%.d/port-80.sock; %%ENDIF listen unix:%PROJECT_DIR%/%TOR_WORKER_PREFIX%-%I%.d/port-443.sock ssl; %%ENDRANGE %%ELSE # hardmap # unix sockets; use .d as a naming convention %%IF not %FORCE_HTTPS% # FORCE_HTTPS is not in use, cleartext data may traverse the internet listen unix:%PROJECT_DIR%/%ONION_DIRNAME%/port-80.sock; %%ENDIF listen unix:%PROJECT_DIR%/%ONION_DIRNAME%/port-443.sock ssl; %%ENDIF %%IF %SSL_CERT_EACH_ONION% # SSL_CERT_EACH_ONION is enabled, use separate certs per onion ssl_certificate %SSL_DIR%/%ONION_TRUNCATED%.cert; ssl_certificate_key %SSL_DIR%/%ONION_TRUNCATED%.pem; %%ELSE # SSL_CERT_EACH_ONION is disabled, see global certificate directive, above %%ENDIF %%IF %LOG_SEPARATE% access_log %LOG_DIR%/nginx-access-%DNS_DOMAIN%.log; %%ENDIF # subdomain regexp captures trailing dot, use carefully; does not need "~*" # NB: this regexp should be kept in-sync with the other FORCE_HTTPS copy server_name %ONION_ADDRESS% ~^(?([-0-9a-z]+\\.)+)%ONION_ADDRESS_RE2%$ ; %%INCLUDE templates.d/nginx-generated-blocks.conf %%IF %COOKIE_LOCK% # if we are visiting the magic path, open the cookie-lock location "%COOKIE_LOCK%" { add_header Set-Cookie "eotk_lock=%COOKIE_LOCK%;Domain=.%ONION_ADDRESS%;Path=/;Max-Age=604800"; return 200 "OK"; } %%ELSE # no cookie_lock cookie setting %%ENDIF %%IF %NGINX_HELLO_ONION% # for test & to help SSL certificate acceptance location ~ "^/hello[-_]onion/?$" { return 200 "Hello, Onion User!"; } %%ELSE # no "hello-onion" endpoint %%ENDIF %%IF %SSL_PROOF_CSV% # ssl_proof_csv: 1=fixed_path,2=response %%CSV %SSL_PROOF_CSV% location "%1%" { return 200 "%2%"; } %%ENDCSV %%ELSE # no ssl_proof_csv %%ENDIF %%IF %HARDCODED_ENDPOINT_CSV% # hardcoded_endpoint_csv: 1=path_re,2=response %%CSV %HARDCODED_ENDPOINT_CSV% location ~ "%1%" { return 200 "%2%"; } %%ENDCSV %%ELSE # no hardcoded_endpoint_csv %%ENDIF %%IF exists templates.d/nginx-site-%ONION_ADDRESS%.conf # A note on spliced content; any file that you splice into the # final configuration MUST NOT contain any EOTK-variables, nor any # EOTK template directives, simply because these will all be # ignored and may cause NGINX syntax errors. The `splice` hack is # meant for small bits of production-ready NGINX config that MUST # occur only in the definition of one particular onion out of # (presumably several) in the eventual configuration file; most # likely this will be something like: per-onion access control. # The SPLICE directive is executed at template-generation time, # and is subject to flow-control like IF/ELSE/ENDIF; by contrast # the INCLUDE directive is executed at template-load time, and is # invisible to the IF/ELSE/ENDIF flow control; in this sense the # INCLUDE directive is more like "#include" from C/C++ # ---- BEGIN SPLICE: templates.d/nginx-site-%ONION_ADDRESS%.conf ---- %%SPLICE templates.d/nginx-site-%ONION_ADDRESS%.conf # ---- END SPLICE: templates.d/nginx-site-%ONION_ADDRESS%.conf ---- %%ELSE # splice: no file: templates.d/nginx-site-%ONION_ADDRESS%.conf %%ENDIF # for traffic location / { %%INCLUDE templates.d/nginx-generated-checks.conf %%IF %COOKIE_LOCK% # check for cookie-lock if ($cookie_eotk_lock != "%COOKIE_LOCK%") { %NGINX_ACTION_ABORT%; } %%ELSE # no cookie-lock checks %%ENDIF # deonionify the request_uri for forwarding (both path and args) set_by_lua_block $request_uri2 { return rewrite_o2d(ngx.var.request_uri) } %%IF %DEONIONIFY_POST_BODIES% # deonionify_post_bodies (specifically the body; path and args come as standard) access_by_lua_block { if ngx.req.get_method() == "POST" then ngx.req.read_body() local old = ngx.req.get_body_data() local new = rewrite_o2d(old) if new ~= old then ngx.req.set_body_data(new) end end } %%ELSE # no deonionify_post_bodies %%ENDIF # note use of both $scheme and the deonionified uri (both path and args) proxy_pass "$scheme://${servernamesubdomain}%DNS_DOMAIN%$request_uri2"; proxy_http_version 1.1; # a note re: proxy_set_header, add_header, similar methods, etc; # if you override *any* header then you will lose the other # headers inherited from the parent contexts / other scopes; # ergo: they all need to be "done" in a single bank. # https://blog.g3rt.nl/nginx-add_header-pitfall.html %%IF %INJECT_HEADERS_UPSTREAM% # inject_headers_upstream; # this is above the ones below in order to disambiguate which ones "win" %%CSV %INJECT_HEADERS_UPSTREAM% proxy_set_header %1% "%2%"; %%ENDCSV %%ELSE # no inject_headers_upstream %%ENDIF proxy_set_header X-From-Onion %X_FROM_ONION_VALUE%; proxy_set_header Host "${servernamesubdomain}%DNS_DOMAIN%"; proxy_set_header Accept-Encoding "identity"; proxy_set_header Connection $connection_upgrade; # SSL proxy_set_header Upgrade $http_upgrade; # SSL proxy_ssl_server_name on; # SSL # NB: it's very tempting to use `$http_host` / ngx.var.http_host # (or similar per-request information) as the context for the # call to `rewrite_origin_o2d` and its friends; my thinking at # the moment is that that would be "too narrow" / "too easy to # break" because wildcards/CDN-hosts would need to be matched, # and that sort of thing. Switching on the TLD of upstream # currently seems cognitively easier to deal with; plus: Lua # interns short strings, so it should be fast. # rewrite/inject request origin TODO set_by_lua_block $origin2 { return rewrite_origin_o2d(ngx.var.http_origin, "%DNS_DOMAIN%") } proxy_set_header Origin $origin2; # rewrite/inject request referer TODO set_by_lua_block $referer2 { return rewrite_referer_o2d(ngx.var.http_referer, "%DNS_DOMAIN%") } proxy_set_header Referer $referer2; # rewrite request cookies set_by_lua_block $cookie2 { return rewrite_cookie_o2d(ngx.var.http_cookie, "%DNS_DOMAIN%") } proxy_set_header Cookie $cookie2; %%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 } } %%END %%IF %DEBUG_ORIGIN_HEADERS% more_set_headers "EOTK-Upstream: ct=$upstream_http_content_type;ce=$upstream_http_content_encoding" %%ELSE # origin headers not debugged %%ENDIF # header purge: TODO: THIS NEEDS REVIEW more_clear_headers "Age"; more_clear_headers "Server"; more_clear_headers "SourceMap"; # rewrites will break SourceMaps 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-SourceMap"; # rewrites will break SourceMaps more_clear_headers "X-Varnish"; }