Skip to content

OPA with Rego rules in config maps

Page as Markdown

Enforce Open Policy Agent (OPA) policies with Rego rules in Kubernetes config maps.

The Solo Enterprise for kgateway external auth service can use an OPA module to enforce Rego rules stored in Kubernetes config maps. You create an AuthConfig resource that references the config map, and the external auth service loads and evaluates the rules on each request.

This guide walks through two examples:

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

Path-based authorization

This example creates a Rego rule that allows or denies requests based on the request path and HTTP method. This approach is convenient for basic environments, quick testing, and small OPA use cases.

Setup

  1. Create a Rego rule.

    cat <<EOF > policy.rego
    package test
    
    default allow = false
    allow {
        startswith(input.http_request.path, "/anything")
        input.http_request.method == "GET"
    }
    allow {
        input.http_request.path == "/status/200"
        any({input.http_request.method == "GET",
            input.http_request.method == "DELETE"
        })
    }
    EOF

    Review the following table to understand this configuration.

    SettingDescription
    default allow = falseDenies all requests by default.
    allow {...}Allows requests that match either condition:
    1. The path starts with /anything and the HTTP method is GET.
    2. The path is exactly /status/200 and the HTTP method is either GET or DELETE.
  2. Store the OPA policy in a Kubernetes config map.

    kubectl -n httpbin create configmap allow-get-users --from-file=policy.rego
  3. Create an AuthConfig resource and add your external authentication rules. The following example configures OPA authentication with the Rego rules that you created earlier.

    kubectl apply -f - <<EOF
    apiVersion: extauth.solo.io/v1
    kind: AuthConfig
    metadata:
      name: opa-auth
      namespace: httpbin
    spec:
      configs:
      - opaAuth:
          modules:
          - name: allow-get-users
            namespace: httpbin
          query: "data.test.allow == true"
    EOF

    Review the following table to understand this configuration.

    SettingDescription
    opaAuthConfigure the OPA authentication details.
    modulesRefer to the name and namespace of the config map that has the OPA policy. Then, Solo Enterprise for kgateway can use the OPA policy to resolve the query. This example uses the config map that you previously created.
    queryThe query that determines the authentication decision. The result of this query must be either a boolean or an array with a boolean as the first element. A value of true means that the request is authorized. Any other value or error means that the request is denied. In this example, data.test.allow is set to true. data is the section in the config map. test.allow is part of the OPA policy that you previously created. Access is allowed only if the response meets the allow conditions in the policy.
  4. Create an EnterpriseKgatewayTrafficPolicy resource that refers to the AuthConfig that you created. 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: opa-auth
      namespace: kgateway-system
    spec:
      targetRefs:
        - name: http
          group: gateway.networking.k8s.io
          kind: Gateway
      entExtAuth:
        authConfigRef:
          name: opa-auth
          namespace: httpbin
    EOF
  5. Verify that the AuthConfig is ACCEPTED.

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

Verify the OPA policy

  1. Send a request to the httpbin app on a path that is not allowed by the OPA policy. Verify that your request is denied and that you get back a 403 HTTP response code.

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

    Example output:

    < HTTP/1.1 403 Forbidden
    < server: envoy
    < content-length: 0
  2. Send another request to the httpbin app. This time, you include the /status/200 path that is allowed in the OPA policy. Verify that the request succeeds and that you get back a 200 HTTP response code.

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

    Example output:

    < HTTP/1.1 200 OK
    < access-control-allow-credentials: true
    < access-control-allow-origin: *
    < content-length: 0
    < x-envoy-upstream-service-time: 1
    < server: envoy

Cleanup

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

Response manipulation and header control

OPA policies can do more than allow or deny requests. You can use Rego rules to manipulate response headers and body, remove sensitive request headers before they reach the upstream, and set dynamic metadata for use by other filters such as rate limiting.

The following example creates a policy that checks for an api-key request header and controls the response based on the result.

Setup

  1. If you still have the resources from the path-based authorization example, delete them first.

    kubectl delete authconfig opa-auth -n httpbin
    kubectl delete EnterpriseKgatewayTrafficPolicy opa-auth -n kgateway-system
    kubectl delete configmap allow-get-users -n httpbin
    rm policy.rego
  2. Create a config map with an advanced Rego rule. This policy checks for a valid api-key header and controls the response behavior for both allowed and denied requests.

    kubectl apply -f - <<EOF
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: opa-policy
      namespace: httpbin
    data:
      policy.rego: |-
        package httpbin
    
        import future.keywords.if
    
        # Default response when the request is denied: return a 403 with a
        # custom body and response header.
        default allow := {
          "allow": false,
          "response_headers_to_add": {"x-opa-deny-reason": "missing-or-invalid-api-key"},
          "body": "Request denied by OPA policy",
          "http_status": 403,
        }
    
        # When the api-key header matches, allow the request. Strip the
        # api-key header so it is not forwarded to the upstream, and add a
        # response header visible only to the client.
        allow := returned_value if {
          input.http_request.headers["api-key"] == "my-secret-key"
    
          returned_value := {
            "allow": true,
            "http_status": 200,
            "headers": {"x-validated-by": "opa-policy"},
            "response_headers_to_add": {"x-opa-status": "approved"},
            "request_headers_to_remove": ["api-key"],
          }
        }
    EOF

    Review the following table to understand this configuration.

    SettingDescription
    default allowThe fallback decision when no other rule matches. Returns a 403 status, a custom body, and an x-opa-deny-reason response header.
    allow := returned_value if {...}Allows the request when the api-key header equals my-secret-key. Strips the api-key header from the upstream request and adds x-validated-by and x-opa-status headers.
  3. Create an AuthConfig that references the config map. The query field points to the allow rule in the httpbin package.

    kubectl apply -f - <<EOF
    apiVersion: extauth.solo.io/v1
    kind: AuthConfig
    metadata:
      name: opa-advanced
      namespace: httpbin
    spec:
      configs:
      - opaAuth:
          modules:
          - name: opa-policy
            namespace: httpbin
          query: "data.httpbin.allow"
    EOF
  4. Create an EnterpriseKgatewayTrafficPolicy that references the AuthConfig.

    kubectl apply -f - <<EOF
    apiVersion: enterprisekgateway.solo.io/v1alpha1
    kind: EnterpriseKgatewayTrafficPolicy
    metadata:
      name: opa-advanced
      namespace: kgateway-system
    spec:
      targetRefs:
        - name: http
          group: gateway.networking.k8s.io
          kind: Gateway
      entExtAuth:
        authConfigRef:
          name: opa-advanced
          namespace: httpbin
    EOF
  5. Verify that the AuthConfig is ACCEPTED.

    kubectl get authconfig opa-advanced -n httpbin -o yaml

Verify the advanced OPA policy

  1. Send a request without an API key. The request is denied with a 403 status, a custom body, and an x-opa-deny-reason response header.

    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 403 Forbidden
    < x-opa-deny-reason: missing-or-invalid-api-key
    < content-length: 29
    < content-type: text/plain
    < server: envoy
    <
    Request denied by OPA policy
  2. Send the request again with a valid api-key header. The request succeeds. Notice that the api-key header is removed from the upstream request, and the x-validated-by and x-opa-status response headers are added.

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

    Example output:

    < HTTP/1.1 200 OK
    < x-opa-status: approved
    < x-validated-by: opa-policy
    < access-control-allow-credentials: true
    < access-control-allow-origin: *
    < content-type: application/json; encoding=utf-8
    < server: envoy

    In the JSON response body from httpbin, notice that the api-key header is not present in the forwarded headers, confirming the OPA policy stripped it before reaching the upstream.

Cleanup

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