Description
Category
Policy evaluation change
Describe the feature you'd like to request
I've been thinking if we somehow can make the UX for authorizing based on label and fieldSelectors more uniform.
Today, if I want to make it possible for someone to get pods with label foo=bar
, I need to do the following for a read:
permit (
principal == k8s::User::"lucas",
action in [k8s::Action::"list", k8s::Action::"watch"],
resource is k8s::Resource
) unless {
resource has labelSelector &&
resource.labelSelector.contains({
"key": "owner",
"operator": "=",
"values": [principal.name]
}, {
"key": "owner",
"operator": "==",
"values": [principal.name]
}, {
"key": "owner",
"operator": "in",
"values": [principal.name]
})
};
(we could fix fold the =
, ==
, and in
maybe on the authorizer side to make things easier, however)
(also, the current state easily breaks, as we have a set within a set. If there was a permit
rule with labelSelectors.contains({"key": "owner", "operator": "in", "values": ["a", "b"]})
, then the policy author expects the principal to see objects with either owners. However, the latter policy actually isn't so easy to use, as if the user asks for ?labelSelector=owner=a
, then the Cedar expression evaluates to false
, and access is denied. Only if the user performed a ?labelSelector=owner in (a, b)
would they be authorized. We could/should "flatten" the labelSelector on the Cedar side to two expressions with atomic key-value pairings, e.g. labelSelector = [{"key": "owner", "operator": "==", "value": "a"}, {"key": "owner", "operator": "==", "value": "b"]
, and then the policy can be written as labelSelectors.contains({"key": "owner", "operator": "==", "value": "a"} || labelSelectors.contains({"key": "owner", "operator": "==", "value": "b"}
, which works both if the only one selector is applied)
If I want to make sure the same user can only create and update, we do the following right now (roughly):
permit (
principal == k8s::User::"lucas",
action in [
k8s::admission::Action::"create",
k8s::admission::Action::"update",
k8s::admission::Action::"delete"],
resource
) unless {
resource has metadata &&
resource.metadata has labels &&
resource.metadata.labels.contains({"key": "owner", "value": principal.name})
};
One idea I got is: what if we kept the schema for labels (both for reads and writes) as an empty record, but guard with resource.labels has owner && resource.labels.owner == principal.name
. This doesn't yield errors in e.g. VS Code, even though there isn't a owner
property of the Labels
type in the schema.
This means I could write the latter write policy like:
permit (
principal == k8s::User::"lucas",
action in [
k8s::admission::Action::"create",
k8s::admission::Action::"update",
k8s::admission::Action::"delete"],
resource
) when {
resource.metadata.labels has owner && resource.metadata.labels.owner == principal.name
};
That seems a bit more natural, and works well for writes, where the object is concrete.
However, for reads, we don't have concrete objects. However, we maybe could do the same thing by internally (in the authorizer), running a check for every permutation of label and field selector paths in the original k8s request.
So for an example request of ?labelSelector=owner in (a, b),env in (dev, prod)
, there are four "archetypes" of objects matched:
owner=a && env=dev
owner=a && env=prod
owner=b && env=dev
owner=b && env=prod
thus, can we run 4 authorization checks against the policies, and enforce a logical AND between them, ensuring that whatever object could be selected by the read, is authorized
Now, the above read rule would apply like this:
permit (
principal == k8s::User::"lucas",
action in [k8s::Action::"list", k8s::Action::"watch"],
resource is k8s::Resource
) when {
resource.labels has owner && resource.labels.owner == principal.name
}
Notably, if there was another label (e.g. env
, this rule would allow any value of the env label).
Now, notice that the read and write policies are the same! (and could even be folded in the same policy possibly).
Describe alternatives you've considered
Haven't had time to consider alternatives yet.
Additional context
No response
Is this something that you'd be interested in working on?
- 👋 I may be able to implement this feature request
-
⚠️ This feature might incur a breaking change