Multiple Kubernetes authorization modules checked in sequence, how?

7/29/2019

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"?

-- Devoops
authorization
kubernetes

1 Answer

7/29/2019

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
    }
-- menya
Source: StackOverflow