Alternative to PAGs
Hi Linus,
Okay, starting with a clean slate and writing PAGs off as a bad idea, how about trying to work out what is required first? As I see it:
THE REQUIREMENTS
================
(1) Credentials/tokens/keys/whatever are held in a "keyring".
(2) A keyring is destroyed when the last reference goes away (kernel resources are precious, though it may be possible to store credentials in swapspace somehow).
(3) Every user has to have a default keyring that is created the first time they log in [Linus demands this].
(4) A user has to be able to override the default keyring, such that they can, for instance, perform operations on a remote filesystem with a different credential.
(5) A user has to be able to run a program with a reduced set of credentials.
(6) A process must be able to pass a subset of its credentials to a second, already running process so that the second process can perform some service on its behalf.
This gets tricky if the service process is performing services for a number of processes simultaneously, each of which has its own set of credentials.
(7) A process should be able to discard any credential it has access to, particularly in conjunction with (6).
(8) It must be possible to withdraw a credential.
(9) The credentials governing access to a file must be associated with a struct file, not with the process context currently accessing a file.
(10) A struct file will gain its credentials at the time it is opened.
POSSIBLE EXTENSIONS
===================
(11) Threads should perhaps share a common set of credentials, but be able to adjust them on a per-thread basis (see (6)).
(12) A SUID process should add the credentials it gains from its new user ID to those it inherited from its original context.
(13) There's one place Win32 has an advantage, I think: calls for setting up handles (files, mutexes, etc) take security context parameters.
I think "groups" specific keyrings and arbitrary joinable keyrings are superfluous (that's what ACLs are for) and a systems maintenance nightmare, so I haven't included them.
SUGGESTED IMPLEMENTATION
========================
As far as implementation goes, perhaps each task_struct and each file should point to a stack of keyrings:
+------+
| |
+--------------------->| USER |
| | |
| +------+
|
+------+ +------+ +------+ +------+
| | | | | | | |
| TASK |----->| SUID |---->| PRIV |---->| USER |
| | | | | | | |
+------+ +------+ +------+ +------+
^ ^
+------+ | |
| | | |
| FILE |---------+ |
| | |
+------+ |
|
+------+ |
| | |
| FILE |----------------------+
| | ^
+------+ |
|
+------+ +------+ +------+
| | | | | |
| TASK |----->| FILE |----------------->| USER |
| | | | | |
+------+ +------+ +------+ The keyrings in the stack would then be refcounted, and the next pointers would be immutable.
struct keyring {
struct keyring *next;
struct keyring *conjunction;
struct list_head keys;
atomic_t usage;
int type;
#define KEYRING_USER 0
#define KEYRING_PRIVATE 1
#define KEYRING_SUID 2
#define KEYRING_FD 3
};
struct key {
struct list_head link;
atomic_t usage;
int type;
#define KEY_POSITIVE 0
#define KEY_NEGATIVE 1
void *credential;
};And then add the following syscalls:
(*) auth_clear_stack(int completely)
Totally clear a process's stack (either completely or of everything but the user credentials).
(*) auth_add_cred(const char *fs, const char *domain, void *data);
Add a new credential to the TOS keyring. The key would be negative if data is NULL.
(*) auth_push_empty()
Push an empty keyring onto this process's stack.
(*) auth_push_fdcreds(int fd)
Push the credentials associated with fd onto the stack as a preferred alternative.
(*) auth_pop()
Pop the top credential off of the stack.
David
Quoted 2 lines · David Howells
(1) Credentials/tokens/keys/whatever are held in a "keyring".
Not "a keyring". At least to me, a "keyring" implies a "bunch of keys", with no structure to it. That's not what you want from a maintenance perspective.
You want to have multiple keyrings, with the ability to say "I am now done with those keys I got from source X". That doesn't mean that you drop all your credentials, it only means that you have stopped one "session" (and clearly a session should be able to have many keys).
In particular, one "session" is your default one, and again, clearly that is not necessarily just a single key.
Also, the fact that you have a default one MUST NOT preclude you from having access to _another_ keyring too. But accessing that other keyring does not mean that the keys from that should be added to your default
keyring: you're sharing the default keyring with other processes, so modifying your default one to add capabilities to _one_ process is clearly the wrong thing to do.
In other words, you want to not maintain individual keys, you always want to maintain a _group_ of keys. You don't get "a key" by default. You get "a group of keys" by default. The group may, of course, contain just a single key.
So what I think you want is a hierarchy:
- "my credentials" is a "set of keyrings"
- a "keyring" is a "set of keys" aka a "session" (when used as a AFS session, this is a PAG. Other users might have other naming conventions for their sets of keys)
- a "key" is an indivisible blob, as far as the base kernel is concerned (and likely most users of the key too, for that matter).
A key must be on a ring, ie all keys are part of a "session". But a session may be associated with an arbitrary number of processes, and a process must be able to maintain multiple sessions at once.
Quoted 3 lines
(4) A user has to be able to override the default keyring, such that they can, for instance, perform operations on a remote filesystem with a different credential.
I disagree. Your (4) comes from your (1) - inability to have multiple keyrings. If you have multiple keyrings, you don't "override" anything. You just have a stackign order (ie the credentials is a _sorted_ list of keyrings), and you search the credentials for valid keys in order.
Quoted 3 lines
(6) A process must be able to pass a subset of its credentials to a second, already running process so that the second process can perform some service on its behalf.
A "subset of credentials" _is_ a keyring. You only ever pass keyrings around. You don't pass individual keys around, but you also aren't forced to pass off _all_ of your credentials at the same time.
Quoted 1 line
(8) It must be possible to withdraw a credential.
I think you have a few (different) cases of invalidation:
- you "change the lock" (ie the old key that gave you something now becomes useless)
- you remove access to a group of keys (ie you remove a keyring, aka "terminate a session" for one user). Other processes in that session still have access to the keyring.
- you maintain a session/keyring (add or remove keys from it, and all processes usign that session will see the changes to the session).
So there are at least three (very different) set of operations that withdraw a credential and work on different levels.
Quoted 2 lines
(9) The credentials governing access to a file must be associated with a struct file, not with the process context currently accessing a file.
This does beg the question: which level of credentials does the file get associated with. Does it get _all_ the credentials that the opener had (ie access to every keyring)? Or does it get associated with the one session that the open() decided was relevant? Or does it get associated with the single key that was used for the open?
I suspect that the open file should be associated with one "session".
Quoted 1 line
(10) A struct file will gain its credentials at the time it is opened.
Agreed.
Quoted 2 lines
(12) A SUID process should add the credentials it gains from its new user ID to those it inherited from its original context.
But it has to keep them _separate_: it needs to be able to drop it's own extra keys at some point, and revert to "non-suid-ness". This is why it's so important to consider the "set of credentials" to be more than just a "bunch of keys". It has to be bunched up some way.
And my suggestion is that the keyring is that "bunch", and that a suid application is nothign more than one that got a special keyring at execve() time. A keyring that it can choose to drop.
Linus
Quoted 11 lines · Linus Torvalds
Quoted 4 lines · David Howells
(4) A user has to be able to override the default keyring, such that they can, for instance, perform operations on a remote filesystem with a different credential.
I disagree. Your (4) comes from your (1) - inability to have multiple keyrings. If you have multiple keyrings, you don't
"override" anything. You just have a stacking order (ie the
credentials is a _sorted_ list of keyrings), and you search the credentials for valid keys in order.
I am not completely sure what David meant. I think I agree with what he meant, but would argue with your assumption.
A process can be both [email protected] and [email protected] at the same moment. By being [email protected], I'm also in any number of AFS groups at RPI (a number that I have no knowledge of or control over).
What AFS does not want is for a single process to be [email protected] and [email protected] at the exact same time. That is to avoid the question of what open() should do on a file which is permitted:
drosehn rlidwka
linus none
The above says [email protected] should have absolutely no access to the file, no matter what AFS or unix groups he is in. At the same time, [email protected] has complete access to the file. If the process claims to be both of those users, then what is open() to do? In the pure-unix world, is a process both userid linus and userid drosehn? No. (it might have different values for userid and effectiveUserid, but it only has one value for userid and only one value for effectiveUserid).
You could argue that open() should give access, or that it should not give access, but my point is that the limitation against being multiple AFS users at a single time is to avoid that question. It was not the side-effect of some other decision.
*1 = Actually, in afs the permissions are on a directory level,
but I'd agree that they *should* be on the file level, and thus I said it that way...
Signature · 3 lines
Garance Alistair Drosehn = [email protected] Senior Systems Programmer or [email protected] Rensselaer Polytechnic Institute or [email protected]
I can see some advantage to the ability to switch into an already-existing pag, so let me switch sides for a moment.
If someone came to me and would pay me to implement "something just like a PAG, except that there must be an ability to join an already-existing PAG", I would suggest the following:
1) Creation of PAGs are handled exactly as they are now. Lightweight creation, etc.
2) If the user of some already-existing PAG wants that PAG to be "join-able", they run some special command which registers the PAG with a name, and with the details of how to authenticate to that named-PAG. One could select from a variety of authentication methods.
3) Then from some other process (which would probably already have a PAG) could run a special setpag command, and if they provide the proper authentication, then that process would switch to the already-existing pag.
This isn't quite what Linus is looking for, but it provides some of it without adding much overhead to the most common usage of PAGs. Step #2 should probably require that the user can not register a name for a PAG unless they pass some authentication test to prove that they can. Ie, you don't want me to walk up to your terminal, gesture excitedly at some fire across the street, and then type in the 'register pag' command while you're not paying attention to me or your computer.
Users won't know or care about the range for PAG values, because they will only be dealing with the "registered names" for a PAG.
I still would prefer PAGs as they are, because I think this still opens up some issues that I'd prefer to leave closed. But I think that would be doable.
I should probably also apologize to David here, because he's actually doing useful work. Here I'm just butting in because it would be so great to see PAG support as part of linux, instead of something we have to keep sticking on the side of it. RPI is moving our AFS cell to redhat linux servers, and the easier it is for openafs on linux, the nicer it will be for us.
Signature · 3 lines
Garance Alistair Drosehn = [email protected] Senior Systems Programmer or [email protected] Rensselaer Polytechnic Institute or [email protected]
Quoted 6 lines · Garance A Drosihn
What AFS does not want is for a single process to be [email protected] and [email protected] at the exact same time. That is to avoid the question of what
open()should do on a file which is permitted:drosehn rlidwka
linus none
An even better example without an obvious answer (which in this case is that the open should be allowed, since that ACL says that drosehn should be able to open the file and says nothing about linus) would be if linus had negative rights (in other words, if the ACL actively asserted that linus should *not* be able to open the file regardless of the other ACLs).
AFS supports the notion of negative rights primarily in combination with groups, so you can have a situation like:
Normal rights:
organization:itss rlidwka
Negative rights:
rra rlidwka
where rra is a member of organization:itss. rra will be denied access to that directory despite the fact that his membership in organization:itss would normally give him full rights.
Signature · 1 line
Russ Allbery ([email protected]) <http://www.eyrie.org/~eagle/>
Quoted 7 lines
I should probably also apologize to David here, because he's actually doing useful work. Here I'm just butting in because it would be so great to see PAG support as part of linux, instead of something we have to keep sticking on the side of it. RPI is moving our AFS cell to redhat linux servers, and the easier it is for openafs on linux, the nicer it will be for us.
I agree, having cleanly supported PAG support in the kernel would be great. The problem is that we can't just "keep sticking on the side of it". We'd be perfectly happy to do so - that's the way we do it on every other unix platform that afs has EVER supported as far as I know.
The problem is that certain kernel developers have specifically and intentionally (YES, intentionally, I've seen the discussions.) gone to efforts to prevent the AFS developers from continuing to do this. We're just looking for an alternative that allows AFS to continue to function at all on newer kernels than 2.4.
-- Nathan
------------------------------------------------------------
Nathan Neulinger EMail: [email protected]
University of Missouri - Rolla Phone: (573) 341-4841
Computing Services Fax: (573) 341-4216
Quoted 6 lines
Quoted 2 lines
(1) Credentials/tokens/keys/whatever are held in a "keyring".
Not "a keyring". At least to me, a "keyring" implies a "bunch of keys", with no structure to it. That's not what you want from a maintenance perspective.
I don't think I was clear. I meant "keyring" as a container holding a bunch of keys, not as in an auth session (ie "a bunch of keyrings, ordered in some fashion").
What do you mean by "structure" anyway? You later seem to assume a session/keyring is just an arbitrary key container holding a set of potentially unrelated keys.
Quoted 4 lines
You want to have multiple keyrings, with the ability to say "I am now done with those keys I got from source X". That doesn't mean that you drop all your credentials, it only means that you have stopped one "session" (and clearly a session should be able to have many keys).
I never suggested that it would mean that.
I take it you mean "session" == "keyring" in this terminology?
Quoted 2 lines
Also, the fact that you have a default one MUST NOT preclude you from having access to _another_ keyring too.
Agreed. But you still need some way to say in any given situation the order in which keyrings should be considered, and whether or not they should be considered.
Quoted 4 lines
But accessing that other keyring does not mean that the keys from that should be added to your default keyring: you're sharing the default keyring with other processes, so modifying your default one to add capabilities to _one_ process is clearly the wrong thing to do.
Okay, I can accept that.
Quoted 4 lines
In other words, you want to not maintain individual keys, you always want to maintain a _group_ of keys. You don't get "a key" by default. You get "a group of keys" by default. The group may, of course, contain just a single key.
Are you thinking that all the keys in a keyring should come from the same authority (for instance Redhat's Kerberos service)? If so, that might make "labelling" them easier... On the other hand, that would then mean you'd need sessions to be groups of rings, so maybe not.
Quoted 8 lines
So what I think you want is a hierarchy:
- "my credentials" is a "set of keyrings"
- a "keyring" is a "set of keys" aka a "session" (when used as a AFS session, this is a PAG. Other users might have other naming conventions for their sets of keys)
- a "key" is an indivisible blob, as far as the base kernel is concerned (and likely most users of the key too, for that matter).
This precludes anything but AFS using PAGs... All a PAG is is a unique (though recyclable) label by which a credentials can be indexed.
Quoted 1 line
A key must be on a ring, ie all keys are part of a "session".
I take it you mean a key must be on at least one ring...
Quoted 2 lines
But a session may be associated with an arbitrary number of processes, and a process must be able to maintain multiple sessions at once.
I think, then, there must be facilities for:
(1) listing (or iterating over) the keyrings/sessions to which a process subscribes.
(2) listing the keys inside a keyring to which a process subscribes.
(3) creating a new keyring.
(4) linking a key from any keyring to another (like the link() function does for files).
(5) adding a new key to a keyring.
(6) updating a key in a keyring (ie: avoiding expiration).
(7) withdrawing a key from a keyring.
(8) unsubscribing from a keyring.
(9) opening a file with reference to a particular keyring.
(10) rearrange the set of keyrings (read sequence rather than set, I suppose) to affect the search ordering.
Quoted 8 lines
Quoted 3 lines
(4) A user has to be able to override the default keyring, such that they can, for instance, perform operations on a remote filesystem with a different credential.
I disagree. Your (4) comes from your (1) - inability to have multiple keyrings. If you have multiple keyrings, you don't "override" anything. You just have a stackign order (ie the credentials is a _sorted_ list of keyrings), and you search the credentials for valid keys in order.
Yes, that is one way of overriding the default keyring.
Quoted 7 lines
Quoted 3 lines
(6) A process must be able to pass a subset of its credentials to a second, already running process so that the second process can perform some service on its behalf.
A "subset of credentials" _is_ a keyring. You only ever pass keyrings around. You don't pass individual keys around, but you also aren't forced to pass off _all_ of your credentials at the same time.
Okay.
Quoted 9 lines
I think you have a few (different) cases of invalidation:
- you "change the lock" (ie the old key that gave you something now becomes useless)
- you remove access to a group of keys (ie you remove a keyring, aka "terminate a session" for one user). Other processes in that session still have access to the keyring.
Quoted 5 lines
- you maintain a session/keyring (add or remove keys from it, and all processes usign that session will see the changes to the session).
So there are at least three (very different) set of operations that withdraw a credential and work on different levels.
I can think of a fourth: updating a key that was due to expire, though that would best be done as a combo delete & add operation as keys ought to be immutable once created.
Quoted 10 lines
Quoted 2 lines
(9) The credentials governing access to a file must be associated with a struct file, not with the process context currently accessing a file.
This does beg the question: which level of credentials does the file get associated with. Does it get _all_ the credentials that the opener had (ie access to every keyring)? Or does it get associated with the one session that the
open()decided was relevant? Or does it get associated with the single key that was used for the open?I suspect that the open file should be associated with one "session".
I agree. If you can construct a new keyring with an arbitrary subset of all the keys you have access to, then you can say "open with _that_ keyring" (though the keyring in question might be the default FS op one, sort of like fsuid).
Quoted 5 lines
Quoted 2 lines
(12) A SUID process should add the credentials it gains from its new user ID to those it inherited from its original context.
But it has to keep them _separate_: it needs to be able to drop it's own extra keys at some point, and revert to "non-suid-ness".
Okay, I'll grant you that. I have seen a document discussing PAGs (for Coda I think it was) that suggested that SUID program execution places the process in a new PAG, and that setuid(SAVED_IDS) reverts to the original.
Quoted 2 lines
This is why it's so important to consider the "set of credentials" to be more than just a "bunch of keys". It has to be bunched up some way.
See my suggestion above
Quoted 3 lines
And my suggestion is that the keyring is that "bunch", and that a suid application is nothign more than one that got a special keyring at
execve()time. A keyring that it can choose to drop.
Fair enough. Should UID/GID/GROUPS[] then be a credential/key in its own right? If that's the case, then a process should be able to select a key (or keyring) as being the one to supply this information.
David