I've been thinking a little about UniConfSecurity. For the PhoneIntegrator?, I'd like to use [users/jnc] as a tree for storing values that are readable/writable to either client or server - stuff like "fullname" or "soundoptions", to be used to communicate settings. But for that, I need to restrict access to clients that can prove they actually are jnc.

Assume for the moment we can exchange credentials (probably using SsoYa?). Then each key would have a set of default access permissions, overridable for each user, for Read, Write and Iterate. The Iterate permission denies the ability to scan for keys. Permissions apply to the subtree rooted at each key.

The obvious place to add this is at the mount point. This means that you would need to mount each user's tree separately in order to give them their own permissions, which is a drawback. A smarter UniConfDaemon would be able to assign permissions by pattern-matching, or simply have a SecurityManager? object which can be overridden to hard-code a permission scheme.

On the client side, we'd want to keep this in UniClientGen?, since it won't apply to most generators. The client only needs to know to send credentials along, for example with the moniker "ssoya:...".

--jnc


apenwarr (2003/03/04): My comments:

  • Forget full arbitrary ACLs. Use the Unix-style owner/group/permissions method instead.

    • jnc (2003/03/04): That's what I was picturing, except I'm not sure what the execute permission would translate to, which is why I swapped it with iterate. There's also the problem that a uniconf node is both a "file" containing data and a "directory" - what does the read permission apply to? The data, or listing of the contents?

      • jnc (2003/03/04): drheld points out that this actually makes more sense for UniConf than for the Unix filesystem, since if we're set read but not exec we actually have something that can be read even if you don't drill down.
      • dgtaylor? (2003/03/04): Well, since you have the "iterate" permission, that should be the permission to look at the listing of contents I'd say, and the "Read" permission would be who can read the data. I'd guess too that "Write" permission would be needed to both a) Add a node, and b) modify data.
  • You could have a UniConfGen wrapper that implements security on its subordinate, kind of like the "readonly" generator. The security settings themselves could be stored in just another UniConf tree. The "defaults" generator drheld wrote will probably be handy for this.
  • it might be pretty obnoxious to only have permissions on a per-mount-point basis. Each subtree should be able to have permissions of some sort.


dgtaylor? (2003/03/04): That is true. Looking at it though, this means you would have to store the permission structure inside the UniConf source somehow.. Say for an ini file some sort of hex string that is read in along with the key name, and have it default to world read/write/iterateable. This would get messy though. Alternate idea might be if you were to store the permissions in a seperate permissions file, where the permissions are stored on a per-subtree basis, with modifications of the topmost node affecting all nodes beneath it. To be more clear what I mean, let me illustrate using the following tree structure:

[Root]
  [Top Node 1]
    [Child Node a]
    [Child Node b]
  [Top Node 2]
    [Child Node c]
      [Child Node c i]

Now, let's say we wanted everyone to be able to view, edit and iterate the contents of [Root], only view and iterate [Top Node 1], be able to view and edit [Child Node a], everyone can view, edit and iterate [Top Node 2], but only the owner can see, read or edit [Child Node c], the permissions file I'm thinking of would be:

[Root] ago+rwi [Top Node 1] ago+rw [Child Node c] o+rwi

Note that [Top Node 2] and [Child Node c i] do not have entries. This is because I'm thinking their permissions would default to those of their immediate parent unless specified.

Problems with these ideas though:

  • Some way to specify the owner would be needed.
  • Need some way to track who is changing permissions.
  • Could conceivably be a file FAR too large to be of any use.


jnc (2003/03/05): Here's the current plan: UniPermGen? is a UniFilterGen? (wrapping gen) which also has a reference to a parallel tree (perms). Each entry of perms is a Unix-style octal int holding the user, group and world permissions for the corresponding key in gen. (Note: we can't use the more readable ".../user/read" through ".../world/exec" subkeys because gen might happen to have keys with the same names.)

  • Just realized this won't be good enough, because we need to store the "owner" and "group". So format it "owner group mode". Yeah, that's the ticket...
  • jnc (2003/03/13): Ok, what was I thinking? Of course we can hang stuff off perms, because we won't stomp on the original data - it's in another tree. The worst that'll happen is we'll get "/pets/fido/owner" (an entry for the permissions of /pets/fido) and "/pets/fido/owner/owner" (an entry for the permissions of /pets/fido/owner), which is confusion because .../owner and .../owner/owner aren't actually related to each other.

    • jbrown (2003/03/20): I don't think name clashes are sufficient reason to use two trees. That said, I can see other reasons for separating contents from security tags though they have only been briefly touched upon here. (I won't go in any further because my view of things usually only has a glancing relationship with reality except in the most general of contexts). Anyways, a simpler solution would be to declare a reserved container name prefix for these metadata keys and hide them from the normal traversal sequence. That's part of the idea behind ReiserFS?. Alternately modify the namespace conventions slightly. For example, right now the // sequence is squashed to /. You could instead set things up such that // delimits a key from its invisible attributes. I think this is a STUPID idea, but whatever.

Generally perms will be a UniDefGen, so that entire trees can be given default permissions. The last-resort default is for a key to be readable and executable to all but writable to none.

UniPermGen? has extra methods to look up a given permission type given a path and credentials object. The credentials contains a username and list of groups that a given connection belongs to. The full list of groups is unfortunately needed to check the execute permission of each component of the path. (Is there a more efficient way to do this?) An unauthenticated connection will just get a null username.

To use a UniPermGen?, create a specialized UniConfDaemon which authenticates each connection, stores a credentials structure for each, and then passes the credentials structure along with every request. Not sure yet whether it's enough to add this and then pass the monikers to gen and perm, or if we we want to make the UniConfDaemon architecture flexible enough to create a whole host of daemons for different behaviours.


drheld (2003/03/05): This is close, but I'm thinking this can be made much nicer through the UniSecureGen?. What we do is have the UniSecureGen? take two objects -- a UniConfGen and a UniPermGen?.

A UniPermGen? is a tree which is mounted to store permissions and nothing else. It's dangerous to have the file permissions directly accessible and the UniPermGen? remains hidden by the UniSecureGen?. We also don't need to worry about overlapping keynames this way. The UniSecureGen? accesses the UniConfGen it holds but checks the UniPermGen? before actually doing anything. This way we never have to worry about storing extra information in the actual uniconf tree. The permisisons can be added, changed, and removed easily while keeping the base UniConfGen unchanged.


  • jnc (2003/03/05): I think what I was calling UniPermGen?, drheld is calling UniSecureGen?, and what he's calling UniPermGen? I was calling a random generator called perms. But he points out it might be useful to have an API to change the permissions tree without needing to mount a specific generator, so we'll go with his scheme.


jbrown (2003/03/20): Some problems to chew on:

  1. Parallel data structures are hard to maintain and fix.

  • jnc (2003/03/20): Yes. However, if we dump the permissions data into the same tree, we end up using up keys that might be wanted for other things (see the "pets/fido/owner" exampe). We'd need to add metadata to each node, which is something apenwarr's been thinking about but hasn't thought of a good way to do yet. (Both LDAP and the Windows registry do this, but apparently we don't like the mechanism but haven't thought of a better one.)

How are the permissions and storage trees synchronized safely? Presumably if I wanted to, I could make a UniSecureGen? out of a UniConfGen and UniPermGen? where both are derived from a common source and automatically synchronized. This might be useful for persisting permissions in INI files. Definitely don't want two INI files for each tree since that would blow away all of Avery's nvi arguments.

  • jnc (2003/03/20): I think 90% of the time permissions will be set with a fairly simple defaults generator, which makes the parallel trees not too heinous. Also, they're useful if you want to apply different permissions in different situations (such as to trusted/untrusted hosts). That said, I think it'd be useful to think more on the metadata idea: I'd be fine with using ";" as a new separator. Two seems reasonable.

    • jbrown (2003/03/20): Possibly, but it's hard to tell upfront what the permissions will look like. Storing permissions along with each file works fine for file systems.

  1. End-to-end solution. Need to sign or protect permissions trees somehow when passing them around and when storing them. How are permissions revealed and accessed through UniConfDaemon setups? Is UniConfClient a UniSecureGen??

  • jnc (2003/03/20): I've pretty much punted this for now. UniConfDaemon's getting a PAM authentication step which it skips if its backing gen isn't a UniSecureGen?. UniConfClient will add a way to recognize the PAM prompts and pass them up to the user (of course, this needs to be reimplemented for each app depending on its UI, but it's only one function) but otherwise doesn't know anything about security - just that sometimes the daemon refuses to talk to it. Keeping your backing tree secure outside of the daemon is Your Responsibility.

    • jnc (2003/03/20): Also, the only reason the whole talk to the user step is needed is in case the PAM administrator sets the uniconfdaemon service to use unix_auth or something else. It would be a little less hassle to just say we only support non-interactive PAM modules (ie. Ssoya), but that might be going a little too far. With Ssoya, client wouldn't need to know anything at all about this because the daemon wouldn't even be sending any prompts along.

      • jbrown (2003/03/20): I think this needs to be treated with the same level of seriousness as any normal System call trap. UniConfDaemon must have the final word on transactions from clients. Data being transferred between daemon and clients should probably proceed over a secure channel like SSL to prevent snooping or spoofing of authenticated connections.

        • jnc (2003/03/20): Quite often we'll have the daemon and client actually running on the same box and talking through a unix socket, in which case SSL will only slow things down. When it's made available over the network, the whole thing should be wrapped in SSL, authentication and data both. (Hmm, on a closer look I believe you said that.)
  • jbrown (2003/03/20): Actually, thinking about it, we can conceptually decompose the secure UniConfGen into a number of simpler interfaces. Namely ContentProvider?, PersistenceProvider?, ErrorReporter?, and PermissionsProvider?. We might also imagine an AuthenticationProvider?, etc... Starting to sound like ReiserFS? to me. Too bad we're using C++ to implement this thing...

    • jnc (2003/03/20): Gah! That way lies madness!

      • jbrown (2003/03/20): Hehe... It's funny though how most of these features are orthogonal on the conceptual level but crosscut at the implementation level.


jnc (2003/03/24): I added conversation functions to pass along user responses, which was easy to add until it got to the actual UniClientGen? implementation. At that point, it would require either blocking until authentication is done (horrible), changing the interface so that the caller knows when it's actually okay to start doing gets and sets (evil), or queueing commands (which doesn't even make sense for gets). So instead, we're going to automatically fail anything that requires user interaction, so it will work just fine with any PAM method that takes no input (most notably Ssoya).

  • jnc (2003/03/24): Or, I suppose, just throwing away any command that happens before authentication is finished, which just sounds like trouble.