aboutsummaryrefslogtreecommitdiff
path: root/infra/libkookie/nixpkgs/nixos/modules/services/security
diff options
context:
space:
mode:
Diffstat (limited to 'infra/libkookie/nixpkgs/nixos/modules/services/security')
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/security/bitwarden_rs/backup.sh17
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/security/bitwarden_rs/default.nix162
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/security/certmgr.nix201
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/security/cfssl.nix209
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/security/clamav.nix147
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/security/fail2ban.nix306
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/security/fprintd.nix54
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/security/fprot.nix79
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/security/haka.nix156
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/security/haveged.nix67
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/security/hologram-agent.nix58
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/security/hologram-server.nix130
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/security/munge.nix68
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/security/nginx-sso.nix67
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/security/oauth2_proxy.nix588
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/security/oauth2_proxy_nginx.nix64
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/security/physlock.nix137
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/security/privacyidea.nix278
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/security/shibboleth-sp.nix75
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/security/sks.nix146
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/security/sshguard.nix155
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/security/tor.nix799
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/security/torify.nix80
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/security/torsocks.nix120
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/security/usbguard.nix214
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/security/vault.nix156
-rw-r--r--infra/libkookie/nixpkgs/nixos/modules/services/security/yubikey-agent.nix60
27 files changed, 4593 insertions, 0 deletions
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/security/bitwarden_rs/backup.sh b/infra/libkookie/nixpkgs/nixos/modules/services/security/bitwarden_rs/backup.sh
new file mode 100644
index 000000000000..264a7da9cbb6
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/security/bitwarden_rs/backup.sh
@@ -0,0 +1,17 @@
+#!/usr/bin/env bash
+
+# Based on: https://github.com/dani-garcia/bitwarden_rs/wiki/Backing-up-your-vault
+if ! mkdir -p "$BACKUP_FOLDER"; then
+ echo "Could not create backup folder '$BACKUP_FOLDER'" >&2
+ exit 1
+fi
+
+if [[ ! -f "$DATA_FOLDER"/db.sqlite3 ]]; then
+ echo "Could not find SQLite database file '$DATA_FOLDER/db.sqlite3'" >&2
+ exit 1
+fi
+
+sqlite3 "$DATA_FOLDER"/db.sqlite3 ".backup '$BACKUP_FOLDER/db.sqlite3'"
+cp "$DATA_FOLDER"/rsa_key.{der,pem,pub.der} "$BACKUP_FOLDER"
+cp -r "$DATA_FOLDER"/attachments "$BACKUP_FOLDER"
+cp -r "$DATA_FOLDER"/icon_cache "$BACKUP_FOLDER"
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/security/bitwarden_rs/default.nix b/infra/libkookie/nixpkgs/nixos/modules/services/security/bitwarden_rs/default.nix
new file mode 100644
index 000000000000..a04bc883bf0f
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/security/bitwarden_rs/default.nix
@@ -0,0 +1,162 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.bitwarden_rs;
+ user = config.users.users.bitwarden_rs.name;
+ group = config.users.groups.bitwarden_rs.name;
+
+ # Convert name from camel case (e.g. disable2FARemember) to upper case snake case (e.g. DISABLE_2FA_REMEMBER).
+ nameToEnvVar = name:
+ let
+ parts = builtins.split "([A-Z0-9]+)" name;
+ partsToEnvVar = parts: foldl' (key: x: let last = stringLength key - 1; in
+ if isList x then key + optionalString (key != "" && substring last 1 key != "_") "_" + head x
+ else if key != "" && elem (substring 0 1 x) lowerChars then # to handle e.g. [ "disable" [ "2FAR" ] "emember" ]
+ substring 0 last key + optionalString (substring (last - 1) 1 key != "_") "_" + substring last 1 key + toUpper x
+ else key + toUpper x) "" parts;
+ in if builtins.match "[A-Z0-9_]+" name != null then name else partsToEnvVar parts;
+
+ # Due to the different naming schemes allowed for config keys,
+ # we can only check for values consistently after converting them to their corresponding environment variable name.
+ configEnv =
+ let
+ configEnv = listToAttrs (concatLists (mapAttrsToList (name: value:
+ if value != null then [ (nameValuePair (nameToEnvVar name) (if isBool value then boolToString value else toString value)) ] else []
+ ) cfg.config));
+ in { DATA_FOLDER = "/var/lib/bitwarden_rs"; } // optionalAttrs (!(configEnv ? WEB_VAULT_ENABLED) || configEnv.WEB_VAULT_ENABLED == "true") {
+ WEB_VAULT_FOLDER = "${pkgs.bitwarden_rs-vault}/share/bitwarden_rs/vault";
+ } // configEnv;
+
+ configFile = pkgs.writeText "bitwarden_rs.env" (concatStrings (mapAttrsToList (name: value: "${name}=${value}\n") configEnv));
+
+ bitwarden_rs = pkgs.bitwarden_rs.override { inherit (cfg) dbBackend; };
+
+in {
+ options.services.bitwarden_rs = with types; {
+ enable = mkEnableOption "bitwarden_rs";
+
+ dbBackend = mkOption {
+ type = enum [ "sqlite" "mysql" "postgresql" ];
+ default = "sqlite";
+ description = ''
+ Which database backend bitwarden_rs will be using.
+ '';
+ };
+
+ backupDir = mkOption {
+ type = nullOr str;
+ default = null;
+ description = ''
+ The directory under which bitwarden_rs will backup its persistent data.
+ '';
+ };
+
+ config = mkOption {
+ type = attrsOf (nullOr (oneOf [ bool int str ]));
+ default = {};
+ example = literalExample ''
+ {
+ domain = "https://bw.domain.tld:8443";
+ signupsAllowed = true;
+ rocketPort = 8222;
+ rocketLog = "critical";
+ }
+ '';
+ description = ''
+ The configuration of bitwarden_rs is done through environment variables,
+ therefore the names are converted from camel case (e.g. disable2FARemember)
+ to upper case snake case (e.g. DISABLE_2FA_REMEMBER).
+ In this conversion digits (0-9) are handled just like upper case characters,
+ so foo2 would be converted to FOO_2.
+ Names already in this format remain unchanged, so FOO2 remains FOO2 if passed as such,
+ even though foo2 would have been converted to FOO_2.
+ This allows working around any potential future conflicting naming conventions.
+
+ Based on the attributes passed to this config option an environment file will be generated
+ that is passed to bitwarden_rs's systemd service.
+
+ The available configuration options can be found in
+ <link xlink:href="https://github.com/dani-garcia/bitwarden_rs/blob/${bitwarden_rs.version}/.env.template">the environment template file</link>.
+ '';
+ };
+
+ environmentFile = mkOption {
+ type = with types; nullOr path;
+ default = null;
+ example = "/root/bitwarden_rs.env";
+ description = ''
+ Additional environment file as defined in <citerefentry>
+ <refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum>
+ </citerefentry>.
+
+ Secrets like <envar>ADMIN_TOKEN</envar> and <envar>SMTP_PASSWORD</envar>
+ may be passed to the service without adding them to the world-readable Nix store.
+
+ Note that this file needs to be available on the host on which
+ <literal>bitwarden_rs</literal> is running.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ assertions = [ {
+ assertion = cfg.backupDir != null -> cfg.dbBackend == "sqlite";
+ message = "Backups for database backends other than sqlite will need customization";
+ } ];
+
+ users.users.bitwarden_rs = {
+ inherit group;
+ isSystemUser = true;
+ };
+ users.groups.bitwarden_rs = { };
+
+ systemd.services.bitwarden_rs = {
+ after = [ "network.target" ];
+ path = with pkgs; [ openssl ];
+ serviceConfig = {
+ User = user;
+ Group = group;
+ EnvironmentFile = [ configFile ] ++ optional (cfg.environmentFile != null) cfg.environmentFile;
+ ExecStart = "${bitwarden_rs}/bin/bitwarden_rs";
+ LimitNOFILE = "1048576";
+ LimitNPROC = "64";
+ PrivateTmp = "true";
+ PrivateDevices = "true";
+ ProtectHome = "true";
+ ProtectSystem = "strict";
+ AmbientCapabilities = "CAP_NET_BIND_SERVICE";
+ StateDirectory = "bitwarden_rs";
+ };
+ wantedBy = [ "multi-user.target" ];
+ };
+
+ systemd.services.backup-bitwarden_rs = mkIf (cfg.backupDir != null) {
+ description = "Backup bitwarden_rs";
+ environment = {
+ DATA_FOLDER = "/var/lib/bitwarden_rs";
+ BACKUP_FOLDER = cfg.backupDir;
+ };
+ path = with pkgs; [ sqlite ];
+ serviceConfig = {
+ SyslogIdentifier = "backup-bitwarden_rs";
+ Type = "oneshot";
+ User = mkDefault user;
+ Group = mkDefault group;
+ ExecStart = "${pkgs.bash}/bin/bash ${./backup.sh}";
+ };
+ wantedBy = [ "multi-user.target" ];
+ };
+
+ systemd.timers.backup-bitwarden_rs = mkIf (cfg.backupDir != null) {
+ description = "Backup bitwarden_rs on time";
+ timerConfig = {
+ OnCalendar = mkDefault "23:00";
+ Persistent = "true";
+ Unit = "backup-bitwarden_rs.service";
+ };
+ wantedBy = [ "multi-user.target" ];
+ };
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/security/certmgr.nix b/infra/libkookie/nixpkgs/nixos/modules/services/security/certmgr.nix
new file mode 100644
index 000000000000..94c0ba141179
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/security/certmgr.nix
@@ -0,0 +1,201 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.certmgr;
+
+ specs = mapAttrsToList (n: v: rec {
+ name = n + ".json";
+ path = if isAttrs v then pkgs.writeText name (builtins.toJSON v) else v;
+ }) cfg.specs;
+
+ allSpecs = pkgs.linkFarm "certmgr.d" specs;
+
+ certmgrYaml = pkgs.writeText "certmgr.yaml" (builtins.toJSON {
+ dir = allSpecs;
+ default_remote = cfg.defaultRemote;
+ svcmgr = cfg.svcManager;
+ before = cfg.validMin;
+ interval = cfg.renewInterval;
+ inherit (cfg) metricsPort metricsAddress;
+ });
+
+ specPaths = map dirOf (concatMap (spec:
+ if isAttrs spec then
+ collect isString (filterAttrsRecursive (n: v: isAttrs v || n == "path") spec)
+ else
+ [ spec ]
+ ) (attrValues cfg.specs));
+
+ preStart = ''
+ ${concatStringsSep " \\\n" (["mkdir -p"] ++ map escapeShellArg specPaths)}
+ ${cfg.package}/bin/certmgr -f ${certmgrYaml} check
+ '';
+in
+{
+ options.services.certmgr = {
+ enable = mkEnableOption "certmgr";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.certmgr;
+ defaultText = "pkgs.certmgr";
+ description = "Which certmgr package to use in the service.";
+ };
+
+ defaultRemote = mkOption {
+ type = types.str;
+ default = "127.0.0.1:8888";
+ description = "The default CA host:port to use.";
+ };
+
+ validMin = mkOption {
+ default = "72h";
+ type = types.str;
+ description = "The interval before a certificate expires to start attempting to renew it.";
+ };
+
+ renewInterval = mkOption {
+ default = "30m";
+ type = types.str;
+ description = "How often to check certificate expirations and how often to update the cert_next_expires metric.";
+ };
+
+ metricsAddress = mkOption {
+ default = "127.0.0.1";
+ type = types.str;
+ description = "The address for the Prometheus HTTP endpoint.";
+ };
+
+ metricsPort = mkOption {
+ default = 9488;
+ type = types.ints.u16;
+ description = "The port for the Prometheus HTTP endpoint.";
+ };
+
+ specs = mkOption {
+ default = {};
+ example = literalExample ''
+ {
+ exampleCert =
+ let
+ domain = "example.com";
+ secret = name: "/var/lib/secrets/''${name}.pem";
+ in {
+ service = "nginx";
+ action = "reload";
+ authority = {
+ file.path = secret "ca";
+ };
+ certificate = {
+ path = secret domain;
+ };
+ private_key = {
+ owner = "root";
+ group = "root";
+ mode = "0600";
+ path = secret "''${domain}-key";
+ };
+ request = {
+ CN = domain;
+ hosts = [ "mail.''${domain}" "www.''${domain}" ];
+ key = {
+ algo = "rsa";
+ size = 2048;
+ };
+ names = {
+ O = "Example Organization";
+ C = "USA";
+ };
+ };
+ };
+ otherCert = "/var/certmgr/specs/other-cert.json";
+ }
+ '';
+ type = with types; attrsOf (either path (submodule {
+ options = {
+ service = mkOption {
+ type = nullOr str;
+ default = null;
+ description = "The service on which to perform &lt;action&gt; after fetching.";
+ };
+
+ action = mkOption {
+ type = addCheck str (x: cfg.svcManager == "command" || elem x ["restart" "reload" "nop"]);
+ default = "nop";
+ description = "The action to take after fetching.";
+ };
+
+ # These ought all to be specified according to certmgr spec def.
+ authority = mkOption {
+ type = attrs;
+ description = "certmgr spec authority object.";
+ };
+
+ certificate = mkOption {
+ type = nullOr attrs;
+ description = "certmgr spec certificate object.";
+ };
+
+ private_key = mkOption {
+ type = nullOr attrs;
+ description = "certmgr spec private_key object.";
+ };
+
+ request = mkOption {
+ type = nullOr attrs;
+ description = "certmgr spec request object.";
+ };
+ };
+ }));
+ description = ''
+ Certificate specs as described by:
+ <link xlink:href="https://github.com/cloudflare/certmgr#certificate-specs" />
+ These will be added to the Nix store, so they will be world readable.
+ '';
+ };
+
+ svcManager = mkOption {
+ default = "systemd";
+ type = types.enum [ "circus" "command" "dummy" "openrc" "systemd" "sysv" ];
+ description = ''
+ This specifies the service manager to use for restarting or reloading services.
+ See: <link xlink:href="https://github.com/cloudflare/certmgr#certmgryaml" />.
+ For how to use the "command" service manager in particular,
+ see: <link xlink:href="https://github.com/cloudflare/certmgr#command-svcmgr-and-how-to-use-it" />.
+ '';
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+ assertions = [
+ {
+ assertion = cfg.specs != {};
+ message = "Certmgr specs cannot be empty.";
+ }
+ {
+ assertion = !any (hasAttrByPath [ "authority" "auth_key" ]) (attrValues cfg.specs);
+ message = ''
+ Inline services.certmgr.specs are added to the Nix store rendering them world readable.
+ Specify paths as specs, if you want to use include auth_key - or use the auth_key_file option."
+ '';
+ }
+ ];
+
+ systemd.services.certmgr = {
+ description = "certmgr";
+ path = mkIf (cfg.svcManager == "command") [ pkgs.bash ];
+ after = [ "network-online.target" ];
+ wantedBy = [ "multi-user.target" ];
+ inherit preStart;
+
+ serviceConfig = {
+ Restart = "always";
+ RestartSec = "10s";
+ ExecStart = "${cfg.package}/bin/certmgr -f ${certmgrYaml}";
+ };
+ };
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/security/cfssl.nix b/infra/libkookie/nixpkgs/nixos/modules/services/security/cfssl.nix
new file mode 100644
index 000000000000..ee6d5d91fe15
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/security/cfssl.nix
@@ -0,0 +1,209 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.cfssl;
+in {
+ options.services.cfssl = {
+ enable = mkEnableOption "the CFSSL CA api-server";
+
+ dataDir = mkOption {
+ default = "/var/lib/cfssl";
+ type = types.path;
+ description = "Cfssl work directory.";
+ };
+
+ address = mkOption {
+ default = "127.0.0.1";
+ type = types.str;
+ description = "Address to bind.";
+ };
+
+ port = mkOption {
+ default = 8888;
+ type = types.ints.u16;
+ description = "Port to bind.";
+ };
+
+ ca = mkOption {
+ defaultText = "\${cfg.dataDir}/ca.pem";
+ type = types.str;
+ description = "CA used to sign the new certificate -- accepts '[file:]fname' or 'env:varname'.";
+ };
+
+ caKey = mkOption {
+ defaultText = "file:\${cfg.dataDir}/ca-key.pem";
+ type = types.str;
+ description = "CA private key -- accepts '[file:]fname' or 'env:varname'.";
+ };
+
+ caBundle = mkOption {
+ default = null;
+ type = types.nullOr types.path;
+ description = "Path to root certificate store.";
+ };
+
+ intBundle = mkOption {
+ default = null;
+ type = types.nullOr types.path;
+ description = "Path to intermediate certificate store.";
+ };
+
+ intDir = mkOption {
+ default = null;
+ type = types.nullOr types.path;
+ description = "Intermediates directory.";
+ };
+
+ metadata = mkOption {
+ default = null;
+ type = types.nullOr types.path;
+ description = ''
+ Metadata file for root certificate presence.
+ The content of the file is a json dictionary (k,v): each key k is
+ a SHA-1 digest of a root certificate while value v is a list of key
+ store filenames.
+ '';
+ };
+
+ remote = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = "Remote CFSSL server.";
+ };
+
+ configFile = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = "Path to configuration file. Do not put this in nix-store as it might contain secrets.";
+ };
+
+ responder = mkOption {
+ default = null;
+ type = types.nullOr types.path;
+ description = "Certificate for OCSP responder.";
+ };
+
+ responderKey = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = "Private key for OCSP responder certificate. Do not put this in nix-store.";
+ };
+
+ tlsKey = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = "Other endpoint's CA private key. Do not put this in nix-store.";
+ };
+
+ tlsCert = mkOption {
+ default = null;
+ type = types.nullOr types.path;
+ description = "Other endpoint's CA to set up TLS protocol.";
+ };
+
+ mutualTlsCa = mkOption {
+ default = null;
+ type = types.nullOr types.path;
+ description = "Mutual TLS - require clients be signed by this CA.";
+ };
+
+ mutualTlsCn = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = "Mutual TLS - regex for whitelist of allowed client CNs.";
+ };
+
+ tlsRemoteCa = mkOption {
+ default = null;
+ type = types.nullOr types.path;
+ description = "CAs to trust for remote TLS requests.";
+ };
+
+ mutualTlsClientCert = mkOption {
+ default = null;
+ type = types.nullOr types.path;
+ description = "Mutual TLS - client certificate to call remote instance requiring client certs.";
+ };
+
+ mutualTlsClientKey = mkOption {
+ default = null;
+ type = types.nullOr types.path;
+ description = "Mutual TLS - client key to call remote instance requiring client certs. Do not put this in nix-store.";
+ };
+
+ dbConfig = mkOption {
+ default = null;
+ type = types.nullOr types.path;
+ description = "Certificate db configuration file. Path must be writeable.";
+ };
+
+ logLevel = mkOption {
+ default = 1;
+ type = types.enum [ 0 1 2 3 4 5 ];
+ description = "Log level (0 = DEBUG, 5 = FATAL).";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ users.extraGroups.cfssl = {
+ gid = config.ids.gids.cfssl;
+ };
+
+ users.extraUsers.cfssl = {
+ description = "cfssl user";
+ createHome = true;
+ home = cfg.dataDir;
+ group = "cfssl";
+ uid = config.ids.uids.cfssl;
+ };
+
+ systemd.services.cfssl = {
+ description = "CFSSL CA API server";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ serviceConfig = {
+ WorkingDirectory = cfg.dataDir;
+ StateDirectory = cfg.dataDir;
+ StateDirectoryMode = 700;
+ Restart = "always";
+ User = "cfssl";
+
+ ExecStart = with cfg; let
+ opt = n: v: optionalString (v != null) ''-${n}="${v}"'';
+ in
+ lib.concatStringsSep " \\\n" [
+ "${pkgs.cfssl}/bin/cfssl serve"
+ (opt "address" address)
+ (opt "port" (toString port))
+ (opt "ca" ca)
+ (opt "ca-key" caKey)
+ (opt "ca-bundle" caBundle)
+ (opt "int-bundle" intBundle)
+ (opt "int-dir" intDir)
+ (opt "metadata" metadata)
+ (opt "remote" remote)
+ (opt "config" configFile)
+ (opt "responder" responder)
+ (opt "responder-key" responderKey)
+ (opt "tls-key" tlsKey)
+ (opt "tls-cert" tlsCert)
+ (opt "mutual-tls-ca" mutualTlsCa)
+ (opt "mutual-tls-cn" mutualTlsCn)
+ (opt "mutual-tls-client-key" mutualTlsClientKey)
+ (opt "mutual-tls-client-cert" mutualTlsClientCert)
+ (opt "tls-remote-ca" tlsRemoteCa)
+ (opt "db-config" dbConfig)
+ (opt "loglevel" (toString logLevel))
+ ];
+ };
+ };
+
+ services.cfssl = {
+ ca = mkDefault "${cfg.dataDir}/ca.pem";
+ caKey = mkDefault "${cfg.dataDir}/ca-key.pem";
+ };
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/security/clamav.nix b/infra/libkookie/nixpkgs/nixos/modules/services/security/clamav.nix
new file mode 100644
index 000000000000..aaf6fb0479ba
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/security/clamav.nix
@@ -0,0 +1,147 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+ clamavUser = "clamav";
+ stateDir = "/var/lib/clamav";
+ runDir = "/run/clamav";
+ clamavGroup = clamavUser;
+ cfg = config.services.clamav;
+ pkg = pkgs.clamav;
+
+ clamdConfigFile = pkgs.writeText "clamd.conf" ''
+ DatabaseDirectory ${stateDir}
+ LocalSocket ${runDir}/clamd.ctl
+ PidFile ${runDir}/clamd.pid
+ TemporaryDirectory /tmp
+ User clamav
+ Foreground yes
+
+ ${cfg.daemon.extraConfig}
+ '';
+
+ freshclamConfigFile = pkgs.writeText "freshclam.conf" ''
+ DatabaseDirectory ${stateDir}
+ Foreground yes
+ Checks ${toString cfg.updater.frequency}
+
+ ${cfg.updater.extraConfig}
+
+ DatabaseMirror database.clamav.net
+ '';
+in
+{
+ imports = [
+ (mkRenamedOptionModule [ "services" "clamav" "updater" "config" ] [ "services" "clamav" "updater" "extraConfig" ])
+ ];
+
+ options = {
+ services.clamav = {
+ daemon = {
+ enable = mkEnableOption "ClamAV clamd daemon";
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra configuration for clamd. Contents will be added verbatim to the
+ configuration file.
+ '';
+ };
+ };
+ updater = {
+ enable = mkEnableOption "ClamAV freshclam updater";
+
+ frequency = mkOption {
+ type = types.int;
+ default = 12;
+ description = ''
+ Number of database checks per day.
+ '';
+ };
+
+ interval = mkOption {
+ type = types.str;
+ default = "hourly";
+ description = ''
+ How often freshclam is invoked. See systemd.time(7) for more
+ information about the format.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra configuration for freshclam. Contents will be added verbatim to the
+ configuration file.
+ '';
+ };
+ };
+ };
+ };
+
+ config = mkIf (cfg.updater.enable || cfg.daemon.enable) {
+ environment.systemPackages = [ pkg ];
+
+ users.users.${clamavUser} = {
+ uid = config.ids.uids.clamav;
+ group = clamavGroup;
+ description = "ClamAV daemon user";
+ home = stateDir;
+ };
+
+ users.groups.${clamavGroup} =
+ { gid = config.ids.gids.clamav; };
+
+ environment.etc."clamav/freshclam.conf".source = freshclamConfigFile;
+ environment.etc."clamav/clamd.conf".source = clamdConfigFile;
+
+ systemd.services.clamav-daemon = mkIf cfg.daemon.enable {
+ description = "ClamAV daemon (clamd)";
+ after = optional cfg.updater.enable "clamav-freshclam.service";
+ requires = optional cfg.updater.enable "clamav-freshclam.service";
+ wantedBy = [ "multi-user.target" ];
+ restartTriggers = [ clamdConfigFile ];
+
+ preStart = ''
+ mkdir -m 0755 -p ${runDir}
+ chown ${clamavUser}:${clamavGroup} ${runDir}
+ '';
+
+ serviceConfig = {
+ ExecStart = "${pkg}/bin/clamd";
+ ExecReload = "${pkgs.coreutils}/bin/kill -USR2 $MAINPID";
+ PrivateTmp = "yes";
+ PrivateDevices = "yes";
+ PrivateNetwork = "yes";
+ };
+ };
+
+ systemd.timers.clamav-freshclam = mkIf cfg.updater.enable {
+ description = "Timer for ClamAV virus database updater (freshclam)";
+ wantedBy = [ "timers.target" ];
+ timerConfig = {
+ OnCalendar = cfg.updater.interval;
+ Unit = "clamav-freshclam.service";
+ };
+ };
+
+ systemd.services.clamav-freshclam = mkIf cfg.updater.enable {
+ description = "ClamAV virus database updater (freshclam)";
+ restartTriggers = [ freshclamConfigFile ];
+
+ preStart = ''
+ mkdir -m 0755 -p ${stateDir}
+ chown ${clamavUser}:${clamavGroup} ${stateDir}
+ '';
+
+ serviceConfig = {
+ Type = "oneshot";
+ ExecStart = "${pkg}/bin/freshclam";
+ SuccessExitStatus = "1"; # if databases are up to date
+ PrivateTmp = "yes";
+ PrivateDevices = "yes";
+ };
+ };
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/security/fail2ban.nix b/infra/libkookie/nixpkgs/nixos/modules/services/security/fail2ban.nix
new file mode 100644
index 000000000000..cf0d72d5c531
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/security/fail2ban.nix
@@ -0,0 +1,306 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.fail2ban;
+
+ fail2banConf = pkgs.writeText "fail2ban.local" cfg.daemonConfig;
+
+ jailConf = pkgs.writeText "jail.local" ''
+ [INCLUDES]
+
+ before = paths-nixos.conf
+
+ ${concatStringsSep "\n" (attrValues (flip mapAttrs cfg.jails (name: def:
+ optionalString (def != "")
+ ''
+ [${name}]
+ ${def}
+ '')))}
+ '';
+
+ pathsConf = pkgs.writeText "paths-nixos.conf" ''
+ # NixOS
+
+ [INCLUDES]
+
+ before = paths-common.conf
+
+ after = paths-overrides.local
+
+ [DEFAULT]
+ '';
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.fail2ban = {
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Whether to enable the fail2ban service.";
+ };
+
+ package = mkOption {
+ default = pkgs.fail2ban;
+ type = types.package;
+ example = "pkgs.fail2ban_0_11";
+ description = "The fail2ban package to use for running the fail2ban service.";
+ };
+
+ packageFirewall = mkOption {
+ default = pkgs.iptables;
+ type = types.package;
+ example = "pkgs.nftables";
+ description = "The firewall package used by fail2ban service.";
+ };
+
+ banaction = mkOption {
+ default = "iptables-multiport";
+ type = types.str;
+ example = "nftables-multiport";
+ description = ''
+ Default banning action (e.g. iptables, iptables-new, iptables-multiport,
+ shorewall, etc) It is used to define action_* variables. Can be overridden
+ globally or per section within jail.local file
+ '';
+ };
+
+ banaction-allports = mkOption {
+ default = "iptables-allport";
+ type = types.str;
+ example = "nftables-allport";
+ description = ''
+ Default banning action (e.g. iptables, iptables-new, iptables-multiport,
+ shorewall, etc) It is used to define action_* variables. Can be overridden
+ globally or per section within jail.local file
+ '';
+ };
+
+ bantime-increment.enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Allows to use database for searching of previously banned ip's to increase
+ a default ban time using special formula, default it is banTime * 1, 2, 4, 8, 16, 32...
+ '';
+ };
+
+ bantime-increment.rndtime = mkOption {
+ default = "4m";
+ type = types.str;
+ example = "8m";
+ description = ''
+ "bantime-increment.rndtime" is the max number of seconds using for mixing with random time
+ to prevent "clever" botnets calculate exact time IP can be unbanned again
+ '';
+ };
+
+ bantime-increment.maxtime = mkOption {
+ default = "10h";
+ type = types.str;
+ example = "48h";
+ description = ''
+ "bantime-increment.maxtime" is the max number of seconds using the ban time can reach (don't grows further)
+ '';
+ };
+
+ bantime-increment.factor = mkOption {
+ default = "1";
+ type = types.str;
+ example = "4";
+ description = ''
+ "bantime-increment.factor" is a coefficient to calculate exponent growing of the formula or common multiplier,
+ default value of factor is 1 and with default value of formula, the ban time grows by 1, 2, 4, 8, 16 ...
+ '';
+ };
+
+ bantime-increment.formula = mkOption {
+ default = "ban.Time * (1<<(ban.Count if ban.Count<20 else 20)) * banFactor";
+ type = types.str;
+ example = "ban.Time * math.exp(float(ban.Count+1)*banFactor)/math.exp(1*banFactor)";
+ description = ''
+ "bantime-increment.formula" used by default to calculate next value of ban time, default value bellow,
+ the same ban time growing will be reached by multipliers 1, 2, 4, 8, 16, 32...
+ '';
+ };
+
+ bantime-increment.multipliers = mkOption {
+ default = "1 2 4 8 16 32 64";
+ type = types.str;
+ example = "2 4 16 128";
+ description = ''
+ "bantime-increment.multipliers" used to calculate next value of ban time instead of formula, coresponding
+ previously ban count and given "bantime.factor" (for multipliers default is 1);
+ following example grows ban time by 1, 2, 4, 8, 16 ... and if last ban count greater as multipliers count,
+ always used last multiplier (64 in example), for factor '1' and original ban time 600 - 10.6 hours
+ '';
+ };
+
+ bantime-increment.overalljails = mkOption {
+ default = false;
+ type = types.bool;
+ example = true;
+ description = ''
+ "bantime-increment.overalljails" (if true) specifies the search of IP in the database will be executed
+ cross over all jails, if false (dafault), only current jail of the ban IP will be searched
+ '';
+ };
+
+ ignoreIP = mkOption {
+ default = [ ];
+ type = types.listOf types.str;
+ example = [ "192.168.0.0/16" "2001:DB8::42" ];
+ description = ''
+ "ignoreIP" can be a list of IP addresses, CIDR masks or DNS hosts. Fail2ban will not ban a host which
+ matches an address in this list. Several addresses can be defined using space (and/or comma) separator.
+ '';
+ };
+
+ daemonConfig = mkOption {
+ default = ''
+ [Definition]
+ logtarget = SYSLOG
+ socket = /run/fail2ban/fail2ban.sock
+ pidfile = /run/fail2ban/fail2ban.pid
+ dbfile = /var/lib/fail2ban/fail2ban.sqlite3
+ '';
+ type = types.lines;
+ description = ''
+ The contents of Fail2ban's main configuration file. It's
+ generally not necessary to change it.
+ '';
+ };
+
+ jails = mkOption {
+ default = { };
+ example = literalExample ''
+ { apache-nohome-iptables = '''
+ # Block an IP address if it accesses a non-existent
+ # home directory more than 5 times in 10 minutes,
+ # since that indicates that it's scanning.
+ filter = apache-nohome
+ action = iptables-multiport[name=HTTP, port="http,https"]
+ logpath = /var/log/httpd/error_log*
+ findtime = 600
+ bantime = 600
+ maxretry = 5
+ ''';
+ }
+ '';
+ type = types.attrsOf types.lines;
+ description = ''
+ The configuration of each Fail2ban “jail”. A jail
+ consists of an action (such as blocking a port using
+ <command>iptables</command>) that is triggered when a
+ filter applied to a log file triggers more than a certain
+ number of times in a certain time period. Actions are
+ defined in <filename>/etc/fail2ban/action.d</filename>,
+ while filters are defined in
+ <filename>/etc/fail2ban/filter.d</filename>.
+ '';
+ };
+
+ };
+
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ warnings = mkIf (config.networking.firewall.enable == false && config.networking.nftables.enable == false) [
+ "fail2ban can not be used without a firewall"
+ ];
+
+ environment.systemPackages = [ cfg.package ];
+
+ environment.etc = {
+ "fail2ban/fail2ban.local".source = fail2banConf;
+ "fail2ban/jail.local".source = jailConf;
+ "fail2ban/fail2ban.conf".source = "${cfg.package}/etc/fail2ban/fail2ban.conf";
+ "fail2ban/jail.conf".source = "${cfg.package}/etc/fail2ban/jail.conf";
+ "fail2ban/paths-common.conf".source = "${cfg.package}/etc/fail2ban/paths-common.conf";
+ "fail2ban/paths-nixos.conf".source = pathsConf;
+ "fail2ban/action.d".source = "${cfg.package}/etc/fail2ban/action.d/*.conf";
+ "fail2ban/filter.d".source = "${cfg.package}/etc/fail2ban/filter.d/*.conf";
+ };
+
+ systemd.services.fail2ban = {
+ description = "Fail2ban Intrusion Prevention System";
+
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ partOf = optional config.networking.firewall.enable "firewall.service";
+
+ restartTriggers = [ fail2banConf jailConf pathsConf ];
+ reloadIfChanged = true;
+
+ path = [ cfg.package cfg.packageFirewall pkgs.iproute ];
+
+ unitConfig.Documentation = "man:fail2ban(1)";
+
+ serviceConfig = {
+ ExecStart = "${cfg.package}/bin/fail2ban-server -xf start";
+ ExecStop = "${cfg.package}/bin/fail2ban-server stop";
+ ExecReload = "${cfg.package}/bin/fail2ban-server reload";
+ Type = "simple";
+ Restart = "on-failure";
+ PIDFile = "/run/fail2ban/fail2ban.pid";
+ # Capabilities
+ CapabilityBoundingSet = [ "CAP_AUDIT_READ" "CAP_DAC_READ_SEARCH" "CAP_NET_ADMIN" "CAP_NET_RAW" ];
+ # Security
+ NoNewPrivileges = true;
+ # Directory
+ RuntimeDirectory = "fail2ban";
+ RuntimeDirectoryMode = "0750";
+ StateDirectory = "fail2ban";
+ StateDirectoryMode = "0750";
+ LogsDirectory = "fail2ban";
+ LogsDirectoryMode = "0750";
+ # Sandboxing
+ ProtectSystem = "strict";
+ ProtectHome = true;
+ PrivateTmp = true;
+ PrivateDevices = true;
+ ProtectHostname = true;
+ ProtectKernelTunables = true;
+ ProtectKernelModules = true;
+ ProtectControlGroups = true;
+ };
+ };
+
+ # Add some reasonable default jails. The special "DEFAULT" jail
+ # sets default values for all other jails.
+ services.fail2ban.jails.DEFAULT = ''
+ ${optionalString cfg.bantime-increment.enable ''
+ # Bantime incremental
+ bantime.increment = ${boolToString cfg.bantime-increment.enable}
+ bantime.maxtime = ${cfg.bantime-increment.maxtime}
+ bantime.factor = ${cfg.bantime-increment.factor}
+ bantime.formula = ${cfg.bantime-increment.formula}
+ bantime.multipliers = ${cfg.bantime-increment.multipliers}
+ bantime.overalljails = ${boolToString cfg.bantime-increment.overalljails}
+ ''}
+ # Miscellaneous options
+ ignoreip = 127.0.0.1/8 ${optionalString config.networking.enableIPv6 "::1"} ${concatStringsSep " " cfg.ignoreIP}
+ maxretry = 3
+ backend = systemd
+ # Actions
+ banaction = ${cfg.banaction}
+ banaction_allports = ${cfg.banaction-allports}
+ '';
+ # Block SSH if there are too many failing connection attempts.
+ services.fail2ban.jails.sshd = mkDefault ''
+ enabled = true
+ port = ${concatMapStringsSep "," (p: toString p) config.services.openssh.ports}
+ '';
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/security/fprintd.nix b/infra/libkookie/nixpkgs/nixos/modules/services/security/fprintd.nix
new file mode 100644
index 000000000000..cbac4ef05b8d
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/security/fprintd.nix
@@ -0,0 +1,54 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.fprintd;
+
+in
+
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.fprintd = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable fprintd daemon and PAM module for fingerprint readers handling.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.fprintd;
+ defaultText = "pkgs.fprintd";
+ description = ''
+ fprintd package to use.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ services.dbus.packages = [ pkgs.fprintd ];
+
+ environment.systemPackages = [ pkgs.fprintd ];
+
+ systemd.packages = [ cfg.package ];
+
+ };
+
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/security/fprot.nix b/infra/libkookie/nixpkgs/nixos/modules/services/security/fprot.nix
new file mode 100644
index 000000000000..3a0b08b3c6d8
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/security/fprot.nix
@@ -0,0 +1,79 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+ fprotUser = "fprot";
+ stateDir = "/var/lib/fprot";
+ fprotGroup = fprotUser;
+ cfg = config.services.fprot;
+in {
+ options = {
+
+ services.fprot = {
+ updater = {
+ enable = mkEnableOption "automatic F-Prot virus definitions database updates";
+
+ productData = mkOption {
+ description = ''
+ product.data file. Defaults to the one supplied with installation package.
+ '';
+ };
+
+ frequency = mkOption {
+ default = 30;
+ description = ''
+ Update virus definitions every X minutes.
+ '';
+ };
+
+ licenseKeyfile = mkOption {
+ description = ''
+ License keyfile. Defaults to the one supplied with installation package.
+ '';
+ };
+
+ };
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.updater.enable {
+
+ services.fprot.updater.productData = mkDefault "${pkgs.fprot}/opt/f-prot/product.data";
+ services.fprot.updater.licenseKeyfile = mkDefault "${pkgs.fprot}/opt/f-prot/license.key";
+
+ environment.systemPackages = [ pkgs.fprot ];
+ environment.etc."f-prot.conf" = {
+ source = "${pkgs.fprot}/opt/f-prot/f-prot.conf";
+ };
+
+ users.users.${fprotUser} =
+ { uid = config.ids.uids.fprot;
+ description = "F-Prot daemon user";
+ home = stateDir;
+ };
+
+ users.groups.${fprotGroup} =
+ { gid = config.ids.gids.fprot; };
+
+ services.cron.systemCronJobs = [ "*/${toString cfg.updater.frequency} * * * * root start fprot-updater" ];
+
+ systemd.services.fprot-updater = {
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = false;
+ };
+ wantedBy = [ "multi-user.target" ];
+
+ # have to copy fpupdate executable because it insists on storing the virus database in the same dir
+ preStart = ''
+ mkdir -m 0755 -p ${stateDir}
+ chown ${fprotUser}:${fprotGroup} ${stateDir}
+ cp ${pkgs.fprot}/opt/f-prot/fpupdate ${stateDir}
+ ln -sf ${cfg.updater.productData} ${stateDir}/product.data
+ '';
+
+ script = "/var/lib/fprot/fpupdate --keyfile ${cfg.updater.licenseKeyfile}";
+ };
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/security/haka.nix b/infra/libkookie/nixpkgs/nixos/modules/services/security/haka.nix
new file mode 100644
index 000000000000..618e689924fd
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/security/haka.nix
@@ -0,0 +1,156 @@
+# This module defines global configuration for Haka.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.haka;
+
+ haka = cfg.package;
+
+ hakaConf = pkgs.writeText "haka.conf"
+ ''
+ [general]
+ configuration = ${if lib.strings.hasPrefix "/" cfg.configFile
+ then "${cfg.configFile}"
+ else "${haka}/share/haka/sample/${cfg.configFile}"}
+ ${optionalString (builtins.lessThan 0 cfg.threads) "thread = ${cfg.threads}"}
+
+ [packet]
+ ${optionalString cfg.pcap ''module = "packet/pcap"''}
+ ${optionalString cfg.nfqueue ''module = "packet/nqueue"''}
+ ${optionalString cfg.dump.enable ''dump = "yes"''}
+ ${optionalString cfg.dump.enable ''dump_input = "${cfg.dump.input}"''}
+ ${optionalString cfg.dump.enable ''dump_output = "${cfg.dump.output}"''}
+
+ interfaces = "${lib.strings.concatStringsSep "," cfg.interfaces}"
+
+ [log]
+ # Select the log module
+ module = "log/syslog"
+
+ # Set the default logging level
+ #level = "info,packet=debug"
+
+ [alert]
+ # Select the alert module
+ module = "alert/syslog"
+
+ # Disable alert on standard output
+ #alert_on_stdout = no
+
+ # alert/file module option
+ #file = "/dev/null"
+ '';
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.haka = {
+
+ enable = mkEnableOption "Haka";
+
+ package = mkOption {
+ default = pkgs.haka;
+ defaultText = "pkgs.haka";
+ type = types.package;
+ description = "
+ Which Haka derivation to use.
+ ";
+ };
+
+ configFile = mkOption {
+ default = "empty.lua";
+ example = "/srv/haka/myfilter.lua";
+ type = types.str;
+ description = ''
+ Specify which configuration file Haka uses.
+ It can be absolute path or a path relative to the sample directory of
+ the haka git repo.
+ '';
+ };
+
+ interfaces = mkOption {
+ default = [ "eth0" ];
+ example = [ "any" ];
+ type = with types; listOf str;
+ description = ''
+ Specify which interface(s) Haka listens to.
+ Use 'any' to listen to all interfaces.
+ '';
+ };
+
+ threads = mkOption {
+ default = 0;
+ example = 4;
+ type = types.int;
+ description = ''
+ The number of threads that will be used.
+ All system threads are used by default.
+ '';
+ };
+
+ pcap = mkOption {
+ default = true;
+ type = types.bool;
+ description = "Whether to enable pcap";
+ };
+
+ nfqueue = mkEnableOption "nfqueue";
+
+ dump.enable = mkEnableOption "dump";
+ dump.input = mkOption {
+ default = "/tmp/input.pcap";
+ example = "/path/to/file.pcap";
+ type = types.path;
+ description = "Path to file where incoming packets are dumped";
+ };
+
+ dump.output = mkOption {
+ default = "/tmp/output.pcap";
+ example = "/path/to/file.pcap";
+ type = types.path;
+ description = "Path to file where outgoing packets are dumped";
+ };
+ };
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ assertions = [
+ { assertion = cfg.pcap != cfg.nfqueue;
+ message = "either pcap or nfqueue can be enabled, not both.";
+ }
+ { assertion = cfg.nfqueue -> !dump.enable;
+ message = "dump can only be used with nfqueue.";
+ }
+ { assertion = cfg.interfaces != [];
+ message = "at least one interface must be specified.";
+ }];
+
+
+ environment.systemPackages = [ haka ];
+
+ systemd.services.haka = {
+ description = "Haka";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ serviceConfig = {
+ ExecStart = "${haka}/bin/haka -c ${hakaConf}";
+ ExecStop = "${haka}/bin/hakactl stop";
+ User = "root";
+ Type = "forking";
+ };
+ };
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/security/haveged.nix b/infra/libkookie/nixpkgs/nixos/modules/services/security/haveged.nix
new file mode 100644
index 000000000000..22ece1883446
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/security/haveged.nix
@@ -0,0 +1,67 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.haveged;
+
+in
+
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.haveged = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable to haveged entropy daemon, which refills
+ /dev/random when low.
+ '';
+ };
+
+ refill_threshold = mkOption {
+ type = types.int;
+ default = 1024;
+ description = ''
+ The number of bits of available entropy beneath which
+ haveged should refill the entropy pool.
+ '';
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ systemd.services.haveged =
+ { description = "Entropy Harvesting Daemon";
+ unitConfig.Documentation = "man:haveged(8)";
+ wantedBy = [ "multi-user.target" ];
+
+ path = [ pkgs.haveged ];
+
+ serviceConfig = {
+ ExecStart = "${pkgs.haveged}/bin/haveged -F -w ${toString cfg.refill_threshold} -v 1";
+ SuccessExitStatus = 143;
+ PrivateTmp = true;
+ PrivateDevices = true;
+ PrivateNetwork = true;
+ ProtectSystem = "full";
+ ProtectHome = true;
+ };
+ };
+
+ };
+
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/security/hologram-agent.nix b/infra/libkookie/nixpkgs/nixos/modules/services/security/hologram-agent.nix
new file mode 100644
index 000000000000..e37334b3cf5e
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/security/hologram-agent.nix
@@ -0,0 +1,58 @@
+{pkgs, config, lib, ...}:
+
+with lib;
+
+let
+ cfg = config.services.hologram-agent;
+
+ cfgFile = pkgs.writeText "hologram-agent.json" (builtins.toJSON {
+ host = cfg.dialAddress;
+ });
+in {
+ options = {
+ services.hologram-agent = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the Hologram agent for AWS instance credentials";
+ };
+
+ dialAddress = mkOption {
+ type = types.str;
+ default = "localhost:3100";
+ description = "Hologram server and port.";
+ };
+
+ httpPort = mkOption {
+ type = types.str;
+ default = "80";
+ description = "Port for metadata service to listen on.";
+ };
+
+ };
+ };
+
+ config = mkIf cfg.enable {
+ boot.kernelModules = [ "dummy" ];
+
+ networking.interfaces.dummy0.ipv4.addresses = [
+ { address = "169.254.169.254"; prefixLength = 32; }
+ ];
+
+ systemd.services.hologram-agent = {
+ description = "Provide EC2 instance credentials to machines outside of EC2";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ requires = [ "network-link-dummy0.service" "network-addresses-dummy0.service" ];
+ preStart = ''
+ /run/current-system/sw/bin/rm -fv /run/hologram.sock
+ '';
+ serviceConfig = {
+ ExecStart = "${pkgs.hologram}/bin/hologram-agent -debug -conf ${cfgFile} -port ${cfg.httpPort}";
+ };
+ };
+
+ };
+
+ meta.maintainers = with lib.maintainers; [ nand0p ];
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/security/hologram-server.nix b/infra/libkookie/nixpkgs/nixos/modules/services/security/hologram-server.nix
new file mode 100644
index 000000000000..4acf6ae0e218
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/security/hologram-server.nix
@@ -0,0 +1,130 @@
+{pkgs, config, lib, ...}:
+
+with lib;
+
+let
+ cfg = config.services.hologram-server;
+
+ cfgFile = pkgs.writeText "hologram-server.json" (builtins.toJSON {
+ ldap = {
+ host = cfg.ldapHost;
+ bind = {
+ dn = cfg.ldapBindDN;
+ password = cfg.ldapBindPassword;
+ };
+ insecureldap = cfg.ldapInsecure;
+ userattr = cfg.ldapUserAttr;
+ baseDN = cfg.ldapBaseDN;
+ enableldapRoles = cfg.enableLdapRoles;
+ roleAttr = cfg.roleAttr;
+ groupClassAttr = cfg.groupClassAttr;
+ };
+ aws = {
+ account = cfg.awsAccount;
+ defaultrole = cfg.awsDefaultRole;
+ };
+ stats = cfg.statsAddress;
+ listen = cfg.listenAddress;
+ cachetimeout = cfg.cacheTimeoutSeconds;
+ });
+in {
+ options = {
+ services.hologram-server = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the Hologram server for AWS instance credentials";
+ };
+
+ listenAddress = mkOption {
+ type = types.str;
+ default = "0.0.0.0:3100";
+ description = "Address and port to listen on";
+ };
+
+ ldapHost = mkOption {
+ type = types.str;
+ description = "Address of the LDAP server to use";
+ };
+
+ ldapInsecure = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to connect to LDAP over SSL or not";
+ };
+
+ ldapUserAttr = mkOption {
+ type = types.str;
+ default = "cn";
+ description = "The LDAP attribute for usernames";
+ };
+
+ ldapBaseDN = mkOption {
+ type = types.str;
+ description = "The base DN for your Hologram users";
+ };
+
+ ldapBindDN = mkOption {
+ type = types.str;
+ description = "DN of account to use to query the LDAP server";
+ };
+
+ ldapBindPassword = mkOption {
+ type = types.str;
+ description = "Password of account to use to query the LDAP server";
+ };
+
+ enableLdapRoles = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to assign user roles based on the user's LDAP group memberships";
+ };
+
+ groupClassAttr = mkOption {
+ type = types.str;
+ default = "groupOfNames";
+ description = "The objectclass attribute to search for groups when enableLdapRoles is true";
+ };
+
+ roleAttr = mkOption {
+ type = types.str;
+ default = "businessCategory";
+ description = "Which LDAP group attribute to search for authorized role ARNs";
+ };
+
+ awsAccount = mkOption {
+ type = types.str;
+ description = "AWS account number";
+ };
+
+ awsDefaultRole = mkOption {
+ type = types.str;
+ description = "AWS default role";
+ };
+
+ statsAddress = mkOption {
+ type = types.str;
+ default = "";
+ description = "Address of statsd server";
+ };
+
+ cacheTimeoutSeconds = mkOption {
+ type = types.int;
+ default = 3600;
+ description = "How often (in seconds) to refresh the LDAP cache";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.hologram-server = {
+ description = "Provide EC2 instance credentials to machines outside of EC2";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ ExecStart = "${pkgs.hologram}/bin/hologram-server --debug --conf ${cfgFile}";
+ };
+ };
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/security/munge.nix b/infra/libkookie/nixpkgs/nixos/modules/services/security/munge.nix
new file mode 100644
index 000000000000..891788864710
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/security/munge.nix
@@ -0,0 +1,68 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.munge;
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.munge = {
+ enable = mkEnableOption "munge service";
+
+ password = mkOption {
+ default = "/etc/munge/munge.key";
+ type = types.path;
+ description = ''
+ The path to a daemon's secret key.
+ '';
+ };
+
+ };
+
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [ pkgs.munge ];
+
+ users.users.munge = {
+ description = "Munge daemon user";
+ isSystemUser = true;
+ group = "munge";
+ };
+
+ users.groups.munge = {};
+
+ systemd.services.munged = {
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ path = [ pkgs.munge pkgs.coreutils ];
+
+ serviceConfig = {
+ ExecStartPre = "+${pkgs.coreutils}/bin/chmod 0400 ${cfg.password}";
+ ExecStart = "${pkgs.munge}/bin/munged --syslog --key-file ${cfg.password}";
+ PIDFile = "/run/munge/munged.pid";
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ User = "munge";
+ Group = "munge";
+ StateDirectory = "munge";
+ StateDirectoryMode = "0711";
+ RuntimeDirectory = "munge";
+ };
+
+ };
+
+ };
+
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/security/nginx-sso.nix b/infra/libkookie/nixpkgs/nixos/modules/services/security/nginx-sso.nix
new file mode 100644
index 000000000000..50d250fc4d76
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/security/nginx-sso.nix
@@ -0,0 +1,67 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.nginx.sso;
+ pkg = getBin cfg.package;
+ configYml = pkgs.writeText "nginx-sso.yml" (builtins.toJSON cfg.configuration);
+in {
+ options.services.nginx.sso = {
+ enable = mkEnableOption "nginx-sso service";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.nginx-sso;
+ defaultText = "pkgs.nginx-sso";
+ description = ''
+ The nginx-sso package that should be used.
+ '';
+ };
+
+ configuration = mkOption {
+ type = types.attrsOf types.unspecified;
+ default = {};
+ example = literalExample ''
+ {
+ listen = { addr = "127.0.0.1"; port = 8080; };
+
+ providers.token.tokens = {
+ myuser = "MyToken";
+ };
+
+ acl = {
+ rule_sets = [
+ {
+ rules = [ { field = "x-application"; equals = "MyApp"; } ];
+ allow = [ "myuser" ];
+ }
+ ];
+ };
+ }
+ '';
+ description = ''
+ nginx-sso configuration
+ (<link xlink:href="https://github.com/Luzifer/nginx-sso/wiki/Main-Configuration">documentation</link>)
+ as a Nix attribute set.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.nginx-sso = {
+ description = "Nginx SSO Backend";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ ExecStart = ''
+ ${pkg}/bin/nginx-sso \
+ --config ${configYml} \
+ --frontend-dir ${pkg}/share/frontend
+ '';
+ Restart = "always";
+ DynamicUser = true;
+ };
+ };
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/security/oauth2_proxy.nix b/infra/libkookie/nixpkgs/nixos/modules/services/security/oauth2_proxy.nix
new file mode 100644
index 000000000000..486f3ab05386
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/security/oauth2_proxy.nix
@@ -0,0 +1,588 @@
+# NixOS module for oauth2_proxy.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+ cfg = config.services.oauth2_proxy;
+
+ # oauth2_proxy provides many options that are only relevant if you are using
+ # a certain provider. This set maps from provider name to a function that
+ # takes the configuration and returns a string that can be inserted into the
+ # command-line to launch oauth2_proxy.
+ providerSpecificOptions = {
+ azure = cfg: {
+ azure-tenant = cfg.azure.tenant;
+ resource = cfg.azure.resource;
+ };
+
+ github = cfg: { github = {
+ inherit (cfg.github) org team;
+ }; };
+
+ google = cfg: { google = with cfg.google; optionalAttrs (groups != []) {
+ admin-email = adminEmail;
+ service-account = serviceAccountJSON;
+ group = groups;
+ }; };
+ };
+
+ authenticatedEmailsFile = pkgs.writeText "authenticated-emails" cfg.email.addresses;
+
+ getProviderOptions = cfg: provider: providerSpecificOptions.${provider} or (_: {}) cfg;
+
+ allConfig = with cfg; {
+ inherit (cfg) provider scope upstream;
+ approval-prompt = approvalPrompt;
+ basic-auth-password = basicAuthPassword;
+ client-id = clientID;
+ client-secret = clientSecret;
+ custom-templates-dir = customTemplatesDir;
+ email-domain = email.domains;
+ http-address = httpAddress;
+ login-url = loginURL;
+ pass-access-token = passAccessToken;
+ pass-basic-auth = passBasicAuth;
+ pass-host-header = passHostHeader;
+ reverse-proxy = reverseProxy;
+ proxy-prefix = proxyPrefix;
+ profile-url = profileURL;
+ redeem-url = redeemURL;
+ redirect-url = redirectURL;
+ request-logging = requestLogging;
+ skip-auth-regex = skipAuthRegexes;
+ signature-key = signatureKey;
+ validate-url = validateURL;
+ htpasswd-file = htpasswd.file;
+ cookie = {
+ inherit (cookie) domain secure expire name secret refresh;
+ httponly = cookie.httpOnly;
+ };
+ set-xauthrequest = setXauthrequest;
+ } // lib.optionalAttrs (cfg.email.addresses != null) {
+ authenticated-emails-file = authenticatedEmailsFile;
+ } // lib.optionalAttrs (cfg.passBasicAuth) {
+ basic-auth-password = cfg.basicAuthPassword;
+ } // lib.optionalAttrs (cfg.htpasswd.file != null) {
+ display-htpasswd-file = cfg.htpasswd.displayForm;
+ } // lib.optionalAttrs tls.enable {
+ tls-cert-file = tls.certificate;
+ tls-key-file = tls.key;
+ https-address = tls.httpsAddress;
+ } // (getProviderOptions cfg cfg.provider) // cfg.extraConfig;
+
+ mapConfig = key: attr:
+ if attr != null && attr != [] then (
+ if isDerivation attr then mapConfig key (toString attr) else
+ if (builtins.typeOf attr) == "set" then concatStringsSep " "
+ (mapAttrsToList (name: value: mapConfig (key + "-" + name) value) attr) else
+ if (builtins.typeOf attr) == "list" then concatMapStringsSep " " (mapConfig key) attr else
+ if (builtins.typeOf attr) == "bool" then "--${key}=${boolToString attr}" else
+ if (builtins.typeOf attr) == "string" then "--${key}='${attr}'" else
+ "--${key}=${toString attr}")
+ else "";
+
+ configString = concatStringsSep " " (mapAttrsToList mapConfig allConfig);
+in
+{
+ options.services.oauth2_proxy = {
+ enable = mkEnableOption "oauth2_proxy";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.oauth2_proxy;
+ defaultText = "pkgs.oauth2_proxy";
+ description = ''
+ The package that provides oauth2_proxy.
+ '';
+ };
+
+ ##############################################
+ # PROVIDER configuration
+ # Taken from: https://github.com/oauth2-proxy/oauth2-proxy/blob/master/providers/providers.go
+ provider = mkOption {
+ type = types.enum [
+ "google"
+ "azure"
+ "facebook"
+ "github"
+ "keycloak"
+ "gitlab"
+ "linkedin"
+ "login.gov"
+ "bitbucket"
+ "nextcloud"
+ "digitalocean"
+ "oidc"
+ ];
+ default = "google";
+ description = ''
+ OAuth provider.
+ '';
+ };
+
+ approvalPrompt = mkOption {
+ type = types.enum ["force" "auto"];
+ default = "force";
+ description = ''
+ OAuth approval_prompt.
+ '';
+ };
+
+ clientID = mkOption {
+ type = types.nullOr types.str;
+ description = ''
+ The OAuth Client ID.
+ '';
+ example = "123456.apps.googleusercontent.com";
+ };
+
+ clientSecret = mkOption {
+ type = types.nullOr types.str;
+ description = ''
+ The OAuth Client Secret.
+ '';
+ };
+
+ skipAuthRegexes = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ Skip authentication for requests matching any of these regular
+ expressions.
+ '';
+ };
+
+ # XXX: Not clear whether these two options are mutually exclusive or not.
+ email = {
+ domains = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ Authenticate emails with the specified domains. Use
+ <literal>*</literal> to authenticate any email.
+ '';
+ };
+
+ addresses = mkOption {
+ type = types.nullOr types.lines;
+ default = null;
+ description = ''
+ Line-separated email addresses that are allowed to authenticate.
+ '';
+ };
+ };
+
+ loginURL = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Authentication endpoint.
+
+ You only need to set this if you are using a self-hosted provider (e.g.
+ Github Enterprise). If you're using a publicly hosted provider
+ (e.g github.com), then the default works.
+ '';
+ example = "https://provider.example.com/oauth/authorize";
+ };
+
+ redeemURL = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Token redemption endpoint.
+
+ You only need to set this if you are using a self-hosted provider (e.g.
+ Github Enterprise). If you're using a publicly hosted provider
+ (e.g github.com), then the default works.
+ '';
+ example = "https://provider.example.com/oauth/token";
+ };
+
+ validateURL = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Access token validation endpoint.
+
+ You only need to set this if you are using a self-hosted provider (e.g.
+ Github Enterprise). If you're using a publicly hosted provider
+ (e.g github.com), then the default works.
+ '';
+ example = "https://provider.example.com/user/emails";
+ };
+
+ redirectURL = mkOption {
+ # XXX: jml suspects this is always necessary, but the command-line
+ # doesn't require it so making it optional.
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The OAuth2 redirect URL.
+ '';
+ example = "https://internalapp.yourcompany.com/oauth2/callback";
+ };
+
+ azure = {
+ tenant = mkOption {
+ type = types.str;
+ default = "common";
+ description = ''
+ Go to a tenant-specific or common (tenant-independent) endpoint.
+ '';
+ };
+
+ resource = mkOption {
+ type = types.str;
+ description = ''
+ The resource that is protected.
+ '';
+ };
+ };
+
+ google = {
+ adminEmail = mkOption {
+ type = types.str;
+ description = ''
+ The Google Admin to impersonate for API calls.
+
+ Only users with access to the Admin APIs can access the Admin SDK
+ Directory API, thus the service account needs to impersonate one of
+ those users to access the Admin SDK Directory API.
+
+ See <link xlink:href="https://developers.google.com/admin-sdk/directory/v1/guides/delegation#delegate_domain-wide_authority_to_your_service_account" />.
+ '';
+ };
+
+ groups = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ Restrict logins to members of these Google groups.
+ '';
+ };
+
+ serviceAccountJSON = mkOption {
+ type = types.path;
+ description = ''
+ The path to the service account JSON credentials.
+ '';
+ };
+ };
+
+ github = {
+ org = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Restrict logins to members of this organisation.
+ '';
+ };
+
+ team = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Restrict logins to members of this team.
+ '';
+ };
+ };
+
+
+ ####################################################
+ # UPSTREAM Configuration
+ upstream = mkOption {
+ type = with types; coercedTo str (x: [x]) (listOf str);
+ default = [];
+ description = ''
+ The http url(s) of the upstream endpoint or <literal>file://</literal>
+ paths for static files. Routing is based on the path.
+ '';
+ };
+
+ passAccessToken = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Pass OAuth access_token to upstream via X-Forwarded-Access-Token header.
+ '';
+ };
+
+ passBasicAuth = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream.
+ '';
+ };
+
+ basicAuthPassword = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The password to set when passing the HTTP Basic Auth header.
+ '';
+ };
+
+ passHostHeader = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Pass the request Host Header to upstream.
+ '';
+ };
+
+ signatureKey = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ GAP-Signature request signature key.
+ '';
+ example = "sha1:secret0";
+ };
+
+ cookie = {
+ domain = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Optional cookie domains to force cookies to (ie: `.yourcompany.com`).
+ The longest domain matching the request's host will be used (or the shortest
+ cookie domain if there is no match).
+ '';
+ example = ".yourcompany.com";
+ };
+
+ expire = mkOption {
+ type = types.str;
+ default = "168h0m0s";
+ description = ''
+ Expire timeframe for cookie.
+ '';
+ };
+
+ httpOnly = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Set HttpOnly cookie flag.
+ '';
+ };
+
+ name = mkOption {
+ type = types.str;
+ default = "_oauth2_proxy";
+ description = ''
+ The name of the cookie that the oauth_proxy creates.
+ '';
+ };
+
+ refresh = mkOption {
+ # XXX: Unclear what the behavior is when this is not specified.
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Refresh the cookie after this duration; 0 to disable.
+ '';
+ example = "168h0m0s";
+ };
+
+ secret = mkOption {
+ type = types.nullOr types.str;
+ description = ''
+ The seed string for secure cookies.
+ '';
+ };
+
+ secure = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Set secure (HTTPS) cookie flag.
+ '';
+ };
+ };
+
+ ####################################################
+ # OAUTH2 PROXY configuration
+
+ httpAddress = mkOption {
+ type = types.str;
+ default = "http://127.0.0.1:4180";
+ description = ''
+ HTTPS listening address. This module does not expose the port by
+ default. If you want this URL to be accessible to other machines, please
+ add the port to <literal>networking.firewall.allowedTCPPorts</literal>.
+ '';
+ };
+
+ htpasswd = {
+ file = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ Additionally authenticate against a htpasswd file. Entries must be
+ created with <literal>htpasswd -s</literal> for SHA encryption.
+ '';
+ };
+
+ displayForm = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Display username / password login form if an htpasswd file is provided.
+ '';
+ };
+ };
+
+ customTemplatesDir = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ Path to custom HTML templates.
+ '';
+ };
+
+ reverseProxy = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ In case when running behind a reverse proxy, controls whether headers
+ like <literal>X-Real-Ip</literal> are accepted. Usage behind a reverse
+ proxy will require this flag to be set to avoid logging the reverse
+ proxy IP address.
+ '';
+ };
+
+ proxyPrefix = mkOption {
+ type = types.str;
+ default = "/oauth2";
+ description = ''
+ The url root path that this proxy should be nested under.
+ '';
+ };
+
+ tls = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to serve over TLS.
+ '';
+ };
+
+ certificate = mkOption {
+ type = types.path;
+ description = ''
+ Path to certificate file.
+ '';
+ };
+
+ key = mkOption {
+ type = types.path;
+ description = ''
+ Path to private key file.
+ '';
+ };
+
+ httpsAddress = mkOption {
+ type = types.str;
+ default = ":443";
+ description = ''
+ <literal>addr:port</literal> to listen on for HTTPS clients.
+
+ Remember to add <literal>port</literal> to
+ <literal>allowedTCPPorts</literal> if you want other machines to be
+ able to connect to it.
+ '';
+ };
+ };
+
+ requestLogging = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Log requests to stdout.
+ '';
+ };
+
+ ####################################################
+ # UNKNOWN
+
+ # XXX: Is this mandatory? Is it part of another group? Is it part of the provider specification?
+ scope = mkOption {
+ # XXX: jml suspects this is always necessary, but the command-line
+ # doesn't require it so making it optional.
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ OAuth scope specification.
+ '';
+ };
+
+ profileURL = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Profile access endpoint.
+ '';
+ };
+
+ setXauthrequest = mkOption {
+ type = types.nullOr types.bool;
+ default = false;
+ description = ''
+ Set X-Auth-Request-User and X-Auth-Request-Email response headers (useful in Nginx auth_request mode). Setting this to 'null' means using the upstream default (false).
+ '';
+ };
+
+ extraConfig = mkOption {
+ default = {};
+ description = ''
+ Extra config to pass to oauth2-proxy.
+ '';
+ };
+
+ keyFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ oauth2-proxy allows passing sensitive configuration via environment variables.
+ Make a file that contains lines like
+ OAUTH2_PROXY_CLIENT_SECRET=asdfasdfasdf.apps.googleuserscontent.com
+ and specify the path here.
+ '';
+ example = "/run/keys/oauth2_proxy";
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+
+ services.oauth2_proxy = mkIf (cfg.keyFile != null) {
+ clientID = mkDefault null;
+ clientSecret = mkDefault null;
+ cookie.secret = mkDefault null;
+ };
+
+ users.users.oauth2_proxy = {
+ description = "OAuth2 Proxy";
+ isSystemUser = true;
+ };
+
+ systemd.services.oauth2_proxy = {
+ description = "OAuth2 Proxy";
+ path = [ cfg.package ];
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ serviceConfig = {
+ User = "oauth2_proxy";
+ Restart = "always";
+ ExecStart = "${cfg.package}/bin/oauth2-proxy ${configString}";
+ EnvironmentFile = mkIf (cfg.keyFile != null) cfg.keyFile;
+ };
+ };
+
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/security/oauth2_proxy_nginx.nix b/infra/libkookie/nixpkgs/nixos/modules/services/security/oauth2_proxy_nginx.nix
new file mode 100644
index 000000000000..be6734f439f3
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/security/oauth2_proxy_nginx.nix
@@ -0,0 +1,64 @@
+{ config, lib, ... }:
+with lib;
+let
+ cfg = config.services.oauth2_proxy.nginx;
+in
+{
+ options.services.oauth2_proxy.nginx = {
+ proxy = mkOption {
+ type = types.str;
+ default = config.services.oauth2_proxy.httpAddress;
+ description = ''
+ The address of the reverse proxy endpoint for oauth2_proxy
+ '';
+ };
+ virtualHosts = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ A list of nginx virtual hosts to put behind the oauth2 proxy
+ '';
+ };
+ };
+ config.services.oauth2_proxy = mkIf (cfg.virtualHosts != [] && (hasPrefix "127.0.0.1:" cfg.proxy)) {
+ enable = true;
+ };
+ config.services.nginx = mkMerge ((optional (cfg.virtualHosts != []) {
+ recommendedProxySettings = true; # needed because duplicate headers
+ }) ++ (map (vhost: {
+ virtualHosts.${vhost} = {
+ locations."/oauth2/" = {
+ proxyPass = cfg.proxy;
+ extraConfig = ''
+ proxy_set_header X-Scheme $scheme;
+ proxy_set_header X-Auth-Request-Redirect $request_uri;
+ '';
+ };
+ locations."/oauth2/auth" = {
+ proxyPass = cfg.proxy;
+ extraConfig = ''
+ proxy_set_header X-Scheme $scheme;
+ # nginx auth_request includes headers but not body
+ proxy_set_header Content-Length "";
+ proxy_pass_request_body off;
+ '';
+ };
+ locations."/".extraConfig = ''
+ auth_request /oauth2/auth;
+ error_page 401 = /oauth2/sign_in;
+
+ # pass information via X-User and X-Email headers to backend,
+ # requires running with --set-xauthrequest flag
+ auth_request_set $user $upstream_http_x_auth_request_user;
+ auth_request_set $email $upstream_http_x_auth_request_email;
+ proxy_set_header X-User $user;
+ proxy_set_header X-Email $email;
+
+ # if you enabled --cookie-refresh, this is needed for it to work with auth_request
+ auth_request_set $auth_cookie $upstream_http_set_cookie;
+ add_header Set-Cookie $auth_cookie;
+ '';
+
+ };
+ }) cfg.virtualHosts));
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/security/physlock.nix b/infra/libkookie/nixpkgs/nixos/modules/services/security/physlock.nix
new file mode 100644
index 000000000000..da5c22a90a09
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/security/physlock.nix
@@ -0,0 +1,137 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.physlock;
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.physlock = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable the <command>physlock</command> screen locking mechanism.
+
+ Enable this and then run <command>systemctl start physlock</command>
+ to securely lock the screen.
+
+ This will switch to a new virtual terminal, turn off console
+ switching and disable SysRq mechanism (when
+ <option>services.physlock.disableSysRq</option> is set)
+ until the root or user password is given.
+ '';
+ };
+
+ allowAnyUser = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to allow any user to lock the screen. This will install a
+ setuid wrapper to allow any user to start physlock as root, which
+ is a minor security risk. Call the physlock binary to use this instead
+ of using the systemd service.
+
+ Note that you might need to relog to have the correct binary in your
+ PATH upon changing this option.
+ '';
+ };
+
+ disableSysRq = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to disable SysRq when locked with physlock.
+ '';
+ };
+
+ lockMessage = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ Message to show on physlock login terminal.
+ '';
+ };
+
+ lockOn = {
+
+ suspend = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to lock screen with physlock just before suspend.
+ '';
+ };
+
+ hibernate = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to lock screen with physlock just before hibernate.
+ '';
+ };
+
+ extraTargets = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "display-manager.service" ];
+ description = ''
+ Other targets to lock the screen just before.
+
+ Useful if you want to e.g. both autologin to X11 so that
+ your <filename>~/.xsession</filename> gets executed and
+ still to have the screen locked so that the system can be
+ booted relatively unattended.
+ '';
+ };
+
+ };
+
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable (mkMerge [
+ {
+
+ # for physlock -l and physlock -L
+ environment.systemPackages = [ pkgs.physlock ];
+
+ systemd.services.physlock = {
+ enable = true;
+ description = "Physlock";
+ wantedBy = optional cfg.lockOn.suspend "suspend.target"
+ ++ optional cfg.lockOn.hibernate "hibernate.target"
+ ++ cfg.lockOn.extraTargets;
+ before = optional cfg.lockOn.suspend "systemd-suspend.service"
+ ++ optional cfg.lockOn.hibernate "systemd-hibernate.service"
+ ++ optional (cfg.lockOn.hibernate || cfg.lockOn.suspend) "systemd-suspend-then-hibernate.service"
+ ++ cfg.lockOn.extraTargets;
+ serviceConfig = {
+ Type = "forking";
+ ExecStart = "${pkgs.physlock}/bin/physlock -d${optionalString cfg.disableSysRq "s"}${optionalString (cfg.lockMessage != "") " -p \"${cfg.lockMessage}\""}";
+ };
+ };
+
+ security.pam.services.physlock = {};
+
+ }
+
+ (mkIf cfg.allowAnyUser {
+
+ security.wrappers.physlock = { source = "${pkgs.physlock}/bin/physlock"; user = "root"; };
+
+ })
+ ]);
+
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/security/privacyidea.nix b/infra/libkookie/nixpkgs/nixos/modules/services/security/privacyidea.nix
new file mode 100644
index 000000000000..c2988858e56c
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/security/privacyidea.nix
@@ -0,0 +1,278 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.privacyidea;
+
+ uwsgi = pkgs.uwsgi.override { plugins = [ "python3" ]; };
+ python = uwsgi.python3;
+ penv = python.withPackages (ps: [ ps.privacyidea ]);
+ logCfg = pkgs.writeText "privacyidea-log.cfg" ''
+ [formatters]
+ keys=detail
+
+ [handlers]
+ keys=stream
+
+ [formatter_detail]
+ class=privacyidea.lib.log.SecureFormatter
+ format=[%(asctime)s][%(process)d][%(thread)d][%(levelname)s][%(name)s:%(lineno)d] %(message)s
+
+ [handler_stream]
+ class=StreamHandler
+ level=NOTSET
+ formatter=detail
+ args=(sys.stdout,)
+
+ [loggers]
+ keys=root,privacyidea
+
+ [logger_privacyidea]
+ handlers=stream
+ qualname=privacyidea
+ level=INFO
+
+ [logger_root]
+ handlers=stream
+ level=ERROR
+ '';
+
+ piCfgFile = pkgs.writeText "privacyidea.cfg" ''
+ SUPERUSER_REALM = [ '${concatStringsSep "', '" cfg.superuserRealm}' ]
+ SQLALCHEMY_DATABASE_URI = 'postgresql:///privacyidea'
+ SECRET_KEY = '${cfg.secretKey}'
+ PI_PEPPER = '${cfg.pepper}'
+ PI_ENCFILE = '${cfg.encFile}'
+ PI_AUDIT_KEY_PRIVATE = '${cfg.auditKeyPrivate}'
+ PI_AUDIT_KEY_PUBLIC = '${cfg.auditKeyPublic}'
+ PI_LOGCONFIG = '${logCfg}'
+ ${cfg.extraConfig}
+ '';
+
+in
+
+{
+ options = {
+ services.privacyidea = {
+ enable = mkEnableOption "PrivacyIDEA";
+
+ stateDir = mkOption {
+ type = types.str;
+ default = "/var/lib/privacyidea";
+ description = ''
+ Directory where all PrivacyIDEA files will be placed by default.
+ '';
+ };
+
+ superuserRealm = mkOption {
+ type = types.listOf types.str;
+ default = [ "super" "administrators" ];
+ description = ''
+ The realm where users are allowed to login as administrators.
+ '';
+ };
+
+ secretKey = mkOption {
+ type = types.str;
+ example = "t0p s3cr3t";
+ description = ''
+ This is used to encrypt the auth_token.
+ '';
+ };
+
+ pepper = mkOption {
+ type = types.str;
+ example = "Never know...";
+ description = ''
+ This is used to encrypt the admin passwords.
+ '';
+ };
+
+ encFile = mkOption {
+ type = types.str;
+ default = "${cfg.stateDir}/enckey";
+ description = ''
+ This is used to encrypt the token data and token passwords
+ '';
+ };
+
+ auditKeyPrivate = mkOption {
+ type = types.str;
+ default = "${cfg.stateDir}/private.pem";
+ description = ''
+ Private Key for signing the audit log.
+ '';
+ };
+
+ auditKeyPublic = mkOption {
+ type = types.str;
+ default = "${cfg.stateDir}/public.pem";
+ description = ''
+ Public key for checking signatures of the audit log.
+ '';
+ };
+
+ adminPasswordFile = mkOption {
+ type = types.path;
+ description = "File containing password for the admin user";
+ };
+
+ adminEmail = mkOption {
+ type = types.str;
+ example = "admin@example.com";
+ description = "Mail address for the admin user";
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra configuration options for pi.cfg.
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "privacyidea";
+ description = "User account under which PrivacyIDEA runs.";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "privacyidea";
+ description = "Group account under which PrivacyIDEA runs.";
+ };
+
+ ldap-proxy = {
+ enable = mkEnableOption "PrivacyIDEA LDAP Proxy";
+
+ configFile = mkOption {
+ type = types.path;
+ default = "";
+ description = ''
+ Path to PrivacyIDEA LDAP Proxy configuration (proxy.ini).
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "pi-ldap-proxy";
+ description = "User account under which PrivacyIDEA LDAP proxy runs.";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "pi-ldap-proxy";
+ description = "Group account under which PrivacyIDEA LDAP proxy runs.";
+ };
+ };
+ };
+ };
+
+ config = mkMerge [
+
+ (mkIf cfg.enable {
+
+ environment.systemPackages = [ python.pkgs.privacyidea ];
+
+ services.postgresql.enable = mkDefault true;
+
+ systemd.services.privacyidea = let
+ piuwsgi = pkgs.writeText "uwsgi.json" (builtins.toJSON {
+ uwsgi = {
+ plugins = [ "python3" ];
+ pythonpath = "${penv}/${uwsgi.python3.sitePackages}";
+ socket = "/run/privacyidea/socket";
+ uid = cfg.user;
+ gid = cfg.group;
+ chmod-socket = 770;
+ chown-socket = "${cfg.user}:nginx";
+ chdir = cfg.stateDir;
+ wsgi-file = "${penv}/etc/privacyidea/privacyideaapp.wsgi";
+ processes = 4;
+ harakiri = 60;
+ reload-mercy = 8;
+ stats = "/run/privacyidea/stats.socket";
+ max-requests = 2000;
+ limit-as = 1024;
+ reload-on-as = 512;
+ reload-on-rss = 256;
+ no-orphans = true;
+ vacuum = true;
+ };
+ });
+ in {
+ wantedBy = [ "multi-user.target" ];
+ after = [ "postgresql.service" ];
+ path = with pkgs; [ openssl ];
+ environment.PRIVACYIDEA_CONFIGFILE = piCfgFile;
+ preStart = let
+ pi-manage = "${pkgs.sudo}/bin/sudo -u privacyidea -HE ${penv}/bin/pi-manage";
+ pgsu = config.services.postgresql.superUser;
+ psql = config.services.postgresql.package;
+ in ''
+ mkdir -p ${cfg.stateDir} /run/privacyidea
+ chown ${cfg.user}:${cfg.group} -R ${cfg.stateDir} /run/privacyidea
+ if ! test -e "${cfg.stateDir}/db-created"; then
+ ${pkgs.sudo}/bin/sudo -u ${pgsu} ${psql}/bin/createuser --no-superuser --no-createdb --no-createrole ${cfg.user}
+ ${pkgs.sudo}/bin/sudo -u ${pgsu} ${psql}/bin/createdb --owner ${cfg.user} privacyidea
+ ${pi-manage} create_enckey
+ ${pi-manage} create_audit_keys
+ ${pi-manage} createdb
+ ${pi-manage} admin add admin -e ${cfg.adminEmail} -p "$(cat ${cfg.adminPasswordFile})"
+ ${pi-manage} db stamp head -d ${penv}/lib/privacyidea/migrations
+ touch "${cfg.stateDir}/db-created"
+ chmod g+r "${cfg.stateDir}/enckey" "${cfg.stateDir}/private.pem"
+ fi
+ ${pi-manage} db upgrade -d ${penv}/lib/privacyidea/migrations
+ '';
+ serviceConfig = {
+ Type = "notify";
+ ExecStart = "${uwsgi}/bin/uwsgi --json ${piuwsgi}";
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ ExecStop = "${pkgs.coreutils}/bin/kill -INT $MAINPID";
+ NotifyAccess = "main";
+ KillSignal = "SIGQUIT";
+ };
+ };
+
+ users.users.privacyidea = mkIf (cfg.user == "privacyidea") {
+ group = cfg.group;
+ };
+
+ users.groups.privacyidea = mkIf (cfg.group == "privacyidea") {};
+ })
+
+ (mkIf cfg.ldap-proxy.enable {
+
+ systemd.services.privacyidea-ldap-proxy = let
+ ldap-proxy-env = pkgs.python2.withPackages (ps: [ ps.privacyidea-ldap-proxy ]);
+ in {
+ description = "privacyIDEA LDAP proxy";
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ User = cfg.ldap-proxy.user;
+ Group = cfg.ldap-proxy.group;
+ ExecStart = ''
+ ${ldap-proxy-env}/bin/twistd \
+ --nodaemon \
+ --pidfile= \
+ -u ${cfg.ldap-proxy.user} \
+ -g ${cfg.ldap-proxy.group} \
+ ldap-proxy \
+ -c ${cfg.ldap-proxy.configFile}
+ '';
+ Restart = "always";
+ };
+ };
+
+ users.users.pi-ldap-proxy = mkIf (cfg.ldap-proxy.user == "pi-ldap-proxy") {
+ group = cfg.ldap-proxy.group;
+ };
+
+ users.groups.pi-ldap-proxy = mkIf (cfg.ldap-proxy.group == "pi-ldap-proxy") {};
+ })
+ ];
+
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/security/shibboleth-sp.nix b/infra/libkookie/nixpkgs/nixos/modules/services/security/shibboleth-sp.nix
new file mode 100644
index 000000000000..5908f727d535
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/security/shibboleth-sp.nix
@@ -0,0 +1,75 @@
+{pkgs, config, lib, ...}:
+
+with lib;
+let
+ cfg = config.services.shibboleth-sp;
+in {
+ options = {
+ services.shibboleth-sp = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the shibboleth service";
+ };
+
+ configFile = mkOption {
+ type = types.path;
+ example = "${pkgs.shibboleth-sp}/etc/shibboleth/shibboleth2.xml";
+ description = "Path to shibboleth config file";
+ };
+
+ fastcgi.enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to include the shibauthorizer and shibresponder FastCGI processes";
+ };
+
+ fastcgi.shibAuthorizerPort = mkOption {
+ type = types.int;
+ default = 9100;
+ description = "Port for shibauthorizer FastCGI proccess to bind to";
+ };
+
+ fastcgi.shibResponderPort = mkOption {
+ type = types.int;
+ default = 9101;
+ description = "Port for shibauthorizer FastCGI proccess to bind to";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.shibboleth-sp = {
+ description = "Provides SSO and federation for web applications";
+ after = lib.optionals cfg.fastcgi.enable [ "shibresponder.service" "shibauthorizer.service" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ ExecStart = "${pkgs.shibboleth-sp}/bin/shibd -F -d ${pkgs.shibboleth-sp} -c ${cfg.configFile}";
+ };
+ };
+
+ systemd.services.shibresponder = mkIf cfg.fastcgi.enable {
+ description = "Provides SSO through Shibboleth via FastCGI";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ path = [ "${pkgs.spawn_fcgi}" ];
+ environment.SHIBSP_CONFIG = "${cfg.configFile}";
+ serviceConfig = {
+ ExecStart = "${pkgs.spawn_fcgi}/bin/spawn-fcgi -n -p ${toString cfg.fastcgi.shibResponderPort} ${pkgs.shibboleth-sp}/lib/shibboleth/shibresponder";
+ };
+ };
+
+ systemd.services.shibauthorizer = mkIf cfg.fastcgi.enable {
+ description = "Provides SSO through Shibboleth via FastCGI";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ path = [ "${pkgs.spawn_fcgi}" ];
+ environment.SHIBSP_CONFIG = "${cfg.configFile}";
+ serviceConfig = {
+ ExecStart = "${pkgs.spawn_fcgi}/bin/spawn-fcgi -n -p ${toString cfg.fastcgi.shibAuthorizerPort} ${pkgs.shibboleth-sp}/lib/shibboleth/shibauthorizer";
+ };
+ };
+ };
+
+ meta.maintainers = with lib.maintainers; [ jammerful ];
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/security/sks.nix b/infra/libkookie/nixpkgs/nixos/modules/services/security/sks.nix
new file mode 100644
index 000000000000..a91060dc659a
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/security/sks.nix
@@ -0,0 +1,146 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.sks;
+ sksPkg = cfg.package;
+ dbConfig = pkgs.writeText "DB_CONFIG" ''
+ ${cfg.extraDbConfig}
+ '';
+
+in {
+ meta.maintainers = with maintainers; [ primeos calbrecht jcumming ];
+
+ options = {
+
+ services.sks = {
+
+ enable = mkEnableOption ''
+ SKS (synchronizing key server for OpenPGP) and start the database
+ server. You need to create "''${dataDir}/dump/*.gpg" for the initial
+ import'';
+
+ package = mkOption {
+ default = pkgs.sks;
+ defaultText = "pkgs.sks";
+ type = types.package;
+ description = "Which SKS derivation to use.";
+ };
+
+ dataDir = mkOption {
+ type = types.path;
+ default = "/var/db/sks";
+ example = "/var/lib/sks";
+ # TODO: The default might change to "/var/lib/sks" as this is more
+ # common. There's also https://github.com/NixOS/nixpkgs/issues/26256
+ # and "/var/db" is not FHS compliant (seems to come from BSD).
+ description = ''
+ Data directory (-basedir) for SKS, where the database and all
+ configuration files are located (e.g. KDB, PTree, membership and
+ sksconf).
+ '';
+ };
+
+ extraDbConfig = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ Set contents of the files "KDB/DB_CONFIG" and "PTree/DB_CONFIG" within
+ the ''${dataDir} directory. This is used to configure options for the
+ database for the sks key server.
+
+ Documentation of available options are available in the file named
+ "sampleConfig/DB_CONFIG" in the following repository:
+ https://bitbucket.org/skskeyserver/sks-keyserver/src
+ '';
+ };
+
+ hkpAddress = mkOption {
+ default = [ "127.0.0.1" "::1" ];
+ type = types.listOf types.str;
+ description = ''
+ Domain names, IPv4 and/or IPv6 addresses to listen on for HKP
+ requests.
+ '';
+ };
+
+ hkpPort = mkOption {
+ default = 11371;
+ type = types.ints.u16;
+ description = "HKP port to listen on.";
+ };
+
+ webroot = mkOption {
+ type = types.nullOr types.path;
+ default = "${sksPkg.webSamples}/OpenPKG";
+ defaultText = "\${pkgs.sks.webSamples}/OpenPKG";
+ description = ''
+ Source directory (will be symlinked, if not null) for the files the
+ built-in webserver should serve. SKS (''${pkgs.sks.webSamples})
+ provides the following examples: "HTML5", "OpenPKG", and "XHTML+ES".
+ The index file can be named index.html, index.htm, index.xhtm, or
+ index.xhtml. Files with the extensions .css, .es, .js, .jpg, .jpeg,
+ .png, or .gif are supported. Subdirectories and filenames with
+ anything other than alphanumeric characters and the '.' character
+ will be ignored.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ users = {
+ users.sks = {
+ isSystemUser = true;
+ description = "SKS user";
+ home = cfg.dataDir;
+ createHome = true;
+ group = "sks";
+ useDefaultShell = true;
+ packages = [ sksPkg pkgs.db ];
+ };
+ groups.sks = { };
+ };
+
+ systemd.services = let
+ hkpAddress = "'" + (builtins.concatStringsSep " " cfg.hkpAddress) + "'" ;
+ hkpPort = builtins.toString cfg.hkpPort;
+ in {
+ sks-db = {
+ description = "SKS database server";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ preStart = ''
+ ${lib.optionalString (cfg.webroot != null)
+ "ln -sfT \"${cfg.webroot}\" web"}
+ mkdir -p dump
+ ${sksPkg}/bin/sks build dump/*.gpg -n 10 -cache 100 || true #*/
+ ${sksPkg}/bin/sks cleandb || true
+ ${sksPkg}/bin/sks pbuild -cache 20 -ptree_cache 70 || true
+ # Check that both database configs are symlinks before overwriting them
+ # TODO: The initial build will be without DB_CONFIG, but this will
+ # hopefully not cause any significant problems. It might be better to
+ # create both directories manually but we have to check that this does
+ # not affect the initial build of the DB.
+ for CONFIG_FILE in KDB/DB_CONFIG PTree/DB_CONFIG; do
+ if [ -e $CONFIG_FILE ] && [ ! -L $CONFIG_FILE ]; then
+ echo "$CONFIG_FILE exists but is not a symlink." >&2
+ echo "Please remove $PWD/$CONFIG_FILE manually to continue." >&2
+ exit 1
+ fi
+ ln -sf ${dbConfig} $CONFIG_FILE
+ done
+ '';
+ serviceConfig = {
+ WorkingDirectory = "~";
+ User = "sks";
+ Group = "sks";
+ Restart = "always";
+ ExecStart = "${sksPkg}/bin/sks db -hkp_address ${hkpAddress} -hkp_port ${hkpPort}";
+ };
+ };
+ };
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/security/sshguard.nix b/infra/libkookie/nixpkgs/nixos/modules/services/security/sshguard.nix
new file mode 100644
index 000000000000..e7a9cefdef30
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/security/sshguard.nix
@@ -0,0 +1,155 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.sshguard;
+
+in {
+
+ ###### interface
+
+ options = {
+
+ services.sshguard = {
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Whether to enable the sshguard service.";
+ };
+
+ attack_threshold = mkOption {
+ default = 30;
+ type = types.int;
+ description = ''
+ Block attackers when their cumulative attack score exceeds threshold. Most attacks have a score of 10.
+ '';
+ };
+
+ blacklist_threshold = mkOption {
+ default = null;
+ example = 120;
+ type = types.nullOr types.int;
+ description = ''
+ Blacklist an attacker when its score exceeds threshold. Blacklisted addresses are loaded from and added to blacklist-file.
+ '';
+ };
+
+ blacklist_file = mkOption {
+ default = "/var/lib/sshguard/blacklist.db";
+ type = types.path;
+ description = ''
+ Blacklist an attacker when its score exceeds threshold. Blacklisted addresses are loaded from and added to blacklist-file.
+ '';
+ };
+
+ blocktime = mkOption {
+ default = 120;
+ type = types.int;
+ description = ''
+ Block attackers for initially blocktime seconds after exceeding threshold. Subsequent blocks increase by a factor of 1.5.
+
+ sshguard unblocks attacks at random intervals, so actual block times will be longer.
+ '';
+ };
+
+ detection_time = mkOption {
+ default = 1800;
+ type = types.int;
+ description = ''
+ Remember potential attackers for up to detection_time seconds before resetting their score.
+ '';
+ };
+
+ whitelist = mkOption {
+ default = [ ];
+ example = [ "198.51.100.56" "198.51.100.2" ];
+ type = types.listOf types.str;
+ description = ''
+ Whitelist a list of addresses, hostnames, or address blocks.
+ '';
+ };
+
+ services = mkOption {
+ default = [ "sshd" ];
+ example = [ "sshd" "exim" ];
+ type = types.listOf types.str;
+ description = ''
+ Systemd services sshguard should receive logs of.
+ '';
+ };
+ };
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ environment.etc."sshguard.conf".text = let
+ args = lib.concatStringsSep " " ([
+ "-afb"
+ "-p info"
+ "-o cat"
+ "-n1"
+ ] ++ (map (name: "-t ${escapeShellArg name}") cfg.services));
+ backend = if config.networking.nftables.enable
+ then "sshg-fw-nft-sets"
+ else "sshg-fw-ipset";
+ in ''
+ BACKEND="${pkgs.sshguard}/libexec/${backend}"
+ LOGREADER="LANG=C ${pkgs.systemd}/bin/journalctl ${args}"
+ '';
+
+ systemd.services.sshguard = {
+ description = "SSHGuard brute-force attacks protection system";
+
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ partOf = optional config.networking.firewall.enable "firewall.service";
+
+ path = with pkgs; if config.networking.nftables.enable
+ then [ nftables iproute systemd ]
+ else [ iptables ipset iproute systemd ];
+
+ # The sshguard ipsets must exist before we invoke
+ # iptables. sshguard creates the ipsets after startup if
+ # necessary, but if we let sshguard do it, we can't reliably add
+ # the iptables rules because postStart races with the creation
+ # of the ipsets. So instead, we create both the ipsets and
+ # firewall rules before sshguard starts.
+ preStart = optionalString config.networking.firewall.enable ''
+ ${pkgs.ipset}/bin/ipset -quiet create -exist sshguard4 hash:net family inet
+ ${pkgs.ipset}/bin/ipset -quiet create -exist sshguard6 hash:net family inet6
+ ${pkgs.iptables}/bin/iptables -I INPUT -m set --match-set sshguard4 src -j DROP
+ ${pkgs.iptables}/bin/ip6tables -I INPUT -m set --match-set sshguard6 src -j DROP
+ '';
+
+ postStop = optionalString config.networking.firewall.enable ''
+ ${pkgs.iptables}/bin/iptables -D INPUT -m set --match-set sshguard4 src -j DROP
+ ${pkgs.iptables}/bin/ip6tables -D INPUT -m set --match-set sshguard6 src -j DROP
+ ${pkgs.ipset}/bin/ipset -quiet destroy sshguard4
+ ${pkgs.ipset}/bin/ipset -quiet destroy sshguard6
+ '';
+
+ unitConfig.Documentation = "man:sshguard(8)";
+
+ serviceConfig = {
+ Type = "simple";
+ ExecStart = let
+ args = lib.concatStringsSep " " ([
+ "-a ${toString cfg.attack_threshold}"
+ "-p ${toString cfg.blocktime}"
+ "-s ${toString cfg.detection_time}"
+ (optionalString (cfg.blacklist_threshold != null) "-b ${toString cfg.blacklist_threshold}:${cfg.blacklist_file}")
+ ] ++ (map (name: "-w ${escapeShellArg name}") cfg.whitelist));
+ in "${pkgs.sshguard}/bin/sshguard ${args}";
+ Restart = "always";
+ ProtectSystem = "strict";
+ ProtectHome = "tmpfs";
+ RuntimeDirectory = "sshguard";
+ StateDirectory = "sshguard";
+ CapabilityBoundingSet = "CAP_NET_ADMIN CAP_NET_RAW";
+ };
+ };
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/security/tor.nix b/infra/libkookie/nixpkgs/nixos/modules/services/security/tor.nix
new file mode 100644
index 000000000000..38dc378887a8
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/security/tor.nix
@@ -0,0 +1,799 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.tor;
+ torDirectory = "/var/lib/tor";
+ torRunDirectory = "/run/tor";
+
+ opt = name: value: optionalString (value != null) "${name} ${value}";
+ optint = name: value: optionalString (value != null && value != 0) "${name} ${toString value}";
+
+ isolationOptions = {
+ type = types.listOf (types.enum [
+ "IsolateClientAddr"
+ "IsolateSOCKSAuth"
+ "IsolateClientProtocol"
+ "IsolateDestPort"
+ "IsolateDestAddr"
+ ]);
+ default = [];
+ example = [
+ "IsolateClientAddr"
+ "IsolateSOCKSAuth"
+ "IsolateClientProtocol"
+ "IsolateDestPort"
+ "IsolateDestAddr"
+ ];
+ description = "Tor isolation options";
+ };
+
+
+ torRc = ''
+ User tor
+ DataDirectory ${torDirectory}
+ ${optionalString cfg.enableGeoIP ''
+ GeoIPFile ${cfg.package.geoip}/share/tor/geoip
+ GeoIPv6File ${cfg.package.geoip}/share/tor/geoip6
+ ''}
+
+ ${optint "ControlPort" cfg.controlPort}
+ ${optionalString cfg.controlSocket.enable "ControlPort unix:${torRunDirectory}/control GroupWritable RelaxDirModeCheck"}
+ ''
+ # Client connection config
+ + optionalString cfg.client.enable ''
+ SOCKSPort ${cfg.client.socksListenAddress} ${toString cfg.client.socksIsolationOptions}
+ SOCKSPort ${cfg.client.socksListenAddressFaster}
+ ${opt "SocksPolicy" cfg.client.socksPolicy}
+
+ ${optionalString cfg.client.transparentProxy.enable ''
+ TransPort ${cfg.client.transparentProxy.listenAddress} ${toString cfg.client.transparentProxy.isolationOptions}
+ ''}
+
+ ${optionalString cfg.client.dns.enable ''
+ DNSPort ${cfg.client.dns.listenAddress} ${toString cfg.client.dns.isolationOptions}
+ AutomapHostsOnResolve 1
+ AutomapHostsSuffixes ${concatStringsSep "," cfg.client.dns.automapHostsSuffixes}
+ ''}
+ ''
+ # Explicitly disable the SOCKS server if the client is disabled. In
+ # particular, this makes non-anonymous hidden services possible.
+ + optionalString (! cfg.client.enable) ''
+ SOCKSPort 0
+ ''
+ # Relay config
+ + optionalString cfg.relay.enable ''
+ ORPort ${toString cfg.relay.port}
+ ${opt "Address" cfg.relay.address}
+ ${opt "Nickname" cfg.relay.nickname}
+ ${opt "ContactInfo" cfg.relay.contactInfo}
+
+ ${optint "RelayBandwidthRate" cfg.relay.bandwidthRate}
+ ${optint "RelayBandwidthBurst" cfg.relay.bandwidthBurst}
+ ${opt "AccountingMax" cfg.relay.accountingMax}
+ ${opt "AccountingStart" cfg.relay.accountingStart}
+
+ ${if (cfg.relay.role == "exit") then
+ opt "ExitPolicy" cfg.relay.exitPolicy
+ else
+ "ExitPolicy reject *:*"}
+
+ ${optionalString (elem cfg.relay.role ["bridge" "private-bridge"]) ''
+ BridgeRelay 1
+ ServerTransportPlugin ${concatStringsSep "," cfg.relay.bridgeTransports} exec ${pkgs.obfs4}/bin/obfs4proxy managed
+ ExtORPort auto
+ ${optionalString (cfg.relay.role == "private-bridge") ''
+ ExtraInfoStatistics 0
+ PublishServerDescriptor 0
+ ''}
+ ''}
+ ''
+ # Hidden services
+ + concatStrings (flip mapAttrsToList cfg.hiddenServices (n: v: ''
+ HiddenServiceDir ${torDirectory}/onion/${v.name}
+ ${optionalString (v.version != null) "HiddenServiceVersion ${toString v.version}"}
+ ${flip concatMapStrings v.map (p: ''
+ HiddenServicePort ${toString p.port} ${p.destination}
+ '')}
+ ${optionalString (v.authorizeClient != null) ''
+ HiddenServiceAuthorizeClient ${v.authorizeClient.authType} ${concatStringsSep "," v.authorizeClient.clientNames}
+ ''}
+ ''))
+ + cfg.extraConfig;
+
+ torRcFile = pkgs.writeText "torrc" torRc;
+
+in
+{
+ imports = [
+ (mkRenamedOptionModule [ "services" "tor" "relay" "portSpec" ] [ "services" "tor" "relay" "port" ])
+ (mkRemovedOptionModule [ "services" "tor" "relay" "isBridge" ] "Use services.tor.relay.role instead.")
+ (mkRemovedOptionModule [ "services" "tor" "relay" "isExit" ] "Use services.tor.relay.role instead.")
+ ];
+
+ options = {
+ services.tor = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable the Tor daemon. By default, the daemon is run without
+ relay, exit, bridge or client connectivity.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.tor;
+ defaultText = "pkgs.tor";
+ example = literalExample "pkgs.tor";
+ description = ''
+ Tor package to use
+ '';
+ };
+
+ enableGeoIP = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whenever to configure Tor daemon to use GeoIP databases.
+
+ Disabling this will disable by-country statistics for
+ bridges and relays and some client and third-party software
+ functionality.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra configuration. Contents will be added verbatim to the
+ configuration file at the end.
+ '';
+ };
+
+ controlPort = mkOption {
+ type = types.nullOr (types.either types.int types.str);
+ default = null;
+ example = 9051;
+ description = ''
+ If set, Tor will accept connections on the specified port
+ and allow them to control the tor process.
+ '';
+ };
+
+ controlSocket = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable Tor control socket. Control socket is created
+ in <literal>${torRunDirectory}/control</literal>
+ '';
+ };
+ };
+
+ client = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable Tor daemon to route application
+ connections. You might want to disable this if you plan
+ running a dedicated Tor relay.
+ '';
+ };
+
+ socksListenAddress = mkOption {
+ type = types.str;
+ default = "127.0.0.1:9050";
+ example = "192.168.0.1:9100";
+ description = ''
+ Bind to this address to listen for connections from
+ Socks-speaking applications. Provides strong circuit
+ isolation, separate circuit per IP address.
+ '';
+ };
+
+ socksListenAddressFaster = mkOption {
+ type = types.str;
+ default = "127.0.0.1:9063";
+ example = "192.168.0.1:9101";
+ description = ''
+ Bind to this address to listen for connections from
+ Socks-speaking applications. Same as
+ <option>socksListenAddress</option> but uses weaker
+ circuit isolation to provide performance suitable for a
+ web browser.
+ '';
+ };
+
+ socksPolicy = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "accept 192.168.0.0/16, reject *";
+ description = ''
+ Entry policies to allow/deny SOCKS requests based on IP
+ address. First entry that matches wins. If no SocksPolicy
+ is set, we accept all (and only) requests from
+ <option>socksListenAddress</option>.
+ '';
+ };
+
+ socksIsolationOptions = mkOption (isolationOptions // {
+ default = ["IsolateDestAddr"];
+ });
+
+ transparentProxy = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable tor transparent proxy";
+ };
+
+ listenAddress = mkOption {
+ type = types.str;
+ default = "127.0.0.1:9040";
+ example = "192.168.0.1:9040";
+ description = ''
+ Bind transparent proxy to this address.
+ '';
+ };
+
+ isolationOptions = mkOption isolationOptions;
+ };
+
+ dns = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable tor dns resolver";
+ };
+
+ listenAddress = mkOption {
+ type = types.str;
+ default = "127.0.0.1:9053";
+ example = "192.168.0.1:9053";
+ description = ''
+ Bind tor dns to this address.
+ '';
+ };
+
+ isolationOptions = mkOption isolationOptions;
+
+ automapHostsSuffixes = mkOption {
+ type = types.listOf types.str;
+ default = [".onion" ".exit"];
+ example = [".onion"];
+ description = "List of suffixes to use with automapHostsOnResolve";
+ };
+ };
+
+ privoxy.enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to enable and configure the system Privoxy to use Tor's
+ faster port, suitable for HTTP.
+
+ To have anonymity, protocols need to be scrubbed of identifying
+ information, and this can be accomplished for HTTP by Privoxy.
+
+ Privoxy can also be useful for KDE torification. A good setup would be:
+ setting SOCKS proxy to the default Tor port, providing maximum
+ circuit isolation where possible; and setting HTTP proxy to Privoxy
+ to route HTTP traffic over faster, but less isolated port.
+ '';
+ };
+ };
+
+ relay = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable relaying TOR traffic for others.
+
+ See <link xlink:href="https://www.torproject.org/docs/tor-doc-relay" />
+ for details.
+
+ Setting this to true requires setting
+ <option>services.tor.relay.role</option>
+ and
+ <option>services.tor.relay.port</option>
+ options.
+ '';
+ };
+
+ role = mkOption {
+ type = types.enum [ "exit" "relay" "bridge" "private-bridge" ];
+ description = ''
+ Your role in Tor network. There're several options:
+
+ <variablelist>
+ <varlistentry>
+ <term><literal>exit</literal></term>
+ <listitem>
+ <para>
+ An exit relay. This allows Tor users to access regular
+ Internet services through your public IP.
+ </para>
+
+ <important><para>
+ Running an exit relay may expose you to abuse
+ complaints. See
+ <link xlink:href="https://www.torproject.org/faq.html.en#ExitPolicies" />
+ for more info.
+ </para></important>
+
+ <para>
+ You can specify which services Tor users may access via
+ your exit relay using <option>exitPolicy</option> option.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>relay</literal></term>
+ <listitem>
+ <para>
+ Regular relay. This allows Tor users to relay onion
+ traffic to other Tor nodes, but not to public
+ Internet.
+ </para>
+
+ <important><para>
+ Note that some misconfigured and/or disrespectful
+ towards privacy sites will block you even if your
+ relay is not an exit relay. That is, just being listed
+ in a public relay directory can have unwanted
+ consequences.
+
+ Which means you might not want to use
+ this role if you browse public Internet from the same
+ network as your relay, unless you want to write
+ e-mails to those sites (you should!).
+ </para></important>
+
+ <para>
+ See
+ <link xlink:href="https://www.torproject.org/docs/tor-doc-relay.html.en" />
+ for more info.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>bridge</literal></term>
+ <listitem>
+ <para>
+ Regular bridge. Works like a regular relay, but
+ doesn't list you in the public relay directory and
+ hides your Tor node behind obfs4proxy.
+ </para>
+
+ <para>
+ Using this option will make Tor advertise your bridge
+ to users through various mechanisms like
+ <link xlink:href="https://bridges.torproject.org/" />, though.
+ </para>
+
+ <important>
+ <para>
+ WARNING: THE FOLLOWING PARAGRAPH IS NOT LEGAL ADVICE.
+ Consult with your lawer when in doubt.
+ </para>
+
+ <para>
+ This role should be safe to use in most situations
+ (unless the act of forwarding traffic for others is
+ a punishable offence under your local laws, which
+ would be pretty insane as it would make ISP
+ illegal).
+ </para>
+ </important>
+
+ <para>
+ See <link xlink:href="https://www.torproject.org/docs/bridges.html.en" />
+ for more info.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>private-bridge</literal></term>
+ <listitem>
+ <para>
+ Private bridge. Works like regular bridge, but does
+ not advertise your node in any way.
+ </para>
+
+ <para>
+ Using this role means that you won't contribute to Tor
+ network in any way unless you advertise your node
+ yourself in some way.
+ </para>
+
+ <para>
+ Use this if you want to run a private bridge, for
+ example because you'll give out your bridge address
+ manually to your friends.
+ </para>
+
+ <para>
+ Switching to this role after measurable time in
+ "bridge" role is pretty useless as some Tor users
+ would have learned about your node already. In the
+ latter case you can still change
+ <option>port</option> option.
+ </para>
+
+ <para>
+ See <link xlink:href="https://www.torproject.org/docs/bridges.html.en" />
+ for more info.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ '';
+ };
+
+ bridgeTransports = mkOption {
+ type = types.listOf types.str;
+ default = ["obfs4"];
+ example = ["obfs2" "obfs3" "obfs4" "scramblesuit"];
+ description = "List of pluggable transports";
+ };
+
+ nickname = mkOption {
+ type = types.str;
+ default = "anonymous";
+ description = ''
+ A unique handle for your TOR relay.
+ '';
+ };
+
+ contactInfo = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "admin@relay.com";
+ description = ''
+ Contact information for the relay owner (e.g. a mail
+ address and GPG key ID).
+ '';
+ };
+
+ accountingMax = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "450 GBytes";
+ description = ''
+ Specify maximum bandwidth allowed during an accounting period. This
+ allows you to limit overall tor bandwidth over some time period.
+ See the <literal>AccountingMax</literal> option by looking at the
+ tor manual <citerefentry><refentrytitle>tor</refentrytitle>
+ <manvolnum>1</manvolnum></citerefentry> for more.
+
+ Note this limit applies individually to upload and
+ download; if you specify <literal>"500 GBytes"</literal>
+ here, then you may transfer up to 1 TBytes of overall
+ bandwidth (500 GB upload, 500 GB download).
+ '';
+ };
+
+ accountingStart = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "month 1 1:00";
+ description = ''
+ Specify length of an accounting period. This allows you to limit
+ overall tor bandwidth over some time period. See the
+ <literal>AccountingStart</literal> option by looking at the tor
+ manual <citerefentry><refentrytitle>tor</refentrytitle>
+ <manvolnum>1</manvolnum></citerefentry> for more.
+ '';
+ };
+
+ bandwidthRate = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ example = 100;
+ description = ''
+ Specify this to limit the bandwidth usage of relayed (server)
+ traffic. Your own traffic is still unthrottled. Units: bytes/second.
+ '';
+ };
+
+ bandwidthBurst = mkOption {
+ type = types.nullOr types.int;
+ default = cfg.relay.bandwidthRate;
+ example = 200;
+ description = ''
+ Specify this to allow bursts of the bandwidth usage of relayed (server)
+ traffic. The average usage will still be as specified in relayBandwidthRate.
+ Your own traffic is still unthrottled. Units: bytes/second.
+ '';
+ };
+
+ address = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "noname.example.com";
+ description = ''
+ The IP address or full DNS name for advertised address of your relay.
+ Leave unset and Tor will guess.
+ '';
+ };
+
+ port = mkOption {
+ type = types.either types.int types.str;
+ example = 143;
+ description = ''
+ What port to advertise for Tor connections. This corresponds to the
+ <literal>ORPort</literal> section in the Tor manual; see
+ <citerefentry><refentrytitle>tor</refentrytitle>
+ <manvolnum>1</manvolnum></citerefentry> for more details.
+
+ At a minimum, you should just specify the port for the
+ relay to listen on; a common one like 143, 22, 80, or 443
+ to help Tor users who may have very restrictive port-based
+ firewalls.
+ '';
+ };
+
+ exitPolicy = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "accept *:6660-6667,reject *:*";
+ description = ''
+ A comma-separated list of exit policies. They're
+ considered first to last, and the first match wins. If you
+ want to _replace_ the default exit policy, end this with
+ either a reject *:* or an accept *:*. Otherwise, you're
+ _augmenting_ (prepending to) the default exit policy.
+ Leave commented to just use the default, which is
+ available in the man page or at
+ <link xlink:href="https://www.torproject.org/documentation.html" />.
+
+ Look at
+ <link xlink:href="https://www.torproject.org/faq-abuse.html#TypicalAbuses" />
+ for issues you might encounter if you use the default
+ exit policy.
+
+ If certain IPs and ports are blocked externally, e.g. by
+ your firewall, you should update your exit policy to
+ reflect this -- otherwise Tor users will be told that
+ those destinations are down.
+ '';
+ };
+ };
+
+ hiddenServices = mkOption {
+ description = ''
+ A set of static hidden services that terminate their Tor
+ circuits at this node.
+
+ Every element in this set declares a virtual onion host.
+
+ You can specify your onion address by putting corresponding
+ private key to an appropriate place in ${torDirectory}.
+
+ For services without private keys in ${torDirectory} Tor
+ daemon will generate random key pairs (which implies random
+ onion addresses) on restart. The latter could take a while,
+ please be patient.
+
+ <note><para>
+ Hidden services can be useful even if you don't intend to
+ actually <emphasis>hide</emphasis> them, since they can
+ also be seen as a kind of NAT traversal mechanism.
+
+ E.g. the example will make your sshd, whatever runs on
+ "8080" and your mail server available from anywhere where
+ the Tor network is available (which, with the help from
+ bridges, is pretty much everywhere), even if both client
+ and server machines are behind NAT you have no control
+ over.
+ </para></note>
+ '';
+ default = {};
+ example = literalExample ''
+ { "my-hidden-service-example".map = [
+ { port = 22; } # map ssh port to this machine's ssh
+ { port = 80; toPort = 8080; } # map http port to whatever runs on 8080
+ { port = "sip"; toHost = "mail.example.com"; toPort = "imap"; } # because we can
+ ];
+ }
+ '';
+ type = types.attrsOf (types.submodule ({name, ...}: {
+ options = {
+
+ name = mkOption {
+ type = types.str;
+ description = ''
+ Name of this tor hidden service.
+
+ This is purely descriptive.
+
+ After restarting Tor daemon you should be able to
+ find your .onion address in
+ <literal>${torDirectory}/onion/$name/hostname</literal>.
+ '';
+ };
+
+ map = mkOption {
+ default = [];
+ description = "Port mapping for this hidden service.";
+ type = types.listOf (types.submodule ({config, ...}: {
+ options = {
+
+ port = mkOption {
+ type = types.either types.int types.str;
+ example = 80;
+ description = ''
+ Hidden service port to "bind to".
+ '';
+ };
+
+ destination = mkOption {
+ internal = true;
+ type = types.str;
+ description = "Forward these connections where?";
+ };
+
+ toHost = mkOption {
+ type = types.str;
+ default = "127.0.0.1";
+ description = "Mapping destination host.";
+ };
+
+ toPort = mkOption {
+ type = types.either types.int types.str;
+ example = 8080;
+ description = "Mapping destination port.";
+ };
+
+ };
+
+ config = {
+ toPort = mkDefault config.port;
+ destination = mkDefault "${config.toHost}:${toString config.toPort}";
+ };
+ }));
+ };
+
+ authorizeClient = mkOption {
+ default = null;
+ description = "If configured, the hidden service is accessible for authorized clients only.";
+ type = types.nullOr (types.submodule ({...}: {
+
+ options = {
+
+ authType = mkOption {
+ type = types.enum [ "basic" "stealth" ];
+ description = ''
+ Either <literal>"basic"</literal> for a general-purpose authorization protocol
+ or <literal>"stealth"</literal> for a less scalable protocol
+ that also hides service activity from unauthorized clients.
+ '';
+ };
+
+ clientNames = mkOption {
+ type = types.nonEmptyListOf (types.strMatching "[A-Za-z0-9+-_]+");
+ description = ''
+ Only clients that are listed here are authorized to access the hidden service.
+ Generated authorization data can be found in <filename>${torDirectory}/onion/$name/hostname</filename>.
+ Clients need to put this authorization data in their configuration file using <literal>HidServAuth</literal>.
+ '';
+ };
+ };
+ }));
+ };
+
+ version = mkOption {
+ default = null;
+ description = "Rendezvous service descriptor version to publish for the hidden service. Currently, versions 2 and 3 are supported. (Default: 2)";
+ type = types.nullOr (types.enum [ 2 3 ]);
+ };
+ };
+
+ config = {
+ name = mkDefault name;
+ };
+ }));
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ # Not sure if `cfg.relay.role == "private-bridge"` helps as tor
+ # sends a lot of stats
+ warnings = optional (cfg.relay.enable && cfg.hiddenServices != {})
+ ''
+ Running Tor hidden services on a public relay makes the
+ presence of hidden services visible through simple statistical
+ analysis of publicly available data.
+
+ You can safely ignore this warning if you don't intend to
+ actually hide your hidden services. In either case, you can
+ always create a container/VM with a separate Tor daemon instance.
+ '';
+
+ users.groups.tor.gid = config.ids.gids.tor;
+ users.users.tor =
+ { description = "Tor Daemon User";
+ createHome = true;
+ home = torDirectory;
+ group = "tor";
+ uid = config.ids.uids.tor;
+ };
+
+ # We have to do this instead of using RuntimeDirectory option in
+ # the service below because systemd has no way to set owners of
+ # RuntimeDirectory and putting this into the service below
+ # requires that service to relax it's sandbox since this needs
+ # writable /run
+ systemd.services.tor-init =
+ { description = "Tor Daemon Init";
+ wantedBy = [ "tor.service" ];
+ script = ''
+ install -m 0700 -o tor -g tor -d ${torDirectory} ${torDirectory}/onion
+ install -m 0750 -o tor -g tor -d ${torRunDirectory}
+ '';
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = true;
+ };
+ };
+
+ systemd.services.tor =
+ { description = "Tor Daemon";
+ path = [ pkgs.tor ];
+
+ wantedBy = [ "multi-user.target" ];
+ after = [ "tor-init.service" "network.target" ];
+ restartTriggers = [ torRcFile ];
+
+ serviceConfig =
+ { Type = "simple";
+ # Translated from the upstream contrib/dist/tor.service.in
+ ExecStartPre = "${cfg.package}/bin/tor -f ${torRcFile} --verify-config";
+ ExecStart = "${cfg.package}/bin/tor -f ${torRcFile}";
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ KillSignal = "SIGINT";
+ TimeoutSec = 30;
+ Restart = "on-failure";
+ LimitNOFILE = 32768;
+
+ # Hardening
+ # this seems to unshare /run despite what systemd.exec(5) says
+ PrivateTmp = mkIf (!cfg.controlSocket.enable) "yes";
+ PrivateDevices = "yes";
+ ProtectHome = "yes";
+ ProtectSystem = "strict";
+ InaccessiblePaths = "/home";
+ ReadOnlyPaths = "/";
+ ReadWritePaths = [ torDirectory torRunDirectory ];
+ NoNewPrivileges = "yes";
+
+ # tor.service.in has this in, but this line it fails to spawn a namespace when using hidden services
+ #CapabilityBoundingSet = "CAP_SETUID CAP_SETGID CAP_NET_BIND_SERVICE";
+ };
+ };
+
+ environment.systemPackages = [ cfg.package ];
+
+ services.privoxy = mkIf (cfg.client.enable && cfg.client.privoxy.enable) {
+ enable = true;
+ extraConfig = ''
+ forward-socks4a / ${cfg.client.socksListenAddressFaster} .
+ toggle 1
+ enable-remote-toggle 0
+ enable-edit-actions 0
+ enable-remote-http-toggle 0
+ '';
+ };
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/security/torify.nix b/infra/libkookie/nixpkgs/nixos/modules/services/security/torify.nix
new file mode 100644
index 000000000000..39551190dd33
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/security/torify.nix
@@ -0,0 +1,80 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+
+ cfg = config.services.tor;
+
+ torify = pkgs.writeTextFile {
+ name = "tsocks";
+ text = ''
+ #!${pkgs.runtimeShell}
+ TSOCKS_CONF_FILE=${pkgs.writeText "tsocks.conf" cfg.tsocks.config} LD_PRELOAD="${pkgs.tsocks}/lib/libtsocks.so $LD_PRELOAD" "$@"
+ '';
+ executable = true;
+ destination = "/bin/tsocks";
+ };
+
+in
+
+{
+
+ ###### interface
+
+ options = {
+
+ services.tor.tsocks = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to build tsocks wrapper script to relay application traffic via Tor.
+
+ <important>
+ <para>You shouldn't use this unless you know what you're
+ doing because your installation of Tor already comes with
+ its own superior (doesn't leak DNS queries)
+ <literal>torsocks</literal> wrapper which does pretty much
+ exactly the same thing as this.</para>
+ </important>
+ '';
+ };
+
+ server = mkOption {
+ type = types.str;
+ default = "localhost:9050";
+ example = "192.168.0.20";
+ description = ''
+ IP address of TOR client to use.
+ '';
+ };
+
+ config = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra configuration. Contents will be added verbatim to TSocks
+ configuration file.
+ '';
+ };
+
+ };
+
+ };
+
+ ###### implementation
+
+ config = mkIf cfg.tsocks.enable {
+
+ environment.systemPackages = [ torify ]; # expose it to the users
+
+ services.tor.tsocks.config = ''
+ server = ${toString(head (splitString ":" cfg.tsocks.server))}
+ server_port = ${toString(tail (splitString ":" cfg.tsocks.server))}
+
+ local = 127.0.0.0/255.128.0.0
+ local = 127.128.0.0/255.192.0.0
+ '';
+ };
+
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/security/torsocks.nix b/infra/libkookie/nixpkgs/nixos/modules/services/security/torsocks.nix
new file mode 100644
index 000000000000..47ac95c4626e
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/security/torsocks.nix
@@ -0,0 +1,120 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.tor.torsocks;
+ optionalNullStr = b: v: optionalString (b != null) v;
+
+ configFile = server: ''
+ TorAddress ${toString (head (splitString ":" server))}
+ TorPort ${toString (tail (splitString ":" server))}
+
+ OnionAddrRange ${cfg.onionAddrRange}
+
+ ${optionalNullStr cfg.socks5Username
+ "SOCKS5Username ${cfg.socks5Username}"}
+ ${optionalNullStr cfg.socks5Password
+ "SOCKS5Password ${cfg.socks5Password}"}
+
+ AllowInbound ${if cfg.allowInbound then "1" else "0"}
+ '';
+
+ wrapTorsocks = name: server: pkgs.writeTextFile {
+ name = name;
+ text = ''
+ #!${pkgs.runtimeShell}
+ TORSOCKS_CONF_FILE=${pkgs.writeText "torsocks.conf" (configFile server)} ${pkgs.torsocks}/bin/torsocks "$@"
+ '';
+ executable = true;
+ destination = "/bin/${name}";
+ };
+
+in
+{
+ options = {
+ services.tor.torsocks = {
+ enable = mkOption {
+ type = types.bool;
+ default = config.services.tor.enable && config.services.tor.client.enable;
+ description = ''
+ Whether to build <literal>/etc/tor/torsocks.conf</literal>
+ containing the specified global torsocks configuration.
+ '';
+ };
+
+ server = mkOption {
+ type = types.str;
+ default = "127.0.0.1:9050";
+ example = "192.168.0.20:1234";
+ description = ''
+ IP/Port of the Tor SOCKS server. Currently, hostnames are
+ NOT supported by torsocks.
+ '';
+ };
+
+ fasterServer = mkOption {
+ type = types.str;
+ default = "127.0.0.1:9063";
+ example = "192.168.0.20:1234";
+ description = ''
+ IP/Port of the Tor SOCKS server for torsocks-faster wrapper suitable for HTTP.
+ Currently, hostnames are NOT supported by torsocks.
+ '';
+ };
+
+ onionAddrRange = mkOption {
+ type = types.str;
+ default = "127.42.42.0/24";
+ description = ''
+ Tor hidden sites do not have real IP addresses. This
+ specifies what range of IP addresses will be handed to the
+ application as "cookies" for .onion names. Of course, you
+ should pick a block of addresses which you aren't going to
+ ever need to actually connect to. This is similar to the
+ MapAddress feature of the main tor daemon.
+ '';
+ };
+
+ socks5Username = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "bob";
+ description = ''
+ SOCKS5 username. The <literal>TORSOCKS_USERNAME</literal>
+ environment variable overrides this option if it is set.
+ '';
+ };
+
+ socks5Password = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "sekret";
+ description = ''
+ SOCKS5 password. The <literal>TORSOCKS_PASSWORD</literal>
+ environment variable overrides this option if it is set.
+ '';
+ };
+
+ allowInbound = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Set Torsocks to accept inbound connections. If set to
+ <literal>true</literal>, listen() and accept() will be
+ allowed to be used with non localhost address.
+ '';
+ };
+
+ };
+ };
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ pkgs.torsocks (wrapTorsocks "torsocks-faster" cfg.fasterServer) ];
+
+ environment.etc."tor/torsocks.conf" =
+ {
+ source = pkgs.writeText "torsocks.conf" (configFile cfg.server);
+ };
+ };
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/security/usbguard.nix b/infra/libkookie/nixpkgs/nixos/modules/services/security/usbguard.nix
new file mode 100644
index 000000000000..71fd71a2cab2
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/security/usbguard.nix
@@ -0,0 +1,214 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+ cfg = config.services.usbguard;
+
+ # valid policy options
+ policy = (types.enum [ "allow" "block" "reject" "keep" "apply-policy" ]);
+
+ defaultRuleFile = "/var/lib/usbguard/rules.conf";
+
+ # decide what file to use for rules
+ ruleFile = if cfg.rules != null then pkgs.writeText "usbguard-rules" cfg.rules else defaultRuleFile;
+
+ daemonConf = ''
+ # generated by nixos/modules/services/security/usbguard.nix
+ RuleFile=${ruleFile}
+ ImplicitPolicyTarget=${cfg.implictPolicyTarget}
+ PresentDevicePolicy=${cfg.presentDevicePolicy}
+ PresentControllerPolicy=${cfg.presentControllerPolicy}
+ InsertedDevicePolicy=${cfg.insertedDevicePolicy}
+ RestoreControllerDeviceState=${boolToString cfg.restoreControllerDeviceState}
+ # this does not seem useful for endusers to change
+ DeviceManagerBackend=uevent
+ IPCAllowedUsers=${concatStringsSep " " cfg.IPCAllowedUsers}
+ IPCAllowedGroups=${concatStringsSep " " cfg.IPCAllowedGroups}
+ IPCAccessControlFiles=/var/lib/usbguard/IPCAccessControl.d/
+ DeviceRulesWithPort=${boolToString cfg.deviceRulesWithPort}
+ # HACK: that way audit logs still land in the journal
+ AuditFilePath=/dev/null
+ '';
+
+ daemonConfFile = pkgs.writeText "usbguard-daemon-conf" daemonConf;
+
+in
+{
+
+ ###### interface
+
+ options = {
+ services.usbguard = {
+ enable = mkEnableOption "USBGuard daemon";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.usbguard;
+ defaultText = "pkgs.usbguard";
+ description = ''
+ The usbguard package to use. If you do not need the Qt GUI, use
+ <literal>pkgs.usbguard-nox</literal> to save disk space.
+ '';
+ };
+
+ rules = mkOption {
+ type = types.nullOr types.lines;
+ default = null;
+ example = ''
+ allow with-interface equals { 08:*:* }
+ '';
+ description = ''
+ The USBGuard daemon will load this as the policy rule set.
+ As these rules are NixOS managed they are immutable and can't
+ be changed by the IPC interface.
+
+ If you do not set this option, the USBGuard daemon will load
+ it's policy rule set from <literal>${defaultRuleFile}</literal>.
+ This file can be changed manually or via the IPC interface.
+
+ Running <literal>usbguard generate-policy</literal> as root will
+ generate a config for your currently plugged in devices.
+
+ For more details see <citerefentry>
+ <refentrytitle>usbguard-rules.conf</refentrytitle>
+ <manvolnum>5</manvolnum></citerefentry>.
+ '';
+ };
+
+ implictPolicyTarget = mkOption {
+ type = policy;
+ default = "block";
+ description = ''
+ How to treat USB devices that don't match any rule in the policy.
+ Target should be one of allow, block or reject (logically remove the
+ device node from the system).
+ '';
+ };
+
+ presentDevicePolicy = mkOption {
+ type = policy;
+ default = "apply-policy";
+ description = ''
+ How to treat USB devices that are already connected when the daemon
+ starts. Policy should be one of allow, block, reject, keep (keep
+ whatever state the device is currently in) or apply-policy (evaluate
+ the rule set for every present device).
+ '';
+ };
+
+ presentControllerPolicy = mkOption {
+ type = policy;
+ default = "keep";
+ description = ''
+ How to treat USB controller devices that are already connected when
+ the daemon starts. One of allow, block, reject, keep or apply-policy.
+ '';
+ };
+
+ insertedDevicePolicy = mkOption {
+ type = policy;
+ default = "apply-policy";
+ description = ''
+ How to treat USB devices that are already connected after the daemon
+ starts. One of block, reject, apply-policy.
+ '';
+ };
+
+ restoreControllerDeviceState = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ The USBGuard daemon modifies some attributes of controller
+ devices like the default authorization state of new child device
+ instances. Using this setting, you can controll whether the daemon
+ will try to restore the attribute values to the state before
+ modificaton on shutdown.
+ '';
+ };
+
+ IPCAllowedUsers = mkOption {
+ type = types.listOf types.str;
+ default = [ "root" ];
+ example = [ "root" "yourusername" ];
+ description = ''
+ A list of usernames that the daemon will accept IPC connections from.
+ '';
+ };
+
+ IPCAllowedGroups = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "wheel" ];
+ description = ''
+ A list of groupnames that the daemon will accept IPC connections
+ from.
+ '';
+ };
+
+ deviceRulesWithPort = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Generate device specific rules including the "via-port" attribute.
+ '';
+ };
+ };
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ environment.systemPackages = [ cfg.package ];
+
+ systemd.services.usbguard = {
+ description = "USBGuard daemon";
+
+ wantedBy = [ "basic.target" ];
+ wants = [ "systemd-udevd.service" ];
+
+ # make sure an empty rule file exists
+ preStart = ''[ -f "${ruleFile}" ] || touch ${ruleFile}'';
+
+ serviceConfig = {
+ Type = "simple";
+ ExecStart = ''${cfg.package}/bin/usbguard-daemon -P -k -c ${daemonConfFile}'';
+ Restart = "on-failure";
+
+ StateDirectory = [
+ "usbguard"
+ "usbguard/IPCAccessControl.d"
+ ];
+
+ AmbientCapabilities = "";
+ CapabilityBoundingSet = "CAP_CHOWN CAP_FOWNER";
+ DeviceAllow = "/dev/null rw";
+ DevicePolicy = "strict";
+ IPAddressDeny = "any";
+ LockPersonality = true;
+ MemoryDenyWriteExecute = true;
+ NoNewPrivileges = true;
+ PrivateDevices = true;
+ PrivateTmp = true;
+ ProtectControlGroups = true;
+ ProtectHome = true;
+ ProtectKernelModules = true;
+ ProtectSystem = true;
+ ReadOnlyPaths = "-/";
+ ReadWritePaths = "-/dev/shm -/tmp";
+ RestrictAddressFamilies = [ "AF_UNIX" "AF_NETLINK" ];
+ RestrictNamespaces = true;
+ RestrictRealtime = true;
+ SystemCallArchitectures = "native";
+ SystemCallFilter = "@system-service";
+ UMask = "0077";
+ };
+ };
+ };
+ imports = [
+ (mkRemovedOptionModule [ "services" "usbguard" "ruleFile" ] "The usbguard module now uses ${defaultRuleFile} as ruleFile. Alternatively, use services.usbguard.rules to configure rules.")
+ (mkRemovedOptionModule [ "services" "usbguard" "IPCAccessControlFiles" ] "The usbguard module now hardcodes IPCAccessControlFiles to /var/lib/usbguard/IPCAccessControl.d.")
+ (mkRemovedOptionModule [ "services" "usbguard" "auditFilePath" ] "Removed usbguard module audit log files. Audit logs can be found in the systemd journal.")
+ ];
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/security/vault.nix b/infra/libkookie/nixpkgs/nixos/modules/services/security/vault.nix
new file mode 100644
index 000000000000..64622454b9de
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/security/vault.nix
@@ -0,0 +1,156 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.vault;
+
+ configFile = pkgs.writeText "vault.hcl" ''
+ listener "tcp" {
+ address = "${cfg.address}"
+ ${if (cfg.tlsCertFile == null || cfg.tlsKeyFile == null) then ''
+ tls_disable = "true"
+ '' else ''
+ tls_cert_file = "${cfg.tlsCertFile}"
+ tls_key_file = "${cfg.tlsKeyFile}"
+ ''}
+ ${cfg.listenerExtraConfig}
+ }
+ storage "${cfg.storageBackend}" {
+ ${optionalString (cfg.storagePath != null) ''path = "${cfg.storagePath}"''}
+ ${optionalString (cfg.storageConfig != null) cfg.storageConfig}
+ }
+ ${optionalString (cfg.telemetryConfig != "") ''
+ telemetry {
+ ${cfg.telemetryConfig}
+ }
+ ''}
+ ${cfg.extraConfig}
+ '';
+in
+
+{
+ options = {
+ services.vault = {
+ enable = mkEnableOption "Vault daemon";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.vault;
+ defaultText = "pkgs.vault";
+ description = "This option specifies the vault package to use.";
+ };
+
+ address = mkOption {
+ type = types.str;
+ default = "127.0.0.1:8200";
+ description = "The name of the ip interface to listen to";
+ };
+
+ tlsCertFile = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "/path/to/your/cert.pem";
+ description = "TLS certificate file. TLS will be disabled unless this option is set";
+ };
+
+ tlsKeyFile = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "/path/to/your/key.pem";
+ description = "TLS private key file. TLS will be disabled unless this option is set";
+ };
+
+ listenerExtraConfig = mkOption {
+ type = types.lines;
+ default = ''
+ tls_min_version = "tls12"
+ '';
+ description = "Extra text appended to the listener section.";
+ };
+
+ storageBackend = mkOption {
+ type = types.enum [ "inmem" "file" "consul" "zookeeper" "s3" "azure" "dynamodb" "etcd" "mssql" "mysql" "postgresql" "swift" "gcs" "raft" ];
+ default = "inmem";
+ description = "The name of the type of storage backend";
+ };
+
+ storagePath = mkOption {
+ type = types.nullOr types.path;
+ default = if cfg.storageBackend == "file" then "/var/lib/vault" else null;
+ description = "Data directory for file backend";
+ };
+
+ storageConfig = mkOption {
+ type = types.nullOr types.lines;
+ default = null;
+ description = "Storage configuration";
+ };
+
+ telemetryConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Telemetry configuration";
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Extra text appended to <filename>vault.hcl</filename>.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ assertions = [
+ { assertion = cfg.storageBackend == "inmem" -> (cfg.storagePath == null && cfg.storageConfig == null);
+ message = ''The "inmem" storage expects no services.vault.storagePath nor services.vault.storageConfig'';
+ }
+ { assertion = (cfg.storageBackend == "file" -> (cfg.storagePath != null && cfg.storageConfig == null)) && (cfg.storagePath != null -> cfg.storageBackend == "file");
+ message = ''You must set services.vault.storagePath only when using the "file" backend'';
+ }
+ ];
+
+ users.users.vault = {
+ name = "vault";
+ group = "vault";
+ uid = config.ids.uids.vault;
+ description = "Vault daemon user";
+ };
+ users.groups.vault.gid = config.ids.gids.vault;
+
+ systemd.tmpfiles.rules = optional (cfg.storagePath != null)
+ "d '${cfg.storagePath}' 0700 vault vault - -";
+
+ systemd.services.vault = {
+ description = "Vault server daemon";
+
+ wantedBy = ["multi-user.target"];
+ after = [ "network.target" ]
+ ++ optional (config.services.consul.enable && cfg.storageBackend == "consul") "consul.service";
+
+ restartIfChanged = false; # do not restart on "nixos-rebuild switch". It would seal the storage and disrupt the clients.
+
+ startLimitIntervalSec = 60;
+ startLimitBurst = 3;
+ serviceConfig = {
+ User = "vault";
+ Group = "vault";
+ ExecStart = "${cfg.package}/bin/vault server -config ${configFile}";
+ ExecReload = "${pkgs.coreutils}/bin/kill -SIGHUP $MAINPID";
+ PrivateDevices = true;
+ PrivateTmp = true;
+ ProtectSystem = "full";
+ ProtectHome = "read-only";
+ AmbientCapabilities = "cap_ipc_lock";
+ NoNewPrivileges = true;
+ KillSignal = "SIGINT";
+ TimeoutStopSec = "30s";
+ Restart = "on-failure";
+ };
+
+ unitConfig.RequiresMountsFor = optional (cfg.storagePath != null) cfg.storagePath;
+ };
+ };
+
+}
diff --git a/infra/libkookie/nixpkgs/nixos/modules/services/security/yubikey-agent.nix b/infra/libkookie/nixpkgs/nixos/modules/services/security/yubikey-agent.nix
new file mode 100644
index 000000000000..2972c64a3641
--- /dev/null
+++ b/infra/libkookie/nixpkgs/nixos/modules/services/security/yubikey-agent.nix
@@ -0,0 +1,60 @@
+# Global configuration for yubikey-agent.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.yubikey-agent;
+
+ # reuse the pinentryFlavor option from the gnupg module
+ pinentryFlavor = config.programs.gnupg.agent.pinentryFlavor;
+in
+{
+ ###### interface
+
+ meta.maintainers = with maintainers; [ philandstuff rawkode ];
+
+ options = {
+
+ services.yubikey-agent = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to start yubikey-agent when you log in. Also sets
+ SSH_AUTH_SOCK to point at yubikey-agent.
+
+ Note that yubikey-agent will use whatever pinentry is
+ specified in programs.gnupg.agent.pinentryFlavor.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.yubikey-agent;
+ defaultText = "pkgs.yubikey-agent";
+ description = ''
+ The package used for the yubikey-agent daemon.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ environment.systemPackages = [ cfg.package ];
+ systemd.packages = [ cfg.package ];
+
+ # This overrides the systemd user unit shipped with the
+ # yubikey-agent package
+ systemd.user.services.yubikey-agent = mkIf (pinentryFlavor != null) {
+ path = [ pkgs.pinentry.${pinentryFlavor} ];
+ };
+
+ environment.extraInit = ''
+ if [ -z "$SSH_AUTH_SOCK" -a -n "$XDG_RUNTIME_DIR" ]; then
+ export SSH_AUTH_SOCK="$XDG_RUNTIME_DIR/yubikey-agent/yubikey-agent.sock"
+ fi
+ '';
+ };
+}