- Use cases
- How fine-grained should the permissions be?
- How to handle auto(de)activation?
- Access API
- Per-connection permissions settings format
- "Private connections only"
- Secrets storage
- General notes/comments/issues
Based on a number of discussions on and and off-list, it seems that we want to get rid of user settings services. In its place, we will extend the security mechanisms in the system settings service so that we can comfortably store all settings there. This will hopefully make our API nicer and cleaner, and it will give us fast-user-switching support basically for free. Best of all, this will simplify the codebase considerably, esp. in the clients. It should make implementing new N-M clients much easier.
We will want to put some serious thought into what kind of security mechanisms to implement. It likely will be some combination of ACLs and polkit.
This is the 2010 Google Summer of Code project of DanielGnoutcheff. Work-in-progress is posted at the rm-userset branch at http://daniel.gnoutcheff.name/nm/daemon.git. Note that this branch is frequently rebased on master, so you won't want to base any work on it without prior arrangement.
Eric, the single user: Eric is the primary user of his laptop. He has full control over networking on his laptop, without password prompts or any other annoyances.
Eric and always-ask: Eric occasionally VPNs into a very important part of his company's network. Eric does not want the risks associated with having the password stored anywhere; he'd prefer to enter it each time he activates the connection. However, he does want other (non-secret) VPN settings to be saved, i.e. he doesn't want to re-enter all VPN details each time.
Erin logs in, activates a wireless connection, works for a while, and then takes a break, locking her session before she goes. A bit later, Ed logs into the same machine and attempts to connect to a different wireless network. But this would break any long-running downloads Erin started, so he is not permitted to do this.
Well, actually, he could kill the connection by the laptop's WiFi killswitch. As Ludwig Nussel points out, local access usually means someone can terminate connections anyway. There's not much that N-M can do about that. We could have the various N-M clients show a warning if deactivating a connection would, say, break any sockets opened by other users, but this is really a separate issue now. It can be addressed later if we want to.
Avery and the family computer: Alex and his son Avery share a family computer. They live in a rural area, and so they must make do with a POTS dial-up modem. Avery is not allowed to change the computer's network configuration, but in order to let him have Internet access when he needs it, he is allowed to activate the modem connection.
However, Alex does not want the connection to run too long (he wants to make regular phone calls!), so he is authorized to deactivate the modem, even if Avery is still logged in.
- Again, doesn't matter what N-M does, he can unplug the modem if he has to. :P
Max and the family computer: Max also has an account on the family computer. He is something of a computer hobbyist, and among other things, he likes to setup and manage the home network. Alex doesn't mind if Max manages the computer's connection to this network, but Max is even worse than Avery is when it comes to spending too much time surfing the 'net, so he is not allowed to activate the modem connection.
Family-computer system-wide secrets: The network that Max manages is a secure WiFi network. The security is mainly intended to keep folks outside the family off the network; anyone who has an account on the family computer can connect without having to enter the WPA password.
Alex and encrypted secrets: Alex often needs to make a VPN connection for work. He is the only one allowed to use the connection; he does not want the kids to mess with his co-workers. The VPN password is stored in gnome-keyring, so he knows that it is reasonably safe even if the computer is stolen.
Larry the lab sysadmin: Larry is the sysadmin of a computer lab. He does not want to deal with users fiddling with network configs, and so he does not allow anyone except himself to do anything with network connections.
Shelly and the WiFi laptops: Shelly manages a set of laptops that are lent out to schoolchildren for various activities. These laptops connect to the 'net via a secure WiFi network at the school. She does not want anything else to connect to these networks, so she does not want students to be able to copy the WiFi secrets from the laptops. Thus, she has N-M setup such that the students cannot read the WiFi secrets.
Shared encrypted secrets: Alan and Andrew share a workstation in a branch office, and they both occasionally VPN into the main office. They use the same VPN connection, and they use the same password. They also do not want the password to be compromised if the machine is stolen, so they both keep a copy of the password in their keyrings. When either one of them activates the connection, N-M knows that it can look in one of their keyrings for the password.
It is annoying to have duplicate copies of the password like that, but maybe that's the only way. Having a shared keyring wouldn't work, since Alan and Andrew would still need to store the shared keyring's unlock password in their own keyrings, resulting in duplicate passwords anyway. I guess that's what they get for sharing passwords.
Stale secrets: Some time later, Andrew no longer uses the VPN connection, and his copy of the password is out of date. Alan, however, has no trouble using the connection, as N-M always uses his copy of the password, even if Andrew is logged in.
Private connections on corporate laptops: Ellen manages a set of laptops lent to employees in a reasonably large office. This office is a consulting firm, so people often need to setup connections to their various client's networks (VPN connections, wifi connections, etc.). To reduce support issues, she'd also rather not let people modify the connection to the office wifi and wired networks, but she does still want to let anyone activate these connections.
Private connections on corporate desktops: Ellen also manages a set of desktops that many employees share. Here, too, she'd like to let people create any VPN connections they need. Here, however, it is particularly important that these connections are kept private to the user who created them, and it'd be nice if that could be enforced. Otherwise, we could end up with a mess of publicly-available VPN connections that everyone has to wade through.
How fine-grained should the permissions be?
First, let's define some permissions a user may have on a connection's settings:
Read/view: the ability to read the non-secret parts of a connection
Activate: the ability to manually activate the connection, possibly replacing other activated connections.
Administrate: the ability to edit the connection and view it's secrets
So how fine-grained to we want to be? Some proposals considered are listed below. We currently plan to use "by visibility with read-only flag".
Have per-connection permission settings determine if that connection is "visible" by a given user. Further restrict users with a set of polkit actions that determine if a user is allowed to perform some operation on any connection. Thus, a user is allowed to perform an operation only if they can perform the corresponding polkit action and if the given connection is visible to them. A good set of polkit actions might be "read", "activate", and "administrate".
- This is a relatively simple approach that will cover most -- but not all -- of the use cases listed above.
- The cases not covered are "Private connections on corporate laptops" and "Private connections on corporate desktops".
- Having a control for "visibility" is nice for dealing with autoactivation. See "How to handle auto(de)activation?" below.
- Having some connections not "visible" to some users will complicate the API, since we'll need to be able to present a different list of connections to each client. This could get particularly annoying when dealing with update notifications, since DBus signals are not client-specific.
- However, information leakage is a real concern; the non-secret parts of a private connections could conceviable be used for some kind of attack. So we'll want to do this anyway.
- See the "Access API" for a discussion of how to deal with this.
By visibility with read-only flag
(This is the currently accepted proposal.)
Follows the "by visibility" proposal above, but also makes use of a "read-only" boolean on each connection. Connections that are marked "read-only" cannot be edited by anyone over DBus, regardless of polkit or visibility ACLs. Read-only connections can be modified only by administrators who can manually edit the config files.
- Almost as simple as "by visibility", but all of the listed use cases are covered.
- Using N-M in the "private connections on corporate laptops" and "private connections on corporate desktops" cases is slightly complicated by the need to edit config files by hand (to set the read-only bit on the connections to the corporate network). However, the simplified permissions format makes N-M easier to use in all of the other cases, so this seems to be a good trade-off.
- We already have a read-only flag on connections; it's currently meant to deal with settings plugins that can read connections but cannot write them. Extending this to make it user-configurable should be very straightforward.
By activation and administration
Have connections be visible to all users. Have per-connection permissions specify which users can activate that connections, and also specify what subset of those users can administrate that connection.
- All use-cases are covered, at the cost of some additional complexity. (This complexity is somewhat mitigated by the assumption that the set of users who can administrate is a subset of those who can activate.)
- Since all connections are "visible" to all users, we avoid the problem of presenting a different list of connections to each client. However, this also makes dealing with auto(de)activation a bit more complicated.
- A lack of control over connection reading also raises information leakage concerns.
By visibility and administration
Have per-connection permission settings determine what users a connection is visible to, and also allow them to specify a subset of those users who also have the power to administrate those connections. Further restrict users with "activate" and "administrate" polkit actions. So, when a user wishes to perform some operation on a connection, their permissions are decided as follows:
- A user may view the connection if they are allowed to do so according to that connection's permission settings
- A user may manually activate the connection if they have the "activate" polkit permission and if they are allowed to view the connection.
- A user may administer the connection if they have the "administrate" polkit permission and they are allowed to do so according to that connection's permission settings.
- Again, all use-cases covered at the cost of some additional complexity.
- The usual pros/cons of connection "visibility" controls (as discussed earlier) apply.
Notes/comments/issues for all approaches
- We could also have a additional set of polkit actions for allowing certain users to override these permission settings so that administrators could fix things if we ended up with connections that nobody could edit otherwise.
How to handle auto(de)activation?
We already have an "autoconnect" flag, but there are many causes were we don't want a connection to be autoactivated even when this flag is set. Some options:
- Since we will be using "visibility" controls: we can use this approach: Connections that are readable by anyone ("world-readable") are always eligible for autoactivation and are never deactivated. Connections that are not world-readable are eligible for autoactivation only if it can be read by at least one user. Non-world-readable connections are autodeactivated when the last user who can read them logs out.
- Note that there are many cases where we would want a connection to never be autodeactiavted but still not want anyone to manually activate it. Thus, this only works we setup per-connection permissions such that the right to "read" (or "access") a connection is separate from the right to "activate" it.
- If we were not to have "visibility" controls, we might do this: Have a separate flag to indicate if a connection can be autoactivated even if none of the logged-in users have the right to manually activate it. When the last user who can manually activate a connection logs out, have another flag specify if the connection should be auto(de)activated.
- It may be possible to use a single flag for both.
Since we plan to have controls on who can read a connection, each client will potentially have a different list of connections that it can read. We'll need to modify the DBus settings API somewhat to accommodate this, particularly since DBus signals don't have the kind of permission controls we would need. Some possible approaches:
For org.freedesktop.NetworkManagerSettings.Connection, we modify the "Updated" signal to remove settings data, and we add a "PermissionsChanged" signal that is emited only when the connection's permission settings are changed. Each client holds an internal list of "accessible connections", and it maintains this list as follows:
Whenever a connection is added, the client attempts to read the connection. If this is successful, the connection is added to the list; if this throws back a "PermissionDenied", we ignore it.
The client subscribes to the "PermissionsChanged" signals of all connections. When a "PermissionsChanged" signal is received, the client attempts to read the connection to determine if the new permissions give it access to the connection. If this succeeds, the connection is added to the internal list (if it wasn't there already). If it fails, the connection is removed from the list (assuming it was there in the first place).
- The client keeps an internal copy of all of the connections on its accessible connections list, and to keep these copies up to date, it subscribes to the "Updated" signals of exactly those connections that it can read.
- It also subscribes to the "Deleted" signals of exactly those connections on its list, and removes connections from the list if/when connections are deleted.
- Are there N-M clients that don't necessarily want to keep an internal copy of all accessible connections? If so, it may make sense to have a separate "check permissions" call to allow a client to determine if it has access to a connection without pulling all settings data over the "wire".
- There is a little bit of information leakage in that everyone can know if some connection was modified, regardless if they actually have access to the connection that was edited. This might give away some info on what a particular user is doing.
- The daemon part of this should be quite easy to implement. However, the clients need to do a bit of work; they would have to make DBus method calls in response to most signals, which could get annoying (esp. since we might have to do everything async to ensure that the GUI update latencies stay low). libnm-glib might be able to mitigate this.
Instead of DBus signals, an N-M client that is interested in connections can export a "notification sink" DBus object and register it with N-M. N-M keeps track of what connections are accessible by each client, and when the list changes for some client (or if one of the connections on the list is updated), N-M calls methods on the "notification sink" of that client to notify it of the changes.
In essence, N-M maintains an "accessible connections" list for each client, and "notification" method calls replace signals 1-for-1.
- One of our goals is to make N-M clients simpler. This approach may help with with that, since clients don't have to keep track of "accessible connections" themselves. We might be able to have the "notification sink" method calls give enough information to the clients to let them update their internal state without making any additional DBus calls.
- Big disadvantage: this is a fair bit of complexity in the N-M daemon.
Per-connection permissions settings format
I.e. for permissions that vary from connection to connection, how do we specify what users have what permissions?
We currently plan to use ACLs. This will be implemented as a 'allowed' property on NMSettingsConnection, a string list of users and groups that are allowed to view the connection. Entires are of the form 'user:<name>' or 'group:<name>'. Duplicates entires are removed, order doesn't matter.
Some options that were considered:
- Private/system-wide: Have the permission given to either a single user or to eveyone on the system. This mirrors the kind of permissions user settings services currently give us.
- This is probably the simplest approach we could get away with and would be very easy to implement, esp. when the connection editor gets involved (only a checkbox or two would be needed).
- This closely mirrors the granularly that system vs. user settings services give us. All use cases that N-M currently covers will still be covered.
- However, the "family computer" use-cases are not fully covered.
- ACLs: A list of users and groups who have some permission. We also may have flags on each entry to specify a subset of the listed users and groups that have some additional permission. (So regardless of which proposal we select from "How fine-grained should the permissions be?", we would only need one ACL per connection.)
- Main pro: this is the most powerful option. It probably can cover *any* use-case anyone could think of.
- Main con: Probably most difficult to implement, and most difficult to make a GUI for.
- Private/group-private/system-wide: Have the permission given to either a single user, a single unix group, or to everyone on the system.
- In theory, this is just has powerful as full ACLs, but perhaps with less implementation effort. There's a good chance sysadmins are already familiar with unix groups and may appreciate being able to administer N-M using tools they already know.
- But non-administrators can't edit groups, and apparently groups can be annoying in quite a few other ways.
"Private connections only"
There are some cases where we want to allow people to add and modify connections, but we want those connections to stay private to the user who created them. How to accomplish this?
- One way is to have a polkit action that controls if a user is allowed to edit per-connection permission settings or specify an initial set of permission settings when adding a connection. Furthermore, when a user adds a connection without specifying initial permission settings, we default to having the connection be private to that user. Then, if a user doesn't have the right to edit permission settings, they can only create private connections.
Even without user settings services, we still want to store secrets in gnome-keyring and the like. We want to figure out the best way to do this.
Proposal 1: secrets agents, services services
Secrets are transfered from keyrings to N-M via "N-M secrets-agents", DBus objects exported on the system bus from which N-M can request secrets for a specific connection. These objects would likely implement org.freedesktop.!NetworkManagerSettings.Connection.Secrets or something very similar.
To deal with auto-activated connections, each logged-in user with a keyring has a "N-M secrets-service" (most likely embedded in nm-applet and the like) that keeps track of what secrets that user has. For each connection that the user has a secret for, the secrets service creates and registers secrets-agent.
When a connection is auto-activated, we would request the secret from some secrets-agent registered for that connection. We'd probably have N-M first try the secrets-agent from the user who most recently provided a "correct" secret for the connection (i.e. a secret that led to a successful connection activation).
When someone explicitly activates a connection, they also have the option of supplying a N-M secrets agent along with the activation request. This is intended to override or stand in the place of a secrets service. When N-M receives such a request and if a secret is indeed needed, it does the following:
- If the connection has a "secret-always-ask" flag, get the secret from the given secrets agent, requesting that the user be prompted if necessary. If no agent was supplied or if this request fails, the connection fails.
- Otherwise, N-M tries the following, in order.
- If the user supplied a secrets agent, try to get the secret from that (but with a flag asking that the user not be prompted).
- Then, then if the requesting user has a secrets service that registered an agent for the connection, try to get the secret from that (again, asking to avoid prompting the user).
- Then, try to get the secret from system-wide settings storage.
- Then, try the supplied secrets agent again, this time requesting that the user get prompted.
- Then try the pre-registered secrets agent again, again requesting that the user get prompted.
- If all that failed, then activation fails.
Note that in the case of explicit activation requests, we only pay attention to secrets agents from the requesting user. (Users probably don't want their keyrings being used to satisfy activation requests made by other users.)