aboutsummaryrefslogtreecommitdiff
path: root/modules/apps/cli.nix
blob: af60710ee27e75f744673ed26ed5248259cef7dd (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
{ config, pkgs, lib, ...}:

let

  inherit (builtins)
    toString ;
  inherit (lib)
    concatMapStrings filterAttrs mapAttrsToList mkOption
    types unique ;
  inherit (types)
    attrsOf path str submodule ;

  explicit = filterAttrs (n: v: n != "_module" && v != null);
  apps = explicit config.nixsap.apps.cli;

  exec = name: { user, command, ... }:
    let
      uid = toString config.users.users.${user}.uid;
      gid = uid;
      src = pkgs.writeText "${name}.c" ''
        #include <unistd.h>
        #include <grp.h>
        #include <pwd.h>
        #include <stdio.h> 
        #include <stdlib.h>
        #include <sys/types.h>

        int main (int __attribute__((unused)) argc, char *argv[])
        {
          int rc;

          if (getuid() != ${uid}) {
            if (geteuid() != 0) {
              fprintf(stderr, "Forbidden.\n");
              return EXIT_FAILURE;
            }

            rc = initgroups("${user}", ${gid});
            if (0 != rc) {
              perror("initgroups()");
              return EXIT_FAILURE;
            }

            rc = setgid(${gid});
            if (0 != rc) {
              perror("setgid()");
              return EXIT_FAILURE;
            }

            rc = setuid(${uid});
            if (0 != rc) {
              perror("setuid()");
              return EXIT_FAILURE;
            }

            if ((getuid() != ${uid}) || (geteuid() != ${uid})) {
              fprintf(stderr, "Something went wrong.\n");
              return EXIT_FAILURE;
            }

            struct passwd * pw = getpwuid(${uid});
            if (NULL == pw) {
              perror("getpwuid()");
              return EXIT_FAILURE;
            }

            if (NULL != pw->pw_dir) {
              rc = chdir(pw->pw_dir);
              if (0 != rc) {
                rc = chdir("/");
              }
            } else {
              rc = chdir("/");
            }
            if (0 != rc) {
              perror("chdir()");
              return EXIT_FAILURE;
            }
          }

          argv[0] = "${command}";
          execv(argv[0], argv);

          perror("execv()");
          return EXIT_FAILURE;
        }
      '';
    in pkgs.runCommand name {} "gcc -Wall -Wextra -Werror -s -std=gnu99 -O2 ${src} -o $out";

  cliapp = submodule({name, ...}:
  {
    options = {
      user = mkOption {
        description = ''
          User (and group) to run as. Only users in this group can execute
          this application.
          '';
        type = str;
        default = name;
      };
      command = mkOption {
        description = "Path to executable";
        type = path;
      };
    };
  });

in {
  options.nixsap = {
    apps.cli = mkOption {
      description = ''
        Command line applications that should run as other users and likely
        have special privileges, e. g. to access secret keys.  This is
        implemented with setuid-wrappers. Each wrapper is launched as root,
        immediately switches to specified user, then executes something
        useful. This is like sudo, but access is controlled via wrapper's
        group: only users in wrapper's group can execute the wrapper.

        Starting as set-uid-non-root is not sufficient, because we might
        need supplementary groups, like "keys".
      '';
      type = attrsOf cliapp;
      default = {};
    };
  };

  config = {
    nixsap.system.users.daemons = unique (mapAttrsToList (_: a: a.user) apps);
    security.setuidOwners = mapAttrsToList (n: a:
      { program = n;
        owner = "root";
        group = a.user;
        setuid = true;
        setgid = false;
        permissions = "u+rx,g+x,o=";
        source = exec n a;
      }) apps;
  };
}