traefik_open_policy_agent

package module
v1.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 29, 2025 License: MIT Imports: 8 Imported by: 0

README

Traefik Open Policy Agent Plugin

A Traefik middleware plugin that integrates with Open Policy Agent (OPA) for request authorization. This plugin allows you to implement flexible and powerful authorization policies using OPA's policy language (Rego).

Features

  • Integrates Traefik with Open Policy Agent
  • Validates requests against OPA policies
  • Supports full request context (headers, path, method, query parameters)
  • Customizable error responses with support for:
    • Custom HTTP status code
    • Custom headers
    • Custom response body
    • Multiple content types (JSON/Plain text)

Configuration

Static Configuration

To enable the plugin in your Traefik instance:

experimental:
  plugins:
    open-policy-agent:
      moduleName: "github.com/unsoon/traefik-open-policy-agent"
      version: "v1.2.1"
Dynamic Configuration
http:
  middlewares:
    my-opa-middleware:
      plugin:
        open-policy-agent:
          url: "http://opa.kube-system:8181/v1/data/httpapi/authz"
          allowField: "allow"
          errorResponse:
            statusCode: 403
            contentType: "application/json"
            headers:
              X-Error-Type: "authorization_failed"
            body:
              error: "Access denied by policy"
Configuration Options
Option Type Required Default Description
url string Yes - OPA server URL with policy path
allowField string No allow Field name in OPA response for allow/deny
errorResponse.statusCode int No 403 HTTP status code for denied requests
errorResponse.contentType string No application/json Content type of error response (text/plain or application/json)
errorResponse.headers map[string]string No {} Additional headers to include in error response
errorResponse.body interface{} No nil Custom response body

How It Works

  1. The plugin intercepts incoming HTTP requests
  2. Sends request data to OPA server including:
    • HTTP method
    • Request path
    • Headers
    • Query parameters
  3. OPA evaluates the request against defined policies
  4. Based on OPA's response:
    • If allowed: request proceeds to the next middleware/handler
    • If denied: returns configured error response

Example OPA Policy

Here's a simple example of an OPA policy that allows requests based on specific criteria:

package httpapi.authz

import data.io.jwt

default allow = false

env := opa.runtime().env

allow if {
    [token] := input.headers["Authorization"]
    substring(token, 0, 6, prefix)

    prefix == "Basic "
    substring(token, 6, -1, basic_token)

    base64url.decode(basic_token, decoded)
    split(decoded, ":", [username, password])

    crypto.hmac.equal(username, env.USERNAME)
    crypto.hmac.equal(crypto.md5(password), env.HASHED_PASSWORD)
}

allow if {
    [token] := input.headers["Authorization"]
    substring(token, 0, 7, prefix)

    prefix == "Bearer "
    substring(token, 7, -1, bearer_token)

    io.jwt.verify_rs256(bearer_token, env.JWKS_URL)
}

Example Usage

Basic Authorization Check
http:
  middlewares:
    api-auth:
      plugin:
        open-policy-agent:
          url: "http://opa.kube-system:8181/v1/data/httpapi/authz"
          allowField: "allow"
Custom Error Response
http:
  middlewares:
    secure-api:
      plugin:
        open-policy-agent:
          url: "http://opa.kube-system:8181/v1/data/httpapi/authz"
          allowField: "allow"
          errorResponse:
            statusCode: 401
            contentType: "application/json"
            headers:
              WWW-Authenticate: 'Bearer realm="example"'
            body:
              message: "Authorization required"
              details: "Please provide valid credentials"

Request Data Format

The plugin sends the following data structure to OPA:

{
  "input": {
    "method": "POST",
    "path": ["api", "posts"],
    "headers": {
      "authorization": ["Bearer token"],
      "content-type": ["application/json"]
    },
    "query": {
      "filter": ["active"],
      "sort": ["desc"]
    },
    "body": {
      "title": "New Post",
      "author": "john.doe",
      "content": "Hello World!"
    }
  }
}
Body-Based Authorization Example

Here's an example of using request body content for authorization decisions:

package httpapi.authz

# Default deny
default allow = false

# Allow users to modify only their own posts
allow if {
    # Check if this is a POST or PUT request
    input.method in ["POST", "PUT"]

    # Extract user from JWT token
    [token] := input.headers["Authorization"]
    
    # Check if the token is a Bearer token
    startswith(token, "Bearer ")

    # Extract the JWT token
    substring(token, 7, -1, jwt)

    # Decode the JWT token
    io.jwt.decode(jwt, [_, payload, _])

    # Extract the username from the JWT token
    username := payload.sub

    # Verify the username is not empty
    username

    # Extract the author from the request body
    author := input.body.author

    # Verify the author is not empty
    author

    # Verify the author in request body matches the authenticated user's username
    crypto.hmac.equal(author, username)
}

Configure the middleware to use this policy:

http:
  middlewares:
    author-check:
      plugin:
        open-policy-agent:
          url: "http://opa.kube-system:8181/v1/data/httpapi/authz"
          errorResponse:
            statusCode: 403
            contentType: "application/json"
            body:
              error: "You can only create/modify your own posts"

This policy ensures users can only create or modify posts where they are the author.

License

This plugin is distributed under the MIT License.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func New

func New(ctx context.Context, next http.Handler, config *Config, name string) (http.Handler, error)

Types

type Config

type Config struct {
	Url           string        `json:"url,omitempty"`
	AllowField    string        `json:"allowField,omitempty"`
	ErrorResponse ErrorResponse `json:"errorResponse,omitempty"`
}

func CreateConfig

func CreateConfig() *Config

type ErrorResponse

type ErrorResponse struct {
	Headers     map[string]string `json:"headers,omitempty"`
	StatusCode  int               `json:"statusCode,omitempty"`
	ContentType string            `json:"contentType,omitempty"`
	Body        *interface{}      `json:"body,omitempty"`
}

type OpenPolicyAgent

type OpenPolicyAgent struct {
	// contains filtered or unexported fields
}

func (*OpenPolicyAgent) ServeHTTP

func (h *OpenPolicyAgent) ServeHTTP(rw http.ResponseWriter, req *http.Request)

type OpenPolicyAgentInput

type OpenPolicyAgentInput struct {
	Host    string              `json:"host"`
	Path    []string            `json:"path"`
	Method  string              `json:"method"`
	Headers map[string][]string `json:"headers"`
	Query   map[string][]string `json:"query"`
	Body    json.RawMessage     `json:"body,omitempty"`
}

type OpenPolicyAgentPayload

type OpenPolicyAgentPayload struct {
	Input OpenPolicyAgentInput `json:"input"`
}

type OpenPolicyAgentResponse

type OpenPolicyAgentResponse struct {
	Result map[string]interface{} `json:"result"`
}

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL