<p>From the Kubernetes <a href="https://kubernetes.io/docs/reference/access-authn-authz/authorization/#determine-whether-a-request-is-allowed-or-denied" rel="nofollow noreferrer">documentation on authorization</a> it states that:</p>

<blockquote>
  <p>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.</p>
</blockquote>

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

<p>It's not clear how multiple <code>AuthorizationModule</code> work from kubernetes official doc. </p>

<p>So I check the source code of apiserver, it create a combine <code>authorizer.Authorizer</code> by <code>union.New(authorizers...)</code>, from <code>union</code> source I find the answer: </p>

<blockquote>
  <p>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</p>
</blockquote>

<p>More detail at <a href="https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apiserver/pkg/authorization/union/union.go" rel="nofollow noreferrer">k8s.io/apiserver/pkg/authorization/union</a>: </p>

<pre><code>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)
}
</code></pre>

<p>So if you want to create your custom webhook AuthozitaionModule, if you want to pass decision to next authorizer, just give permissive response like:</p>

<pre><code>{
  "apiVersion": "authorization.k8s.io/v1beta1",
  "kind": "SubjectAccessReview",
  "status": {
    "reason": "no decision",
    "allowed": false,
    "denied": false
  }
}
</code></pre>

<p>Then <a href="https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apiserver/plugin/pkg/authorizer/webhook/webhook.go#L208-L217" rel="nofollow noreferrer">apiserver can make a decision by this reponse</a>:</p>

<pre><code>    switch {
    case r.Status.Denied &amp;&amp; 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
    }
</code></pre>
