{ specialArgs, hostBridge, hostAddress, localAddress, httpPort, httpsPort, forgejoSshPort, autoStart ? false, }: let domain = "www.stefanjunker.de"; in { inherit specialArgs; config = { config, pkgs, lib, repoFlake, nodeFlake, system, ... }: { system.stateVersion = "22.05"; # Did you read the comment? disabledModules = [ "services/misc/forgejo.nix" "services/security/kanidm.nix" ]; imports = [ "${nodeFlake.inputs.nixpkgs-unstable}/nixos/modules/services/misc/forgejo.nix" "${repoFlake.inputs.nixpkgs-kanidm}/nixos/modules/services/security/kanidm.nix" ../profiles/containers/configuration.nix repoFlake.inputs.sops-nix.nixosModules.sops ]; sops.defaultSopsFile = ./webserver_secrets.yaml; networking.firewall.allowedTCPPorts = [ httpPort httpsPort forgejoSshPort ]; sops.age.sshKeyPaths = ["/etc/ssh/ssh_host_ed25519_key"]; sops.secrets.hedgedoc_environment_file = { sopsFile = ./webserver_secrets.yaml; owner = config.users.users.hedgedoc.name; }; services.caddy = { enable = true; logFormat = '' level ERROR ''; virtualHosts."${domain}" = { extraConfig = '' redir /hedgedoc* https://hedgedoc.${domain} file_server /*/* { browse root /var/www/stefanjunker.de/htdocs/caddy pass_thru } # respond "Hi" # respond (not /*/*) "Hi" ''; }; virtualHosts."hedgedoc.${domain}" = { extraConfig = '' reverse_proxy http://[::1]:3000 ''; }; virtualHosts."authelia.${domain}" = { extraConfig = '' reverse_proxy http://127.0.0.1:${builtins.toString config.services.authelia.instances.default.settings.server.port} ''; }; virtualHosts."lldap.${domain}" = { extraConfig = '' reverse_proxy http://127.0.0.1:${builtins.toString config.services.lldap.settings.http_port} ''; }; virtualHosts."forgejo.${domain}" = { extraConfig = '' reverse_proxy http://127.0.0.1:${builtins.toString config.services.forgejo.settings.server.HTTP_PORT} ''; }; virtualHosts."kanidm.${domain}" = { extraConfig = '' reverse_proxy https://${builtins.toString config.services.kanidm.serverSettings.bindaddress} { transport http { tls_server_name ${config.services.kanidm.serverSettings.domain} } } ''; }; }; services.hedgedoc = { enable = true; settings = { domain = "hedgedoc.${domain}"; urlPath = ""; protocolUseSSL = true; db = { dialect = "sqlite"; storage = "/var/lib/hedgedoc/db.hedgedoc.sqlite"; }; allowAnonymous = false; allowAnonymousEdits = false; allowGravatar = false; allowFreeURL = false; defaultPermission = "private"; allowEmailRegister = false; email = false; ldap = { url = "ldap://127.0.0.1:${builtins.toString config.services.lldap.settings.ldap_port}"; bindDn = "uid=admin,ou=people,dc=stefanjunker,dc=de"; # these are set via the `environmentFile` # bindCredentials = "$LDAP_ADMIN_PASSWORD"; searchBase = "ou=people,dc=stefanjunker,dc=de"; searchFilter = "(&(memberOf=cn=hedgedoc,ou=groups,dc=stefanjunker,dc=de)(uid={{username}}))"; useridField = "uid"; }; oauth2 = let originURL = config.services.kanidm.serverSettings.origin; in { providerName = "kanidm (${originURL})"; authorizationURL = "${originURL}/ui/oauth2"; tokenURL = "${originURL}/oauth2/token"; userProfileURL = "${originURL}/oauth2/openid/hedgedoc/userinfo"; scope = "openid email profile"; # rolesClaim = "roles"; # accessRole = "role/hedgedoc"; userProfileUsernameAttr = "name"; userProfileDisplayNameAttr = "displayname"; userProfileEmailAttr = "email"; clientID = "hedgedoc"; # set via the `environmentFile` # clientSecret = "$CMD_OAUTH2_CLIENT_SECRET"; }; uploadsPath = "/var/lib/hedgedoc/uploads"; }; environmentFile = config.sops.secrets.hedgedoc_environment_file.path; }; services.jitsi-meet = { enable = false; hostName = "meet.${domain}"; config = { prejoinPageEnabled = true; }; caddy.enable = true; nginx.enable = false; }; sops.secrets.authelia_storageEncryptionKey = { sopsFile = ./webserver_secrets.yaml; owner = config.users.users.authelia-default.name; }; sops.secrets.authelia_jwtSecret = { sopsFile = ./webserver_secrets.yaml; owner = config.users.users.authelia-default.name; }; services.authelia.instances.default = let baseDir = "/var/lib/authelia-default"; in { enable = true; secrets.storageEncryptionKeyFile = config.sops.secrets.authelia_storageEncryptionKey.path; secrets.jwtSecretFile = config.sops.secrets.authelia_jwtSecret.path; settings = { theme = "auto"; default_2fa_method = "totp"; log.level = "debug"; server = { disable_healthcheck = true; host = "127.0.0.1"; port = 9091; # path = "authelia"; }; storage = { local.path = "${baseDir}/authelia.sqlite"; }; authentication_backend = { file.path = "${baseDir}/first_factor.yaml"; file.search.email = true; file.search.case_insensitive = false; }; access_control = { default_policy = "one_factor"; }; session.domain = "stefanjunker.de"; notifier = { disable_startup_check = true; filesystem.filename = "${baseDir}/notification.txt"; }; }; }; users.groups.lldap = {}; users.users.lldap = { isSystemUser = true; group = "lldap"; }; sops.secrets.lldap_jwtSecret = { sopsFile = ./webserver_secrets.yaml; owner = config.users.users.lldap.name; }; sops.secrets.lldap_adminPassword = { sopsFile = ./webserver_secrets.yaml; owner = config.users.users.lldap.name; }; sops.secrets.lldap_environmentFile = { sopsFile = ./webserver_secrets.yaml; owner = config.users.users.lldap.name; }; services.lldap = { enable = true; environment = { LLDAP_JWT_SECRET_FILE = config.sops.secrets.lldap_jwtSecret.path; LLDAP_LDAP_USER_PASS_FILE = config.sops.secrets.lldap_adminPassword.path; }; environmentFile = config.sops.secrets.lldap_environmentFile.path; settings = { verbose = true; ldap_base_dn = "dc=stefanjunker,dc=de"; http_url = "https://lldap.${domain}"; ## Options to configure SMTP parameters, to send password reset emails. ## To set these options from environment variables, use the following format ## (example with "password"): LLDAP_SMTP_OPTIONS__PASSWORD smtp_options = { ## Whether to enabled password reset via email, from LLDAP. enable_password_reset = true; # port = 465; ## How the connection is encrypted, either "NONE" (no encryption), "TLS" or "STARTTLS". # smtp_encryption = "TLS"; }; # database_url = "sqlite:///var/lib/lldap/users.db?mode=rwc"; }; }; sops.secrets.FORGEJO_JWT_SECRET = {}; sops.secrets.FORGEJO_INTERNAL_TOKEN = {}; sops.secrets.FORGEJO_SECRET_KEY = {}; services.forgejo = { enable = true; package = nodeFlake.inputs.nixpkgs-unstable.legacyPackages.${pkgs.system}.forgejo; settings = { service.DISABLE_REGISTRATION = true; server.HTTP_ADDR = "127.0.0.1"; server.START_SSH_SERVER = true; server.SSH_PORT = forgejoSshPort; server.ROOT_URL = "https://forgejo.${domain}"; server.HTTP_PORT = 3001; # TODO: how do i get a 3072 length SSH key with the yubikey? "ssh.minimum_key_sizes".RSA = 2048; }; secrets = { oauth2.JWT_SECRET = lib.mkForce config.sops.secrets.FORGEJO_JWT_SECRET.path; security.INTERNAL_TOKEN = lib.mkForce config.sops.secrets.FORGEJO_INTERNAL_TOKEN.path; security.SECRET_KEY = lib.mkForce config.sops.secrets.FORGEJO_SECRET_KEY.path; }; }; systemd.services.lldap.serviceConfig.User = config.users.users.lldap.name; systemd.services.lldap.serviceConfig.Group = config.users.groups.lldap.name; systemd.services.lldap.serviceConfig.DynamicUser = lib.mkForce false; # combine a path watcher with a service that transfers the certs by caddy to kanidm systemd.paths.kanidm-tls-watch = { enable = true; requiredBy = ["kanidm.service"]; pathConfig = { PathChanged = [ "${config.services.caddy.dataDir}/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/${config.services.kanidm.serverSettings.domain}/${config.services.kanidm.serverSettings.domain}.key" "${config.services.caddy.dataDir}/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/${config.services.kanidm.serverSettings.domain}/${config.services.kanidm.serverSettings.domain}.crt" ]; Unit = "kanidm-tls-update.service"; }; }; systemd.services.kanidm-tls-update = let dbDir = builtins.dirOf config.services.kanidm.serverSettings.db_path; in { enable = true; requiredBy = ["kanidm.service"]; unitConfig = { # ConditionPathExists = [ # "${config.services.caddy.dataDir}/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/${config.services.kanidm.serverSettings.domain}/${config.services.kanidm.serverSettings.domain}.key" # "${config.services.caddy.dataDir}/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/${config.services.kanidm.serverSettings.domain}/${config.services.kanidm.serverSettings.domain}.crt" # ]; }; serviceConfig.Type = "oneshot"; script = let tlsDir = builtins.dirOf config.services.kanidm.serverSettings.tls_key; in '' set -xe cat "${config.services.caddy.dataDir}/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/${config.services.kanidm.serverSettings.domain}/${config.services.kanidm.serverSettings.domain}.key" > tls.key cat "${config.services.caddy.dataDir}/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/${config.services.kanidm.serverSettings.domain}/${config.services.kanidm.serverSettings.domain}.crt" > tls.chain chown ${config.systemd.services.kanidm.serviceConfig.User}:${config.systemd.services.kanidm.serviceConfig.Group} tls.{key,chain} chmod 400 tls.{key,chain} # create the kanidm directory in case it's missing if [[ ! -d ${tlsDir} ]]; then mkdir -p ${tlsDir} chown -R ${config.systemd.services.kanidm.serviceConfig.User}:${config.systemd.services.kanidm.serviceConfig.Group} ${tlsDir} chmod 700 ${tlsDir} fi mv tls.key ${config.services.kanidm.serverSettings.tls_key} mv tls.chain ${config.services.kanidm.serverSettings.tls_chain} if [[ ! -d ${dbDir} ]]; then mkdir -p ${dbDir} chown -R ${config.systemd.services.kanidm.serviceConfig.User}:${config.systemd.services.kanidm.serviceConfig.Group} ${dbDir} chmod 700 ${dbDir} fi ''; }; systemd.services.kanidm.serviceConfig = let dbDir = builtins.dirOf config.services.kanidm.serverSettings.db_path; # stateDir = "/var/lib/${config.systemd.services.kanidm.serviceConfig.StateDirectory}"; in { # ExecStartPre = '' # mkdir -p ${dbDir} # ''; BindPaths = [ dbDir # stateDir ]; }; services.kanidm = let dataDir = "/var/lib/kanidm"; in { package = repoFlake.inputs.nixpkgs-kanidm.legacyPackages.${pkgs.system}.kanidm; enablePam = false; enableClient = false; enableServer = true; serverSettings = { role = "WriteReplica"; log_level = "debug"; domain = "kanidm.${domain}"; origin = "https://kanidm.${domain}"; db_path = "${dataDir}/db/kanidm.db"; bindaddress = "127.0.0.1:8444"; # don't expose ldap # ldapbindaddress = "[::1]:6636"; tls_key = "${dataDir}/tls/tls.key"; tls_chain = "${dataDir}/tls/tls.chain"; online_backup = { schedule = "00 06 * * *"; }; }; }; }; inherit autoStart; bindMounts = { # FIXME/REMINDER: this is used so that the container can decrypt the secrets that are deployed to the host "/etc/ssh/ssh_host_ed25519_key".isReadOnly = true; "/etc/ssh/ssh_host_ed25519_key.pub".isReadOnly = true; "/var/www" = { hostPath = "/var/lib/container-volumes/webserver/var-www"; isReadOnly = false; }; "/var/lib/mysql" = { hostPath = "/var/lib/container-volumes/webserver/var-lib-mysql"; isReadOnly = false; }; "/var/lib/hedgedoc" = { hostPath = "/var/lib/container-volumes/webserver/var-lib-hedgedoc"; isReadOnly = false; }; "/var/lib/authelia-default" = { hostPath = "/var/lib/container-volumes/webserver/var-lib-authelia-default"; isReadOnly = false; }; "/var/lib/lldap" = { hostPath = "/var/lib/container-volumes/webserver/var-lib-lldap"; isReadOnly = false; }; "/var/lib/forgejo" = { hostPath = "/var/lib/container-volumes/webserver/var-lib-forgejo"; isReadOnly = false; }; "/var/lib/kanidm" = { hostPath = "/var/lib/container-volumes/webserver/var-lib-kanidm"; isReadOnly = false; }; }; privateNetwork = true; forwardPorts = [ { # http containerPort = 80; hostPort = httpPort; protocol = "tcp"; } { # https containerPort = 443; hostPort = httpsPort; protocol = "tcp"; } { # forgejo ssh containerPort = forgejoSshPort; hostPort = forgejoSshPort; protocol = "tcp"; } ]; inherit hostBridge hostAddress localAddress; }