Skip to content

Auth chaining

Page as Markdown

Chain multiple authentication and authorization methods together with boolean expressions.

When you configure multiple authentication methods, the external auth service processes each named config in the order that they appear in the configs list of an AuthConfig resource. By default, all configs must succeed for the request to be authorized. To change this behavior, you can use the booleanExpr field to chain configs together with logical operators.

With booleanExpr, you can build expressions such as:

Each operand in the expression must match the name field of a config in the configs list. The expression is evaluated left to right, honors parentheses, and supports short-circuiting. For example, if the first operand of an || succeeds, the second operand is skipped.

Supported operators

OperatorDescription
&&Logical AND. Both operands must succeed.
||Logical OR. At least one operand must succeed.
!Logical NOT. Inverts the result of the operand.
()Parentheses. Groups expressions to control evaluation order.

Before you begin

  1. Follow the Get started guide to install Solo Enterprise for kgateway.

    1. Follow the Sample app guide to create a gateway proxy with an HTTP listener and deploy the httpbin sample app.
  2. Get the external address of the gateway and save it in an environment variable.

    export INGRESS_GW_ADDRESS=$(kubectl get svc -n kgateway-system http -o jsonpath="{.status.loadBalancer.ingress[0]['hostname','ip']}")
    echo $INGRESS_GW_ADDRESS  
    kubectl port-forward deployment/http -n kgateway-system 8080:8080

Combine API key and OPA authorization

The following example sets up an AuthConfig that requires both API key authentication and OPA authorization to succeed. The API key config verifies the caller’s identity, and the OPA config enforces a Rego policy that controls which request paths are allowed.

Step 1: Create an API key

Store an API key in a Kubernetes secret so that the external auth service can verify incoming requests.

kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
  name: infra-apikey
  namespace: httpbin
  labels:
    team: infrastructure
type: extauth.solo.io/apikey
stringData:
  api-key: my-test-api-key
EOF

Step 2: Create a Rego policy

  1. Create a Rego rule that allows requests only to specific paths. This example allows GET requests to paths starting with /get or /status/200.

    cat <<EOF > policy.rego
    package test
    
    default allow = false
    allow {
        startswith(input.http_request.path, "/get")
        input.http_request.method == "GET"
    }
    allow {
        input.http_request.path == "/status/200"
        input.http_request.method == "GET"
    }
    EOF
  2. Store the OPA policy in a Kubernetes config map.

    kubectl -n httpbin create configmap httpbin-rego --from-file=policy.rego

Step 3: Create the AuthConfig

Create an AuthConfig resource that chains API key authentication and OPA authorization together with a boolean expression.

kubectl apply -f - <<EOF
apiVersion: extauth.solo.io/v1
kind: AuthConfig
metadata:
  name: apikey-opa-auth
  namespace: httpbin
spec:
  booleanExpr: "apikey && opa"
  configs:
  - name: apikey
    apiKeyAuth:
      headerName: api-key
      labelSelector:
        team: infrastructure
  - name: opa
    opaAuth:
      modules:
      - name: httpbin-rego
        namespace: httpbin
      query: "data.test.allow == true"
EOF

Review the following table to understand this configuration.

SettingDescription
booleanExprA logical expression that controls how the named configs are evaluated. The apikey && opa expression requires both the apikey and opa configs to succeed. The expression is evaluated left to right and supports short-circuiting, so if apikey fails, opa is skipped.
configs.name: apikeyThe name of the API key auth config. The apiKeyAuth setting checks for a valid API key in the api-key request header and matches it against secrets with the team: infrastructure label. The name must match the operand used in the booleanExpr.
configs.name: opaThe name of the OPA auth config. The opaAuth setting references a Rego policy stored in a config map. In the example, the policy allows GET requests only to paths starting with /get or exactly matching /status/200.

Step 4: Create the traffic policy

Create an EnterpriseKgatewayTrafficPolicy resource that refers to the AuthConfig. The following policy applies external auth to all routes that the Gateway serves.

kubectl apply -f - <<EOF
apiVersion: enterprisekgateway.solo.io/v1alpha1
kind: EnterpriseKgatewayTrafficPolicy
metadata:
  name: apikey-opa-policy
  namespace: kgateway-system
spec:
  targetRefs:
    - name: http
      group: gateway.networking.k8s.io
      kind: Gateway
  entExtAuth:
    authConfigRef:
      name: apikey-opa-auth
      namespace: httpbin
EOF

Step 5: Verify the AuthConfig

Verify that the AuthConfig is ACCEPTED.

kubectl get authconfig apikey-opa-auth -n httpbin -o yaml

Step 6: Test the combined auth

  1. Send a request without any credentials. The request is denied with a 401 because no API key is provided, and the OPA check is skipped due to short-circuiting.

    curl -v http://$INGRESS_GW_ADDRESS:8080/get -H "host: www.example.com:8080"
    curl -v localhost:8080/get -H "host: www.example.com"

    Example output:

    < HTTP/1.1 401 Unauthorized
    < content-length: 0
    < server: envoy
  2. Send a request with a valid API key to a path that is not allowed by the OPA policy. The API key check succeeds, but the OPA policy denies the request with a 403.

    curl -v http://$INGRESS_GW_ADDRESS:8080/headers -H "host: www.example.com:8080" \
    -H "api-key: my-test-api-key"
    curl -v localhost:8080/headers -H "host: www.example.com" \
    -H "api-key: my-test-api-key"

    Example output:

    < HTTP/1.1 403 Forbidden
    < content-length: 0
    < server: envoy
  3. Send a request with a valid API key to an allowed path. Both the API key and OPA checks succeed, and the request is allowed.

    curl -v http://$INGRESS_GW_ADDRESS:8080/get -H "host: www.example.com:8080" \
    -H "api-key: my-test-api-key"
    curl -v localhost:8080/get -H "host: www.example.com" \
    -H "api-key: my-test-api-key"

    Example output:

    < HTTP/1.1 200 OK
    < access-control-allow-credentials: true
    < access-control-allow-origin: *
    < content-type: application/json; encoding=utf-8
    < server: envoy

More boolean expression examples

You can test these examples by following a similar setup flow as the previous API key and OPA walkthrough, substituting the AuthConfig with the examples in the following sections.

Fallback authentication

Use || to allow either API key or basic auth to succeed, so clients can authenticate with whichever method they have available.

apiVersion: extauth.solo.io/v1
kind: AuthConfig
metadata:
  name: fallback-auth
  namespace: httpbin
spec:
  booleanExpr: "apikey || basic"
  configs:
  - name: apikey
    apiKeyAuth:
      headerName: api-key
      labelSelector:
        team: infrastructure
  - name: basic
    basicAuth:
      apr:
        users:
          user:
            hashedPassword: <password>
            salt: TYiryv0/

With this configuration, if a valid API key is provided, the basic auth check is skipped entirely due to short-circuiting. If no API key is present, the external auth service falls back to checking basic auth credentials.

ScenarioExpression evaluationResponse
Valid API key providedapikey succeeds, basic skipped200 Success
No API key, valid basic auth credentialsapikey fails, basic succeeds200 Success
No API key, invalid basic auth credentialsapikey fails, basic fails401 Unauthorized
Invalid API key, no basic auth credentialsapikey fails, basic fails401 Unauthorized

Authentication with authorization

Use parentheses to group JWT and OIDC authentication, and then require an OPA authorization check. This example requires an external identity provider, such as Keycloak, for the OIDC configuration.

apiVersion: extauth.solo.io/v1
kind: AuthConfig
metadata:
  name: auth-authz
  namespace: httpbin
spec:
  booleanExpr: "(jwt || oidc) && opa"
  configs:
  - name: jwt
    jwt: {}
  - name: oidc
    oauth2:
      oidcAuthorizationCode:
        appUrl: https://example.com
        callbackPath: /callback
        clientId: ${CLIENT_ID}
        clientSecretRef:
          name: oidc-secret
          namespace: httpbin
        issuerUrl: https://your-issuer.example.com
        scopes:
        - openid
  - name: opa
    opaAuth:
      modules:
      - name: httpbin-rego
        namespace: httpbin
      query: "data.test.allow == true"

The following table shows the expected responses for different authentication scenarios.

ScenarioExpression evaluationResponse
Valid JWT provided, OPA policy passesjwt succeeds, oidc skipped, opa succeeds200 Success
Valid JWT provided, OPA policy failsjwt succeeds, oidc skipped, opa fails403 Forbidden
No JWT, OIDC succeeds, OPA policy passesjwt fails, oidc succeeds, opa succeeds200 Success
No JWT, OIDC succeeds, OPA policy failsjwt fails, oidc succeeds, opa fails403 Forbidden
No JWT, OIDC failsjwt fails, oidc fails, opa skipped401 Unauthorized

Cleanup

You can optionally remove the resources that you set up as part of this guide.
kubectl delete authconfig apikey-opa-auth -n httpbin
kubectl delete EnterpriseKgatewayTrafficPolicy apikey-opa-policy -n kgateway-system
kubectl delete configmap httpbin-rego -n httpbin
kubectl delete secret infra-apikey -n httpbin
rm policy.rego