infra/nix/hosts/wyse/modules/caddy.nix
2026-01-05 23:09:19 +01:00

338 lines
9.2 KiB
Nix

{
lib,
config,
pkgs,
sops-nix,
...
}:
with lib;
let
p = config.sops.placeholder;
domain = p.domainName;
svc = "caddy.service";
caddyLogPath = "/var/log/caddy";
caddyLog =
sub:
if (sub != "") then
''
log {
hostnames ${sub}.${domain}
level INFO
format json
output file ${caddyLogPath}/${sub}.${domain}.log {
roll_size 100MiB
roll_keep 5
roll_keep_for 240d
}
}''
else
''
log {
hostnames ${domain}
level INFO
format json
output file ${caddyLogPath}/${domain}.log {
roll_size 100MiB
roll_keep 5
roll_keep_for 240d
}
}'';
# a little script to dynamically replace the domain tag with an actual domain
# under which it's accessed.
#script = ''
# const domain = window.location.hostname;
# document.getElementById("domain").textContent="auth."+domain;
#'';
# cat - | base64 -w0
scriptB64 = "ICAgIGNvbnN0IGRvbWFpbiA9IHdpbmRvdy5sb2NhdGlvbi5ob3N0bmFtZTsKICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJkb21haW4iKS50ZXh0Q29udGVudD0iYXV0aC4iK2RvbWFpbjsK";
landingPage = ''
<<HTML
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, height=device-height initial-scale=1.0, minimum-scale=1.0">
<title>Welcome! @( * O * )@</title>
<style>
.cursor {
display: inline-block;
background-color: black;
animation-name: cursor;
animation-duration: .8s;
animation-iteration-count: infinite;
}
@keyframes cursor {
0% { background-color: #ffffff; }
20% { background-color: #555; }
50% { background-color: #555; }
60% { background-color: #ffffff; }
100% { background-color: #ffffff; }
}
</style>
<script defer src="data:text/javascript;base64,${scriptB64}"></script>
</head>
<body>
<pre style="font-family: monospace">
@ @
$ Welcome! <div class="cursor"> </div>
###############
###############
###############
Please log in using <code id=domain>auth.&lt;domain&gt;</code> to use the services.
</pre>
</body>
</html>
HTML
'';
in
{
networking.firewall.allowedTCPPorts = [
80
443
];
# so that caddy can access netbox's static files.
# users.users.caddy.extraGroups = ["netbox"];
services = {
caddy = {
enable = true;
# package = caddyPkg;
configFile = config.sops.templates.caddyPls.path;
adapter = "caddyfile";
};
};
systemd = {
services.caddy = {
description = "Caddy web server";
after = [
"network-online.target"
"sops-nix.service"
];
wants = [ "network-online.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
TimeoutStopSec = "5s";
# LimitNOFILE = 1048576;
# LimitNPROC = 512;
PrivateTmp = true;
# ProtectSystem = "full";
AmbientCapabilities = "cap_net_bind_service";
};
};
# restart Caddy on config file change.
paths.caddy = {
pathConfig = {
PathChanged = config.sops.templates.caddyPls.path;
Unit = svc;
};
};
};
sops.secrets = {
njallaApiKey = {
restartUnits = [ svc ];
sopsFile = ../../../secrets/web/njalla.yaml;
};
"caddy/email".restartUnits = [ svc ];
"desecToken".restartUnits = [ svc ];
};
sops.templates.caddyPls = {
owner = config.systemd.services.caddy.serviceConfig.User;
content = ''
(tlsCommon) {
tls {
# dns njalla ${p."njallaApiKey"}
dns desec {
token ${p.desecToken}
}
# propagation_timeout 1m
propagation_timeout 2m
# propagation_timeout -1
curves x25519
key_type p384
protocols tls1.2 tls1.3
# resolvers 8.8.8.8 8.8.4.4
resolvers 1.1.1.1 8.8.8.8
}
}
(hsts) {
header Strict-Transport-Security "max-age=86400; preload"
# header Strict-Transport-Security "max-age=15552000; preload"
# header Strict-Transport-Security "max-age=3600"
# header Strict-Transport-Security "max-age=86400"
}
(headersCommon) {
header / {
x-frame-options "sameorigin"
x-content-type-options "nosniff"
x-xss-protection "1; mode=block"
content-security-policy "
upgrade-insecure-requests;
default-src 'self';
style-src 'self';
script-src 'self';
font-src 'self';
img-src data: 'self';
form-action 'self';
connect-src 'self';
frame-ancestors 'none';
"
cross-origin-opener-policy "same-origin"
permissions-policy "geolocation=(), midi=(), sync-xhr=(), microphone=(), camera=(), magnetometer=(), gyroscope=(), fullscreen=(self), payment=()"
referrer-policy "strict-origin-when-cross-origin"
-Server
-server
Permissions-Policy interest-cohort=()
# Strict-Transport-Security "max-age=86400"
}
}
(authentik) {
# Always forward outpost path to actual outpost
reverse_proxy /outpost.goauthentik.io/* http://localhost:${toString config.wanderllama.authentik.port}
# Forward authentication to outpost
forward_auth http://localhost:${toString config.wanderllama.authentik.port} {
uri /outpost.goauthentik.io/auth/caddy
# Capitalization of the headers is important, otherwise they will be empty
copy_headers X-Authentik-Username X-Authentik-Groups X-Authentik-Email X-Authentik-Name X-Authentik-Uid X-Authentik-Jwt X-Authentik-Meta-Jwks X-Authentik-Meta-Outpost X-Authentik-Meta-Provider X-Authentik-Meta-App X-Authentik-Meta-Version
}
}
{
#admin off
# servers {
metrics
# }
# acme_dns njalla ${p."njallaApiKey"}
acme_dns desec {
token ${p.desecToken}
}
email ${p."caddy/email"}
#grace_period 30s
grace_period 60s
log default {
output stdout
format json
# level INFO
}
# import tlsCommon
}
*.${domain}, ${domain} {
import tlsCommon
log {
hostnames ${domain}
level INFO
output file /var/log/caddy/${domain}.log
}
@${domain} host ${domain}
handle @${domain} {
encode zstd br
import headersCommon
import hsts
header Content-Type "text/html; charset=utf-8"
respond ${landingPage}
}
''
+ ''
${caddyLog "whoami"}
@whoami host whoami.${domain}
handle @whoami {
encode zstd br
import headersCommon
import hsts
# import authentik
respond "I am whoami on ${config.networking.hostName}"
}
''
+ optionalString config.services.forgejo.enable ''
${caddyLog "git"}
@git host git.${domain}
handle @git {
encode zstd br
import headersCommon
reverse_proxy 127.0.0.1:${toString config.services.forgejo.settings.server.HTTP_PORT}
}
''
+ optionalString config.services.ntopng.enable ''
${caddyLog "ntopng"}
@ntopng host ntopng.${domain}
handle @ntopng {
encode zstd br
import headersCommon
reverse_proxy localhost:${toString config.services.ntopng.httpPort}
}
''
+ optionalString config.services.netbox.enable ''
${caddyLog "netbox"}
@netbox host netbox.${domain}
handle @netbox {
encode zstd br
import headersCommon
### special handling for netbox static files
route {
file_server /static/* {
root /var/lib/netbox
}
}
reverse_proxy ${config.services.netbox.listenAddress}:${toString config.wanderllama.netbox.port}
}
''
+ optionalString config.services.atuin.enable ''
${caddyLog "atuin"}
# atuin - magical shell history.
@atuin host atuin.${domain}
handle @atuin {
encode zstd br
import headersCommon
import hsts
reverse_proxy localhost:${toString config.wanderllama.atuin.port}
}
''
+ optionalString config.services.vaultwarden.enable ''
${caddyLog "vaultwarden"}
@vaultwarden host waldemar.${domain}
handle @vaultwarden {
encode zstd br
import headersCommon
import hsts
reverse_proxy localhost:${toString config.wanderllama.vaultwarden.port}
}
''
+ ''} # end *.${domain}, ${domain}'';
};
}