diff options
Diffstat (limited to 'nixos/modules/services/security')
-rw-r--r-- | nixos/modules/services/security/frandom.nix | 31 | ||||
-rw-r--r-- | nixos/modules/services/security/oauth2_proxy.nix | 9 | ||||
-rw-r--r-- | nixos/modules/services/security/sks.nix | 82 | ||||
-rw-r--r-- | nixos/modules/services/security/tor.nix | 394 | ||||
-rw-r--r-- | nixos/modules/services/security/usbguard.nix | 200 | ||||
-rw-r--r-- | nixos/modules/services/security/vault.nix | 143 |
6 files changed, 748 insertions, 111 deletions
diff --git a/nixos/modules/services/security/frandom.nix b/nixos/modules/services/security/frandom.nix deleted file mode 100644 index 2d43d12e541d..000000000000 --- a/nixos/modules/services/security/frandom.nix +++ /dev/null @@ -1,31 +0,0 @@ -{lib, config, ...}: - -let kernel = config.boot.kernelPackages; -in - -{ - - ###### interface - - options = { - - services.frandom.enable = lib.mkOption { - default = false; - type = lib.types.bool; - description = '' - enable the /dev/frandom device (a very fast random number generator) - ''; - }; - - }; - - - ###### implementation - - config = lib.mkIf config.services.frandom.enable { - boot.kernelModules = [ "frandom" ]; - boot.extraModulePackages = [ kernel.frandom ]; - services.udev.packages = [ kernel.frandom ]; - }; - -} diff --git a/nixos/modules/services/security/oauth2_proxy.nix b/nixos/modules/services/security/oauth2_proxy.nix index e292fd9851e3..3e5087766b1c 100644 --- a/nixos/modules/services/security/oauth2_proxy.nix +++ b/nixos/modules/services/security/oauth2_proxy.nix @@ -21,21 +21,20 @@ let ''; github = cfg: '' - $(optionalString (!isNull cfg.github.org) "--github-org=${cfg.github.org}") \ - $(optionalString (!isNull cfg.github.team) "--github-org=${cfg.github.team}") \ + ${optionalString (!isNull cfg.github.org) "--github-org=${cfg.github.org}"} \ + ${optionalString (!isNull cfg.github.team) "--github-org=${cfg.github.team}"} \ ''; google = cfg: '' --google-admin-email=${cfg.google.adminEmail} \ --google-service-account=${cfg.google.serviceAccountJSON} \ - $(repeatedArgs (group: "--google-group=${group}") cfg.google.groups) \ + ${repeatedArgs (group: "--google-group=${group}") cfg.google.groups} \ ''; }; authenticatedEmailsFile = pkgs.writeText "authenticated-emails" cfg.email.addresses; - getProviderOptions = cfg: provider: - if providerSpecificOptions ? provider then providerSpecificOptions.provider cfg else ""; + getProviderOptions = cfg: provider: providerSpecificOptions.${provider} or (_: "") cfg; mkCommandLine = cfg: '' --provider='${cfg.provider}' \ diff --git a/nixos/modules/services/security/sks.nix b/nixos/modules/services/security/sks.nix new file mode 100644 index 000000000000..62308428f326 --- /dev/null +++ b/nixos/modules/services/security/sks.nix @@ -0,0 +1,82 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.services.sks; + + sksPkg = cfg.package; + +in + +{ + + options = { + + services.sks = { + + enable = mkEnableOption "sks"; + + package = mkOption { + default = pkgs.sks; + defaultText = "pkgs.sks"; + type = types.package; + description = " + Which sks derivation to use. + "; + }; + + hkpAddress = mkOption { + default = [ "127.0.0.1" "::1" ]; + type = types.listOf types.str; + description = " + Wich ip addresses the sks-keyserver is listening on. + "; + }; + + hkpPort = mkOption { + default = 11371; + type = types.int; + description = " + Which port the sks-keyserver is listening on. + "; + }; + }; + }; + + config = mkIf cfg.enable { + + environment.systemPackages = [ sksPkg ]; + + users.users.sks = { + createHome = true; + home = "/var/db/sks"; + isSystemUser = true; + shell = "${pkgs.coreutils}/bin/true"; + }; + + systemd.services = let + hkpAddress = "'" + (builtins.concatStringsSep " " cfg.hkpAddress) + "'" ; + hkpPort = builtins.toString cfg.hkpPort; + home = config.users.users.sks.home; + user = config.users.users.sks.name; + in { + sks-keyserver = { + wantedBy = [ "multi-user.target" ]; + preStart = '' + mkdir -p ${home}/dump + ${pkgs.sks}/bin/sks build ${home}/dump/*.gpg -n 10 -cache 100 || true #*/ + ${pkgs.sks}/bin/sks cleandb || true + ${pkgs.sks}/bin/sks pbuild -cache 20 -ptree_cache 70 || true + ''; + serviceConfig = { + WorkingDirectory = home; + User = user; + Restart = "always"; + ExecStart = "${pkgs.sks}/bin/sks db -hkp_address ${hkpAddress} -hkp_port ${hkpPort}"; + }; + }; + }; + }; +} diff --git a/nixos/modules/services/security/tor.nix b/nixos/modules/services/security/tor.nix index 10596d6431d0..04b065f6ae4b 100644 --- a/nixos/modules/services/security/tor.nix +++ b/nixos/modules/services/security/tor.nix @@ -7,7 +7,7 @@ let torDirectory = "/var/lib/tor"; opt = name: value: optionalString (value != null) "${name} ${value}"; - optint = name: value: optionalString (value != 0) "${name} ${toString value}"; + optint = name: value: optionalString (value != null && value != 0) "${name} ${toString value}"; torRc = '' User tor @@ -17,7 +17,7 @@ let GeoIPv6File ${pkgs.tor.geoip}/share/tor/geoip6 ''} - ${optint "ControlPort" cfg.controlPort} + ${optint "ControlPort" (toString cfg.controlPort)} '' # Client connection config + optionalString cfg.client.enable '' @@ -27,7 +27,8 @@ let '' # Relay config + optionalString cfg.relay.enable '' - ORPort ${cfg.relay.portSpec} + ORPort ${toString cfg.relay.port} + ${opt "Address" cfg.relay.address} ${opt "Nickname" cfg.relay.nickname} ${opt "ContactInfo" cfg.relay.contactInfo} @@ -36,19 +37,32 @@ let ${opt "AccountingMax" cfg.relay.accountingMax} ${opt "AccountingStart" cfg.relay.accountingStart} - ${if cfg.relay.isExit then + ${if (cfg.relay.role == "exit") then opt "ExitPolicy" cfg.relay.exitPolicy else "ExitPolicy reject *:*"} - ${optionalString cfg.relay.isBridge '' + ${optionalString (elem cfg.relay.role ["bridge" "private-bridge"]) '' BridgeRelay 1 ServerTransportPlugin obfs2,obfs3 exec ${pkgs.pythonPackages.obfsproxy}/bin/obfsproxy 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} + ${flip concatMapStrings v.map (p: '' + HiddenServicePort ${toString p.port} ${p.destination} + '')} + '')) + cfg.extraConfig; torRcFile = pkgs.writeText "torrc" torRc; + in { options = { @@ -84,8 +98,8 @@ in }; controlPort = mkOption { - type = types.int; - default = 0; + type = types.nullOr (types.either types.int types.str); + default = null; example = 9051; description = '' If set, Tor will accept connections on the specified port @@ -121,9 +135,10 @@ in example = "192.168.0.1:9101"; description = '' Bind to this address to listen for connections from - Socks-speaking applications. Same as socksListenAddress - but uses weaker circuit isolation to provide performance - suitable for a web browser. + Socks-speaking applications. Same as + <option>socksListenAddress</option> but uses weaker + circuit isolation to provide performance suitable for a + web browser. ''; }; @@ -133,9 +148,9 @@ in 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 + address. First entry that matches wins. If no SocksPolicy is set, we accept all (and only) requests from - SocksListenAddress. + <option>socksListenAddress</option>. ''; }; @@ -164,45 +179,147 @@ in description = '' Whether to enable relaying TOR traffic for others. - See https://www.torproject.org/docs/tor-doc-relay for details. - ''; - }; - - isBridge = mkOption { - type = types.bool; - default = false; - description = '' - Bridge relays (or "bridges") are Tor relays that aren't - listed in the main directory. Since there is no complete - public list of them, even if an ISP is filtering - connections to all the known Tor relays, they probably - won't be able to block all the bridges. - - A bridge relay can't be an exit relay. - - You need to set relay.enable to true for this option to - take effect. + See <link xlink:href="https://www.torproject.org/docs/tor-doc-relay" /> + for details. - The bridge is set up with an obfuscated transport proxy. - - See https://www.torproject.org/bridges.html.en for more info. + Setting this to true requires setting + <option>services.tor.relay.role</option> + and + <option>services.tor.relay.port</option> + options. ''; }; - isExit = mkOption { - type = types.bool; - default = false; + role = mkOption { + type = types.enum [ "exit" "relay" "bridge" "private-bridge" ]; description = '' - An exit relay allows Tor users to access regular Internet - services. - - Unlike running a non-exit relay, running an exit relay may - expose you to abuse complaints. See - https://www.torproject.org/faq.html.en#ExitPolicies for - more info. - - You can specify which services Tor users may access via - your exit relay using exitPolicy option. + 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 obfsproxy. + </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 ADVISE. + 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> ''; }; @@ -229,11 +346,11 @@ in 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 (<literal>man tor</literal>) for more. + 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> @@ -247,16 +364,17 @@ in 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 (<literal>man tor</literal>) for more. + 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.int; - default = 0; + type = types.nullOr types.int; + default = null; example = 100; description = '' Specify this to limit the bandwidth usage of relayed (server) @@ -265,7 +383,7 @@ in }; bandwidthBurst = mkOption { - type = types.int; + type = types.nullOr types.int; default = cfg.relay.bandwidthRate; example = 200; description = '' @@ -275,13 +393,24 @@ in ''; }; - portSpec = mkOption { - type = types.str; - example = "143"; + 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 - <literal>man tor</literal> for more details. + 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 @@ -299,13 +428,15 @@ in 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 + _augmenting_ (prepending to) the default exit policy. + Leave commented to just use the default, which is available in the man page or at - https://www.torproject.org/documentation.html + <link xlink:href="https://www.torproject.org/documentation.html" />. - Look at https://www.torproject.org/faq-abuse.html#TypicalAbuses - for issues you might encounter if you use the default exit policy. + 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 @@ -314,15 +445,124 @@ in ''; }; }; + + 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.loaOf (types.submodule ({name, config, ...}: { + 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}"; + }; + })); + }; + + }; + + config = { + name = mkDefault name; + }; + })); + }; }; }; config = mkIf cfg.enable { - assertions = singleton - { message = "Can't be both an exit and a bridge relay at the same time"; - assertion = - cfg.relay.enable -> !(cfg.relay.isBridge && cfg.relay.isExit); - }; + # 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.extraGroups.tor.gid = config.ids.gids.tor; users.extraUsers.tor = @@ -342,9 +582,13 @@ in restartTriggers = [ torRcFile ]; # Translated from the upstream contrib/dist/tor.service.in + preStart = '' + install -o tor -g tor -d ${torDirectory}/onion + ${pkgs.tor}/bin/tor -f ${torRcFile} --verify-config + ''; + serviceConfig = { Type = "simple"; - ExecStartPre = "${pkgs.tor}/bin/tor -f ${torRcFile} --verify-config"; ExecStart = "${pkgs.tor}/bin/tor -f ${torRcFile} --RunAsDaemon 0"; ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; KillSignal = "SIGINT"; diff --git a/nixos/modules/services/security/usbguard.nix b/nixos/modules/services/security/usbguard.nix new file mode 100644 index 000000000000..1f2c56a9efa1 --- /dev/null +++ b/nixos/modules/services/security/usbguard.nix @@ -0,0 +1,200 @@ +{config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.services.usbguard; + + # valid policy options + policy = (types.enum [ "allow" "block" "reject" "keep" "apply-policy" ]); + + # decide what file to use for rules + ruleFile = if cfg.rules != null then pkgs.writeText "usbguard-rules" cfg.rules else cfg.ruleFile; + + daemonConf = '' + # generated by nixos/modules/services/security/usbguard.nix + RuleFile=${ruleFile} + ImplicitPolicyTarget=${cfg.implictPolicyTarget} + PresentDevicePolicy=${cfg.presentDevicePolicy} + PresentControllerPolicy=${cfg.presentControllerPolicy} + InsertedDevicePolicy=${cfg.insertedDevicePolicy} + RestoreControllerDeviceState=${if cfg.restoreControllerDeviceState then "true" else "false"} + # this does not seem useful for endusers to change + DeviceManagerBackend=uevent + IPCAllowedUsers=${concatStringsSep " " cfg.IPCAllowedUsers} + IPCAllowedGroups=${concatStringsSep " " cfg.IPCAllowedGroups} + IPCAccessControlFiles=${cfg.IPCAccessControlFiles} + DeviceRulesWithPort=${if cfg.deviceRulesWithPort then "true" else "false"} + AuditFilePath=${cfg.auditFilePath} + ''; + + daemonConfFile = pkgs.writeText "usbguard-daemon-conf" daemonConf; + +in { + + ###### interface + + options = { + services.usbguard = { + enable = mkEnableOption "USBGuard daemon"; + + ruleFile = mkOption { + type = types.path; + default = "/var/lib/usbguard/rules.conf"; + description = '' + The USBGuard daemon will use this file to load the policy rule set + from it and to write new rules received via the IPC interface. + + Running the command <literal>usbguard generate-policy</literal> as + root will generate a config for your currently plugged in devices. + For a in depth guide consult the official documentation. + + Setting the <literal>rules</literal> option will ignore the + <literal>ruleFile</literal> option. + ''; + }; + + rules = mkOption { + type = types.nullOr types.str; + default = null; + example = '' + allow with-interface equals { 08:*:* } + ''; + description = '' + The USBGuard daemon will load this policy rule set. Modifying it via + the IPC interface won't work if you use this option, since the + contents of this option will be written into the nix-store it will be + read-only. + + You can still use <literal> usbguard generate-policy</literal> to + generate rules, but you would have to insert them here. + + Setting the <literal>rules</literal> option will ignore the + <literal>ruleFile</literal> option. + ''; + }; + + 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. + ''; + }; + + IPCAccessControlFiles = mkOption { + type = types.path; + default = "/var/lib/usbguard/IPCAccessControl.d/"; + description = '' + The files at this location will be interpreted by the daemon as IPC + access control definition files. See the IPC ACCESS CONTROL section + in <citerefentry><refentrytitle>usbguard-daemon.conf</refentrytitle> + <manvolnum>5</manvolnum></citerefentry> for more details. + ''; + }; + + deviceRulesWithPort = mkOption { + type = types.bool; + default = false; + description = '' + Generate device specific rules including the "via-port" attribute. + ''; + }; + + auditFilePath = mkOption { + type = types.path; + default = "/var/log/usbguard/usbguard-audit.log"; + description = '' + USBGuard audit events log file path. + ''; + }; + }; + }; + + + ###### implementation + + config = mkIf cfg.enable { + + environment.systemPackages = [ pkgs.usbguard ]; + + systemd.services.usbguard = { + description = "USBGuard daemon"; + + wantedBy = [ "basic.target" ]; + wants = [ "systemd-udevd.service" "local-fs.target" ]; + + # make sure an empty rule file and required directories exist + preStart = ''mkdir -p $(dirname "${cfg.ruleFile}") "${cfg.IPCAccessControlFiles}" && ([ -f "${cfg.ruleFile}" ] || touch ${cfg.ruleFile})''; + + serviceConfig = { + Type = "simple"; + ExecStart = ''${pkgs.usbguard}/bin/usbguard-daemon -d -k -c ${daemonConfFile}''; + Restart = "on-failure"; + }; + }; + }; +} diff --git a/nixos/modules/services/security/vault.nix b/nixos/modules/services/security/vault.nix new file mode 100644 index 000000000000..146afec344ab --- /dev/null +++ b/nixos/modules/services/security/vault.nix @@ -0,0 +1,143 @@ +{ 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} + } + ''} + ''; +in +{ + options = { + + services.vault = { + + enable = mkEnableOption "Vault daemon"; + + 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 configuration"; + }; + + storageBackend = mkOption { + type = types.enum [ "inmem" "file" "consul" "zookeeper" "s3" "azure" "dynamodb" "etcd" "mssql" "mysql" "postgresql" "swift" "gcs" ]; + 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"; + }; + }; + }; + + 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.extraUsers.vault = { + name = "vault"; + group = "vault"; + uid = config.ids.uids.vault; + description = "Vault daemon user"; + }; + users.extraGroups.vault.gid = config.ids.gids.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. + + preStart = optionalString (cfg.storagePath != null) '' + install -d -m0700 -o vault -g vault "${cfg.storagePath}" + ''; + + serviceConfig = { + User = "vault"; + Group = "vault"; + PermissionsStartOnly = true; + ExecStart = "${pkgs.vault}/bin/vault server -config ${configFile}"; + PrivateDevices = true; + PrivateTmp = true; + ProtectSystem = "full"; + ProtectHome = "read-only"; + AmbientCapabilities = "cap_ipc_lock"; + NoNewPrivileges = true; + KillSignal = "SIGINT"; + TimeoutStopSec = "30s"; + Restart = "on-failure"; + StartLimitInterval = "60s"; + StartLimitBurst = 3; + }; + + unitConfig.RequiresMountsFor = optional (cfg.storagePath != null) cfg.storagePath; + }; + }; + +} |