keys: Allow request_key upcalls from a container to be intercepted
Provide a mechanism by which daemons can intercept request_key upcalls,
filtered by namespace and key type, and service them. The list of active
services is per-user_namespace.
====
WHY?
====
Requests to upcall and instantiate a key are directed to /sbin/request_key
run in the init namespaces. This is the wrong thing to do for
request_key() called inside a container. Rather, the container manager
should be able to intercept a request and deal with it itself, applying the
appropriate namespaces.
For example, a container with a different network namespace that routes
packets out of a different NIC should probably not use the main system DNS
settings.
==================
SETTING INTERCEPTS
==================
An intercept is set by calling:
keyctl(KEYCTL_SERVICE_INTERCEPT,
int queue_keyring, int userns_fd,
const char *type_name, unsigned int ns_mask);
where "queue_keyring" indicates a keyring into which authorisation keys
will be placed as request_key() calls happen; "userns_fd" indicates the
user namespace on which to place the interception (or -1 for the caller's);
"type_name" indicates the type of key to filter for (or NULL); and
"ns_mask" is a bitwise-OR of:
KEY_SERVICE_NS_UTS
KEY_SERVICE_NS_IPC
KEY_SERVICE_NS_MNT
KEY_SERVICE_NS_PID
KEY_SERVICE_NS_NET
KEY_SERVICE_NS_CGROUP
which select those namespaces of the caller that must match for the
interceptionto occur.. So, for example, a daemon that wanted to service
DNS requests from the kernel would do something like:
queue = add_key("keyring", "queue", NULL, 0,
KEY_SPEC_THREAD_KEYRING);
keyctl(KEYCTL_SERVICE_INTERCEPT, queue, -1, "dns_resolver",
KEY_SERVICE_NS_NET);
so that it gets all the DNS records issued by processes in the current
network namespace, no matter what other namespaces are in force at the
time. On the other hand, the following call:
keyctl(KEYCTL_SERVICE_INTERCEPT, queue, -1, NULL, 0);
will match everything.
If conflicts arise between two filter records (and different daemons can
have filter records in the same list), the most specific record is matched
by preference, otherwise the first added gets the work. So, in the example
above, the dns_resolver-specific record would match in preference to the
match-everything record as the former in more specific (it specifies a type
and a particular namespace). EEXIST is given if an intercept exactly
matches one already in place.
An intercept can be removed by setting queue_keyring to 0, e.g.:
keyctl(KEYCTL_SERVICE_INTERCEPT, 0, -1, "dns_resolver",
KEY_SERVICE_NS_NET);
All the other parameters must be given as when the intercept was set.
Notes:
(1) Note that anyone can create a channel, but only a sysadmin or the root
user of the current user_namespace may add filters.
[!] NOTE: I'm really not sure how to get the security right here. Who
is allowed to intercept requests? Getting it wrong opens a
definite security hole.
(2) It doesn't really handle multiple service threads watching the same
keyring.
(3) The intercepts are not tied to the lifetime of the queue keyring,
though they can be removed later. This is probably wrong, but it's
more tricky since they currently pin the queue keyring. They are,
however, cleaned up when the user namespace that owns then is
destroyed.
(4) I'm not sure it really handles cases where some of the caller's
namespaces aren't owned by the caller's user_namespace.
==================
SERVICING REQUESTS
==================
The daemon servicing requests should place a watch on the queue keyring.
This will inform it of when an authorisation key is placed in there.
An authorisation key's description indicates the target key and the callout
data can be read from the authorisation key.
The daemon can then gain permission to instantiate the associated key.
keyctl_assume_authority(key_id);
After which it can do one of:
keyctl_instantiate(key_id, "foo_bar", 7, 0);
keyctl_negate(key_id, 0, 0);
keyctl_reject(key_id, 0, ENOANO, 0);
and the authorisation key will be automatically revoked and unlinked.
If the authorisation key is unlinked from all keyrings, the target key will
be rejected if it hasn't been instantiated yet.
[!] NOTE: Need to provide some way to find out the operation type and other
parameters from the auth key. /sbin/request_key supplies this on the
command line, but I can't do that here. It's probably something that
needs storing into the request_key_auth-type key and an additional
keyctl providing to retrieve it.
===========
SAMPLE CODE
===========
A sample program is provided. This can be run as:
./samples/watch_queue/key_req_intercept
it will then watch for requests to be made for user keyrings in the user
namespace in which it resides. Such requests can be made by:
keyctl request2 user a @s
This key will be rejected and the command will fail with ENOANO.
Subsequent accesses to the key will also fail with ENOANO.
Signed-off-by: David Howells <dhowells@redhat.com>
16 files changed