|
| 1 | +--- |
| 2 | +title: User/Group Record Lookup API via Varlink |
| 3 | +category: Interfaces |
| 4 | +layout: default |
| 5 | +--- |
| 6 | + |
| 7 | +# User/Group Record Lookup API via Varlink |
| 8 | + |
| 9 | +JSON User/Group Records (as described in the [JSON User |
| 10 | +Records](https://systemd.io/USER_RECORD) and [JSON Group |
| 11 | +Records](https://systemd.io/GROUP_RECORD) documents) that are defined on the |
| 12 | +local system may be queried with a [Varlink](https://varlink.org/) API. This |
| 13 | +API takes both the role of what |
| 14 | +[`getpwnam(3)`](http://man7.org/linux/man-pages/man3/getpwnam.3.html) and |
| 15 | +related calls are for `struct passwd`, as well as the interfaces modules |
| 16 | +implementing the [glibc Name Service Switch |
| 17 | +(NSS)](https://www.gnu.org/software/libc/manual/html_node/Name-Service-Switch.html) |
| 18 | +expose. Or in other words, it both allows applications to efficiently query |
| 19 | +user/group records from local services, and allows local subsystems to provide |
| 20 | +user/group records efficiently to local applications. |
| 21 | + |
| 22 | +This simple API only exposes only three method calls, and requires only a small |
| 23 | +subset of the Varlink functionality. |
| 24 | + |
| 25 | +## Why Varlink? |
| 26 | + |
| 27 | +The API described in this document is based on a simple subset of the |
| 28 | +mechanisms described by [Varlink](https://varlink.org/). The choice of |
| 29 | +preferring Varlink over D-Bus and other IPCs in this context was made for three |
| 30 | +reasons: |
| 31 | + |
| 32 | +1. User/Group record resolution should work during early boot and late shutdown |
| 33 | + without special handling. This is very hard to do with D-Bus, as the broker |
| 34 | + service for D-Bus generally runs as regular system daemon and is hence only |
| 35 | + available at the latest boot stage. |
| 36 | + |
| 37 | +2. The JSON user/group records are native JSON data, hence picking an IPC |
| 38 | + system that natively operates with JSON data is natural and clean. |
| 39 | + |
| 40 | +3. IPC systems such as D-Bus do not provide flow control and are thus unusable |
| 41 | + for streaming data. They are useful to pass around short control messages, |
| 42 | + but as soon as potentially many and large objects shall be transferred, |
| 43 | + D-Bus is not suitable, as any such streaming of messages would be considered |
| 44 | + flooding in D-Bus' logic, and thus possibly result in termination of |
| 45 | + communication. Since the APIs defined in this document need to support |
| 46 | + enumerating potentially large numbers of users and groups, D-Bus is simply |
| 47 | + not an appropriate option. |
| 48 | + |
| 49 | +## Concepts |
| 50 | + |
| 51 | +Each subsystem that needs to define users and groups on the local system is |
| 52 | +supposed to implement this API, and offer its interfaces on a Varlink |
| 53 | +`AF_UNIX`/`SOCK_STREAM` file system socket bound into the |
| 54 | +`/run/systemd/userdb/` directory. When a client wants to look up a user or |
| 55 | +group record, it contacts all sockets bound in this directory in parallel, and |
| 56 | +enqueues the same query to each. The first positive reply is then returned to |
| 57 | +the application, or if all fail the last seen error is returned |
| 58 | +instead. (Alternatively a special Varlink service is available, |
| 59 | +`io.systemd.Multiplexer` which acts as frontend and will do the parallel |
| 60 | +queries on behalf of the client, drastically simplifying client |
| 61 | +development. This service is not available during earliest boot and final |
| 62 | +shutdown phases.) |
| 63 | + |
| 64 | +Unlike with glibc NSS there's no order or programmatic expression language |
| 65 | +defined in which queries are issued to the various services. Instead, all |
| 66 | +queries are always enqueued in parallel to all defined services, in order to |
| 67 | +make look-ups efficient, and the simple rule of "first successful lookup wins" |
| 68 | +is unconditionally followed for user and group look-ups (though not for |
| 69 | +membership lookups, see below). |
| 70 | + |
| 71 | +This simple scheme only works safely as long as every service providing |
| 72 | +user/group records carefully makes sure not to answer with conflicting |
| 73 | +records. This API does not define any mechanisms for dealing with user/group |
| 74 | +name/ID collisions during look-up nor during record registration. It assumes |
| 75 | +the various subsystems that want to offer user and group records to the rest of |
| 76 | +the system have made sufficiently sure in advance that their definitions do not |
| 77 | +collide with those of other services. Clients are not expected to merge |
| 78 | +multiple definitions for the same user or group, and will also not be able to |
| 79 | +detect conflicts and suppress such conflicting records. |
| 80 | + |
| 81 | +It is recommended to name the sockets in the directory in reverse domain name |
| 82 | +notation, but this is neither required nor enforced. |
| 83 | + |
| 84 | +## Well-Known Services |
| 85 | + |
| 86 | +Any subsystem that wants to provide user/group records can do so, simply by |
| 87 | +binding a socket in the aforementioned directory. By default two |
| 88 | +services are listening there, that have special relevance: |
| 89 | + |
| 90 | +1. `io.systemd.NameServiceSwitch` → This service makes the classic UNIX/glibc |
| 91 | + NSS user/group records available as JSON User/Group records. Any such |
| 92 | + records are automatically converted as needed, and possibly augmented with |
| 93 | + information from the shadow databases. |
| 94 | + |
| 95 | +2. `io.systemd.Multiplexer` → This service multiplexes client queries to all |
| 96 | + other running services. It's supposed to simplify client development: in |
| 97 | + order to look up or enumerate user/group records it's sufficient to talk to |
| 98 | + one service instead of all of them in parallel. Note that it is not availabe |
| 99 | + during earliest boot and final shutdown phases, hence for programs running |
| 100 | + in that context it is preferable to implement the parallel lookup |
| 101 | + themselves. |
| 102 | + |
| 103 | +Both these services are implemented by the same daemon |
| 104 | +`systemd-userdbd.service`. |
| 105 | + |
| 106 | +Note that these services currently implement a subset of Varlink only. For |
| 107 | +example, introspection is not available, and the resolver logic is not used. |
| 108 | + |
| 109 | +## Other Services |
| 110 | + |
| 111 | +The `systemd` project provides two other services implementing this |
| 112 | +interface. Specifically: |
| 113 | + |
| 114 | +1. `io.systemd.DynamicUser` → This service is implemented by the service |
| 115 | + manager itself, and provides records for the users and groups synthesized |
| 116 | + via `DynamicUser=` in unit files. |
| 117 | + |
| 118 | +2. `io.systemd.Home` → This service is implemented by `systemd-homed.service` |
| 119 | + and provides records for the users and groups defined by the home |
| 120 | + directories it manages. |
| 121 | + |
| 122 | +Other projects are invited to implement these services too. For example it |
| 123 | +would make sense for LDAP/ActiveDirectory projects to implement these |
| 124 | +interfaces, which would provide them a way to do per-user resource management |
| 125 | +enforced by systemd and defined directly in LDAP directories. |
| 126 | + |
| 127 | +## Compatibility with NSS |
| 128 | + |
| 129 | +Two-way compatibility with classic UNIX/glibc NSS user/group records is |
| 130 | +provided. When using the Varlink API, lookups into databases provided only via |
| 131 | +NSS (and not natively via Varlink) are handled by the |
| 132 | +`io.systemd.NameServiceSwitch` service (see above). When using the NSS API |
| 133 | +(i.e. `getpwnam()` and friends) the `nss-systemd` module will automatically |
| 134 | +synthesize NSS records for users/groups natively defined via a Varlink |
| 135 | +API. Special care is taken to avoid recursion between these two compatibility |
| 136 | +mechanisms. |
| 137 | + |
| 138 | +Subsystems that shall provide user/group records to the system may choose |
| 139 | +between offering them via an NSS module or via a this Varlink API, either way |
| 140 | +all records are accessible via both APIs, due to the bidirectional |
| 141 | +forwarding. It is also possible to provide the same records via both APIs |
| 142 | +directly, but in that case the compatibility logic must be turned off. There |
| 143 | +are mechanisms in place for this, please contact the systemd project for |
| 144 | +details, as these are currently not documented. |
| 145 | + |
| 146 | +## Caching of User Records |
| 147 | + |
| 148 | +This API defines no concepts for caching records. If caching is desired it |
| 149 | +should be implemented in the subsystems that provide the user records, not in |
| 150 | +the clients consuming them. |
| 151 | + |
| 152 | +## Method Calls |
| 153 | + |
| 154 | +``` |
| 155 | +interface io.systemd.UserDatabase |
| 156 | +
|
| 157 | +method GetUserRecord( |
| 158 | + uid : ?int, |
| 159 | + userName : ?string, |
| 160 | + service : string |
| 161 | +) -> ( |
| 162 | + record : object, |
| 163 | + incomplete : boolean |
| 164 | +) |
| 165 | +
|
| 166 | +method GetGroupRecord( |
| 167 | + gid : ?int, |
| 168 | + groupName : ?string, |
| 169 | + service : string |
| 170 | +) -> ( |
| 171 | + record : object, |
| 172 | + incomplete : boolean |
| 173 | +) |
| 174 | +
|
| 175 | +method GetMemberships( |
| 176 | + userName : ?string, |
| 177 | + groupName : ?string, |
| 178 | + service : string |
| 179 | +) -> ( |
| 180 | + userName : string, |
| 181 | + groupName : string |
| 182 | +) |
| 183 | +
|
| 184 | +error NoRecordFound() |
| 185 | +error BadService() |
| 186 | +error ServiceNotAvailable() |
| 187 | +error ConflictingRecordFound() |
| 188 | +``` |
| 189 | + |
| 190 | +The `GetUserRecord` method looks up or enumerates a user record. If the `uid` |
| 191 | +parameter is set it specifies the numeric UNIX UID to search for. If the |
| 192 | +`userName` parameter is set it specifies the name of the user to search |
| 193 | +for. Typically, only one of the two parameters are set, depending whether a |
| 194 | +look-up by UID or by name is desired. However, clients may also specify both |
| 195 | +parameters, in which case a record matching both will be returned, and if only |
| 196 | +one exists that matches one of the two parameters but not the other an error of |
| 197 | +`ConflictingRecordFound` is returned. If neither of the two parameters are set |
| 198 | +the whole user database is enumerated. In this case the method call needs to be |
| 199 | +made with `more` set, so that multiple method call replies may be generated as |
| 200 | +effect, each carrying one user record. |
| 201 | + |
| 202 | +The `service` parameter is mandatory and should be set to the service name |
| 203 | +being talked to (i.e. to the same name as the `AF_UNIX` socket path, with the |
| 204 | +`/run/systemd/userdb/` prefix removed). This is useful to allow implementation |
| 205 | +of multiple services on the same socket (which is used by |
| 206 | +`systemd-userdbd.service`). |
| 207 | + |
| 208 | +The method call returns one or more user records, depending which type of query is |
| 209 | +used (see above). The record is returned in the `record` field. The |
| 210 | +`incomplete` field indicates whether the record is complete. Services providing |
| 211 | +user record lookup should only pass the `privileged` section of user records to |
| 212 | +clients that either match the user the record is about or to sufficiently |
| 213 | +privileged clients, for all others the section must be removed so that no |
| 214 | +sensitive data is leaked this way. The `incomplete` parameter should indicate |
| 215 | +whether the record has been modified like this or not (i.e. it is `true` if a |
| 216 | +`privileged` section existed in the user record and was removed, and `false` if |
| 217 | +no `privileged` section existed or one existed but hasn't been removed). |
| 218 | + |
| 219 | +If no user record matching the specified UID or name is known the error |
| 220 | +`NoRecordFound` is returned (this is also returned if neither UID nor name are |
| 221 | +specified, and hence enumeration requested but the subsystem currently has no |
| 222 | +users defined). |
| 223 | + |
| 224 | +If a method call with an incorrectly set `service` field is received |
| 225 | +(i.e. either not set at all, or not to the service's own name) a `BadService` |
| 226 | +error is generated. Finally, `ServiceNotAvailable` should be returned when the |
| 227 | +backing subsystem is not operational for some reason and hence no information |
| 228 | +about existence or non-existence of a record can be returned nor any user |
| 229 | +record at all. (The `service` field is defined in order to allow implementation |
| 230 | +of daemons that provide multiple distinct user/group services over the same |
| 231 | +`AF_UNIX` socket: in order to correctly determine which service a client wants |
| 232 | +to talk to the client needs to provide the name in each request.) |
| 233 | + |
| 234 | +The `GetGroupRecord` method call works analogously but for groups. |
| 235 | + |
| 236 | +The `GetMemberships` method call may be used to inquire about group |
| 237 | +memberships. The `userName` and `groupName` arguments take what the name |
| 238 | +suggests. If one of the two is specified all matching memberships are returned, |
| 239 | +if neither is specified all known memberships of any user and any group are |
| 240 | +returned. The return value is a pair of user name and group name, where the |
| 241 | +user is a member of the group. If both arguments are specified the specified |
| 242 | +membership will be tested for, but no others, and the pair is returned if it is |
| 243 | +defined. Unless both arguments are specified the method call needs to be made |
| 244 | +with `more` set, so that multiple replies can be returned (since typically |
| 245 | +there are are multiple members per group and also multiple groups a user is |
| 246 | +member of). As with `GetUserRecord` and `GetGroupRecord` the `service` |
| 247 | +parameter needs to contain the name of the service being talked to, in order to |
| 248 | +allow implementation of multiple service within the same IPC socket. In case no |
| 249 | +matching membership is known `NoRecordFound` is returned. The other two errors |
| 250 | +are also generated in the same cases as for `GetUserRecord` and |
| 251 | +`GetGroupRecord`. |
| 252 | + |
| 253 | +Unlike with `GetUserRecord` and `GetGroupRecord` the lists of memberships |
| 254 | +returned by services are always combined. Thus unlike the other two calls a |
| 255 | +membership lookup query has to wait for the last simultaneous query to complete |
| 256 | +before the complete list is acquired. |
| 257 | + |
| 258 | +Note that only the `GetMemberships` call is authoritative about memberships of |
| 259 | +users in groups. i.e. it should not be considered sufficient to check the |
| 260 | +`memberOf` field of user records and the `members` field of group records to |
| 261 | +acquire the full list of memberships. The full list can only bet determined by |
| 262 | +`GetMemberships`, and as mentioned requires merging of these lists of all local |
| 263 | +services. Result of this is that it can be one service that defines a user A, |
| 264 | +and another service that defines a group B, and a third service that declares |
| 265 | +that A is a member of B. |
| 266 | + |
| 267 | +And that's really all there is to it. |
0 commit comments