So, in the recent thread about pam_auths we raised the idea of checking for specific authorizations for a user who is logging in. In the long run it would be quite nice if such a mechanism could be used to replace per-service configuration files that try to control root (or other privileged account) logins.
The difficulty in doing that is that it would require that root is granted quite a long list of authorizations: pretty much everything in auth_attr(4) except the few that are undesirable (solaris.login.remote, solaris.ftpd.access, etc). An additional complexity is that that also requires adding each authorization that is defined in the future both in auth_attr(4) and in root's user_attr(4) entry. What makes things less straightforward when considering negative authorizations is the semantics of things: does removing an authorization remove it from the user's current set (it can be added again later), or from the user's set at any time (it can never be regained), or from the set currently being added ("grant foo.* except for foo.bar", but if the user already has foo.bar we don't care), or from the set one that is being added one level up (basically can a profile that is included in another profile restrict what its parent can grant?), or from the set that is being added one level down (can a profile restrict what its children can grant). All in all it would seem most desirable to be able to stop a user from ever getting a specific (set of) authorizations as well as to be able to grant a subset easily (and in a way that permits more sub-authorizations to be added). The current implementation of chkauthattr(3SECDB): - checks policy.conf(4) AUTHS_GRANTED=, if the authorization is present it returns success - [PSARC 2008/034] checks policy.conf(4) CONSOLE_USER=, if the authorization is somewhere in the profile or included profiles it returns success - checks policy.conf(4) PROFS_GRANTED=, if the authorization is somewhere in the profiles or included profiles it returns success - checks user_attr(4) auths=, if the authorization is present it returns success - checks user_attr(4) profiles=, if the authorization is somewhere in the profiles or included profiles it returns success To implement negative authorizations, i.e. the ability to remove authorizations from the user's total set as well as the ability to grant an incomplete set efficiently, I believe we need to have two "actions" and change the processing order a little. The first action would be to remove an authorization from the user's set (as it is right now). I'll be using "-" (minus sign) in the examples that follow to indicate this. The second action would be to remove an authorization from the level at which we're working right now, for which I'll be using the "!" (exclamation mark) in the example that follow. To allow denying a specific user from ever getting one or more authorizations the sequencing would need to be changed so that the auths= entry in user_attr(4) is the last to be processed. This permits a global denial of specific authorizations, by removing them from the user's set at the last stage. This permits a root entry in user_attr(4) of the form: root::::auths=solaris.*,-solaris.login.local,-solaris.login.remote,-solaris.ftpd.access Now, in practice I would expect most users to be granted authorizations via rights profiles, so that logical bundles of rights can be easily granted. In such a context the other action (removal from the current level) seems it would be more commonly used, thereby not manipulating the set of already granted authorizations -- something that otherwise could cause quite a bit of administrative complexity. A "level" in this context is a single profile's aggregated authorizations: included profiles are evaluated first, the resulting set is then evaluated against the profile that included the other profiles' auths= statement. I'll try to clarify this last statement: A simple profile that contains no other profiles is quite straightforward: a profile authorization set is created. It is populated with all listed auths. Those auths that are specified with a "!" are dropped from the set. The resulting set is returned and added to the user's authorization set. A more complex profile that contains other profiles would go down one branch (included profile) and construct the set there using the semantics of the simple profile mentioned above. (If the included profile itself includes profiles we follow the semantics of the more complex example we're exploring here) The set of authorizations that is returned by this included profile is merged with any other returned sets, and the resulting set is then evaluated using the simple rules against the auths= statement of the profile from which we started. An example may clarify this: auth_attr has the following auths: a.b.1 a.b.2 a.b.3 a.c.1 a.c.2 a.c.3 a.d.1 a.d.2 a.d.3 profile x has auths=a.b.*,!a.b.1,!a.c.1 and profiles=y,z profile y has auths=a.c.*,!a.c.2 profile z has auths=a.d.1,!a.c.3 We evaluate x, where we find included profiles, so we first evaluate the included profiles. We evaluate y, where we find no included profiles. The auths statement when evaluated results in the set a.c.1,a.c.3 We evaluate z, where we find no included profiles. The auths statement evaluates to the set a.d.1 We now merge the sets from y and z into a.c.1,a.c.3,a.d.1 and we evaluate the auths= statement from x against this set, resulting in: a.b.2,a.b.3 (which we got from x itself) a.c.3,a.d.1 (which we got from y and z, but modified by x) So profile z trying to drop something that came from profile y has no effect: they're disjoint sets, so can't touch each other. However, profile y and z are sub-profiles of profile x, so x can have an impact on what is returned from y and z. If y had included profiles itself then its auths= statement could impact the resulting set that it returns and so on. While it would technically be possible to drop authorizations from the user's granted set I would not expect this to be used often (as it seems administratively tricky to use). That said, there may be cases where it turns out to be useful (e.g. dropping all authorizations to start with a clean set), so I don't think the "-/authname/" should be restricted to only user_attr(4)'s auths= statement. The above logic does mean that having a complete auth_attr(4) is again quite important, as things like auths(1) would need to be able to create a list of a resultant set (granting someone "solaris.*,!solaris.login.remote" would show as quite a long list of authorizations: everything underneath solaris.* with one exception). (An extra option to auths(1) to show the shorter form, using the "!"/"-" syntax might be useful, but its default behaviour only lists granted authorizations -- not denied ones) Thoughts & suggestions welcome. Bart