1 { config, lib, pkgs, ... }:
6 cfg = config.security.duosec;
8 boolToStr = b: if b then "yes" else "no";
12 ikey=${cfg.integrationKey}
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}
22 configFileLogin = configFilePam + ''
23 motd=${boolToStr cfg.motd}
24 accept_env_factor=${boolToStr cfg.acceptEnvFactor}
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.")
36 ssh.enable = mkOption {
39 description = "If enabled, protect SSH logins with Duo Security.";
42 pam.enable = mkOption {
45 description = "If enabled, protect logins with Duo Security using PAM support.";
48 integrationKey = mkOption {
50 description = "Integration key.";
53 secretKeyFile = mkOption {
54 type = types.nullOr types.path;
57 A file containing your secret key. The security of your Duo application is tied to the security of your secret key.
59 example = "/run/keys/duo-skey";
64 description = "Duo API hostname.";
70 example = "users,!wheel,!*admin guests";
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.
80 type = types.enum [ "safe" "secure" ];
83 On service or configuration errors that prevent Duo
84 authentication, fail "safe" (allow access) or "secure" (deny
85 access). The default is "safe".
93 Include information such as the command to be executed in
102 If `true`, Duo Unix will automatically send
103 a push login request to the user’s 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
116 Print the contents of `/etc/motd` to screen
117 after a successful login.
122 type = types.enum [ 1 2 3 ];
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
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.
137 When configured with `autopush = true`, we
138 recommend setting `prompts = 1`.
142 acceptEnvFactor = mkOption {
146 Look for factor selection or passcode in the
147 `$DUO_PASSCODE` environment variable before
148 prompting the user for input.
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.
157 fallbackLocalIP = mkOption {
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.
167 If you are using IP whitelisting, enabling this option could
168 cause unauthorized logins if the local IP is listed in the
173 allowTcpForwarding = mkOption {
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.
186 config = mkIf (cfg.ssh.enable || cfg.pam.enable) {
187 environment.systemPackages = [ pkgs.duo-unix ];
189 security.wrappers.login_duo =
193 source = "${pkgs.duo-unix.out}/bin/login_duo";
196 systemd.services.login-duo = lib.mkIf cfg.ssh.enable {
197 wantedBy = [ "sysinit.target" ];
198 before = [ "sysinit.target" "shutdown.target" ];
199 conflicts = [ "shutdown.target" ];
200 unitConfig.DefaultDependencies = false;
202 if test -f "${cfg.secretKeyFile}"; then
209 cat ${pkgs.writeText "login_duo.conf" configFileLogin}
210 printf 'skey = %s\n' "$(cat ${cfg.secretKeyFile})"
214 mv -fT "$conf" /etc/duo/login_duo.conf
219 systemd.services.pam-duo = lib.mkIf cfg.ssh.enable {
220 wantedBy = [ "sysinit.target" ];
221 before = [ "sysinit.target" "shutdown.target" ];
222 conflicts = [ "shutdown.target" ];
223 unitConfig.DefaultDependencies = false;
225 if test -f "${cfg.secretKeyFile}"; then
232 cat ${pkgs.writeText "login_duo.conf" configFilePam}
233 printf 'skey = %s\n' "$(cat ${cfg.secretKeyFile})"
236 mv -fT "$conf" /etc/duo/pam_duo.conf
241 /* If PAM *and* SSH are enabled, then don't do anything special.
242 If PAM isn't used, set the default SSH-only options. */
243 services.openssh.extraConfig = mkIf (cfg.ssh.enable || cfg.pam.enable) (
244 if cfg.pam.enable then "UseDNS no" else ''
245 # Duo Security configuration
246 ForceCommand ${config.security.wrapperDir}/login_duo
248 ${optionalString (!cfg.allowTcpForwarding) ''
249 AllowTcpForwarding no