434 lines
12 KiB
Nix
434 lines
12 KiB
Nix
{ config, lib, pkgs, ... }@toplevel:
|
|
|
|
with lib;
|
|
let
|
|
cfg = config.fudo.paris-container;
|
|
|
|
packages = with pkgs; [ rtorrent ];
|
|
|
|
hostname = config.instance.hostname;
|
|
|
|
hostSecrets = config.fudo.secrets.host-secrets."${hostname}";
|
|
|
|
parisKeypairs = config.fudo.secrets.files.ssh.host-keypairs.paris;
|
|
|
|
keypairFilename = keypair: "host-paris-${keypair.key-type}-private-key";
|
|
|
|
in {
|
|
options.fudo.paris-container = with types; {
|
|
enable = mkEnableOption "Enable Fudo Paris user server.";
|
|
|
|
state-directory = mkOption {
|
|
type = str;
|
|
description = "Directory at which to store server state.";
|
|
};
|
|
|
|
ports = mkOption {
|
|
type = listOf port;
|
|
description = "List of ports to open to the public internet.";
|
|
default = [ ];
|
|
};
|
|
|
|
ssh-keys = mkOption {
|
|
type = listOf str;
|
|
description = "List of SSH keys to use.";
|
|
default = [ ];
|
|
};
|
|
|
|
nixos-modules = mkOption {
|
|
type = listOf unspecified;
|
|
default = [ ];
|
|
description = "NixOS modules to add to Paris.";
|
|
};
|
|
|
|
kerberos = mkOption {
|
|
type = nullOr (submodule {
|
|
options = {
|
|
keytab = mkOption {
|
|
type = str;
|
|
description = "Location of Paris keytab.";
|
|
};
|
|
};
|
|
});
|
|
default = null;
|
|
};
|
|
|
|
ldap = {
|
|
image = mkOption {
|
|
type = str;
|
|
description = "Authentik LDAP outpost Docker image.";
|
|
default = "ghcr.io/goauthentik/ldap:latest";
|
|
};
|
|
|
|
listen-ips = mkOption {
|
|
type = listOf str;
|
|
description = "Address on which to listen for requests.";
|
|
};
|
|
|
|
port = mkOption {
|
|
type = port;
|
|
description = "Port on which to listen for LDAP requests.";
|
|
default = 4636;
|
|
};
|
|
|
|
access-group = mkOption {
|
|
type = str;
|
|
description = "Group to which users must belong for access.";
|
|
default = let ldapCfg = toplevel.config.fudo.paris-container.ldap;
|
|
in "cn=shell,${ldapCfg.group-ou},${ldapCfg.base}";
|
|
};
|
|
|
|
domain = mkOption {
|
|
type = str;
|
|
description =
|
|
"Domain for which data is served. Only used for internal mapping.";
|
|
};
|
|
|
|
authentik-host = mkOption {
|
|
type = str;
|
|
description = "Hostname of the LDAP outpost provider.";
|
|
};
|
|
|
|
outpost-token-file = mkOption {
|
|
type = str;
|
|
description =
|
|
"File containing token with which to authenticate to the Authentik host.";
|
|
};
|
|
|
|
bind-dn = mkOption {
|
|
type = str;
|
|
description = "DN as which to bind with the LDAP server.";
|
|
};
|
|
|
|
bind-token-file = mkOption {
|
|
type = str;
|
|
description =
|
|
"File containing token with which to bind with the LDAP server.";
|
|
};
|
|
|
|
base = mkOption {
|
|
type = str;
|
|
description = "Base of the LDAP server.";
|
|
example = "dc=fudo,dc=org";
|
|
};
|
|
|
|
user-ou = mkOption {
|
|
type = str;
|
|
description = "Organizational unit containing users.";
|
|
default = "ou=users";
|
|
};
|
|
|
|
group-ou = mkOption {
|
|
type = str;
|
|
description = "Organizational unit containing users.";
|
|
default = "ou=groups";
|
|
};
|
|
};
|
|
|
|
networking = {
|
|
internal = {
|
|
interface = mkOption {
|
|
type = str;
|
|
description =
|
|
"Parent host interface on which to listen for internal traffic.";
|
|
};
|
|
|
|
ipv4 = mkOption {
|
|
type = nullOr (submodule {
|
|
options = {
|
|
address = mkOption {
|
|
type = str;
|
|
description = "IP address.";
|
|
};
|
|
prefixLength = mkOption {
|
|
type = int;
|
|
description = "Significant bits in the address.";
|
|
};
|
|
};
|
|
});
|
|
default = null;
|
|
};
|
|
|
|
ipv6 = mkOption {
|
|
type = nullOr (submodule {
|
|
options = {
|
|
address = mkOption {
|
|
type = str;
|
|
description = "IP address.";
|
|
};
|
|
prefixLength = mkOption {
|
|
type = int;
|
|
description = "Significant bits in the address.";
|
|
};
|
|
};
|
|
});
|
|
default = null;
|
|
};
|
|
};
|
|
external = {
|
|
interface = mkOption {
|
|
type = str;
|
|
description = "Parent host interface on which to listen.";
|
|
};
|
|
|
|
ipv4 = mkOption {
|
|
type = nullOr (submodule {
|
|
options = {
|
|
address = mkOption {
|
|
type = str;
|
|
description = "IP address.";
|
|
};
|
|
prefixLength = mkOption {
|
|
type = int;
|
|
description = "Significant bits in the address.";
|
|
};
|
|
};
|
|
});
|
|
default = null;
|
|
};
|
|
|
|
ipv6 = mkOption {
|
|
type = nullOr (submodule {
|
|
options = {
|
|
address = mkOption {
|
|
type = str;
|
|
description = "IP address.";
|
|
};
|
|
prefixLength = mkOption {
|
|
type = int;
|
|
description = "Significant bits in the address.";
|
|
};
|
|
};
|
|
});
|
|
default = null;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
|
|
config = mkIf cfg.enable {
|
|
fudo.secrets.host-secrets."${hostname}" = {
|
|
parisLdapEnv = {
|
|
source-file = pkgs.writeText "paris-ldap-proxy.env"
|
|
(concatStringsSep "\n" [
|
|
"AUTHENTIK_HOST=https://${cfg.ldap.authentik-host}"
|
|
"AUTHENTIK_TOKEN=${readFile cfg.ldap.outpost-token-file}"
|
|
"AUTHENTIK_INSECURE=0"
|
|
]);
|
|
target-file = "/run/paris/ldap.env";
|
|
};
|
|
parisSssdEnv = {
|
|
source-file = pkgs.writeText "paris-sssd.env"
|
|
"LDAP_DEFAULT_AUTHTOKEN=${readFile cfg.ldap.bind-token-file}";
|
|
target-file = "/run/paris/sssd.env";
|
|
};
|
|
parisKeytab = mkIf (!isNull cfg.kerberos) {
|
|
source-file = cfg.kerberos.keytab;
|
|
target-file = "/run/paris/keytab";
|
|
};
|
|
} // (listToAttrs (map (keypair:
|
|
nameValuePair (keypairFilename keypair) {
|
|
source-file = keypair.private-key;
|
|
target-file = "/run/paris/openssh/${keypairFilename keypair}";
|
|
}) parisKeypairs));
|
|
|
|
virtualisation.oci-containers.containers.paris-ldap-proxy = {
|
|
image = cfg.ldap.image;
|
|
autoStart = true;
|
|
ports =
|
|
map (ip: "${ip}:${toString cfg.ldap.port}:6636") cfg.ldap.listen-ips;
|
|
environmentFiles = [ hostSecrets.parisLdapEnv.target-file ];
|
|
};
|
|
|
|
systemd = {
|
|
tmpfiles.rules = [ "d ${cfg.state-directory}/home 0700 - - - -" ];
|
|
services."container@paris".after =
|
|
optional (!isNull cfg.kerberos) config.fudo.secrets.secret-target;
|
|
};
|
|
|
|
containers.paris = {
|
|
autoStart = true;
|
|
macvlans =
|
|
[ cfg.networking.internal.interface cfg.networking.external.interface ];
|
|
bindMounts = {
|
|
"/home" = {
|
|
hostPath = "${cfg.state-directory}/home";
|
|
isReadOnly = false;
|
|
};
|
|
"/run/paris/sssd.env" = {
|
|
hostPath = hostSecrets.parisSssdEnv.target-file;
|
|
isReadOnly = true;
|
|
};
|
|
|
|
"/etc/krb5.keytab" = {
|
|
hostPath = hostSecrets.parisKeytab.target-file;
|
|
isReadOnly = true;
|
|
};
|
|
|
|
"/etc/krb5.conf" = {
|
|
hostPath = "/etc/krb5.conf";
|
|
isReadOnly = true;
|
|
};
|
|
} // (listToAttrs (map (keypair:
|
|
nameValuePair "/run/openssh/keys/${keypairFilename keypair}" {
|
|
hostPath = "/run/paris/openssh/${keypairFilename keypair}";
|
|
isReadOnly = true;
|
|
}) parisKeypairs));
|
|
additionalCapabilities = [ "CAP_NET_ADMIN" ];
|
|
config = {
|
|
nixpkgs.pkgs = pkgs;
|
|
|
|
imports = cfg.nixos-modules;
|
|
|
|
environment.systemPackages = packages;
|
|
|
|
security = {
|
|
acme.acceptTerms = true;
|
|
|
|
krb5 = mkIf (!isNull cfg.kerberos) {
|
|
enable = true;
|
|
package = pkgs.heimdal;
|
|
settings = config.security.krb5.settings;
|
|
};
|
|
};
|
|
|
|
programs.ssh = {
|
|
extraConfig = ''
|
|
GSSAPIAuthentication yes
|
|
GSSAPIDelegateCredentials yes
|
|
'';
|
|
};
|
|
|
|
security.pam = {
|
|
krb5.enable = true;
|
|
services.sshd.makeHomeDir = true;
|
|
};
|
|
|
|
services = {
|
|
openssh = {
|
|
enable = true;
|
|
startWhenNeeded = true;
|
|
permitRootLogin = "no";
|
|
hostKeys = map (keypair: {
|
|
path = "/run/openssh/keys/${keypairFilename keypair}";
|
|
type = keypair.key-type;
|
|
}) parisKeypairs;
|
|
settings = {
|
|
UseDns = true;
|
|
PermitRootLogin = "no";
|
|
};
|
|
extraConfig = optionalString (!isNull cfg.kerberos) ''
|
|
GSSAPIAuthentication yes
|
|
GSSAPICleanupCredentials yes
|
|
'';
|
|
};
|
|
|
|
tailscale.enable = true;
|
|
|
|
nginx = {
|
|
enable = true;
|
|
recommendedGzipSettings = true;
|
|
recommendedOptimisation = true;
|
|
virtualHosts."users.fudo.org" = {
|
|
enableACME = true;
|
|
forceSSL = true;
|
|
locations = {
|
|
"~ ^/~(.+?)(/.*)?$" = {
|
|
alias = "/home/$1/public_html$2";
|
|
index = "index.html";
|
|
extraConfig = "autoindex on;";
|
|
};
|
|
"/".return = "404 not found";
|
|
};
|
|
};
|
|
};
|
|
|
|
sssd = {
|
|
enable = true;
|
|
kcm = true;
|
|
environmentFile = "/run/paris/sssd.env";
|
|
config = lib.generators.toINI { } {
|
|
sssd = {
|
|
config_file_version = 2;
|
|
reconnection_retries = 3;
|
|
services = concatStringsSep ", " [ "nss" "pam" "ssh" ];
|
|
domains = concatStringsSep ", " [ cfg.ldap.domain ];
|
|
};
|
|
|
|
pam = { reconnection_retries = 3; };
|
|
|
|
nss.reconnection_retries = 3;
|
|
|
|
"domain/${cfg.ldap.domain}" = {
|
|
cache_credentials = true;
|
|
id_provider = "ldap";
|
|
chpass_provider = "ldap";
|
|
auth_provider = "ldap";
|
|
access_provider = "ldap";
|
|
|
|
ldap_uri = "ldaps://${head cfg.ldap.listen-ips}:${
|
|
toString cfg.ldap.port
|
|
}";
|
|
ldap_schema = "rfc2307bis";
|
|
|
|
ldap_search_base = cfg.ldap.base;
|
|
ldap_user_search_base = "${cfg.ldap.user-ou},${cfg.ldap.base}";
|
|
ldap_group_search_base =
|
|
"${cfg.ldap.group-ou},${cfg.ldap.base}";
|
|
|
|
ldap_user_object_class = "user";
|
|
ldap_user_name = "cn";
|
|
ldap_group_object_class = "group";
|
|
ldap_group_name = "cn";
|
|
|
|
ldap_access_filter = "memberOf=${cfg.ldap.access-group}";
|
|
|
|
ldap_default_bind_dn = cfg.ldap.bind-dn;
|
|
ldap_default_authtok = "$LDAP_DEFAULT_AUTHTOKEN";
|
|
|
|
ldap_tls_reqcert = "allow";
|
|
|
|
enumerate = true;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
|
|
networking = let
|
|
external = cfg.networking.external;
|
|
internal = cfg.networking.internal;
|
|
in {
|
|
defaultGateway = {
|
|
address = config.networking.defaultGateway.address;
|
|
interface = "mv-${external.interface}";
|
|
};
|
|
enableIPv6 = !isNull internal.ipv6 || !isNull external.ipv6;
|
|
nameservers = config.networking.nameservers;
|
|
firewall = {
|
|
enable = true;
|
|
allowedTCPPorts = [ 22 ] ++ cfg.ports;
|
|
};
|
|
interfaces = {
|
|
"mv-${external.interface}" = {
|
|
ipv4.addresses = optional (!isNull external.ipv4) {
|
|
inherit (external.ipv4) address prefixLength;
|
|
};
|
|
ipv6.addresses = optional (!isNull external.ipv6) {
|
|
inherit (external.ipv6) address prefixLength;
|
|
};
|
|
};
|
|
"mv-${internal.interface}" = {
|
|
ipv4.addresses = optional (!isNull internal.ipv4) {
|
|
inherit (internal.ipv4) address prefixLength;
|
|
};
|
|
ipv6.addresses = optional (!isNull internal.ipv6) {
|
|
inherit (internal.ipv6) address prefixLength;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
}
|