| 
									
										
										
										
											2014-04-14 16:26:48 +02:00
										 |  |  |  | { config, lib, pkgs, ... }: | 
					
						
							| 
									
										
										
										
											2014-02-18 03:38:35 -06:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-04-14 16:26:48 +02:00
										 |  |  |  | with lib; | 
					
						
							| 
									
										
										
										
											2014-02-18 03:38:35 -06:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | let | 
					
						
							|  |  |  |  |   cfg = config.security.duosec; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   boolToStr = b: if b then "yes" else "no"; | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-13 01:39:22 +00:00
										 |  |  |  |   configFilePam = ''
 | 
					
						
							| 
									
										
										
										
											2014-02-18 03:38:35 -06:00
										 |  |  |  |     [duo] | 
					
						
							| 
									
										
										
										
											2020-01-30 21:21:47 -05:00
										 |  |  |  |     ikey=${cfg.integrationKey} | 
					
						
							| 
									
										
										
										
											2014-02-18 03:38:35 -06:00
										 |  |  |  |     host=${cfg.host} | 
					
						
							| 
									
										
										
										
											2020-01-30 14:16:17 -05:00
										 |  |  |  |     ${optionalString (cfg.groups != "") ("groups="+cfg.groups)} | 
					
						
							| 
									
										
										
										
											2014-02-18 03:38:35 -06:00
										 |  |  |  |     failmode=${cfg.failmode} | 
					
						
							|  |  |  |  |     pushinfo=${boolToStr cfg.pushinfo} | 
					
						
							|  |  |  |  |     autopush=${boolToStr cfg.autopush} | 
					
						
							|  |  |  |  |     prompts=${toString cfg.prompts} | 
					
						
							|  |  |  |  |     fallback_local_ip=${boolToStr cfg.fallbackLocalIP} | 
					
						
							|  |  |  |  |   '';
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-13 01:39:22 +00:00
										 |  |  |  |   configFileLogin = configFilePam + ''
 | 
					
						
							|  |  |  |  |     motd=${boolToStr cfg.motd} | 
					
						
							|  |  |  |  |     accept_env_factor=${boolToStr cfg.acceptEnvFactor} | 
					
						
							|  |  |  |  |   '';
 | 
					
						
							| 
									
										
										
										
											2014-02-18 03:38:35 -06:00
										 |  |  |  | in | 
					
						
							|  |  |  |  | { | 
					
						
							| 
									
										
										
										
											2020-01-30 14:16:17 -05:00
										 |  |  |  |   imports = [ | 
					
						
							|  |  |  |  |     (mkRenamedOptionModule [ "security" "duosec" "group" ] [ "security" "duosec" "groups" ]) | 
					
						
							| 
									
										
										
										
											2020-01-30 21:21:47 -05:00
										 |  |  |  |     (mkRenamedOptionModule [ "security" "duosec" "ikey" ] [ "security" "duosec" "integrationKey" ]) | 
					
						
							| 
									
										
										
										
											2020-01-30 21:18:43 -05:00
										 |  |  |  |     (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.") | 
					
						
							| 
									
										
										
										
											2020-01-30 14:16:17 -05:00
										 |  |  |  |   ]; | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-02-18 03:38:35 -06:00
										 |  |  |  |   options = { | 
					
						
							|  |  |  |  |     security.duosec = { | 
					
						
							|  |  |  |  |       ssh.enable = mkOption { | 
					
						
							|  |  |  |  |         type = types.bool; | 
					
						
							|  |  |  |  |         default = false; | 
					
						
							|  |  |  |  |         description = "If enabled, protect SSH logins with Duo Security."; | 
					
						
							|  |  |  |  |       }; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |       pam.enable = mkOption { | 
					
						
							|  |  |  |  |         type = types.bool; | 
					
						
							|  |  |  |  |         default = false; | 
					
						
							|  |  |  |  |         description = "If enabled, protect logins with Duo Security using PAM support."; | 
					
						
							|  |  |  |  |       }; | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-30 21:21:47 -05:00
										 |  |  |  |       integrationKey = mkOption { | 
					
						
							| 
									
										
										
										
											2014-02-18 03:38:35 -06:00
										 |  |  |  |         type = types.str; | 
					
						
							|  |  |  |  |         description = "Integration key."; | 
					
						
							|  |  |  |  |       }; | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-30 21:18:43 -05:00
										 |  |  |  |       secretKeyFile = mkOption { | 
					
						
							| 
									
										
										
										
											2020-04-28 19:13:21 +02:00
										 |  |  |  |         type = types.nullOr types.path; | 
					
						
							| 
									
										
										
										
											2020-01-30 21:18:43 -05:00
										 |  |  |  |         default = null; | 
					
						
							|  |  |  |  |         description = ''
 | 
					
						
							|  |  |  |  |           A file containing your secret key. The security of your Duo application is tied to the security of your secret key. | 
					
						
							|  |  |  |  |         '';
 | 
					
						
							|  |  |  |  |         example = "/run/keys/duo-skey"; | 
					
						
							| 
									
										
										
										
											2014-02-18 03:38:35 -06:00
										 |  |  |  |       }; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |       host = mkOption { | 
					
						
							|  |  |  |  |         type = types.str; | 
					
						
							|  |  |  |  |         description = "Duo API hostname."; | 
					
						
							|  |  |  |  |       }; | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-30 14:16:17 -05:00
										 |  |  |  |       groups = mkOption { | 
					
						
							| 
									
										
										
										
											2014-02-18 03:38:35 -06:00
										 |  |  |  |         type = types.str; | 
					
						
							|  |  |  |  |         default = ""; | 
					
						
							| 
									
										
										
										
											2020-01-30 14:16:17 -05:00
										 |  |  |  |         example = "users,!wheel,!*admin guests"; | 
					
						
							|  |  |  |  |         description = ''
 | 
					
						
							|  |  |  |  |           If specified, Duo authentication is required only for users | 
					
						
							|  |  |  |  |           whose primary group or supplementary group list matches one | 
					
						
							|  |  |  |  |           of the space-separated pattern lists. Refer to | 
					
						
							|  |  |  |  |           <link xlink:href="https://duo.com/docs/duounix"/> for details. | 
					
						
							|  |  |  |  |         '';
 | 
					
						
							| 
									
										
										
										
											2014-02-18 03:38:35 -06:00
										 |  |  |  |       }; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |       failmode = mkOption { | 
					
						
							| 
									
										
										
										
											2019-03-17 18:25:20 -07:00
										 |  |  |  |         type = types.enum [ "safe" "secure" ]; | 
					
						
							| 
									
										
										
										
											2014-02-18 03:38:35 -06:00
										 |  |  |  |         default = "safe"; | 
					
						
							|  |  |  |  |         description = ''
 | 
					
						
							|  |  |  |  |           On service or configuration errors that prevent Duo | 
					
						
							|  |  |  |  |           authentication, fail "safe" (allow access) or "secure" (deny | 
					
						
							|  |  |  |  |           access). The default is "safe". | 
					
						
							|  |  |  |  |         '';
 | 
					
						
							|  |  |  |  |       }; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |       pushinfo = mkOption { | 
					
						
							|  |  |  |  |         type = types.bool; | 
					
						
							|  |  |  |  |         default = false; | 
					
						
							|  |  |  |  |         description = ''
 | 
					
						
							|  |  |  |  |           Include information such as the command to be executed in | 
					
						
							|  |  |  |  |           the Duo Push message. | 
					
						
							|  |  |  |  |         '';
 | 
					
						
							|  |  |  |  |       }; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |       autopush = mkOption { | 
					
						
							|  |  |  |  |         type = types.bool; | 
					
						
							|  |  |  |  |         default = false; | 
					
						
							|  |  |  |  |         description = ''
 | 
					
						
							|  |  |  |  |           If <literal>true</literal>, Duo Unix will automatically send | 
					
						
							|  |  |  |  |           a push login request to the user’s phone, falling back on a | 
					
						
							|  |  |  |  |           phone call if push is unavailable. If | 
					
						
							|  |  |  |  |           <literal>false</literal>, the user will be prompted to | 
					
						
							|  |  |  |  |           choose an authentication method. When configured with | 
					
						
							|  |  |  |  |           <literal>autopush = yes</literal>, we recommend setting | 
					
						
							|  |  |  |  |           <literal>prompts = 1</literal>. | 
					
						
							|  |  |  |  |         '';
 | 
					
						
							|  |  |  |  |       }; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |       motd = mkOption { | 
					
						
							|  |  |  |  |         type = types.bool; | 
					
						
							|  |  |  |  |         default = false; | 
					
						
							|  |  |  |  |         description = ''
 | 
					
						
							|  |  |  |  |           Print the contents of <literal>/etc/motd</literal> to screen | 
					
						
							| 
									
										
										
										
											2014-12-30 03:31:03 +01:00
										 |  |  |  |           after a successful login. | 
					
						
							| 
									
										
										
										
											2014-02-18 03:38:35 -06:00
										 |  |  |  |         '';
 | 
					
						
							|  |  |  |  |       }; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |       prompts = mkOption { | 
					
						
							| 
									
										
										
										
											2016-11-16 22:36:05 +09:00
										 |  |  |  |         type = types.enum [ 1 2 3 ]; | 
					
						
							| 
									
										
										
										
											2014-02-18 03:38:35 -06:00
										 |  |  |  |         default = 3; | 
					
						
							|  |  |  |  |         description = ''
 | 
					
						
							|  |  |  |  |           If a user fails to authenticate with a second factor, Duo | 
					
						
							|  |  |  |  |           Unix will prompt the user to authenticate again. This option | 
					
						
							|  |  |  |  |           sets the maximum number of prompts that Duo Unix will | 
					
						
							|  |  |  |  |           display before denying access. Must be 1, 2, or 3. Default | 
					
						
							|  |  |  |  |           is 3. | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |           For example, when <literal>prompts = 1</literal>, the user | 
					
						
							|  |  |  |  |           will have to successfully authenticate on the first prompt, | 
					
						
							|  |  |  |  |           whereas if <literal>prompts = 2</literal>, if the user | 
					
						
							|  |  |  |  |           enters incorrect information at the initial prompt, he/she | 
					
						
							|  |  |  |  |           will be prompted to authenticate again. | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |           When configured with <literal>autopush = true</literal>, we | 
					
						
							|  |  |  |  |           recommend setting <literal>prompts = 1</literal>. | 
					
						
							|  |  |  |  |         '';
 | 
					
						
							|  |  |  |  |       }; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |       acceptEnvFactor = mkOption { | 
					
						
							|  |  |  |  |         type = types.bool; | 
					
						
							|  |  |  |  |         default = false; | 
					
						
							|  |  |  |  |         description = ''
 | 
					
						
							|  |  |  |  |           Look for factor selection or passcode in the | 
					
						
							|  |  |  |  |           <literal>$DUO_PASSCODE</literal> environment variable before | 
					
						
							|  |  |  |  |           prompting the user for input. | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |           When $DUO_PASSCODE is non-empty, it will override | 
					
						
							|  |  |  |  |           autopush. The SSH client will need SendEnv DUO_PASSCODE in | 
					
						
							| 
									
										
										
										
											2014-12-30 03:31:03 +01:00
										 |  |  |  |           its configuration, and the SSH server will similarly need | 
					
						
							| 
									
										
										
										
											2014-02-18 03:38:35 -06:00
										 |  |  |  |           AcceptEnv DUO_PASSCODE. | 
					
						
							|  |  |  |  |         '';
 | 
					
						
							|  |  |  |  |       }; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |       fallbackLocalIP = mkOption { | 
					
						
							|  |  |  |  |         type = types.bool; | 
					
						
							|  |  |  |  |         default = false; | 
					
						
							|  |  |  |  |         description = ''
 | 
					
						
							|  |  |  |  |           Duo Unix reports the IP address of the authorizing user, for | 
					
						
							|  |  |  |  |           the purposes of authorization and whitelisting. If Duo Unix | 
					
						
							|  |  |  |  |           cannot detect the IP address of the client, setting | 
					
						
							|  |  |  |  |           <literal>fallbackLocalIP = yes</literal> will cause Duo Unix | 
					
						
							|  |  |  |  |           to send the IP address of the server it is running on. | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |           If you are using IP whitelisting, enabling this option could | 
					
						
							|  |  |  |  |           cause unauthorized logins if the local IP is listed in the | 
					
						
							|  |  |  |  |           whitelist. | 
					
						
							|  |  |  |  |         '';
 | 
					
						
							|  |  |  |  |       }; | 
					
						
							| 
									
										
										
										
											2014-05-20 02:42:31 -05:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |       allowTcpForwarding = mkOption { | 
					
						
							|  |  |  |  |         type = types.bool; | 
					
						
							|  |  |  |  |         default = false; | 
					
						
							|  |  |  |  |         description = ''
 | 
					
						
							|  |  |  |  |           By default, when SSH forwarding, enabling Duo Security will | 
					
						
							|  |  |  |  |           disable TCP forwarding. By enabling this, you potentially | 
					
						
							|  |  |  |  |           undermine some of the SSH based login security. Note this is | 
					
						
							|  |  |  |  |           not needed if you use PAM. | 
					
						
							|  |  |  |  |         '';
 | 
					
						
							|  |  |  |  |       }; | 
					
						
							| 
									
										
										
										
											2014-02-18 03:38:35 -06:00
										 |  |  |  |     }; | 
					
						
							|  |  |  |  |   }; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   config = mkIf (cfg.ssh.enable || cfg.pam.enable) { | 
					
						
							| 
									
										
										
										
											2020-01-30 21:15:56 -05:00
										 |  |  |  |     environment.systemPackages = [ pkgs.duo-unix ]; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     security.wrappers.login_duo.source = "${pkgs.duo-unix.out}/bin/login_duo"; | 
					
						
							| 
									
										
										
										
											2020-01-30 21:18:43 -05:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |     system.activationScripts = { | 
					
						
							|  |  |  |  |       login_duo = mkIf cfg.ssh.enable ''
 | 
					
						
							|  |  |  |  |         if test -f "${cfg.secretKeyFile}"; then | 
					
						
							|  |  |  |  |           mkdir -m 0755 -p /etc/duo | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |           umask 0077 | 
					
						
							|  |  |  |  |           conf="$(mktemp)" | 
					
						
							|  |  |  |  |           { | 
					
						
							|  |  |  |  |             cat ${pkgs.writeText "login_duo.conf" configFileLogin} | 
					
						
							|  |  |  |  |             printf 'skey = %s\n' "$(cat ${cfg.secretKeyFile})" | 
					
						
							|  |  |  |  |           } >"$conf" | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |           chown sshd "$conf" | 
					
						
							|  |  |  |  |           mv -fT "$conf" /etc/duo/login_duo.conf | 
					
						
							|  |  |  |  |         fi | 
					
						
							|  |  |  |  |       '';
 | 
					
						
							|  |  |  |  |       pam_duo = mkIf cfg.pam.enable ''
 | 
					
						
							|  |  |  |  |         if test -f "${cfg.secretKeyFile}"; then | 
					
						
							|  |  |  |  |           mkdir -m 0755 -p /etc/duo | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |           umask 0077 | 
					
						
							|  |  |  |  |           conf="$(mktemp)" | 
					
						
							|  |  |  |  |           { | 
					
						
							|  |  |  |  |             cat ${pkgs.writeText "login_duo.conf" configFilePam} | 
					
						
							|  |  |  |  |             printf 'skey = %s\n' "$(cat ${cfg.secretKeyFile})" | 
					
						
							|  |  |  |  |           } >"$conf" | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |           mv -fT "$conf" /etc/duo/pam_duo.conf | 
					
						
							|  |  |  |  |         fi | 
					
						
							|  |  |  |  |       '';
 | 
					
						
							|  |  |  |  |     }; | 
					
						
							| 
									
										
										
										
											2020-01-30 21:15:56 -05:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |     /* If PAM *and* SSH are enabled, then don't do anything special.
 | 
					
						
							|  |  |  |  |     If PAM isn't used, set the default SSH-only options. */ | 
					
						
							|  |  |  |  |     services.openssh.extraConfig = mkIf (cfg.ssh.enable || cfg.pam.enable) ( | 
					
						
							|  |  |  |  |     if cfg.pam.enable then "UseDNS no" else ''
 | 
					
						
							|  |  |  |  |       # Duo Security configuration | 
					
						
							|  |  |  |  |       ForceCommand ${config.security.wrapperDir}/login_duo | 
					
						
							|  |  |  |  |       PermitTunnel no | 
					
						
							|  |  |  |  |       ${optionalString (!cfg.allowTcpForwarding) ''
 | 
					
						
							|  |  |  |  |         AllowTcpForwarding no | 
					
						
							|  |  |  |  |       ''}
 | 
					
						
							|  |  |  |  |     '');
 | 
					
						
							| 
									
										
										
										
											2014-02-18 03:38:35 -06:00
										 |  |  |  |   }; | 
					
						
							|  |  |  |  | } |