accesspolicies

package
v0.33.0 Latest Latest
Warning

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

Go to latest
Published: Sep 19, 2026 License: Apache-2.0 Imports: 26 Imported by: 0

Documentation

Overview

Package accesspolicies discovers and decodes the DALgo access policies a DataTug user keeps on disk, runs a query through them as a secured application would, and explains what the policies did to that query.

Index

Constants

View Source
const (
	ProjectsCollection       = "datatug_projects"
	ProjectQueriesCollection = "queries"
)

ProjectsCollection and ProjectQueriesCollection name DataTug project files as DALgo policy resources: a saved query is the record /datatug_projects/{projectID}/queries/{queryID}, where queryID is the query's canonical, folder-qualified ID kept as one path segment. The "datatug_" prefix keeps project files apart from every data source's own collection names, so a grant on a data collection (path: /Customer) never grants a project write, while a catch-all grant (path: /**) - the demo-project-1 admin rule set - covers them.

The separation rests on that prefix alone: policy paths share one namespace, so a data source with a collection literally named "datatug_projects" would put its rows under the same paths as project files, and a grant written for either would apply to both. Never give a data collection that name.

View Source
const DefaultDir = ".datatug/policies"

DefaultDir is the per-user policies directory relative to the home directory.

View Source
const DirEnv = "DATATUG_POLICIES_DIR"

DirEnv names the environment variable that overrides the default policies directory.

Variables

View Source
var ErrInvalidQuery = errors.New("invalid query")

ErrInvalidQuery marks a query the command refuses before execution: an unresolved or misplaced parameter, or an alias under a field allow-list.

View Source
var ErrNoPolicies = errors.New("no access policies loaded; pass --no-policies to run unrestricted")

ErrNoPolicies is returned by Load when nothing was loaded and the caller did not ask to run unrestricted.

Functions

func AuthorizeWrite added in v0.25.0

func AuthorizeWrite(ctx context.Context, o WriteOptions, operation access.Operations, resource access.Resource) error

AuthorizeWrite decides whether o's principal may perform operation (one of Insert, Set, Update or Delete) on resource, returning nil when it may and a *WriteDeniedError when it may not.

It is for project files only - saved queries, named by ProjectQueryResource - and must never decide a data write. It refuses any grant that holds only under a row condition, check or field list, and it evaluates each policy's project-write view, whose query ids are canonicalized; a data write must go through DALgo's residual-enforcing write session, which checks those conditions against the rows written. Nor may grants from any other source, such as incident-scoped read grants, be appended to o.Policies: every policy listed must allow a write, and none of them is written to widen one.

Deny by default:

  • An Unrestricted session is the explicit --no-policies local-owner profile and is allowed.
  • Otherwise at least one policy must be loaded and every loaded policy must allow the write, for the principal on the context - the same intersection a secured session applies to reads. A principal no grant binds, a read-only grant and a missing principal are all denied.
  • A grant that holds only under a row condition, a check or a field allow-list is denied too: a project file has no rows or fields to evaluate it against, so such a grant cannot be enforced here.

How a policy names a query. A saved query is the record /datatug_projects/{projectID}/queries/{queryID} (ProjectQueryResource):

  • queryID is the query's folder-qualified id kept as ONE path segment. A policy names the query "revenue" in folder "reports" as /datatug_projects/demo/queries/reports%2Frevenue, the "/" inside the id percent-encoded, and every query in every folder as /datatug_projects/demo/queries/* (or .../queries/**).
  • Query ids match by CanonicalQueryID on both sides, the resource and every query id in the loaded policies' paths: case- and Unicode-normalization-insensitively, so "Revenue", "REVENUE" and "revenue" are one query to every rule, as they are one file on APFS.
  • Folder-scoped rules are not supported. A rule path below the query-id segment (.../queries/reports/**, .../queries/reports/x) can never match a query, so rather than let such a rule silently not apply, every project query write is refused while a policy holding one is loaded, and the refusal names the rule. Reads are unaffected: they run through the policy as loaded.

func BaseResource

func BaseResource(query dal.Query) access.Resource

BaseResource names the collection a query reads.

func CanonicalQueryID added in v0.25.0

func CanonicalQueryID(id string) string

CanonicalQueryID returns the spelling of a saved query's id that project write authorization compares. Two ids with the same CanonicalQueryID get the same decision, on every platform, because both the resource being written (ProjectQueryResource) and every query id a policy path names (AuthorizeWrite's view of a loaded policy) are compared in this form.

A query id names files, and the file systems DataTug serves from resolve more than one spelling to the same file. The canonical form is coarser than every one of them, so it can only ever merge more spellings into one decision, never split one file's spellings apart:

  • APFS and Linux ext4 with casefolding compare names after canonical decomposition and full case folding, so NFC "café" and NFD "café", and "straße" and "STRASSE", are one name;
  • HFS+ decomposes and folds case through its own older table, and ignores the default-ignorable code points (a zero-width joiner, a soft hyphen, a byte-order mark) entirely;
  • NTFS and exFAT compare through an uppercase table, which merges spellings full case folding does not - "ı" with "i" and "I".

So one round of the canonical form decomposes (NFD), applies full case folding, drops every default-ignorable code point, and maps each remaining rune to the smallest rune of its case class - the transitive closure of simple upper-, lower- and title-casing and simple case folding, which covers every one of those tables' mappings, whichever direction each maps in. Full folding merges what maps to more than one rune ("ß" to "ss"), and the case class merges what the folding tables leave apart ("ı" with "I", and the Cherokee letters, whose folding maps in the opposite direction to most scripts').

The result is a fixed point: rounds are applied until the string stops changing, so C(C(x)) == C(x) for every string. Convergence is exhaustive over every Unicode scalar value in the package's own tests; a string that somehow did not converge within canonicalRounds becomes one fixed unstableCanonicalID, which is coarser still - such ids all share one decision - and never splits a file's spellings.

func FieldAllowed added in v0.28.0

func FieldAllowed(patterns []string, path string) bool

FieldAllowed exposes the exact field-pattern decision used by secured DALgo reads so transport adapters can redact persisted fact values without reimplementing policy matching.

func Fingerprint added in v0.27.0

func Fingerprint(loaded []Loaded, unrestricted bool, principal *access.Principal) string

Fingerprint returns a deterministic server-attested identity for the exact policy documents in their evaluation order. An unrestricted session has a stable, distinct fingerprint as well.

func ParseVariables

func ParseVariables(pairs []string) (map[string]any, error)

ParseVariables turns name=value pairs into policy variables. Names follow DALgo's parameter grammar; currentUser, principal.* and path.* are reserved for --as, --role, --group and path captures. Values are YAML scalars or flow sequences so numbers, booleans and lists keep their type; quote a value to force a string; a value that is not valid YAML is kept as the literal string.

func Policies

func Policies(loaded []Loaded) []access.Policy

Policies returns the decoded policies in load order.

func ProjectQueryResource added in v0.25.0

func ProjectQueryResource(projectID, queryID string) access.Resource

ProjectQueryResource returns the policy resource for the saved query queryID of project projectID. The query id is kept in its canonical caseless form (CanonicalQueryID), so every spelling a file system resolves to the same query file names the same resource.

func QueryParameters added in v0.25.0

func QueryParameters(query dal.StructuredQuery) ([]string, error)

QueryParameters lists the parameter names (without the "$") query references, sorted and de-duplicated. A parameter anywhere other than the right-hand side of a where comparison is an ErrInvalidQuery - the same rule Run applies before it executes a query - so a query this accepts is one Run can bind.

func ResolveDir

func ResolveDir(flagValue string) (dir string, explicit bool, err error)

ResolveDir picks the policies directory: an explicit value first, then $DATATUG_POLICIES_DIR, then ~/.datatug/policies. explicit reports whether the directory was configured by the caller (and so must exist).

Types

type Line

type Line struct {
	Policy      string
	Source      string
	Resource    string
	Rule        string
	Allowed     bool
	Condition   string   // as access.Decision.Condition renders it; parameter names, never values
	Bindings    []string // "name=value" for the caller-supplied variables the condition references
	FieldLists  [][]string
	Via         string // binding attribution from a principal-bound policy ("role:editor")
	Explanation string
}

Line is what one policy decided for a query's base collection.

func Explain

func Explain(ctx context.Context, loaded []Loaded, query dal.Query, bindings map[string]any) []Line

Explain asks every loaded policy what it decides for the query's base collection and returns one Line per policy, in load order. bindings are the variables the caller supplied; only those are echoed.

func (Line) String

func (l Line) String() string

String renders the pinned report format:

access: policy "<name>" (<source>) rule "<rule>" allows|denies query on <resource>: <limitations>[ via <binding>]

type LoadOptions

type LoadOptions struct {
	// Dir is the --policies-dir value; empty means environment or default.
	Dir string
	// Files are additional --policy documents, appended after the directory.
	Files []string
	// None skips discovery (--no-policies); it conflicts with Files.
	None bool
}

LoadOptions says where policies come from.

type Loaded

type Loaded struct {
	Policy access.Policy
	Source string
	// contains filtered or unexported fields
}

Loaded is one decoded access document and the file it came from.

func DecodeLoaded added in v0.25.0

func DecodeLoaded(data []byte, codec access.Codec, source string) (Loaded, error)

DecodeLoaded decodes one access document held in data, as LoadFile does for a file; source is where it came from and is never shown to a client. Besides the policy every read runs through, it prepares the document's project-write view (prepareProjectWrites): a document that cannot be prepared still loads, and AuthorizeWrite refuses project writes under it.

func Load

func Load(o LoadOptions) ([]Loaded, error)

Load discovers and decodes the caller's policies. A missing default directory is empty; a missing explicit one is an error. Nothing loaded is ErrNoPolicies unless None is set.

func LoadDir

func LoadDir(dir string) ([]Loaded, error)

LoadDir decodes every regular *.yaml, *.yml and *.json file in dir, in file name order. A directory that does not exist is reported with fs.ErrNotExist.

func LoadFile

func LoadFile(path string) (Loaded, error)

LoadFile decodes one access document; the codec follows the extension.

type Options

type Options struct {
	// Principal is the caller; nil runs anonymously.
	Principal *access.Principal
	// Variables are the caller's --var values.
	Variables map[string]any
	// Policies are the loaded documents.
	Policies []Loaded
	// Unrestricted must be set explicitly to run with no policies at all.
	Unrestricted bool
}

Options configure one secured run.

type Result

type Result struct {
	// Reader yields the rows the secured session admits.
	Reader dal.RecordsReader
	// Lines explains, per policy, what applied to the query; nil when
	// unrestricted.
	Lines []Line
	// Query is the query as executed, with parameters substituted.
	Query dal.Query
}

Result is what Run returns before any row is read.

func Run

func Run(ctx context.Context, session dal.ReadSession, query dal.Query, o Options) (Result, error)

Run executes query over session as a policy-secured application would for the principal: it puts the principal and variables on the context, substitutes the query's own parameters, refuses aliases and references to fields the caller may not see, explains the policies' decisions and opens the reader through access.SecureReadSession. Denials wrap access.ErrAccessDenied; refused queries wrap ErrInvalidQuery.

type WriteDeniedError added in v0.25.0

type WriteDeniedError struct {
	// Policy is the name of the policy that refused the write; empty when
	// the write was refused by default, with no policy deciding.
	Policy    string
	Operation access.Operations
	Resource  string
	Reason    string
}

WriteDeniedError reports a refused project write. It names the deciding policy, the operation, the resource and a reason, and never the policy file's location on the serving machine. It unwraps to access.ErrAccessDenied.

func (*WriteDeniedError) Error added in v0.25.0

func (e *WriteDeniedError) Error() string

func (*WriteDeniedError) Unwrap added in v0.25.0

func (e *WriteDeniedError) Unwrap() error

Unwrap makes errors.Is(err, access.ErrAccessDenied) true.

type WriteOptions added in v0.25.0

type WriteOptions struct {
	// Principal is the caller; nil means no principal, which no grant binds.
	Principal *access.Principal
	// Policies are the loaded documents; every one must allow the write.
	Policies []Loaded
	// Unrestricted is the explicit --no-policies local-owner profile.
	Unrestricted bool
}

WriteOptions is the identity and policy set a project write is decided under - a `datatug serve` session's fixed principal and loaded policies.

Jump to

Keyboard shortcuts

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