diff options
Diffstat (limited to 'nixos/modules/security')
-rw-r--r-- | nixos/modules/security/apparmor-suid.nix | 46 | ||||
-rw-r--r-- | nixos/modules/security/apparmor.nix | 68 | ||||
-rw-r--r-- | nixos/modules/security/ca.nix | 26 | ||||
-rw-r--r-- | nixos/modules/security/pam.nix | 286 | ||||
-rw-r--r-- | nixos/modules/security/pam_usb.nix | 41 | ||||
-rw-r--r-- | nixos/modules/security/polkit.nix | 121 | ||||
-rw-r--r-- | nixos/modules/security/rngd.nix | 37 | ||||
-rw-r--r-- | nixos/modules/security/rtkit.nix | 39 | ||||
-rw-r--r-- | nixos/modules/security/setuid-wrapper.c | 81 | ||||
-rw-r--r-- | nixos/modules/security/setuid-wrappers.nix | 121 | ||||
-rw-r--r-- | nixos/modules/security/sudo.nix | 90 |
11 files changed, 956 insertions, 0 deletions
diff --git a/nixos/modules/security/apparmor-suid.nix b/nixos/modules/security/apparmor-suid.nix new file mode 100644 index 0000000000000..bc661164fdc29 --- /dev/null +++ b/nixos/modules/security/apparmor-suid.nix @@ -0,0 +1,46 @@ +{pkgs, config, ...}: +let + cfg = config.security.apparmor; +in +with pkgs.lib; +{ + + options.security.apparmor.confineSUIDApplications = mkOption { + default = true; + description = '' + Install AppArmor profiles for commonly-used SUID application + to mitigate potential privilege escalation attacks due to bugs + in such applications. + + Currently available profiles: ping + ''; + }; + + config = mkIf (cfg.confineSUIDApplications) { + security.apparmor.profiles = [ (pkgs.writeText "ping" '' + #include <tunables/global> + /var/setuid-wrappers/ping { + #include <abstractions/base> + #include <abstractions/consoles> + #include <abstractions/nameservice> + + capability net_raw, + capability setuid, + network inet raw, + + ${pkgs.glibc}/lib/*.so mr, + ${pkgs.libcap}/lib/libcap.so* mr, + ${pkgs.attr}/lib/libattr.so* mr, + + ${pkgs.iputils}/bin/ping mixr, + /var/setuid-wrappers/ping.real r, + + #/etc/modules.conf r, + + ## Site-specific additions and overrides. See local/README for details. + ##include <local/bin.ping> + } + '') ]; + }; + +} diff --git a/nixos/modules/security/apparmor.nix b/nixos/modules/security/apparmor.nix new file mode 100644 index 0000000000000..d4aa0598dd3de --- /dev/null +++ b/nixos/modules/security/apparmor.nix @@ -0,0 +1,68 @@ +{pkgs, config, ...}: + +let + cfg = config.security.apparmor; +in + +with pkgs.lib; + +{ + + ###### interface + + options = { + + security.apparmor = { + + enable = mkOption { + default = false; + description = '' + Enable AppArmor application security system. Enable only if + you want to further improve AppArmor. + ''; + }; + + profiles = mkOption { + default = []; + merge = mergeListOption; + description = '' + List of file names of AppArmor profiles. + ''; + }; + + }; + }; + + + ###### implementation + + config = mkIf (cfg.enable) { + + assertions = [ { assertion = config.boot.kernelPackages.kernel.features ? apparmor + && config.boot.kernelPackages.kernel.features.apparmor; + message = "AppArmor is enabled, but the kernel doesn't have AppArmor support"; } + ]; + + environment.systemPackages = [ pkgs.apparmor ]; + + systemd.services.apparmor = { + #wantedBy = [ "basic.target" ]; + wantedBy = [ "local-fs.target" ]; + path = [ pkgs.apparmor ]; + + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = "yes"; + ExecStart = concatMapStrings (profile: + ''${pkgs.apparmor}/sbin/apparmor_parser -rKv -I ${pkgs.apparmor}/etc/apparmor.d/ "${profile}" ; '' + ) cfg.profiles; + ExecStop = concatMapStrings (profile: + ''${pkgs.apparmor}/sbin/apparmor_parser -Rv -I ${pkgs.apparmor}/etc/apparmor.d/ "${profile}" ; '' + ) cfg.profiles; + }; + + }; + + }; + +} diff --git a/nixos/modules/security/ca.nix b/nixos/modules/security/ca.nix new file mode 100644 index 0000000000000..2e93fb36b4505 --- /dev/null +++ b/nixos/modules/security/ca.nix @@ -0,0 +1,26 @@ +{ config, pkgs, ... }: + +with pkgs.lib; + +{ + + config = { + + environment.etc = + [ { source = "${pkgs.cacert}/etc/ca-bundle.crt"; + target = "ssl/certs/ca-bundle.crt"; + } + + # Backward compatibility; may remove at some point. + { source = "${pkgs.cacert}/etc/ca-bundle.crt"; + target = "ca-bundle.crt"; + } + ]; + + environment.variables.OPENSSL_X509_CERT_FILE = "/etc/ssl/certs/ca-bundle.crt"; + environment.variables.CURL_CA_BUNDLE = "/etc/ssl/certs/ca-bundle.crt"; + environment.variables.GIT_SSL_CAINFO = "/etc/ssl/certs/ca-bundle.crt"; + + }; + +} diff --git a/nixos/modules/security/pam.nix b/nixos/modules/security/pam.nix new file mode 100644 index 0000000000000..3ef01ea2c1791 --- /dev/null +++ b/nixos/modules/security/pam.nix @@ -0,0 +1,286 @@ +# This module provides configuration for the PAM (Pluggable +# Authentication Modules) system. + +{config, pkgs, ...}: + +with pkgs.lib; + +let + + inherit (pkgs) pam_krb5 pam_ccreds; + + pam_ldap = if config.users.ldap.daemon.enable then pkgs.nss_pam_ldapd else pkgs.pam_ldap; + + otherService = pkgs.writeText "other.pam" + '' + auth required pam_warn.so + auth required pam_deny.so + account required pam_warn.so + account required pam_deny.so + password required pam_warn.so + password required pam_deny.so + session required pam_warn.so + session required pam_deny.so + ''; + + # Create a limits.conf(5) file. + makeLimitsConf = limits: + pkgs.writeText "limits.conf" + (concatStringsSep "\n" + (map ({ domain, type, item, value }: + concatStringsSep " " [ domain type item value ]) + limits)); + + motd = pkgs.writeText "motd" config.users.motd; + + makePAMService = + { name + , # If set, root doesn't need to authenticate (e.g. for the "chsh" + # service). + rootOK ? false + , # If set, user listed in /etc/pamusb.conf are able to log in with + # the associated usb key. + usbAuth ? config.security.pam.usb.enable + , # If set, OTPW system will be used (if ~/.otpw exists) + otpwAuth ? config.security.pam.enableOTPW + , # If set, the calling user's SSH agent is used to authenticate + # against the keys in the calling user's ~/.ssh/authorized_keys. + # This is useful for "sudo" on password-less remote systems. + sshAgentAuth ? false + , # If set, the service will register a new session with systemd's + # login manager. If the service is running locally, this will + # give the user ownership of audio devices etc. + startSession ? false + , # Set the login uid of the process (/proc/self/loginuid) for + # auditing purposes. The login uid is only set by "entry + # points" like login and sshd, not by commands like sudo. + setLoginUid ? startSession + , # Whether to forward XAuth keys between users. Mostly useful + # for "su". + forwardXAuth ? false + , # Whether to allow logging into accounts that have no password + # set (i.e., have an empty password field in /etc/passwd or + # /etc/group). This does not enable logging into disabled + # accounts (i.e., that have the password field set to `!'). + # Note that regardless of what the pam_unix documentation says, + # accounts with hashed empty passwords are always allowed to log + # in. + allowNullPassword ? false + , # The limits, as per limits.conf(5). + limits ? config.security.pam.loginLimits + , # Whether to show the message of the day. + showMotd ? false + , # Whether to update /var/log/wtmp. + updateWtmp ? false + }: + + { source = pkgs.writeText "${name}.pam" + # !!! TODO: move the LDAP stuff to the LDAP module, and the + # Samba stuff to the Samba module. This requires that the PAM + # module provides the right hooks. + '' + # Account management. + account sufficient pam_unix.so + ${optionalString config.users.ldap.enable + "account sufficient ${pam_ldap}/lib/security/pam_ldap.so"} + ${optionalString config.krb5.enable + "account sufficient ${pam_krb5}/lib/security/pam_krb5.so"} + + # Authentication management. + ${optionalString rootOK + "auth sufficient pam_rootok.so"} + ${optionalString (config.security.pam.enableSSHAgentAuth && sshAgentAuth) + "auth sufficient ${pkgs.pam_ssh_agent_auth}/libexec/pam_ssh_agent_auth.so file=~/.ssh/authorized_keys:~/.ssh/authorized_keys2:/etc/ssh/authorized_keys.d/%u"} + ${optionalString usbAuth + "auth sufficient ${pkgs.pam_usb}/lib/security/pam_usb.so"} + auth sufficient pam_unix.so ${optionalString allowNullPassword "nullok"} likeauth + ${optionalString otpwAuth + "auth sufficient ${pkgs.otpw}/lib/security/pam_otpw.so"} + ${optionalString config.users.ldap.enable + "auth sufficient ${pam_ldap}/lib/security/pam_ldap.so use_first_pass"} + ${optionalString config.krb5.enable '' + auth [default=ignore success=1 service_err=reset] ${pam_krb5}/lib/security/pam_krb5.so use_first_pass + auth [default=die success=done] ${pam_ccreds}/lib/security/pam_ccreds.so action=validate use_first_pass + auth sufficient ${pam_ccreds}/lib/security/pam_ccreds.so action=store use_first_pass + ''} + auth required pam_deny.so + + # Password management. + password requisite pam_unix.so nullok sha512 + ${optionalString config.users.ldap.enable + "password sufficient ${pam_ldap}/lib/security/pam_ldap.so"} + ${optionalString config.krb5.enable + "password sufficient ${pam_krb5}/lib/security/pam_krb5.so use_first_pass"} + ${optionalString config.services.samba.syncPasswordsByPam + "password optional ${pkgs.samba}/lib/security/pam_smbpass.so nullok use_authtok try_first_pass"} + + # Session management. + session required pam_unix.so + ${optionalString updateWtmp + "session required ${pkgs.pam}/lib/security/pam_lastlog.so silent"} + ${optionalString config.users.ldap.enable + "session optional ${pam_ldap}/lib/security/pam_ldap.so"} + ${optionalString config.krb5.enable + "session optional ${pam_krb5}/lib/security/pam_krb5.so"} + ${optionalString otpwAuth + "session optional ${pkgs.otpw}/lib/security/pam_otpw.so"} + ${optionalString startSession + "session optional ${pkgs.systemd}/lib/security/pam_systemd.so"} + ${optionalString setLoginUid + "session required pam_loginuid.so"} + ${optionalString forwardXAuth + "session optional pam_xauth.so xauthpath=${pkgs.xorg.xauth}/bin/xauth systemuser=99"} + ${optionalString (limits != []) + "session required ${pkgs.pam}/lib/security/pam_limits.so conf=${makeLimitsConf limits}"} + ${optionalString (showMotd && config.users.motd != null) + "session optional ${pkgs.pam}/lib/security/pam_motd.so motd=${motd}"} + ''; + target = "pam.d/${name}"; + }; + +in + +{ + + ###### interface + + options = { + + security.pam.loginLimits = mkOption { + default = []; + example = + [ { domain = "ftp"; + type = "hard"; + item = "nproc"; + value = "0"; + } + { domain = "@student"; + type = "-"; + item = "maxlogins"; + value = "4"; + } + ]; + + description = + '' Define resource limits that should apply to users or groups. + Each item in the list should be an attribute set with a + <varname>domain</varname>, <varname>type</varname>, + <varname>item</varname>, and <varname>value</varname> + attribute. The syntax and semantics of these attributes + must be that described in the limits.conf(5) man page. + ''; + }; + + security.pam.services = mkOption { + default = []; + example = [ + { name = "chsh"; rootOK = true; } + { name = "login"; startSession = true; allowNullPassword = true; + limits = [ + { domain = "ftp"; + type = "hard"; + item = "nproc"; + value = "0"; + } + ]; + } + ]; + + description = + '' + This option defines the PAM services. A service typically + corresponds to a program that uses PAM, + e.g. <command>login</command> or <command>passwd</command>. + Each element of this list is an attribute set describing a + service. The attribute <varname>name</varname> specifies + the name of the service. The attribute + <varname>rootOK</varname> specifies whether the root user is + allowed to use this service without authentication. The + attribute <varname>startSession</varname> specifies whether + systemd's PAM connector module should be used to start a new + session; for local sessions, this will give the user + ownership of devices such as audio and CD-ROM drives. The + attribute <varname>forwardXAuth</varname> specifies whether + X authentication keys should be passed from the calling user + to the target user (e.g. for <command>su</command>). + + The attribute <varname>limits</varname> defines resource limits + that should apply to users or groups for the service. Each item in + the list should be an attribute set with a + <varname>domain</varname>, <varname>type</varname>, + <varname>item</varname>, and <varname>value</varname> attribute. + The syntax and semantics of these attributes must be that described + in the limits.conf(5) man page. + ''; + }; + + security.pam.enableSSHAgentAuth = mkOption { + default = false; + description = + '' + Enable sudo logins if the user's SSH agent provides a key + present in <filename>~/.ssh/authorized_keys</filename>. + This allows machines to exclusively use SSH keys instead of + passwords. + ''; + }; + + security.pam.enableOTPW = mkOption { + default = false; + description = '' + Enable the OTPW (one-time password) PAM module + ''; + }; + + users.motd = mkOption { + default = null; + example = "Today is Sweetmorn, the 4th day of The Aftermath in the YOLD 3178."; + type = types.nullOr types.string; + description = "Message of the day shown to users when they log in."; + }; + + }; + + + ###### implementation + + config = { + + environment.systemPackages = + # Include the PAM modules in the system path mostly for the manpages. + [ pkgs.pam ] + ++ optional config.users.ldap.enable pam_ldap + ++ optionals config.krb5.enable [pam_krb5 pam_ccreds] + ++ optionals config.security.pam.enableOTPW [ pkgs.otpw ]; + + environment.etc = + map makePAMService config.security.pam.services + ++ singleton + { source = otherService; + target = "pam.d/other"; + }; + + security.setuidOwners = [ { + program = "unix_chkpwd"; + source = "${pkgs.pam}/sbin/unix_chkpwd.orig"; + owner = "root"; + setuid = true; + } ]; + + security.pam.services = + # Most of these should be moved to specific modules. + [ { name = "cups"; } + { name = "ejabberd"; } + { name = "ftp"; } + { name = "i3lock"; } + { name = "lshd"; } + { name = "samba"; } + { name = "screen"; } + { name = "vlock"; } + { name = "xlock"; } + { name = "xscreensaver"; } + ]; + + }; + +} diff --git a/nixos/modules/security/pam_usb.nix b/nixos/modules/security/pam_usb.nix new file mode 100644 index 0000000000000..1c2a6a05f2612 --- /dev/null +++ b/nixos/modules/security/pam_usb.nix @@ -0,0 +1,41 @@ +{config, pkgs, ...}: + +with pkgs.lib; + +let + + inherit (pkgs) pam_usb; + + cfg = config.security.pam.usb; + + anyUsbAuth = any (attrByPath ["usbAuth"] false) config.security.pam.services; + +in + +{ + options = { + + security.pam.usb = { + enable = mkOption { + default = false; + description = '' + Enable USB login for all login system unless the service disabled + it. For more information, visit <link + xlink:href="http://pamusb.org/doc/quickstart#setting_up" />. + ''; + }; + + }; + + }; + + config = mkIf (cfg.enable || anyUsbAuth) { + + # pmount need to have a set-uid bit to make pam_usb works in user + # environment. (like su, sudo) + + security.setuidPrograms = [ "pmount" "pumount" ]; + environment.systemPackages = [ pkgs.pmount ]; + + }; +} diff --git a/nixos/modules/security/polkit.nix b/nixos/modules/security/polkit.nix new file mode 100644 index 0000000000000..b9b32496a3687 --- /dev/null +++ b/nixos/modules/security/polkit.nix @@ -0,0 +1,121 @@ +{ config, pkgs, ... }: + +with pkgs.lib; + +let + + cfg = config.security.polkit; + +in + +{ + + options = { + + security.polkit.enable = mkOption { + default = true; + description = "Whether to enable PolKit."; + }; + + security.polkit.permissions = mkOption { + default = ""; + example = + '' + [Disallow Users To Suspend] + Identity=unix-group:users + Action=org.freedesktop.upower.* + ResultAny=no + ResultInactive=no + ResultActive=no + + [Allow Anybody To Eject Disks] + Identity=unix-user:* + Action=org.freedesktop.udisks.drive-eject + ResultAny=yes + ResultInactive=yes + ResultActive=yes + + [Allow Alice To Mount Filesystems After Admin Authentication] + Identity=unix-user:alice + Action=org.freedesktop.udisks.filesystem-mount + ResultAny=auth_admin + ResultInactive=auth_admin + ResultActive=auth_admin + ''; + description = + '' + Allows the default permissions of privileged actions to be overridden. + ''; + }; + + security.polkit.adminIdentities = mkOption { + default = "unix-user:0;unix-group:wheel"; + example = ""; + description = + '' + Specifies which users are considered “administrators”, for those + actions that require the user to authenticate as an + administrator (i.e. have an <literal>auth_admin</literal> + value). By default, this is the <literal>root</literal> + user and all users in the <literal>wheel</literal> group. + ''; + }; + + }; + + + config = mkIf cfg.enable { + + environment.systemPackages = [ pkgs.polkit ]; + + # The polkit daemon reads action files + environment.pathsToLink = [ "/share/polkit-1/actions" ]; + + environment.etc = + [ # No idea what the "null backend" is, but it seems to need this. + { source = "${pkgs.polkit}/etc/polkit-1/nullbackend.conf.d"; + target = "polkit-1/nullbackend.conf.d"; + } + + # This file determines what users are considered + # "administrators". + { source = pkgs.writeText "10-nixos.conf" + '' + [Configuration] + AdminIdentities=${cfg.adminIdentities} + ''; + target = "polkit-1/localauthority.conf.d/10-nixos.conf"; + } + + { source = pkgs.writeText "org.nixos.pkla" cfg.permissions; + target = "polkit-1/localauthority/10-vendor.d/org.nixos.pkla"; + } + ]; + + services.dbus.packages = [ pkgs.polkit ]; + + security.pam.services = [ { name = "polkit-1"; } ]; + + security.setuidPrograms = [ "pkexec" ]; + + security.setuidOwners = singleton + { program = "polkit-agent-helper-1"; + owner = "root"; + group = "root"; + setuid = true; + source = "${pkgs.polkit}/libexec/polkit-1/polkit-agent-helper-1"; + }; + + system.activationScripts.polkit = + '' + mkdir -p /var/lib/polkit-1/localauthority + chmod 700 /var/lib/polkit-1{/localauthority,} + + # Force polkitd to be restarted so that it reloads its + # configuration. + ${pkgs.procps}/bin/pkill -INT -u root -x polkitd + ''; + + }; + +} diff --git a/nixos/modules/security/rngd.nix b/nixos/modules/security/rngd.nix new file mode 100644 index 0000000000000..dd251fe69d310 --- /dev/null +++ b/nixos/modules/security/rngd.nix @@ -0,0 +1,37 @@ +{ config, pkgs, ... }: + +with pkgs.lib; + +{ + options = { + security.rngd.enable = mkOption { + default = true; + description = '' + Whether to enable the rng daemon, which adds entropy from + hardware sources of randomness to the kernel entropy pool when + available. + ''; + }; + }; + + config = mkIf config.security.rngd.enable { + services.udev.extraRules = '' + KERNEL=="random", TAG+="systemd" + SUBSYSTEM=="cpu", ENV{MODALIAS}=="x86cpu:*feature:*009E*", TAG+="systemd", ENV{SYSTEMD_WANTS}+="rngd.service" + KERNEL=="hw_random", TAG+="systemd", ENV{SYSTEMD_WANTS}+="rngd.service" + KERNEL=="tmp0", TAG+="systemd", ENV{SYSTEMD_WANTS}+="rngd.service" + ''; + + systemd.services.rngd = { + bindsTo = [ "dev-random.device" ]; + + after = [ "dev-random.device" ]; + + description = "Hardware RNG Entropy Gatherer Daemon"; + + serviceConfig.ExecStart = "${pkgs.rng_tools}/sbin/rngd -f"; + + restartTriggers = [ pkgs.rng_tools ]; + }; + }; +} diff --git a/nixos/modules/security/rtkit.nix b/nixos/modules/security/rtkit.nix new file mode 100644 index 0000000000000..e47e7baa2b84d --- /dev/null +++ b/nixos/modules/security/rtkit.nix @@ -0,0 +1,39 @@ +# A module for ‘rtkit’, a DBus system service that hands out realtime +# scheduling priority to processes that ask for it. + +{ config, pkgs, ... }: + +with pkgs.lib; + +{ + + options = { + + security.rtkit.enable = mkOption { + default = false; + description = '' + Whether to enable the RealtimeKit system service, which hands + out realtime scheduling priority to user processes on + demand. For example, the PulseAudio server uses this to + acquire realtime priority. + ''; + }; + + }; + + + config = mkIf config.security.rtkit.enable { + + environment.systemPackages = [ pkgs.rtkit ]; + + services.dbus.packages = [ pkgs.rtkit ]; + + users.extraUsers = singleton + { name = "rtkit"; + uid = config.ids.uids.rtkit; + description = "RealtimeKit daemon"; + }; + + }; + +} diff --git a/nixos/modules/security/setuid-wrapper.c b/nixos/modules/security/setuid-wrapper.c new file mode 100644 index 0000000000000..007ffbc34fe9d --- /dev/null +++ b/nixos/modules/security/setuid-wrapper.c @@ -0,0 +1,81 @@ +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <dirent.h> +#include <assert.h> +#include <string.h> +#include <errno.h> + +/* Make sure assertions are not compiled out. */ +#undef NDEBUG + +extern char **environ; + +static char * wrapperDir = WRAPPER_DIR; + +int main(int argc, char * * argv) +{ + char self[PATH_MAX]; + + int len = readlink("/proc/self/exe", self, sizeof(self) - 1); + assert (len > 0); + self[len] = 0; + + /* Make sure that we are being executed from the right location, + i.e., `wrapperDir'. This is to prevent someone from + creating hard link `X' from some other location, along with a + false `X.real' file, to allow arbitrary programs from being + executed setuid. */ + assert ((strncmp(self, wrapperDir, sizeof(wrapperDir)) == 0) && + (self[strlen(wrapperDir)] == '/')); + + /* Make *really* *really* sure that we were executed as `self', + and not, say, as some other setuid program. That is, our + effective uid/gid should match the uid/gid of `self'. */ + //printf("%d %d\n", geteuid(), getegid()); + + struct stat st; + assert (lstat(self, &st) != -1); + + //printf("%d %d\n", st.st_uid, st.st_gid); + + assert ((st.st_mode & S_ISUID) == 0 || + (st.st_uid == geteuid())); + + assert ((st.st_mode & S_ISGID) == 0 || + st.st_gid == getegid()); + + /* And, of course, we shouldn't be writable. */ + assert (!(st.st_mode & (S_IWGRP | S_IWOTH))); + + + /* Read the path of the real (wrapped) program from <self>.real. */ + char realFN[PATH_MAX + 10]; + int realFNSize = snprintf (realFN, sizeof(realFN), "%s.real", self); + assert (realFNSize < sizeof(realFN)); + + int fdSelf = open(realFN, O_RDONLY); + assert (fdSelf != -1); + + char real[PATH_MAX]; + len = read(fdSelf, real, PATH_MAX); + assert (len != -1); + assert (len < sizeof (real)); + assert (len > 0); + real[len] = 0; + + close(fdSelf); + + //printf("real = %s, len = %d\n", real, len); + + execve(real, argv, environ); + + fprintf(stderr, "%s: cannot run `%s': %s\n", + argv[0], real, strerror(errno)); + + exit(1); +} diff --git a/nixos/modules/security/setuid-wrappers.nix b/nixos/modules/security/setuid-wrappers.nix new file mode 100644 index 0000000000000..e75679e5ff69d --- /dev/null +++ b/nixos/modules/security/setuid-wrappers.nix @@ -0,0 +1,121 @@ +{pkgs, config, ...}: + +with pkgs.lib; + +let + + inherit (config.security) wrapperDir; + + setuidWrapper = pkgs.stdenv.mkDerivation { + name = "setuid-wrapper"; + buildCommand = '' + ensureDir $out/bin + gcc -Wall -O2 -DWRAPPER_DIR=\"${wrapperDir}\" \ + ${./setuid-wrapper.c} -o $out/bin/setuid-wrapper + strip -s $out/bin/setuid-wrapper + ''; + }; + +in + +{ + + ###### interface + + options = { + + security.setuidPrograms = mkOption { + default = []; + description = '' + The Nix store cannot contain setuid/setgid programs directly. + For this reason, NixOS can automatically generate wrapper + programs that have the necessary privileges. This option + lists the names of programs in the system environment for + which setuid root wrappers should be created. + ''; + }; + + security.setuidOwners = mkOption { + default = []; + example = + [ { program = "sendmail"; + owner = "nobody"; + group = "postdrop"; + setuid = false; + setgid = true; + } + ]; + description = '' + This option allows the ownership and permissions on the setuid + wrappers for specific programs to be overridden from the + default (setuid root, but not setgid root). + ''; + }; + + security.wrapperDir = mkOption { + default = "/var/setuid-wrappers"; + description = '' + This option defines the path to the setuid wrappers. It + should generally not be overriden. Some packages in Nixpkgs + expect that <option>wrapperDir</option> is + <filename>/var/setuid-wrappers</filename>. + ''; + }; + + }; + + + ###### implementation + + config = { + + security.setuidPrograms = + [ "fusermount" "wodim" "cdrdao" "growisofs" ]; + + system.activationScripts.setuid = + let + setuidPrograms = + (map (x: { program = x; owner = "root"; group = "root"; setuid = true; }) + config.security.setuidPrograms) + ++ config.security.setuidOwners; + + makeSetuidWrapper = + { program + , source ? "" + , owner ? "nobody" + , group ? "nogroup" + , setuid ? false + , setgid ? false + , permissions ? "u+rx,g+x,o+x" + }: + + '' + source=${if source != "" then source else "$(PATH=$SETUID_PATH type -tP ${program})"} + if test -z "$source"; then + # If we can't find the program, fall back to the + # system profile. + source=/nix/var/nix/profiles/default/bin/${program} + fi + + cp ${setuidWrapper}/bin/setuid-wrapper ${wrapperDir}/${program} + echo -n "$source" > ${wrapperDir}/${program}.real + chmod 0000 ${wrapperDir}/${program} # to prevent races + chown ${owner}.${group} ${wrapperDir}/${program} + chmod "u${if setuid then "+" else "-"}s,g${if setgid then "+" else "-"}s,${permissions}" ${wrapperDir}/${program} + ''; + + in stringAfter [ "users" ] + '' + # Look in the system path and in the default profile for + # programs to be wrapped. + SETUID_PATH=${config.system.path}/bin:${config.system.path}/sbin + + if test -d ${wrapperDir}; then rm -f ${wrapperDir}/*; fi # */ + mkdir -p ${wrapperDir} + + ${concatMapStrings makeSetuidWrapper setuidPrograms} + ''; + + }; + +} diff --git a/nixos/modules/security/sudo.nix b/nixos/modules/security/sudo.nix new file mode 100644 index 0000000000000..cd548f4a4fe0c --- /dev/null +++ b/nixos/modules/security/sudo.nix @@ -0,0 +1,90 @@ +{pkgs, config, ...}: + +with pkgs.lib; + +let + + cfg = config.security.sudo; + + inherit (pkgs) sudo; + +in + +{ + + ###### interface + + options = { + + security.sudo.enable = mkOption { + default = true; + description = + '' + Whether to enable the <command>sudo</command> command, which + allows non-root users to execute commands as root. + ''; + }; + + security.sudo.wheelNeedsPassword = mkOption { + default = true; + description = + '' + Whether users of the <code>wheel</code> group can execute + commands as super user without entering a password. + ''; + }; + + security.sudo.configFile = mkOption { + # Note: if syntax errors are detected in this file, the NixOS + # configuration will fail to build. + description = + '' + This string contains the contents of the + <filename>sudoers</filename> file. + ''; + }; + }; + + + ###### implementation + + config = mkIf cfg.enable { + + security.sudo.configFile = + '' + # Don't edit this file. Set the NixOS option ‘security.sudo.configFile’ instead. + + # Environment variables to keep for root and %wheel. + Defaults:root,%wheel env_keep+=LOCALE_ARCHIVE + Defaults:root,%wheel env_keep+=NIX_CONF_DIR + Defaults:root,%wheel env_keep+=NIX_PATH + Defaults:root,%wheel env_keep+=TERMINFO_DIRS + + # Keep SSH_AUTH_SOCK so that pam_ssh_agent_auth.so can do its magic. + Defaults env_keep+=SSH_AUTH_SOCK + + # "root" is allowed to do anything. + root ALL=(ALL) SETENV: ALL + + # Users in the "wheel" group can do anything. + %wheel ALL=(ALL) ${if cfg.wheelNeedsPassword then "" else "NOPASSWD: ALL, "}SETENV: ALL + ''; + + security.setuidPrograms = [ "sudo" "sudoedit" ]; + + environment.systemPackages = [ sudo ]; + + security.pam.services = [ { name = "sudo"; sshAgentAuth = true; } ]; + + environment.etc = singleton + { source = 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"; + target = "sudoers"; + mode = "0440"; + }; + + }; + +} |