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 = lib.mdDoc "If enabled, protect SSH logins with Duo Security.";
42 pam.enable = mkOption {
45 description = lib.mdDoc "If enabled, protect logins with Duo Security using PAM support.";
48 integrationKey = mkOption {
50 description = lib.mdDoc "Integration key.";
53 secretKeyFile = mkOption {
54 type = types.nullOr types.path;
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.
59 example = "/run/keys/duo-skey";
64 description = lib.mdDoc "Duo API hostname.";
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.
80 type = types.enum [ "safe" "secure" ];
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".
92 description = lib.mdDoc ''
93 Include information such as the command to be executed in
101 description = lib.mdDoc ''
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
115 description = lib.mdDoc ''
116 Print the contents of `/etc/motd` to screen
117 after a successful login.
122 type = types.enum [ 1 2 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
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 {
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.
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 {
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.
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 {
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.
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 system.activationScripts = {
197 login_duo = mkIf cfg.ssh.enable ''
198 if test -f "${cfg.secretKeyFile}"; then
199 mkdir -m 0755 -p /etc/duo
204 cat ${pkgs.writeText "login_duo.conf" configFileLogin}
205 printf 'skey = %s\n' "$(cat ${cfg.secretKeyFile})"
209 mv -fT "$conf" /etc/duo/login_duo.conf
212 pam_duo = mkIf cfg.pam.enable ''
213 if test -f "${cfg.secretKeyFile}"; then
214 mkdir -m 0755 -p /etc/duo
219 cat ${pkgs.writeText "login_duo.conf" configFilePam}
220 printf 'skey = %s\n' "$(cat ${cfg.secretKeyFile})"
223 mv -fT "$conf" /etc/duo/pam_duo.conf
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
235 ${optionalString (!cfg.allowTcpForwarding) ''
236 AllowTcpForwarding no