grafana-alloy: don't build the frontend twice
[NixPkgs.git] / nixos / modules / security / duosec.nix
blobe755b5f0ee53497ed048383cf2a6d8d61f129146
1 { config, lib, pkgs, ... }:
3 with lib;
5 let
6   cfg = config.security.duosec;
8   boolToStr = b: if b then "yes" else "no";
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   '';
22   configFileLogin = configFilePam + ''
23     motd=${boolToStr cfg.motd}
24     accept_env_factor=${boolToStr cfg.acceptEnvFactor}
25   '';
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   ];
34   options = {
35     security.duosec = {
36       ssh.enable = mkOption {
37         type = types.bool;
38         default = false;
39         description = "If enabled, protect SSH logins with Duo Security.";
40       };
42       pam.enable = mkOption {
43         type = types.bool;
44         default = false;
45         description = "If enabled, protect logins with Duo Security using PAM support.";
46       };
48       integrationKey = mkOption {
49         type = types.str;
50         description = "Integration key.";
51       };
53       secretKeyFile = mkOption {
54         type = types.nullOr types.path;
55         default = null;
56         description = ''
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       };
62       host = mkOption {
63         type = types.str;
64         description = "Duo API hostname.";
65       };
67       groups = mkOption {
68         type = types.str;
69         default = "";
70         example = "users,!wheel,!*admin guests";
71         description = ''
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       };
79       failmode = mkOption {
80         type = types.enum [ "safe" "secure" ];
81         default = "safe";
82         description = ''
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       };
89       pushinfo = mkOption {
90         type = types.bool;
91         default = false;
92         description = ''
93           Include information such as the command to be executed in
94           the Duo Push message.
95         '';
96       };
98       autopush = mkOption {
99         type = types.bool;
100         default = false;
101         description = ''
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
108           `prompts = 1`.
109         '';
110       };
112       motd = mkOption {
113         type = types.bool;
114         default = false;
115         description = ''
116           Print the contents of `/etc/motd` to screen
117           after a successful login.
118         '';
119       };
121       prompts = mkOption {
122         type = types.enum [ 1 2 3 ];
123         default = 3;
124         description = ''
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.
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`.
139         '';
140       };
142       acceptEnvFactor = mkOption {
143         type = types.bool;
144         default = false;
145         description = ''
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.
154         '';
155       };
157       fallbackLocalIP = mkOption {
158         type = types.bool;
159         default = false;
160         description = ''
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
169           whitelist.
170         '';
171       };
173       allowTcpForwarding = mkOption {
174         type = types.bool;
175         default = false;
176         description = ''
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   };
186   config = mkIf (cfg.ssh.enable || cfg.pam.enable) {
187     environment.systemPackages = [ pkgs.duo-unix ];
189     security.wrappers.login_duo =
190       { setuid = true;
191         owner = "root";
192         group = "root";
193         source = "${pkgs.duo-unix.out}/bin/login_duo";
194       };
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;
201       script = ''
202         if test -f "${cfg.secretKeyFile}"; then
203           mkdir -p /etc/duo
204           chmod 0755 /etc/duo
206           umask 0077
207           conf="$(mktemp)"
208           {
209             cat ${pkgs.writeText "login_duo.conf" configFileLogin}
210             printf 'skey = %s\n' "$(cat ${cfg.secretKeyFile})"
211           } >"$conf"
213           chown sshd "$conf"
214           mv -fT "$conf" /etc/duo/login_duo.conf
215         fi
216       '';
217     };
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;
224       script = ''
225         if test -f "${cfg.secretKeyFile}"; then
226           mkdir -p /etc/duo
227           chmod 0755 /etc/duo
229           umask 0077
230           conf="$(mktemp)"
231           {
232             cat ${pkgs.writeText "login_duo.conf" configFilePam}
233             printf 'skey = %s\n' "$(cat ${cfg.secretKeyFile})"
234           } >"$conf"
236           mv -fT "$conf" /etc/duo/pam_duo.conf
237         fi
238       '';
239     };
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
247       PermitTunnel no
248       ${optionalString (!cfg.allowTcpForwarding) ''
249         AllowTcpForwarding no
250       ''}
251     '');
252   };