Description
When Authorino performs a TokenReview, the structure of the extracted identity object varies depending whether the token is a JWT (e.g. a Kubernetes SA token) or an opaque token (e.g. OpenShift OAuth token). While for JWTs, the identity object is the JWT payload, for opaque tokens, it is the status.user
from the TokenReview response.
This behaviour makes it hard to write authorisation rules based on attributes of the identity object, especially in hybrid authentication systems where both kinds of tokens are simultaneously accepted. Differences such as the name of the property that carries the subject ID (i.e., either sub
or username
) and presence/absence of relevant properties (e.g. iss
, groups
) can be hard to workaround, often requiring extra requests to the cluster authentication system to retrieve further user info – when such endpoint is available! –, and/or repetition of configuration under when
conditions (only to control the expected identity object format).
Authorino extended properties feature doesn't play nice to normalise tokens in such cases where the source value of new (extension) property may not be present.
To exemplify the issue...
The payload of a Kubernetes JWT issued for a SA looks like the following:
{
"aud": [
"https://kubernetes.default.svc"
],
"exp": 1684835538,
"iat": 1684834938,
"iss": "https://kubernetes.default.svc",
"kubernetes.io": {
"namespace": "talker-api",
"serviceaccount": {
"name": "talker-api-bff",
"uid": "bd2d95ad-5ca8-42fc-a6f8-e11600094b44"
}
},
"nbf": 1684834938,
"sub": "system:serviceaccount:talker-api:talker-api-bff"
}
The status
subresource returned with the response to the TokenRequest request in an OpenShift cluster looks like the following:
{
"audiences": [
"https://kubernetes.default.svc"
],
"authenticated": true,
"user": {
"groups": [
"system:serviceaccounts",
"system:serviceaccounts:talker-api",
"system:authenticated"
],
"uid": "bd2d95ad-5ca8-42fc-a6f8-e11600094b44",
"username": "system:serviceaccount:talker-api:talker-api-bff"
}
}
- While JWT claims such as
iss
(from the SA JWT) as well as user info such asuser.groups
(from the TokenReview status) can both be useful for writing authorisation rules; nevertheless either only one or the other may be present. - Writing an authorisation check (e.g. SubjectAccessReview) with user ID fetched from either
auth.identity.sub
orauth.identity.username
is not straightforward.
The user should have a way to tell if the JWT payload or the returned user info is desired to fill the identity object. By default, I'd personally go with the user info, but only if the keeping of other attributes, such as audiences
, was possible (not the case today).
A few alternatives considered:
- Both objects (JWT and TokenRequest status) could be merged together when the token is a JWT, such as in a structure like the following:
type KubeTokenIdentity struct { Token struct { Opaque string `json:"opaque,omitempty"` JWT any `json:"jwt,omitempty"` } `json:"token"` Status kubeauthnv1.TokenReviewStatus `json:"status"` // https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#tokenreviewstatus-v1-authentication-k8s-io }
- The API for the TokenReview identity verification method in the AuthConfig could include a field to use the JWT payload:
identity: - name: k8s-tokens kubernetes: jwt: true # default: false
- Possibility to extend the identity object conditionally, e.g. by setting a (default) value only in case the property is absent in the source object – only for the issue of
sub
vsusername
:identity: - name: k8s-tokens kubernetes: {} extendedProperties: - name: username valueFrom: authJSON: auth.identity.sub.@default:{"value":auth.identity.username}