From the Kubernetes documentation on authorization it states that:
When multiple authorization modules are configured, each is checked in sequence. If any authorizer approves or denies a request, that decision is immediately returned and no other authorizer is consulted. If all modules have no opinion on the request, then the request is denied. A deny returns an HTTP status code 403.
I am now writing a custom webhook for authorization and I would want the logic to fallback to RBAC for a few cases - i.e. have my webhook respond with what the documentation refers to as "no opinion". The documentation however only details how to approve or deny a request and doesn't come back to this third option which seems essential for having multiple authorization modules checked in sequence. How would I best in the context of my webhook respond with "I have no opinion on this request, please pass it on to the next authorizer"?
It's not clear how multiple AuthorizationModule
work from kubernetes official doc.
So I check the source code of apiserver, it create a combine authorizer.Authorizer
by union.New(authorizers...)
, from union
source I find the answer:
The union authorizer iterates over each subauthorizer and returns the first decision that is either an Allow decision or a Deny decision. If a subauthorizer returns a NoOpinion, then the union authorizer moves onto the next authorizer or, if the subauthorizer was the last authorizer, returns NoOpinion as the aggregate decision
More detail at k8s.io/apiserver/pkg/authorization/union:
func (authzHandler unionAuthzHandler) Authorize(a authorizer.Attributes) (authorizer.Decision, string, error) {
var (
errlist []error
reasonlist []string
)
for _, currAuthzHandler := range authzHandler {
decision, reason, err := currAuthzHandler.Authorize(a)
if err != nil {
errlist = append(errlist, err)
}
if len(reason) != 0 {
reasonlist = append(reasonlist, reason)
}
switch decision {
case authorizer.DecisionAllow, authorizer.DecisionDeny:
return decision, reason, err
case authorizer.DecisionNoOpinion:
// continue to the next authorizer
}
}
return authorizer.DecisionNoOpinion, strings.Join(reasonlist, "\n"), utilerrors.NewAggregate(errlist)
}
So if you want to create your custom webhook AuthozitaionModule, if you want to pass decision to next authorizer, just give permissive response like:
{
"apiVersion": "authorization.k8s.io/v1beta1",
"kind": "SubjectAccessReview",
"status": {
"reason": "no decision",
"allowed": false,
"denied": false
}
}
Then apiserver can make a decision by this reponse:
switch {
case r.Status.Denied && r.Status.Allowed:
return authorizer.DecisionDeny, r.Status.Reason, fmt.Errorf("webhook subject access review returned both allow and deny response")
case r.Status.Denied:
return authorizer.DecisionDeny, r.Status.Reason, nil
case r.Status.Allowed:
return authorizer.DecisionAllow, r.Status.Reason, nil
default:
return authorizer.DecisionNoOpinion, r.Status.Reason, nil
}