Help us shape the future of Go! Take the 2026 Go Developer Survey

opa

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Nov 1, 2026 License: MIT Imports: 16 Imported by: 0

README

OPA Plugin

Status: Proof of Concept — This plugin demonstrates integration with external policy engines but is not production-ready. Use it as a reference implementation when building your own policy-driven plugins.

For general plugin usage, see the Plugins guide. For plugin development, see the Plugin System README.

The OPA plugin integrates Open Policy Agent with gosbee to enforce row-level filtering and column-level masking on SQL queries. Policies are evaluated externally by an OPA server (or in-process via a Go callback) and the results are injected into the query AST before SQL generation.

How It Works

The plugin operates as a Transformer that is applied to the AST before SQL generation. It does two things:

  1. Row filtering — Calls the OPA Compile API (POST /v1/compile) with the table name as an unknown. OPA returns partial evaluation residuals — conditions the query must satisfy. These are translated into AST nodes and appended to the WHERE clause.

  2. Column masking — Calls the OPA Data API (POST /v1/data/<package>/masks) to evaluate mask rules. Columns marked for masking have their projections replaced with the mask value as a bind parameter (e.g. $1 AS "billed_total", or '<MASKED>' AS "billed_total" without parameters). When the query uses SELECT *, the star is expanded into explicit column references using a ColumnResolver so that individual columns can be masked. A masked column used anywhere else (under an alias, inside a function, or in WHERE, GROUP BY, ORDER BY and so on) cannot be masked, so the query is refused with opa.ErrMaskedColumnUse. Masks apply inside subqueries, CTE bodies and set-operation branches too.

The plugin only polices SELECT queries. INSERT, UPDATE and DELETE are refused: applying the plugin to one of those managers makes ToSQL return opa.ErrUnsupportedStatement rather than silently emitting an unpoliced statement.

Supported Operators

The Compile API response is translated into SQL using these OPA operators:

OPA Operator SQL
eq / equal =
neq !=
lt <
lte <=
gt >
gte >=
startswith LIKE 'value%'
endswith LIKE '%value'
contains LIKE '%value%'

Multiple queries in the Compile response are AND'd within each query and OR'd across queries, matching OPA's partial evaluation semantics. An empty query set means access denied. A single empty query means unconditional allow (no conditions injected).

Mask Resolution

Masks are fetched from the Data API by deriving the masks path from the policy path. For example, if the policy path is data.policies.filtering.ecommerce.orders.include, the masks are fetched from policies/filtering/ecommerce/orders/masks.

The mask response is a nested map of table -> column -> action. A replace action with a string value means the column is masked. A non-string value (such as an empty object {}) means no mask applies — this allows role-based masking where a superadmin sees all columns unmasked.

Two Modes
  • Server mode (NewFromServer) — Calls a running OPA server. Supports both row filtering and column masking.
  • PolicyFunc mode (New) — Calls a Go function directly for row filtering. No masking support (projections are untouched).

Examples

Using the Plugin in Code
package main

import (
    "fmt"

    "github.com/bawdo/gosbee/managers"
    "github.com/bawdo/gosbee/nodes"
    "github.com/bawdo/gosbee/plugins/opa"
)

func main() {
    // --- PolicyFunc mode (in-process, no OPA server) ---

    policy := func(table string) ([]nodes.Node, error) {
        if table == "orders" {
            t := nodes.NewTable(table)
            cond := t.Col("merchant_name").Eq("Koala Commerce Pty Ltd")
            return []nodes.Node{cond}, nil
        }
        return nil, nil
    }

    table := nodes.NewTable("orders")
    query := managers.NewSelectManager(table)
    query.Use(opa.New(policy))

    sql := query.ToSQL()
    fmt.Println(sql)
    // SELECT * FROM "orders" WHERE "orders"."merchant_name" = 'Koala Commerce Pty Ltd'

    // --- Server mode (calls a running OPA server) ---

    input := map[string]any{
        "user": map[string]any{
            "merchant_name": "Koala Commerce Pty Ltd",
            "state":         "NSW",
            "role":          "sales",
        },
    }

    // Column resolver provides schema knowledge for star expansion.
    resolver := func(tableName string) ([]string, error) {
        if tableName == "orders" {
            return []string{"merchant_name", "total_amount", "customer_state"}, nil
        }
        return nil, fmt.Errorf("unknown table %q", tableName)
    }

    table = nodes.NewTable("orders")
    query = managers.NewSelectManager(table)
    query.Use(opa.NewFromServer(
        "http://localhost:8181",
        "data.policies.filtering.ecommerce.orders.include",
        input,
        opa.WithColumnResolver(resolver),
    ))

    sql = query.ToSQL()
    fmt.Println(sql)
    // SELECT "orders"."merchant_name", '<MASKED>' AS "total_amount", "orders"."customer_state"
    // FROM "orders"
    // WHERE "orders"."merchant_name" = 'Koala Commerce Pty Ltd'
    //   AND "orders"."customer_state" = 'NSW'
}
Using the REPL

The REPL provides an interactive opa command that connects to an OPA server, auto-discovers required inputs, and applies both row filtering and column masking to queries.

gosbee> table orders
  Registered table "orders"
gosbee> from orders
  Query FROM "orders"
gosbee> select orders.merchant_name , orders.total_amount , orders.customer_state
  Projections set (3 columns)
gosbee> limit 10
  LIMIT set to 10
gosbee> run
  SELECT "orders"."merchant_name", "orders"."total_amount", "orders"."customer_state" FROM "orders" LIMIT $1;
  Params: [10]
+---------------------------+--------------+----------------+
| merchant_name             | total_amount | customer_state |
+---------------------------+--------------+----------------+
| Fair Dinkum Books         | 89.95        | NSW            |
| Fair Dinkum Books         | 145.50       | NSW            |
| Billabong Homewares       | 299.00       | VIC            |
| Koala Commerce Pty Ltd    | 54.90        | QLD            |
| Vegemite Ventures         | 199.95       | NSW            |
| Koala Commerce Pty Ltd    | 129.00       | NSW            |
| Wattle & Co Trading       | 78.50        | WA             |
| Fair Dinkum Books         | 234.00       | VIC            |
| Koala Commerce Pty Ltd    | 89.95        | NSW            |
| Billabong Homewares       | 449.00       | SA             |
+---------------------------+--------------+----------------+
(10 rows)
gosbee> opa
  OPA setup:
[Config]   OPA server URL [http://localhost:8181]:
[Config]   Policy path (e.g. data.authz.allow): data.policies.filtering.ecommerce.orders.include
[Config]   Table name (for data discovery): orders
  Policy requires 3 input(s):
[Config]   user.merchant_name: Koala Commerce Pty Ltd
[Config]   user.state: NSW
[Config]   user.role: sales
  OPA enabled — policy: data.policies.filtering.ecommerce.orders.include
gosbee> run
  SELECT "orders"."merchant_name", $1 AS "total_amount", "orders"."customer_state" FROM "orders" WHERE "orders"."merchant_name" = $2 AND "orders"."customer_state" = $3 LIMIT $4;
  Params: [<MASKED> Koala Commerce Pty Ltd NSW 10]
+------------------------+--------------+----------------+
| merchant_name          | total_amount | customer_state |
+------------------------+--------------+----------------+
| Koala Commerce Pty Ltd | <MASKED>     | NSW            |
| Koala Commerce Pty Ltd | <MASKED>     | NSW            |
| Koala Commerce Pty Ltd | <MASKED>     | NSW            |
| Koala Commerce Pty Ltd | <MASKED>     | NSW            |
| Koala Commerce Pty Ltd | <MASKED>     | NSW            |
| Koala Commerce Pty Ltd | <MASKED>     | NSW            |
| Koala Commerce Pty Ltd | <MASKED>     | NSW            |
| Koala Commerce Pty Ltd | <MASKED>     | NSW            |
| Koala Commerce Pty Ltd | <MASKED>     | NSW            |
| Koala Commerce Pty Ltd | <MASKED>     | NSW            |
+------------------------+--------------+----------------+
(10 rows)
gosbee> sql
  SELECT "orders"."merchant_name", '<MASKED>' AS "total_amount", "orders"."customer_state" FROM "orders" WHERE "orders"."merchant_name" = 'Koala Commerce Pty Ltd' AND "orders"."customer_state" = 'NSW' LIMIT 10;
gosbee> opa inputs
  Policy requires 3 input(s):
[Config]   user.merchant_name [Koala Commerce Pty Ltd]:
[Config]   user.state [NSW]:
[Config]   user.role [sales]: admin
  OPA inputs updated and reloaded.
gosbee> run
  SELECT "orders"."merchant_name", "orders"."total_amount", "orders"."customer_state" FROM "orders" LIMIT $1;
  Params: [10]
+---------------------------+--------------+----------------+
| merchant_name             | total_amount | customer_state |
+---------------------------+--------------+----------------+
| Fair Dinkum Books         | 89.95        | NSW            |
| Fair Dinkum Books         | 145.50       | NSW            |
| Billabong Homewares       | 299.00       | VIC            |
| Koala Commerce Pty Ltd    | 54.90        | QLD            |
| Vegemite Ventures         | 199.95       | NSW            |
| Koala Commerce Pty Ltd    | 129.00       | NSW            |
| Wattle & Co Trading       | 78.50        | WA             |
| Fair Dinkum Books         | 234.00       | VIC            |
| Koala Commerce Pty Ltd    | 89.95        | NSW            |
| Billabong Homewares       | 449.00       | SA             |
+---------------------------+--------------+----------------+

Enabling OPA as a sales user injects WHERE conditions that restrict results to the user's merchant and state, and masks the total_amount column. Switching the role to admin removes both the row filters and the column mask — the policy returns unconditional allow with no masks, so the original query runs unmodified.

REPL Commands
Command Description
opa Interactive setup wizard (server URL, policy, inputs)
opa status Show OPA configuration and active mask count
opa off Disable OPA plugin
opa reload Rebuild plugin with current config
opa inputs Re-discover and set input values
opa explain <table> Show how OPA translates to SQL conditions and masks
opa explain <table> verbose Include raw OPA request/response and translation trace
opa conditions Show OPA-injected conditions on the current query
opa masks Show column masks for the current query tables
opa url <url> Change OPA server URL
opa policy <path> Change OPA policy path
opa input <key> <value> Set or update a single input value
opa input <key> Remove an input value

Documentation

Overview

Package opa provides a Transformer that enforces Open Policy Agent policies on queries by injecting policy-derived WHERE conditions.

You supply a PolicyFunc that is called once per table referenced in the query (FROM and JOINs). The function inspects the table name and returns zero or more AST condition nodes to append to the WHERE clause. If the function returns an error the query is rejected entirely — useful for hard "access denied" rules.

Basic usage

policy := func(table string) ([]nodes.Node, error) {
    if table == "secrets" {
        return nil, errors.New("access denied")
    }
    // Restrict "users" to tenant_id = 42
    if table == "users" {
        t := nodes.NewTable(table)
        cond := t.Col("tenant_id").Eq(42)
        return []nodes.Node{cond}, nil
    }
    return nil, nil // no extra conditions
}

o := opa.New(policy)
query := managers.NewSelectManager(table)
query.Use(o)
// SELECT * FROM "users" WHERE "users"."tenant_id" = 42

Combining with other plugins

OPA composes with any other Transformer. Register multiple plugins with successive query.Use() calls and they are applied in order:

query.Use(softdelete.New())
query.Use(opa.New(policy))

Design notes

The OPA plugin is code-only — it requires a PolicyFunc at construction time, so it is not exposed in the interactive REPL. For REPL-friendly plugins see the softdelete package.

Nested queries and raw SQL

Via a manager, the policy is applied inside subqueries, CTE bodies and set-operation branches, and is evaluated once per table per query. A name that refers to a CTE is not a table and is skipped. Queries containing raw SQL (SqlLiteral, StringJoin) are refused with plugins.ErrUnpoliceable.

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrMaskedColumnUse = errors.New("opa: masked column used outside a plain projection")

ErrMaskedColumnUse is returned when a masked column is used anywhere other than as a bare projection, where it cannot be replaced by its mask.

View Source
var ErrUnsupportedStatement = errors.New("opa: only SELECT statements can be policed")

ErrUnsupportedStatement is returned when the plugin is applied to an INSERT, UPDATE or DELETE. The plugin only polices SELECT, so it refuses other statements rather than letting them through unpoliced.

Functions

This section is empty.

Types

type Client

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

Client communicates with an OPA server's Compile API.

func NewClient

func NewClient(baseURL, policyPath string, input map[string]any) *Client

NewClient creates an OPA Client with the given base URL, policy path, and input. The policy path is normalized to include the "data." prefix if not already present.

SECURITY: The baseURL is used as-is for HTTP requests. In production, use HTTPS to prevent policy decisions and input data from being transmitted in plain text.

func (*Client) Compile

func (c *Client) Compile(tableName string) ([]nodes.Node, error)

Compile calls the OPA Compile API for the given table and returns AST condition nodes that can be injected into a WHERE clause.

func (*Client) CompileWithMasks

func (c *Client) CompileWithMasks(tableName string) (*CompileResult, error)

CompileWithMasks calls the OPA Compile API for the given table and fetches masks from the OPA Data API. Returns a CompileResult containing both row-filtering conditions and column masks.

func (*Client) DiscoverInputs

func (c *Client) DiscoverInputs(dataUnknowns ...string) ([]string, error)

DiscoverInputs calls the OPA Compile API to discover which input fields a policy references, including both filter rules and mask rules. Additional data paths (e.g. "data.consignments") can be passed so that rules referencing those paths also produce residuals, exposing all required input fields.

func (*Client) DiscoverPolicies

func (c *Client) DiscoverPolicies() ([]PolicyInfo, error)

DiscoverPolicies queries the OPA server's policies list and returns all policies whose package path contains "include", "filter", or "mask" as a substring. One PolicyInfo is returned per top-level rule found within each qualifying package. Results are sorted by FullPath.

func (*Client) DiscoverTables

func (c *Client) DiscoverTables() ([]string, error)

DiscoverTables inspects the policy source for the current client's package and returns the table names referenced as collections (via iteration or indexing). Identifiers that match a known OPA package path segment are excluded to avoid confusing sub-packages with database tables. Returns nil (not an error) when the package is not found on the server.

func (*Client) Explain

func (c *Client) Explain(tableName string) (*ExplainResult, error)

Explain calls the OPA Compile API and returns diagnostic information about how the response translates to SQL conditions.

func (*Client) FetchMasks

func (c *Client) FetchMasks() (map[string]map[string]MaskAction, error)

FetchMasks queries the OPA Data API to evaluate the masks rule for the current policy. Returns nil if no masks are defined or all mask values are non-string (meaning "no mask").

type ColumnResolver

type ColumnResolver func(tableName string) ([]string, error)

ColumnResolver returns the column names for a given table. It is required when masks are returned by the OPA server and the query uses star projections, because the star must be expanded into explicit column references to allow individual columns to be replaced with their mask values.

type CompileResult

type CompileResult struct {
	Conditions []nodes.Node
	Masks      map[string]map[string]MaskAction
}

CompileResult holds both the row-filtering conditions and column masks.

type ExplainResult

type ExplainResult struct {
	RequestJSON        string
	RawJSON            string
	QueryCount         int
	ExpressionCount    int
	Translations       []ExplainTranslation
	Conditions         []nodes.Node
	Masks              map[string]map[string]MaskAction
	UnconditionalAllow bool
	AccessDenied       bool
}

ExplainResult holds the diagnostic output from an Explain call.

type ExplainTranslation

type ExplainTranslation struct {
	Operator string // OPA operator (eq, neq, lt, etc.)
	Column   string // column name from data ref
	Value    any    // literal value
	SQL      string // resulting SQL fragment
}

ExplainTranslation records how a single OPA expression was translated.

type MaskAction

type MaskAction struct {
	Replace *ReplaceAction `json:"replace"`
}

MaskAction describes how to mask a single column.

type OPA

type OPA struct {
	plugins.BaseTransformer
	// contains filtered or unexported fields
}

OPA is a Transformer that evaluates a policy function against every table in the query and injects the resulting conditions. It supports two modes:

  • PolicyFunc mode (via New): calls a Go function to evaluate policy
  • Server mode (via NewFromServer): calls an OPA server's Compile API

func New

func New(policy PolicyFunc) *OPA

New creates an OPA transformer with the given policy function.

Example

In PolicyFunc mode a Go function plays the policy engine: it returns the conditions each table must satisfy, and the plugin appends them to WHERE.

package main

import (
	"fmt"

	"github.com/bawdo/gosbee/managers"
	"github.com/bawdo/gosbee/nodes"
	"github.com/bawdo/gosbee/plugins/opa"
	"github.com/bawdo/gosbee/visitors"
)

func main() {
	policy := func(table string) ([]nodes.Node, error) {
		if table == "orders" {
			return []nodes.Node{nodes.NewTable("orders").Col("tenant_id").Eq(42)}, nil
		}
		return nil, nil
	}

	orders := nodes.NewTable("orders")
	query := managers.NewSelectManager(orders).
		Select(orders.Col("id"), orders.Col("total")).
		Use(opa.New(policy))

	sql, params, err := query.ToSQL(visitors.NewPostgresVisitor())
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	fmt.Println(sql)
	fmt.Println(params)
}
Output:
SELECT "orders"."id", "orders"."total" FROM "orders" WHERE "orders"."tenant_id" = $1
[42]

func NewFromServer

func NewFromServer(url, policyPath string, input map[string]any, opts ...Option) *OPA

NewFromServer creates an OPA transformer that calls an OPA server's Compile API to evaluate policies. The url is the base URL of the OPA server (e.g., "http://localhost:8181"), policyPath is the Rego policy path (e.g., "data.authz.allow"), and input is the input document to send with each request. Optional Option values can configure additional behavior such as column resolvers for masking.

Example

In server mode the plugin asks OPA for both the row filter and the column masks. Masking a SELECT * needs the table's columns, which a ColumnResolver supplies: the star is expanded and the masked column is replaced by its mask value, bound as a parameter under the column's name.

package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"

	"github.com/bawdo/gosbee/managers"
	"github.com/bawdo/gosbee/nodes"
	"github.com/bawdo/gosbee/plugins/opa"
	"github.com/bawdo/gosbee/visitors"
)

// fakeOPA is an in-process stand-in for an OPA server, so the examples need
// no policy server and no network beyond the loopback listener httptest
// opens. It answers the two calls the plugin makes:
//
//   - POST /v1/compile, the partial evaluation of the row filter, with one
//     residual: tenant_id = 42.
//   - POST /v1/data/<package>/masks, the column masks, replacing
//     billed_total with "<MASKED>".
//
// A real deployment points NewFromServer at its OPA server instead.
func fakeOPA() *httptest.Server {
	return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		switch {
		case r.URL.Path == "/v1/compile":
			_, _ = w.Write([]byte(`{"result":{"queries":[[{"index":0,"terms":[` +
				`{"type":"ref","value":[{"type":"var","value":"eq"}]},` +
				`{"type":"ref","value":[{"type":"var","value":"data"},{"type":"string","value":"consignments"},` +
				`{"type":"var","value":"$0"},{"type":"string","value":"tenant_id"}]},` +
				`{"type":"number","value":42}]}]]}}`))
		case strings.HasSuffix(r.URL.Path, "/masks"):
			_, _ = w.Write([]byte(`{"result":{"consignments":{"billed_total":{"replace":{"value":"<MASKED>"}}}}}`))
		default:
			w.WriteHeader(http.StatusNotFound)
		}
	}))
}

func main() {
	srv := fakeOPA()
	defer srv.Close()

	columns := func(table string) ([]string, error) {
		if table == "consignments" {
			return []string{"id", "account_name", "billed_total"}, nil
		}
		return nil, fmt.Errorf("unknown table %q", table)
	}

	plugin := opa.NewFromServer(srv.URL, "data.policies.consignments.include",
		map[string]any{"subject": map[string]any{"tenant_id": 42}},
		opa.WithColumnResolver(columns))

	query := managers.NewSelectManager(nodes.NewTable("consignments")).
		Use(plugin)

	sql, params, err := query.ToSQL(visitors.NewPostgresVisitor())
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	fmt.Println(sql)
	fmt.Println(params)
}
Output:
SELECT "consignments"."id", "consignments"."account_name", $1 AS "billed_total" FROM "consignments" WHERE "consignments"."tenant_id" = $2
[<MASKED> 42]
Example (MaskedColumnRefused)

A masked column can only be replaced where it is projected bare. Anywhere else, here a WHERE condition, replacing it would change what the query means, so the query is refused with ErrMaskedColumnUse rather than rendered unmasked.

package main

import (
	"errors"
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"

	"github.com/bawdo/gosbee/managers"
	"github.com/bawdo/gosbee/nodes"
	"github.com/bawdo/gosbee/plugins/opa"
	"github.com/bawdo/gosbee/visitors"
)

// fakeOPA is an in-process stand-in for an OPA server, so the examples need
// no policy server and no network beyond the loopback listener httptest
// opens. It answers the two calls the plugin makes:
//
//   - POST /v1/compile, the partial evaluation of the row filter, with one
//     residual: tenant_id = 42.
//   - POST /v1/data/<package>/masks, the column masks, replacing
//     billed_total with "<MASKED>".
//
// A real deployment points NewFromServer at its OPA server instead.
func fakeOPA() *httptest.Server {
	return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		switch {
		case r.URL.Path == "/v1/compile":
			_, _ = w.Write([]byte(`{"result":{"queries":[[{"index":0,"terms":[` +
				`{"type":"ref","value":[{"type":"var","value":"eq"}]},` +
				`{"type":"ref","value":[{"type":"var","value":"data"},{"type":"string","value":"consignments"},` +
				`{"type":"var","value":"$0"},{"type":"string","value":"tenant_id"}]},` +
				`{"type":"number","value":42}]}]]}}`))
		case strings.HasSuffix(r.URL.Path, "/masks"):
			_, _ = w.Write([]byte(`{"result":{"consignments":{"billed_total":{"replace":{"value":"<MASKED>"}}}}}`))
		default:
			w.WriteHeader(http.StatusNotFound)
		}
	}))
}

func main() {
	srv := fakeOPA()
	defer srv.Close()

	plugin := opa.NewFromServer(srv.URL, "data.policies.consignments.include", nil)

	consignments := nodes.NewTable("consignments")
	query := managers.NewSelectManager(consignments).
		Select(consignments.Col("id")).
		Where(consignments.Col("billed_total").Gt(1000)).
		Use(plugin)

	_, _, err := query.ToSQL(visitors.NewPostgresVisitor())
	fmt.Println(errors.Is(err, opa.ErrMaskedColumnUse))
}
Output:
true

func (*OPA) RefusesRawSQL added in v0.2.0

func (o *OPA) RefusesRawSQL() bool

RefusesRawSQL always reports true: a raw SQL fragment (SqlLiteral, StringJoin) may read from a table the policy has not been asked about, so the walker refuses it with an error wrapping plugins.ErrUnpoliceable.

func (*OPA) TransformDelete added in v0.2.0

func (o *OPA) TransformDelete(*nodes.DeleteStatement) (*nodes.DeleteStatement, error)

TransformDelete refuses the statement: the plugin only polices SELECT.

func (*OPA) TransformInsert added in v0.2.0

func (o *OPA) TransformInsert(*nodes.InsertStatement) (*nodes.InsertStatement, error)

TransformInsert refuses the statement: the plugin only polices SELECT.

func (*OPA) TransformSelect

func (o *OPA) TransformSelect(core *nodes.SelectCore) (*nodes.SelectCore, error)

TransformSelect applies the transformer to core and every query nested in it via plugins.ApplySelect, so a direct call outside a manager gets the same scope (qualifiers, CTE names, raw SQL refusal) as the plugin walker. The input is not modified.

func (*OPA) TransformSelectScoped added in v0.2.0

func (o *OPA) TransformSelectScoped(core *nodes.SelectCore, scope plugins.Scope) (*nodes.SelectCore, error)

TransformSelectScoped evaluates the policy for each table referenced in the query (FROM and JOINs) and appends any returned conditions to the WHERE clause. Names that refer to a CTE, and derived-table aliases, are not tables and are skipped. If the policy returns an error for any table, the query is rejected. Conditions for an aliased table (FROM users AS u) are rebound onto the alias, so they filter u rather than another users row.

Policy results are memoised per table for the duration of one walk, so a table referenced in several nested queries is compiled once. In server mode masks are fetched once per walk from the OPA Data API and applied to the projections. In PolicyFunc mode the user-supplied function is called directly (no masking support).

func (*OPA) TransformUpdate added in v0.2.0

func (o *OPA) TransformUpdate(*nodes.UpdateStatement) (*nodes.UpdateStatement, error)

TransformUpdate refuses the statement: the plugin only polices SELECT.

type Option

type Option func(*OPA)

Option configures an OPA transformer.

func WithColumnResolver

func WithColumnResolver(resolver ColumnResolver) Option

WithColumnResolver sets the column resolver used to expand star projections when column masks are present. Without a resolver, star projections with masks will produce an error.

type PolicyFunc

type PolicyFunc func(tableName string) ([]nodes.Node, error)

PolicyFunc evaluates a policy for the given table name and returns conditions to inject into the query's WHERE clause. Returning a non-nil error rejects the query entirely (e.g., "access denied").

type PolicyInfo

type PolicyInfo struct {
	PackagePath string // e.g. "policies.filtering.platform.consignment"
	RuleName    string // e.g. "include"
	FullPath    string // e.g. "data.policies.filtering.platform.consignment.include"
}

PolicyInfo describes a single queryable policy rule discovered on the OPA server.

type ReplaceAction

type ReplaceAction struct {
	Value string `json:"value"`
}

ReplaceAction replaces the column value with a literal string.

Jump to

Keyboard shortcuts

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