Projects/kadmin access interface
This project implements a pluggable interface to allow more flexible access control for kadmin operations.
The kadmin facility allows administration of a Kerberos database in two ways. In the normal scenario, the kadmin client calls into libkadm5clnt, which authenticates to kadmind and transmits requests via an XDR-based protocol. kadmind executes the requests by calling into libkadm5srv, which in turns calls into libkdb5:
kadmin --> libkadm5clnt ==> kadmind --> libkadm5srv --> libkdb5
Alternatively, an administrator can run kadmin.local on a KDC host (or with permissions to directly access the database, if LDAP or a similar KDB module is in use). kadmin.local can "authenticate" as any user, and directly calls into libkadm5srv to fulfill requests:
kadmin.local --> libkadm5srv --> libkdb5
The current built-in access control mechanism is implemented at the kadmind layer, so it does not affect kadmin.local. It reads an ACL file which contains access rules, and checks the authenticated user and requested operation against the rules. The ACL file can also apply restrictions to add-principal and modify-principal operations, which may result in changes to the added or modified principal.
A pluggable interface for access control can solve two use cases:
- Administrators can implement programmatic access control if the kadm5.acl format is not flexible enough. For instances, a module could allow users to create host principals for hosts in particular subdomains, which is currently not possible with kadm5.acl as we only allow wildcarding of whole principal components.
- Projects which implement their own KDB module (particularly Samba and IPA) can use the database to control access.
One simple high-level design option is to make kadmind tell the KDB module the authenticated client principal. The KDB module could then check access for subsequent operations such as put_principal and change_pwd. kadmin.local would not supply a client principal (or would perhaps only supply one if specifically requested), so the KDB module would not check access control. This option does not satisfy the first use case and would require the administrator to install a permissive kadm5.acl file, but it would be easy to implement.
Another option, similar to the above, is to amend the kadm5_hook pluggable interface to receive the client principal, either by revising the five existing methods (chpass, create, modify, remove, rename) or by adding a new method to specify the client principal for subsequent operations. This option is not a great fit for the first use case because the kadm5_hook interface only addresses the subset of kadm5_operations which alter principal entries, not those which affect policy objects or those which only request data. It might be helpful, if not completely sufficient, for the second use case insofar as it allows control of set-password operations. As with the first option, this option would require the administrator to install a permissive kadm5.acl file, as kadm5_hook operates at a lower layer than kadm5.acl.
The option most in keeping with our past designs is to create a pluggable interface for kadmin access control and convert the existing kadm5.acl code into a module for the interface. Multiple modules can be combined using the usual accumulator semantics for access control modules (a module can return yes, no, or pass; at least one module must say yes and zero modules must say no for an operation to succeed). If a shared object which implements a kadmin access control module also implements a KDB module, it can retrieve the KDB handle from the krb5_context operation to perform database operations.
The remainder of this page will assume the third option.
The existing kadm5.acl code resides in libkadm5srv, but is only used by kadmind and could be moved there. It has the following interface design:
- A set of operation types (ADD, DELETE, MODIFY, CHANGEPW, INQUIRE, EXTRACT, LIST, SETKEY, and IPROP)
- A check function which accepts a client principal, an operation type, and a target principal, and produces a success-or-failure status and an optional restriction object.
- An impose_restrictions function which accepts a kadm5_principal_ent_rec, mask, and restriction object, and applies the restrictions to the entry and mask.
Not all parameters of the check function apply to all operations. Restrictions are only applicable to the ADD and MODIFY operations when applied to principals, and the target principal is not applicable to the LIST operation, or to the ADD, DELETE, INQUIRE, and MODIFY operations when applied to policy objects.
Several options for the pluggable interface include:
- A design similar to the above, with some possible variations:
- The restrictions object could be transparent, and applied by the krb5 code, so that the interface does not depend on a kadmin type.
- The restrictions object could be eliminated; instead, optional entry and mask parameters could be provided to the check function to be modified. (However, note that renames are denied if the ACL entry for the client principal imposes any restrictions on the modify operation, which might be harder to detect with this design.)
- One method per operation, with parameters specific to the operation, and policy operations separated from principal operations.
- A few methods grouped by operation class, such as "principal operation", "policy operation", and "general operation".
After consulting with potential module authors, the second option was selected. To avoid having to repeat the accumulator code many times, the consumer interface inside kadmind will use option 1, and will fan out to one method per operation when calling out to the module. The two methods which support restrictions will use a separate accumulator function to keep the other accumulator simple.
kadm5_auth modules which are also KDB modules will want to correlate authorized kadmin operations with database calls. The kadm5_auth interface will include an "end" method to indicate when an authorized operation is finished; all database calls between an authorization method call and an end call will be associated with the authorized operation. This design has repercussions for thread safety--a multi-threaded consumer of both the kadm5_auth and KDB interfaces must use a separate krb5_context (and therefore a separate database context) for each thread.
Self-service and kadmin/changepw
kadmind currently makes several decisions before consulting the ACL file:
- A client principal can change its own password, randomize its own keys, purge its own keys, get its own principal entry, get its own string attributes, and get the policy entry associated with its own principal entry.
- If the client authenticated to kadmin/changepw but uses the kadmin protocol rather than the password change protocol, it is only allowed to use the change-password, randomize-key, get-principal, or get-policy operations, and is only allowed to operate on its own principal or policy. The get-principal and get-policy operations are used by the old kadmin-based kpasswd program to display policy restrictions to the user before asking for the new password.
- If a client changes its own keys, the policy minimum lifetime (if present) must have elapsed since the last key change, even if the client is authorized to change its key via the ACL file.
- A client is only authorized to change its own keys via the password change protocol if it uses an initial ticket to authenticate. This restriction is redundant if kadmin/changepw has the DISALLOW_TGT_BASED flag, as it does by default. This rule is not enforced in the kadmin protocol.
In some unusual deployments, it could be valuable to be able to turn off the self-service permissions when using an authorization module. This could be accomplished by moving the self-service decisions to a module which operates alongside the ACL module; that module could then be disabled through module configuration.
To apply the second rule in the new framework, when the service principal is kadmin/changepw, we must apply the self-service restriction outside of the authorization layer, but then still call into the authorization layer (which may again grant access based on self-service) if the restriction is met. For the get-policy operation, applying the self-service restriction requires fetching the client principal entry from the database. It is probably easiest to fetch the entry unconditionally in the server stub, and pass the client principal's policy name to the authorization framework's get-policy method as a second string argument.
To apply the third rule in the new framework, we must again check for self-service outside of the authorization layer to see if we should enforce minimum life, but still call into the authorization layer for self-service requests.
To apply the fourth rule in the new framework without complicating the kadm5_auth interface, we must modify it to be more strict: a client must use an initial ticket to change its own keys, even if it is authorized via the ACL file. We should also enforce this rule in the kadmin protocol as well as the password change protocol.