238 lines
9.1 KiB
Nix
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;
|
|
};
|
|
};
|
|
}
|