at 23.05-pre 7.7 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 cfg = config.security.duosec; 7 8 boolToStr = b: if b then "yes" else "no"; 9 10 configFilePam = '' 11 [duo] 12 ikey=${cfg.integrationKey} 13 host=${cfg.host} 14 ${optionalString (cfg.groups != "") ("groups="+cfg.groups)} 15 failmode=${cfg.failmode} 16 pushinfo=${boolToStr cfg.pushinfo} 17 autopush=${boolToStr cfg.autopush} 18 prompts=${toString cfg.prompts} 19 fallback_local_ip=${boolToStr cfg.fallbackLocalIP} 20 ''; 21 22 configFileLogin = configFilePam + '' 23 motd=${boolToStr cfg.motd} 24 accept_env_factor=${boolToStr cfg.acceptEnvFactor} 25 ''; 26in 27{ 28 imports = [ 29 (mkRenamedOptionModule [ "security" "duosec" "group" ] [ "security" "duosec" "groups" ]) 30 (mkRenamedOptionModule [ "security" "duosec" "ikey" ] [ "security" "duosec" "integrationKey" ]) 31 (mkRemovedOptionModule [ "security" "duosec" "skey" ] "The insecure security.duosec.skey option has been replaced by a new security.duosec.secretKeyFile option. Use this new option to store a secure copy of your key instead.") 32 ]; 33 34 options = { 35 security.duosec = { 36 ssh.enable = mkOption { 37 type = types.bool; 38 default = false; 39 description = lib.mdDoc "If enabled, protect SSH logins with Duo Security."; 40 }; 41 42 pam.enable = mkOption { 43 type = types.bool; 44 default = false; 45 description = lib.mdDoc "If enabled, protect logins with Duo Security using PAM support."; 46 }; 47 48 integrationKey = mkOption { 49 type = types.str; 50 description = lib.mdDoc "Integration key."; 51 }; 52 53 secretKeyFile = mkOption { 54 type = types.nullOr types.path; 55 default = null; 56 description = lib.mdDoc '' 57 A file containing your secret key. The security of your Duo application is tied to the security of your secret key. 58 ''; 59 example = "/run/keys/duo-skey"; 60 }; 61 62 host = mkOption { 63 type = types.str; 64 description = lib.mdDoc "Duo API hostname."; 65 }; 66 67 groups = mkOption { 68 type = types.str; 69 default = ""; 70 example = "users,!wheel,!*admin guests"; 71 description = lib.mdDoc '' 72 If specified, Duo authentication is required only for users 73 whose primary group or supplementary group list matches one 74 of the space-separated pattern lists. Refer to 75 <https://duo.com/docs/duounix> for details. 76 ''; 77 }; 78 79 failmode = mkOption { 80 type = types.enum [ "safe" "secure" ]; 81 default = "safe"; 82 description = lib.mdDoc '' 83 On service or configuration errors that prevent Duo 84 authentication, fail "safe" (allow access) or "secure" (deny 85 access). The default is "safe". 86 ''; 87 }; 88 89 pushinfo = mkOption { 90 type = types.bool; 91 default = false; 92 description = lib.mdDoc '' 93 Include information such as the command to be executed in 94 the Duo Push message. 95 ''; 96 }; 97 98 autopush = mkOption { 99 type = types.bool; 100 default = false; 101 description = lib.mdDoc '' 102 If `true`, Duo Unix will automatically send 103 a push login request to the users phone, falling back on a 104 phone call if push is unavailable. If 105 `false`, the user will be prompted to 106 choose an authentication method. When configured with 107 `autopush = yes`, we recommend setting 108 `prompts = 1`. 109 ''; 110 }; 111 112 motd = mkOption { 113 type = types.bool; 114 default = false; 115 description = lib.mdDoc '' 116 Print the contents of `/etc/motd` to screen 117 after a successful login. 118 ''; 119 }; 120 121 prompts = mkOption { 122 type = types.enum [ 1 2 3 ]; 123 default = 3; 124 description = lib.mdDoc '' 125 If a user fails to authenticate with a second factor, Duo 126 Unix will prompt the user to authenticate again. This option 127 sets the maximum number of prompts that Duo Unix will 128 display before denying access. Must be 1, 2, or 3. Default 129 is 3. 130 131 For example, when `prompts = 1`, the user 132 will have to successfully authenticate on the first prompt, 133 whereas if `prompts = 2`, if the user 134 enters incorrect information at the initial prompt, he/she 135 will be prompted to authenticate again. 136 137 When configured with `autopush = true`, we 138 recommend setting `prompts = 1`. 139 ''; 140 }; 141 142 acceptEnvFactor = mkOption { 143 type = types.bool; 144 default = false; 145 description = lib.mdDoc '' 146 Look for factor selection or passcode in the 147 `$DUO_PASSCODE` environment variable before 148 prompting the user for input. 149 150 When $DUO_PASSCODE is non-empty, it will override 151 autopush. The SSH client will need SendEnv DUO_PASSCODE in 152 its configuration, and the SSH server will similarly need 153 AcceptEnv DUO_PASSCODE. 154 ''; 155 }; 156 157 fallbackLocalIP = mkOption { 158 type = types.bool; 159 default = false; 160 description = lib.mdDoc '' 161 Duo Unix reports the IP address of the authorizing user, for 162 the purposes of authorization and whitelisting. If Duo Unix 163 cannot detect the IP address of the client, setting 164 `fallbackLocalIP = yes` will cause Duo Unix 165 to send the IP address of the server it is running on. 166 167 If you are using IP whitelisting, enabling this option could 168 cause unauthorized logins if the local IP is listed in the 169 whitelist. 170 ''; 171 }; 172 173 allowTcpForwarding = mkOption { 174 type = types.bool; 175 default = false; 176 description = lib.mdDoc '' 177 By default, when SSH forwarding, enabling Duo Security will 178 disable TCP forwarding. By enabling this, you potentially 179 undermine some of the SSH based login security. Note this is 180 not needed if you use PAM. 181 ''; 182 }; 183 }; 184 }; 185 186 config = mkIf (cfg.ssh.enable || cfg.pam.enable) { 187 environment.systemPackages = [ pkgs.duo-unix ]; 188 189 security.wrappers.login_duo = 190 { setuid = true; 191 owner = "root"; 192 group = "root"; 193 source = "${pkgs.duo-unix.out}/bin/login_duo"; 194 }; 195 196 system.activationScripts = { 197 login_duo = mkIf cfg.ssh.enable '' 198 if test -f "${cfg.secretKeyFile}"; then 199 mkdir -m 0755 -p /etc/duo 200 201 umask 0077 202 conf="$(mktemp)" 203 { 204 cat ${pkgs.writeText "login_duo.conf" configFileLogin} 205 printf 'skey = %s\n' "$(cat ${cfg.secretKeyFile})" 206 } >"$conf" 207 208 chown sshd "$conf" 209 mv -fT "$conf" /etc/duo/login_duo.conf 210 fi 211 ''; 212 pam_duo = mkIf cfg.pam.enable '' 213 if test -f "${cfg.secretKeyFile}"; then 214 mkdir -m 0755 -p /etc/duo 215 216 umask 0077 217 conf="$(mktemp)" 218 { 219 cat ${pkgs.writeText "login_duo.conf" configFilePam} 220 printf 'skey = %s\n' "$(cat ${cfg.secretKeyFile})" 221 } >"$conf" 222 223 mv -fT "$conf" /etc/duo/pam_duo.conf 224 fi 225 ''; 226 }; 227 228 /* If PAM *and* SSH are enabled, then don't do anything special. 229 If PAM isn't used, set the default SSH-only options. */ 230 services.openssh.extraConfig = mkIf (cfg.ssh.enable || cfg.pam.enable) ( 231 if cfg.pam.enable then "UseDNS no" else '' 232 # Duo Security configuration 233 ForceCommand ${config.security.wrapperDir}/login_duo 234 PermitTunnel no 235 ${optionalString (!cfg.allowTcpForwarding) '' 236 AllowTcpForwarding no 237 ''} 238 ''); 239 }; 240}