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

Reply via email to