summaryrefslogtreecommitdiffstats
path: root/nixos/modules/security/polkit.nix
blob: 841554ab5729960a3c15def97c03b8b7bad1127d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
{
  config,
  lib,
  pkgs,
  ...
}:
let

  inherit (lib)
    mkEnableOption
    mkOption
    mkIf
    mkPackageOption
    mkRemovedOptionModule
    types
    ;

  cfg = config.security.polkit;

  iniFmt = pkgs.formats.ini { };
in

{
  imports = [
    (mkRemovedOptionModule [ "security" "polkit" "debug" ] "Use security.polkit.extraArgs instead")
  ];

  options.security.polkit = {
    enable = mkEnableOption "polkit";

    enablePkexecWrapper = mkEnableOption "the setuid pkexec wrapper";

    package = mkPackageOption pkgs "polkit" { };

    settings = mkOption {
      description = ''
        Options for polkitd.
        See {manpage}`polkitd.conf(5)` for available options.
      '';
      type = types.submodule {
        freeformType = iniFmt.type;
        options.Polkitd.ExpirationSeconds = lib.mkOption {
          description = "Expiration timeout of authenticated sesssions.";
          type = types.ints.positive;
          default = 300; # current polkit upstream default
        };
      };
    };

    extraArgs = mkOption {
      type = types.listOf types.str;
      default = [
        "--no-debug"
        "--log-level=notice"
      ];
      description = ''
        List of arguments to pass to the polkitd executable.

        ::: {.note}
        To see debug logs you need to negate the default `--no-debug` setting.
        :::
      '';
    };

    extraConfig = mkOption {
      type = types.lines;
      default = "";
      example = ''
        /* Log authorization checks. */
        polkit.addRule(function(action, subject) {
          // Make sure to negate --no-debug in services.polkit.extraArgs: { security.polkit.extraArgs = [ "--log-level=notice" ]; }
          polkit.log("user " +  subject.user + " is attempting action " + action.id + " from PID " + subject.pid);
        });

        /* Allow any local user to do anything (dangerous!). */
        polkit.addRule(function(action, subject) {
          if (subject.local) return "yes";
        });
      '';
      description = ''
        Any polkit rules to be added to config (in JavaScript ;-). See:
        <https://www.freedesktop.org/software/polkit/docs/latest/polkit.8.html#polkit-rules>
      '';
    };

    adminIdentities = mkOption {
      type = with types; listOf str;
      default = [ "unix-group:wheel" ];
      example = [
        "unix-user:alice"
        "unix-group:admin"
      ];
      description = ''
        Specifies which users are considered “administrators”, for those
        actions that require the user to authenticate as an
        administrator (i.e. have an `auth_admin`
        value).  By default, this is all users in the `wheel` group.
      '';
    };

  };

  config = mkIf cfg.enable {

    environment.systemPackages = [
      cfg.package.bin
      cfg.package.out
    ];

    services.dbus.packages = [ cfg.package.out ];

    systemd.packages = [ cfg.package.out ];

    systemd.services.polkit = {
      restartTriggers = [ config.system.path ];
      reloadTriggers = [
        config.environment.etc."polkit-1/rules.d/10-nixos.rules".source
      ];
      serviceConfig.ExecStart = [
        # nuke default ExecStart
        ""
        # provide our own instead
        (toString (
          [
            "${lib.getLib cfg.package}/lib/polkit-1/polkitd"
          ]
          ++ cfg.extraArgs
        ))
      ];
    };

    systemd.sockets."polkit-agent-helper".wantedBy = [ "sockets.target" ];

    systemd.services."polkit-agent-helper@".serviceConfig = lib.mkMerge [
      # The upstream unit inherits stderr to the polkit agent, which causes
      # agent processes to misinterpret diagnostic output from PAM modules
      # as protocol errors, resulting in tight re-execution loops.
      { StandardError = "journal"; }

      # The upstream unit uses PrivateDevices=yes and ProtectHome=yes,
      # which prevents PAM modules from accessing hardware (e.g. FIDO
      # tokens via /dev/hidraw*) or reading key files from home directories.
      (mkIf config.security.pam.u2f.enable {
        # Override upstream PrivateDevices=yes to allow access to /dev/hidraw*
        PrivateDevices = false;
        DeviceAllow = [
          "/dev/urandom r"
          "char-hidraw rw"
        ];
        # Override upstream ProtectHome=yes so pam_u2f can read
        # ~/.config/Yubico/u2f_keys (the default key file location)
        ProtectHome = "read-only";
      })
      (mkIf config.security.pam.yubico.enable {
        # Override upstream PrivateDevices=yes to allow access to /dev/hidraw*
        PrivateDevices = false;
        DeviceAllow = [ "char-hidraw rw" ];
      })
      (mkIf config.services.fprintd.enable {
        # Override upstream PrivateDevices=yes to allow access to /dev/bus/usb/**
        PrivateDevices = false;
        DeviceAllow = [ "char-usb_device rw" ];
        RestrictAddressFamilies = [ "AF_NETLINK" ];
      })
      (mkIf config.security.pam.zfs.enable {
        PrivateDevices = false;
        DeviceAllow = [
          "/dev/zfs rw"
        ];
      })
    ];

    # The polkit daemon reads action/rule files
    environment.pathsToLink = [ "/share/polkit-1" ];

    # PolKit rules for NixOS.
    environment.etc."polkit-1/rules.d/10-nixos.rules".text = ''
      polkit.addAdminRule(function(action, subject) {
        return [${lib.concatStringsSep ", " (map (i: "\"${i}\"") cfg.adminIdentities)}];
      });

      ${cfg.extraConfig}
    ''; # TODO: validation on compilation (at least against typos)

    environment.etc."polkit-1/polkitd.conf".source = iniFmt.generate "polkitd.conf" cfg.settings;
    security.pam.services.polkit-1 = { };

    security.wrappers.pkexec = {
      enable = cfg.enablePkexecWrapper;
      setuid = true;
      owner = "root";
      group = "root";
      source = lib.getExe' cfg.package "pkexec";
    };

    users.users.polkituser = {
      description = "PolKit daemon";
      uid = config.ids.uids.polkituser;
      group = "polkituser";
    };

    users.groups.polkituser = { };
  };

  meta = {
    maintainers = with lib.maintainers; [ zimward ];
  };
}