nixfiles/services/nginx/default.nix
Sebastian Walz 860d31cee1
Tohu vaBohu
2023-04-21 00:22:52 +02:00

238 lines
9.1 KiB
Nix

{ commonHttpConfig, enableACME, extraConfig, forceSSL, ... }:
Service "Nginx: HTTP-Server"
{
configuration
= { config, core, network, store, web, ... }:
let
inherit(core) debug list path set string type;
inherit(network) allowLegacyTLS domain hostName ips tcp;
inherit(store) write;
inherit(tcp.ports) exporters;
websites
= let
importWebsite
= value:
type.matchPrimitiveOrPanic value
{
lambda = value { inherit core store web; } { inherit domain hostName; };
null = {};
path = importWebsite (path.import value);
set = value;
};
in
importWebsite (path.import ./hosts).${hostName};
allowedIPs
= string.concatMappedLines
(ip: "allow ${ip};")
ips;
locations
= let
extraConfig
= ''
${allowedIPs}
deny all;
'';
in
{
"/metrics/nginx"
= {
inherit extraConfig;
proxyPass = "http://localhost:${string exporters.nginx}/metrics";
};
"/metrics/node"
= {
inherit extraConfig;
proxyPass = "http://localhost:${string exporters.node}/metrics";
};
};
comments
= {
Acknowledgments = "Our security acknowledgments page";
Canonical = "Canonical URI for this file";
Contact = "Our security address";
Encryption = "Our OpenPGP key";
Expires = "DO NOT USE this security.txt after this date";
Hiring = "Open job-positions";
Policy = "Our security policy";
Preferred-Languages = "Preferred Languages for security reports";
};
sign
= message:
signature:
let
text = string.concatLines (message ++ [ "" ]);
result
= string.concatLines
(
[ "-----BEGIN PGP SIGNED MESSAGE-----" "Hash: SHA256\n" ]
++ message
++ [ "-----BEGIN PGP SIGNATURE-----\n" ]
++ [ (string.trim signature) ]
++ [ "-----END PGP SIGNATURE-----\n" ]
);
in
debug.panic "sign"
{
text
= ''
Missing signature for message, run:
cat ${path.toFile "message" text} | gpg --clearsign | tail -n 14 | head -n -1
'';
when = signature == null;
nice = true;
}
debug.info "sign"
{
text
= ''
Please verify this signed message:
cat ${path.toFile "message" result} | gpg --verify
'';
}
result;
mapLocation
= domain:
location:
{
index ? "index.html",
redirect ? null,
root ? null,
tryFiles ? "$uri $uri.html $uri.txt $uri.asc /index.html",
well-known ? {},
...
}:
let
foo
= string.replace'
{
"\t" = "_";
"\n" = "_";
"\r" = "_";
" " = "_";
"@" = "-at-";
"/" = "-";
};
directory
= write.directory (foo (if location == "/" then domain else "${domain}${location}"))
(
set.map
(
fileName:
page:
if path.isInstanceOf page
then
"${page}"
else
path.toFile (foo fileName) "${page}"
)
root
// canary-txt
// security-txt
);
canary-txt
= let
inherit(well-known."canary.txt") body signature;
in
set.ifOrEmpty (well-known."canary.txt" or {} != {})
{
".well-known/canary.txt"
= path.toFile "canary.txt" (sign body signature);
};
security-txt
= let
inherit(well-known."security.txt") body signature;
format
= name:
value:
[ "" "# ${comments.${name}}" ]
++ (
if list.isInstanceOf value
then
list.map
( "${name}: ${value}" )
value
else
[ "${name}: ${value}" ]
);
in
set.ifOrEmpty (well-known."security.txt" or {} != {})
{
".well-known/security.txt"
= path.toFile "security.txt"
(
sign
(list.tail (list.concat (set.mapToList format body)))
signature
);
};
in
if root != null
then
{
inherit index tryFiles;
root = "${directory}";
}
else if redirect != null
then
{
return = "301 ${redirect}";
}
else
debug.panic
"mapLocation"
"Either root or redirect must be set!";
sslProtocols
= if allowLegacyTLS
then
"TLSv1.2 TLSv1.3"
else
"TLSv1.3";
in
{
nginx
= {
inherit commonHttpConfig sslProtocols;
enable = true;
recommendedGzipSettings = true;
recommendedOptimisation = true;
recommendedProxySettings = true;
recommendedTlsSettings = true;
statusPage = true;
virtualHosts
= set.mapNamesAndValues
(
subdomain:
locations':
let
fqdn
= if subdomain != ""
then
"${subdomain}.${domain}"
else
domain;
in
{
name = fqdn;
value
= {
inherit enableACME extraConfig forceSSL;
locations
= locations
// (set.map (mapLocation fqdn) locations');
};
}
)
websites;
};
};
}