mirror of
https://github.com/rubenhensen/k8scd.git
synced 2026-09-17 02:12:55 +02:00
311 lines
11 KiB
Nix
311 lines
11 KiB
Nix
{ config, pkgs, lib, ... }:
|
|
let
|
|
k8sUpstreamConf = "/run/nginx/k8s-upstream.conf";
|
|
k8sHttpUpstreamConf = "/run/nginx/k8s-http-upstream.conf";
|
|
|
|
updateK8sIp = pkgs.writeShellScriptBin "update-k8s-ip" ''
|
|
NEW_IP="$SSH_ORIGINAL_COMMAND"
|
|
|
|
if ! echo "$NEW_IP" | ${pkgs.gnugrep}/bin/grep -qE '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'; then
|
|
echo "Invalid IP: $NEW_IP"
|
|
exit 1
|
|
fi
|
|
|
|
CONF="${k8sUpstreamConf}"
|
|
CURRENT_IP=$(${pkgs.gnugrep}/bin/grep -oP 'server \K[0-9.]+' "$CONF" 2>/dev/null | head -1)
|
|
|
|
if [ "$CURRENT_IP" = "$NEW_IP" ]; then
|
|
echo "IP unchanged: $NEW_IP"
|
|
exit 0
|
|
fi
|
|
|
|
printf 'upstream k8s_tls {\n server %s:443;\n}\n\nupstream k8s_ldap {\n server %s:3389;\n}\n' "$NEW_IP" "$NEW_IP" > "$CONF"
|
|
printf 'upstream k8s_http {\n server %s:80;\n}\n' "$NEW_IP" > ${k8sHttpUpstreamConf}
|
|
|
|
${pkgs.nginx}/bin/nginx -t && ${pkgs.systemd}/bin/systemctl reload nginx
|
|
echo "Updated K8s backend IP to $NEW_IP"
|
|
'';
|
|
in
|
|
{
|
|
# ──────────────────────────────────────────────
|
|
# Firewall (replaces UFW)
|
|
# ──────────────────────────────────────────────
|
|
networking.firewall.allowedTCPPorts = [
|
|
25 # SMTP
|
|
465 # SMTP submissions (implicit TLS)
|
|
587 # SMTP submission (STARTTLS)
|
|
993 # IMAP (implicit TLS)
|
|
443 # HTTPS
|
|
80 # HTTP (ACME + redirect)
|
|
];
|
|
|
|
# ──────────────────────────────────────────────
|
|
# Sysctl hardening (replaces base role)
|
|
# ──────────────────────────────────────────────
|
|
boot.kernel.sysctl = {
|
|
"net.ipv4.conf.all.rp_filter" = 1;
|
|
"net.ipv4.conf.default.rp_filter" = 1;
|
|
"net.ipv4.conf.all.accept_redirects" = 0;
|
|
"net.ipv4.conf.default.accept_redirects" = 0;
|
|
"net.ipv4.conf.all.send_redirects" = 0;
|
|
"net.ipv4.conf.default.send_redirects" = 0;
|
|
"net.ipv4.tcp_syncookies" = 1;
|
|
"net.ipv4.icmp_echo_ignore_broadcasts" = 1;
|
|
"net.ipv6.conf.all.accept_redirects" = 0;
|
|
"net.ipv6.conf.default.accept_redirects" = 0;
|
|
};
|
|
|
|
# ──────────────────────────────────────────────
|
|
# Fail2ban
|
|
# ──────────────────────────────────────────────
|
|
services.fail2ban = {
|
|
enable = true;
|
|
maxretry = 5;
|
|
bantime = "1h";
|
|
|
|
jails = {
|
|
sshd = {
|
|
settings = {
|
|
enabled = true;
|
|
port = "ssh";
|
|
maxretry = 3;
|
|
bantime = "1h";
|
|
findtime = "10m";
|
|
};
|
|
};
|
|
};
|
|
};
|
|
|
|
# ──────────────────────────────────────────────
|
|
# ACME / Let's Encrypt (replaces certbot)
|
|
# ──────────────────────────────────────────────
|
|
security.acme = {
|
|
acceptTerms = true;
|
|
defaults.email = "admin@rubenhensen.nl";
|
|
certs."stalwart.rubenhensen.nl" = {
|
|
group = "stalwart-mail";
|
|
reloadServices = [ "stalwart-mail" ];
|
|
webroot = "/var/lib/acme/acme-challenge";
|
|
};
|
|
};
|
|
|
|
|
|
# ──────────────────────────────────────────────
|
|
# Stalwart mail server
|
|
# ──────────────────────────────────────────────
|
|
services.stalwart-mail = {
|
|
enable = true;
|
|
settings = {
|
|
server = {
|
|
hostname = "stalwart.rubenhensen.nl";
|
|
max-connections = 8192;
|
|
listener = {
|
|
smtp = {
|
|
bind = "[::]:25";
|
|
protocol = "smtp";
|
|
};
|
|
submission = {
|
|
bind = "[::]:587";
|
|
protocol = "smtp";
|
|
};
|
|
submissions = {
|
|
bind = "[::]:465";
|
|
protocol = "smtp";
|
|
tls.implicit = true;
|
|
};
|
|
imaptls = {
|
|
bind = "[::]:993";
|
|
protocol = "imap";
|
|
tls.implicit = true;
|
|
};
|
|
https = {
|
|
bind = "127.0.0.1:8443";
|
|
protocol = "http";
|
|
tls.implicit = true;
|
|
};
|
|
http = {
|
|
bind = "127.0.0.1:8080";
|
|
protocol = "http";
|
|
};
|
|
};
|
|
};
|
|
|
|
certificate.default = {
|
|
cert = "%{file:/var/lib/acme/stalwart.rubenhensen.nl/fullchain.pem}%";
|
|
private-key = "%{file:/var/lib/acme/stalwart.rubenhensen.nl/key.pem}%";
|
|
};
|
|
|
|
storage = {
|
|
data = "rocksdb";
|
|
fts = "rocksdb";
|
|
blob = "rocksdb";
|
|
lookup = "rocksdb";
|
|
directory = "ldap";
|
|
};
|
|
|
|
store.rocksdb = {
|
|
type = "rocksdb";
|
|
path = "/var/lib/stalwart-mail/data";
|
|
compression = "lz4";
|
|
};
|
|
|
|
directory.ldap = {
|
|
type = "ldap";
|
|
url = "ldap://127.0.0.1:3389";
|
|
base-dn = "DC=ldap,DC=goauthentik,DC=io";
|
|
bind.dn = "cn=ldapservice,ou=users,DC=ldap,DC=goauthentik,DC=io";
|
|
bind.secret = "%{file:/run/secrets/stalwart-ldap-password}%";
|
|
filter.name = "(&(objectClass=user)(cn=?))";
|
|
filter.email = "(&(objectClass=user)(mail=?))";
|
|
filter.verify = "(&(objectClass=user)(|(mail=*?*)(cn=*?*)))";
|
|
filter.expand = "(&(objectClass=group)(cn=?))";
|
|
attribute.name = "cn";
|
|
attribute.email = "mail";
|
|
attribute.description = "displayName";
|
|
};
|
|
|
|
tracer.stdout = {
|
|
type = "stdout";
|
|
level = "info";
|
|
ansi = false;
|
|
enable = true;
|
|
};
|
|
|
|
tracer.log = {
|
|
type = "log";
|
|
level = "info";
|
|
path = "/var/lib/stalwart-mail/logs";
|
|
prefix = "stalwart.log";
|
|
rotate = "daily";
|
|
ansi = false;
|
|
enable = true;
|
|
};
|
|
|
|
authentication.fallback-admin = {
|
|
user = "admin";
|
|
secret = "%{file:/run/secrets/stalwart-admin-password}%";
|
|
};
|
|
};
|
|
};
|
|
|
|
# Grant stalwart and nginx access to ACME certs
|
|
users.users.stalwart-mail.extraGroups = [ "acme" ];
|
|
users.users.nginx.extraGroups = [ "stalwart-mail" ];
|
|
|
|
# ──────────────────────────────────────────────
|
|
# Nginx (reverse proxy + stream proxy to K8s)
|
|
# ──────────────────────────────────────────────
|
|
services.nginx = {
|
|
enable = true;
|
|
recommendedTlsSettings = true;
|
|
recommendedOptimisation = true;
|
|
recommendedGzipSettings = true;
|
|
recommendedProxySettings = true;
|
|
eventsConfig = "worker_connections 4096;";
|
|
|
|
# HTTP upstream for K8s (included from mutable file)
|
|
appendHttpConfig = ''
|
|
include /run/nginx/k8s-http-upstream.conf;
|
|
'';
|
|
|
|
# Stream config for TLS SNI routing + LDAP proxy
|
|
streamConfig = ''
|
|
log_format stream '$remote_addr [$time_local] '
|
|
'$protocol $status $bytes_sent $bytes_received '
|
|
'$session_time "$ssl_preread_server_name"';
|
|
access_log /var/log/nginx/stream.log stream;
|
|
|
|
map $ssl_preread_server_name $tls_backend {
|
|
stalwart.rubenhensen.nl local_tls;
|
|
default k8s_tls;
|
|
}
|
|
|
|
upstream local_tls {
|
|
server 127.0.0.1:8443;
|
|
}
|
|
|
|
include /run/nginx/k8s-upstream.conf;
|
|
|
|
server {
|
|
listen 443;
|
|
listen [::]:443;
|
|
ssl_preread on;
|
|
proxy_pass $tls_backend;
|
|
}
|
|
|
|
# LDAP proxy to K8s Authentik LDAP outpost
|
|
server {
|
|
listen 127.0.0.1:3389;
|
|
proxy_pass k8s_ldap;
|
|
}
|
|
'';
|
|
};
|
|
|
|
# Create stream.d directory and initial upstream config
|
|
systemd.tmpfiles.rules = [
|
|
"d /run/secrets 0700 root root -"
|
|
"d /var/lib/acme/acme-challenge 0755 acme acme -"
|
|
];
|
|
|
|
# ──────────────────────────────────────────────
|
|
# K8s IP update script (called via SSH)
|
|
# ──────────────────────────────────────────────
|
|
# Allow nginx to read/write mutable upstream configs
|
|
systemd.services.nginx.serviceConfig.ReadWritePaths = [ "/run/nginx" ];
|
|
systemd.services.nginx.serviceConfig.LimitNOFILE = 65536;
|
|
systemd.services.nginx.preStart = lib.mkBefore ''
|
|
mkdir -p /run/nginx
|
|
test -f /run/nginx/k8s-upstream.conf || printf 'upstream k8s_tls {\n server 127.0.0.1:443;\n}\n\nupstream k8s_ldap {\n server 127.0.0.1:3389;\n}\n' > /run/nginx/k8s-upstream.conf
|
|
test -f /run/nginx/k8s-http-upstream.conf || printf 'upstream k8s_http {\n server 127.0.0.1:80;\n}\n' > /run/nginx/k8s-http-upstream.conf
|
|
'';
|
|
|
|
# Stalwart ACME HTTP-01 challenge
|
|
services.nginx.virtualHosts."stalwart.rubenhensen.nl" = {
|
|
listen = [
|
|
{ addr = "0.0.0.0"; port = 80; }
|
|
{ addr = "[::]"; port = 80; }
|
|
];
|
|
locations."/.well-known/acme-challenge/" = {
|
|
root = "/var/lib/acme/acme-challenge";
|
|
};
|
|
locations."/" = {
|
|
return = "301 https://$host$request_uri";
|
|
};
|
|
};
|
|
|
|
# Catch-all port 80 — proxy to K8s for ACME challenges + redirect
|
|
services.nginx.virtualHosts."_" = {
|
|
default = true;
|
|
listen = [
|
|
{ addr = "0.0.0.0"; port = 80; }
|
|
{ addr = "[::]"; port = 80; }
|
|
];
|
|
locations."/" = {
|
|
proxyPass = "http://k8s_http";
|
|
extraConfig = ''
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
'';
|
|
};
|
|
};
|
|
|
|
# IP update script (as a proper Nix package)
|
|
environment.systemPackages = [ updateK8sIp pkgs.openssl ];
|
|
|
|
# SSH authorized key for K8s IP updater (add the actual pubkey)
|
|
users.users.root.openssh.authorizedKeys.keys = [
|
|
# nix-infra will set the main SSH key via configuration.nix
|
|
# Add the IP updater key with command restriction:
|
|
''command="${updateK8sIp}/bin/update-k8s-ip",no-port-forwarding,no-X11-forwarding,no-agent-forwarding ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAII5cMc73rlUCn3mS5FXlu3nO+AUeW2L28jRh22VYIPY4 k8s-ip-updater''
|
|
];
|
|
|
|
# ──────────────────────────────────────────────
|
|
# Automatic updates
|
|
# ──────────────────────────────────────────────
|
|
system.autoUpgrade = {
|
|
enable = true;
|
|
allowReboot = false;
|
|
};
|
|
}
|