diff options
Diffstat (limited to 'nixos/modules/security')
-rw-r--r-- | nixos/modules/security/acme.nix | 61 | ||||
-rw-r--r-- | nixos/modules/security/audit.nix | 6 | ||||
-rw-r--r-- | nixos/modules/security/duosec.nix | 4 | ||||
-rw-r--r-- | nixos/modules/security/pam.nix | 50 | ||||
-rw-r--r-- | nixos/modules/security/sudo.nix | 135 | ||||
-rw-r--r-- | nixos/modules/security/wrappers/default.nix | 4 | ||||
-rw-r--r-- | nixos/modules/security/wrappers/wrapper.c | 2 |
7 files changed, 217 insertions, 45 deletions
diff --git a/nixos/modules/security/acme.nix b/nixos/modules/security/acme.nix index df0b4986eb82..eb705007d028 100644 --- a/nixos/modules/security/acme.nix +++ b/nixos/modules/security/acme.nix @@ -6,10 +6,11 @@ let cfg = config.security.acme; - certOpts = { ... }: { + certOpts = { name, ... }: { options = { webroot = mkOption { type = types.str; + example = "/var/lib/acme/acme-challenges"; description = '' Where the webroot of the HTTP vhost is located. <filename>.well-known/acme-challenge/</filename> directory @@ -20,8 +21,8 @@ let }; domain = mkOption { - type = types.nullOr types.str; - default = null; + type = types.str; + default = name; description = "Domain to fetch certificate for (defaults to the entry name)"; }; @@ -48,7 +49,7 @@ let default = false; description = '' Give read permissions to the specified group - (<option>security.acme.group</option>) to read SSL private certificates. + (<option>security.acme.cert.<name>.group</option>) to read SSL private certificates. ''; }; @@ -110,7 +111,7 @@ let } ''; description = '' - Extra domain names for which certificates are to be issued, with their + A list of extra domain names, which are included in the one certificate to be issued, with their own server roots if needed. ''; }; @@ -211,8 +212,9 @@ in domain = if data.domain != null then data.domain else cert; cpath = lpath + optionalString (data.activationDelay != null) ".staging"; lpath = "${cfg.directory}/${cert}"; + cpath = "${cfg.directory}/${cert}"; rights = if data.allowKeysForGroup then "750" else "700"; - cmdline = [ "-v" "-d" domain "--default_root" data.webroot "--valid_min" cfg.validMin ] + cmdline = [ "-v" "-d" data.domain "--default_root" data.webroot "--valid_min" cfg.validMin ] ++ optionals (data.email != null) [ "--email" data.email ] ++ concatMap (p: [ "-f" p ]) data.plugins ++ concatLists (mapAttrsToList (name: root: [ "-d" (if root == null then name else "${name}:${root}")]) data.extraDomains) @@ -284,6 +286,7 @@ in }; selfsignedService = { description = "Create preliminary self-signed certificate for ${cert}"; + path = [ pkgs.openssl ]; preStart = '' if [ ! -d '${cpath}' ] then @@ -294,37 +297,41 @@ in ''; script = '' - # Create self-signed key - workdir="/run/acme-selfsigned-${cert}" - ${pkgs.openssl.bin}/bin/openssl genrsa -des3 -passout pass:x -out $workdir/server.pass.key 2048 - ${pkgs.openssl.bin}/bin/openssl rsa -passin pass:x -in $workdir/server.pass.key -out $workdir/server.key - ${pkgs.openssl.bin}/bin/openssl req -new -key $workdir/server.key -out $workdir/server.csr \ + workdir="$(mktemp -d)" + + # Create CA + openssl genrsa -des3 -passout pass:x -out $workdir/ca.pass.key 2048 + openssl rsa -passin pass:x -in $workdir/ca.pass.key -out $workdir/ca.key + openssl req -new -key $workdir/ca.key -out $workdir/ca.csr \ + -subj "/C=UK/ST=Warwickshire/L=Leamington/O=OrgName/OU=Security Department/CN=example.com" + openssl x509 -req -days 1 -in $workdir/ca.csr -signkey $workdir/ca.key -out $workdir/ca.crt + + # Create key + openssl genrsa -des3 -passout pass:x -out $workdir/server.pass.key 2048 + openssl rsa -passin pass:x -in $workdir/server.pass.key -out $workdir/server.key + openssl req -new -key $workdir/server.key -out $workdir/server.csr \ -subj "/C=UK/ST=Warwickshire/L=Leamington/O=OrgName/OU=IT Department/CN=example.com" - ${pkgs.openssl.bin}/bin/openssl x509 -req -days 1 -in $workdir/server.csr -signkey $workdir/server.key -out $workdir/server.crt + openssl x509 -req -days 1 -in $workdir/server.csr -CA $workdir/ca.crt \ + -CAkey $workdir/ca.key -CAserial $workdir/ca.srl -CAcreateserial \ + -out $workdir/server.crt - # Move key to destination - mv $workdir/server.key ${cpath}/key.pem - mv $workdir/server.crt ${cpath}/fullchain.pem + # Copy key to destination + cp $workdir/server.key ${cpath}/key.pem - # Create full.pem for e.g. lighttpd (same format as "simp_le ... -f full.pem" creates) - cat "${cpath}/key.pem" "${cpath}/fullchain.pem" > "${cpath}/full.pem" + # Create fullchain.pem (same format as "simp_le ... -f fullchain.pem" creates) + cat $workdir/{server.crt,ca.crt} > "${cpath}/fullchain.pem" - # Clean up working directory - rm $workdir/server.csr - rm $workdir/server.pass.key + # Create full.pem for e.g. lighttpd + cat $workdir/{server.key,server.crt,ca.crt} > "${cpath}/full.pem" # Give key acme permissions - chmod ${rights} '${cpath}/key.pem' - chown '${data.user}:${data.group}' '${cpath}/key.pem' - chmod ${rights} '${cpath}/fullchain.pem' - chown '${data.user}:${data.group}' '${cpath}/fullchain.pem' - chmod ${rights} '${cpath}/full.pem' - chown '${data.user}:${data.group}' '${cpath}/full.pem' + chown '${data.user}:${data.group}' "${cpath}/"{key,fullchain,full}.pem + chmod ${rights} "${cpath}/"{key,fullchain,full}.pem ''; serviceConfig = { Type = "oneshot"; - RuntimeDirectory = "acme-selfsigned-${cert}"; PermissionsStartOnly = true; + PrivateTmp = true; User = data.user; Group = data.group; }; diff --git a/nixos/modules/security/audit.nix b/nixos/modules/security/audit.nix index 7ac21fd96507..2b22bdd9f0ae 100644 --- a/nixos/modules/security/audit.nix +++ b/nixos/modules/security/audit.nix @@ -13,7 +13,7 @@ let }; disableScript = pkgs.writeScript "audit-disable" '' - #!${pkgs.stdenv.shell} -eu + #!${pkgs.runtimeShell} -eu # Explicitly disable everything, as otherwise journald might start it. auditctl -D auditctl -e 0 -a task,never @@ -23,7 +23,7 @@ let # put in the store like this. At the same time, it doesn't feel like a huge deal and working # around that is a pain so I'm leaving it like this for now. startScript = pkgs.writeScript "audit-start" '' - #!${pkgs.stdenv.shell} -eu + #!${pkgs.runtimeShell} -eu # Clear out any rules we may start with auditctl -D @@ -43,7 +43,7 @@ let ''; stopScript = pkgs.writeScript "audit-stop" '' - #!${pkgs.stdenv.shell} -eu + #!${pkgs.runtimeShell} -eu # Clear the rules auditctl -D diff --git a/nixos/modules/security/duosec.nix b/nixos/modules/security/duosec.nix index 9ca818e86ffa..df6108dede7c 100644 --- a/nixos/modules/security/duosec.nix +++ b/nixos/modules/security/duosec.nix @@ -25,14 +25,14 @@ let loginCfgFile = optional cfg.ssh.enable { source = pkgs.writeText "login_duo.conf" configFile; mode = "0600"; - uid = config.ids.uids.sshd; + user = "sshd"; target = "duo/login_duo.conf"; }; pamCfgFile = optional cfg.pam.enable { source = pkgs.writeText "pam_duo.conf" configFile; mode = "0600"; - uid = config.ids.uids.sshd; + user = "sshd"; target = "duo/pam_duo.conf"; }; in diff --git a/nixos/modules/security/pam.nix b/nixos/modules/security/pam.nix index 2d6713311a45..48998285d89d 100644 --- a/nixos/modules/security/pam.nix +++ b/nixos/modules/security/pam.nix @@ -46,6 +46,18 @@ let ''; }; + googleAuthenticator = { + enable = mkOption { + default = false; + type = types.bool; + description = '' + If set, users with enabled Google Authenticator (created + <filename>~/.google_authenticator</filename>) will be required + to provide Google Authenticator token to log in. + ''; + }; + }; + usbAuth = mkOption { default = config.security.pam.usb.enable; type = types.bool; @@ -222,6 +234,22 @@ let password, KDE will prompt separately after login. ''; }; + sssdStrictAccess = mkOption { + default = false; + type = types.bool; + description = "enforce sssd access control"; + }; + + enableGnomeKeyring = mkOption { + default = false; + type = types.bool; + description = '' + If enabled, pam_gnome_keyring will attempt to automatically unlock the + user's default Gnome keyring upon login. If the user login password does + not match their keyring password, Gnome Keyring will prompt separately + after login. + ''; + }; text = mkOption { type = types.nullOr types.lines; @@ -241,11 +269,13 @@ let text = mkDefault ('' # Account management. - account sufficient pam_unix.so + account ${if cfg.sssdStrictAccess then "required" else "sufficient"} pam_unix.so ${optionalString use_ldap "account sufficient ${pam_ldap}/lib/security/pam_ldap.so"} - ${optionalString config.services.sssd.enable + ${optionalString (config.services.sssd.enable && cfg.sssdStrictAccess==false) "account sufficient ${pkgs.sssd}/lib/security/pam_sss.so"} + ${optionalString (config.services.sssd.enable && cfg.sssdStrictAccess) + "account [default=bad success=ok user_unknown=ignore] ${pkgs.sssd}/lib/security/pam_sss.so"} ${optionalString config.krb5.enable "account sufficient ${pam_krb5}/lib/security/pam_krb5.so"} @@ -273,7 +303,12 @@ let # prompts the user for password so we run it once with 'required' at an # earlier point and it will run again with 'sufficient' further down. # We use try_first_pass the second time to avoid prompting password twice - (optionalString (cfg.unixAuth && (config.security.pam.enableEcryptfs || cfg.pamMount || cfg.enableKwallet)) '' + (optionalString (cfg.unixAuth && + (config.security.pam.enableEcryptfs + || cfg.pamMount + || cfg.enableKwallet + || cfg.enableGnomeKeyring + || cfg.googleAuthenticator.enable)) '' auth required pam_unix.so ${optionalString cfg.allowNullPassword "nullok"} likeauth ${optionalString config.security.pam.enableEcryptfs "auth optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so unwrap"} @@ -282,6 +317,10 @@ let ${optionalString cfg.enableKwallet ("auth optional ${pkgs.plasma5.kwallet-pam}/lib/security/pam_kwallet5.so" + " kwalletd=${pkgs.libsForQt5.kwallet.bin}/bin/kwalletd5")} + ${optionalString cfg.enableGnomeKeyring + ("auth optional ${pkgs.gnome3.gnome-keyring}/lib/security/pam_gnome_keyring.so")} + ${optionalString cfg.googleAuthenticator.enable + "auth required ${pkgs.googleAuthenticator}/lib/security/pam_google_authenticator.so no_increment_hotp"} '') + '' ${optionalString cfg.unixAuth "auth sufficient pam_unix.so ${optionalString cfg.allowNullPassword "nullok"} likeauth try_first_pass"} @@ -351,6 +390,10 @@ let ${optionalString (cfg.enableKwallet) ("session optional ${pkgs.plasma5.kwallet-pam}/lib/security/pam_kwallet5.so" + " kwalletd=${pkgs.libsForQt5.kwallet.bin}/bin/kwalletd5")} + ${optionalString (cfg.enableGnomeKeyring) + "session optional ${pkgs.gnome3.gnome-keyring}/lib/security/pam_gnome_keyring.so auto_start"} + ${optionalString (config.virtualisation.lxc.lxcfs.enable) + "session optional ${pkgs.lxc}/lib/security/pam_cgfs.so -c all"} ''); }; @@ -519,7 +562,6 @@ in ftp = {}; i3lock = {}; i3lock-color = {}; - swaylock = {}; screen = {}; vlock = {}; xlock = {}; diff --git a/nixos/modules/security/sudo.nix b/nixos/modules/security/sudo.nix index cfd0595e63b7..24283e1d6165 100644 --- a/nixos/modules/security/sudo.nix +++ b/nixos/modules/security/sudo.nix @@ -8,6 +8,22 @@ let inherit (pkgs) sudo; + toUserString = user: if (isInt user) then "#${toString user}" else "${user}"; + toGroupString = group: if (isInt group) then "%#${toString group}" else "%${group}"; + + toCommandOptionsString = options: + "${concatStringsSep ":" options}${optionalString (length options != 0) ":"} "; + + toCommandsString = commands: + concatStringsSep ", " ( + map (command: + if (isString command) then + command + else + "${toCommandOptionsString command.options}${command.command}" + ) commands + ); + in { @@ -31,8 +47,8 @@ in default = true; description = '' - Whether users of the <code>wheel</code> group can execute - commands as super user without entering a password. + Whether users of the <code>wheel</code> group must + provide a password to run commands as super user via <command>sudo</command>. ''; }; @@ -47,6 +63,97 @@ in ''; }; + security.sudo.extraRules = mkOption { + description = '' + Define specific rules to be in the <filename>sudoers</filename> file. + ''; + default = []; + example = [ + # Allow execution of any command by all users in group sudo, + # requiring a password. + { groups = [ "sudo" ]; commands = [ "ALL" ]; } + + # Allow execution of "/home/root/secret.sh" by user `backup`, `database` + # and the group with GID `1006` without a password. + { users = [ "backup" ]; groups = [ 1006 ]; + commands = [ { command = "/home/root/secret.sh"; options = [ "SETENV" "NOPASSWD" ]; } ]; } + + # Allow all users of group `bar` to run two executables as user `foo` + # with arguments being pre-set. + { groups = [ "bar" ]; runAs = "foo"; + commands = + [ "/home/baz/cmd1.sh hello-sudo" + { command = ''/home/baz/cmd2.sh ""''; options = [ "SETENV" ]; } ]; } + ]; + type = with types; listOf (submodule { + options = { + users = mkOption { + type = with types; listOf (either string int); + description = '' + The usernames / UIDs this rule should apply for. + ''; + default = []; + }; + + groups = mkOption { + type = with types; listOf (either string int); + description = '' + The groups / GIDs this rule should apply for. + ''; + default = []; + }; + + host = mkOption { + type = types.string; + default = "ALL"; + description = '' + For what host this rule should apply. + ''; + }; + + runAs = mkOption { + type = with types; string; + default = "ALL:ALL"; + description = '' + Under which user/group the specified command is allowed to run. + + A user can be specified using just the username: <code>"foo"</code>. + It is also possible to specify a user/group combination using <code>"foo:bar"</code> + or to only allow running as a specific group with <code>":bar"</code>. + ''; + }; + + commands = mkOption { + description = '' + The commands for which the rule should apply. + ''; + type = with types; listOf (either string (submodule { + + options = { + command = mkOption { + type = with types; string; + description = '' + A command being either just a path to a binary to allow any arguments, + the full command with arguments pre-set or with <code>""</code> used as the argument, + not allowing arguments to the command at all. + ''; + }; + + options = mkOption { + type = with types; listOf (enum [ "NOPASSWD" "PASSWD" "NOEXEC" "EXEC" "SETENV" "NOSETENV" "LOG_INPUT" "NOLOG_INPUT" "LOG_OUTPUT" "NOLOG_OUTPUT" ]); + description = '' + Options for running the command. Refer to the <a href="https://www.sudo.ws/man/1.7.10/sudoers.man.html">sudo manual</a>. + ''; + default = []; + }; + }; + + })); + }; + }; + }); + }; + security.sudo.extraConfig = mkOption { type = types.lines; default = ""; @@ -61,10 +168,16 @@ in config = mkIf cfg.enable { + security.sudo.extraRules = [ + { groups = [ "wheel" ]; + commands = [ { command = "ALL"; options = (if cfg.wheelNeedsPassword then [ "SETENV" ] else [ "NOPASSWD" "SETENV" ]); } ]; + } + ]; + security.sudo.configFile = '' # Don't edit this file. Set the NixOS options ‘security.sudo.configFile’ - # or ‘security.sudo.extraConfig’ instead. + # or ‘security.sudo.extraRules’ instead. # Keep SSH_AUTH_SOCK so that pam_ssh_agent_auth.so can do its magic. Defaults env_keep+=SSH_AUTH_SOCK @@ -72,8 +185,18 @@ in # "root" is allowed to do anything. root ALL=(ALL:ALL) SETENV: ALL - # Users in the "wheel" group can do anything. - %wheel ALL=(ALL:ALL) ${if cfg.wheelNeedsPassword then "" else "NOPASSWD: ALL, "}SETENV: ALL + # extraRules + ${concatStringsSep "\n" ( + lists.flatten ( + map ( + rule: if (length rule.commands != 0) then [ + (map (user: "${toUserString user} ${rule.host}=(${rule.runAs}) ${toCommandsString rule.commands}") rule.users) + (map (group: "${toGroupString group} ${rule.host}=(${rule.runAs}) ${toCommandsString rule.commands}") rule.groups) + ] else [] + ) cfg.extraRules + ) + )} + ${cfg.extraConfig} ''; @@ -92,7 +215,7 @@ in { src = pkgs.writeText "sudoers-in" cfg.configFile; } # Make sure that the sudoers file is syntactically valid. # (currently disabled - NIXOS-66) - "${pkgs.sudo}/sbin/visudo -f $src -c && cp $src $out"; + "${pkgs.buildPackages.sudo}/sbin/visudo -f $src -c && cp $src $out"; target = "sudoers"; mode = "0440"; }; diff --git a/nixos/modules/security/wrappers/default.nix b/nixos/modules/security/wrappers/default.nix index 1f64213accd4..77e4b2a616d8 100644 --- a/nixos/modules/security/wrappers/default.nix +++ b/nixos/modules/security/wrappers/default.nix @@ -17,7 +17,7 @@ let hardeningEnable = [ "pie" ]; installPhase = '' mkdir -p $out/bin - gcc -Wall -O2 -DWRAPPER_DIR=\"${parentWrapperDir}\" \ + $CC -Wall -O2 -DWRAPPER_DIR=\"${parentWrapperDir}\" \ -lcap-ng -lcap ${./wrapper.c} -o $out/bin/security-wrapper ''; }; @@ -79,7 +79,7 @@ let ({ owner = "root"; group = "root"; } // s) - else if + else if (s ? "setuid" && s.setuid) || (s ? "setgid" && s.setgid) || (s ? "permissions") diff --git a/nixos/modules/security/wrappers/wrapper.c b/nixos/modules/security/wrappers/wrapper.c index 7091e314bb22..494e9e93ac22 100644 --- a/nixos/modules/security/wrappers/wrapper.c +++ b/nixos/modules/security/wrappers/wrapper.c @@ -10,8 +10,8 @@ #include <errno.h> #include <linux/capability.h> #include <sys/capability.h> -#include <linux/prctl.h> #include <sys/prctl.h> +#include <limits.h> #include <cap-ng.h> // Make sure assertions are not compiled out, we use them to codify |