grants

package
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Jun 23, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

Documentation

Overview

Package grants turns Legant's delegated authority into a declarative, version-controllable file. Instead of minting authority imperatively (CLI flags or a browser consent click), a team writes a `legant.grants.yaml` that says "principal P delegates to agent A the scopes S, capped by these constraints," reviews it in a pull request, lints it in CI, and `apply`s it to materialize the signed tokens offline.

It is deliberately NOT a policy language: the schema is a fixed, 1:1 serialization of Legant's own constraint dimensions (max_amount, categories, tools, resources/RFC-8707 audiences, time_window). That fixedness is the whole point — it travels inside the token and verifies offline at any resource server, which an OPA/Kyverno-style content gate cannot do. Keep it from growing into a Turing-complete DSL.

Index

Constants

View Source
const StarterYAML = `` /* 1457-byte string literal not displayed */

StarterYAML is the commented template `legant init grants` writes.

Variables

This section is empty.

Functions

func HasErrors

func HasErrors(issues []Issue) bool

HasErrors reports whether any issue is an error (for the lint exit code).

Types

type ApplyResult

type ApplyResult struct {
	Changes []Change
	Orphans []string // token files in the dir not in the declared set
	Minted  int
}

ApplyResult is the outcome of reconciling a grants file into a setup dir.

type Change

type Change struct {
	Name     string
	File     string
	Action   string // "create" | "update" | "unchanged"
	Subject  string
	Agent    string // leaf actor
	Audience string
}

Change is one entry in an apply diff.

type ChildSpec

type ChildSpec struct {
	Agent       string          `yaml:"agent"`
	Scopes      []string        `yaml:"scopes"`
	Constraints *ConstraintSpec `yaml:"constraints,omitempty"`
}

ChildSpec is an attenuated sub-delegation of its parent grant.

type ConstraintSpec

type ConstraintSpec struct {
	MaxAmount  *float64        `yaml:"max_amount,omitempty"`
	Categories []string        `yaml:"categories,omitempty"`
	Tools      []string        `yaml:"tools,omitempty"`
	Resources  []string        `yaml:"resources,omitempty"`
	TimeWindow *TimeWindowSpec `yaml:"time_window,omitempty"`
}

ConstraintSpec maps 1:1 to delegation.Constraints (Legant's fixed dimensions).

type Defaults

type Defaults struct {
	TTL string `yaml:"ttl,omitempty"`
}

Defaults are applied to any grant that omits the field.

type File

type File struct {
	Version  int         `yaml:"version"`
	Issuer   string      `yaml:"issuer,omitempty"`
	Audience string      `yaml:"audience,omitempty"` // default audience for grants that omit one
	Defaults Defaults    `yaml:"defaults,omitempty"`
	Grants   []GrantSpec `yaml:"grants"`
}

File is a parsed legant.grants.yaml.

func Parse

func Parse(path string) (*File, error)

Parse reads and unmarshals a grants file. It rejects unknown fields so a typo'd key fails loudly at lint time instead of being silently ignored.

func (*File) Apply

func (f *File) Apply(s *Setup, force bool, now time.Time) (*ApplyResult, error)

Apply reconciles the declared grants into the setup dir: it mints a signed token per grant (and per declared sub-delegation), writing one file each, and reports a created/updated/unchanged diff against what is already there. It is idempotent — a grant whose declared shape matches the existing token is left untouched unless force is set — so re-applying an unchanged file is a no-op.

func (*File) Lint

func (f *File) Lint() []Issue

Lint validates the file semantically WITHOUT side effects: it catches the errors that would otherwise silently 401/403 at runtime — an empty scope list, a child that widens past its parent, a time window that never opens, a missing audience, a duplicate grant. It returns every issue found (errors and warnings) so a CI gate can fail on errors.

func (*File) WhoCan

func (f *File) WhoCan(s *Setup, action sdk.Action, now time.Time) ([]Match, error)

WhoCan answers "which declared grants would permit this action?" by minting each grant in memory and running the SAME offline verify+authorize a resource server runs — so the answer is exactly what production would decide, not a re-implemented guess. An empty Resource/Tool/etc. on the action skips that dimension.

Authorization is time-sensitive, but a reviewer asking "who can touch finance?" should not have a structurally-capable grant hidden just because they ran the query after hours. So a grant that passes every dimension EXCEPT its time window is still returned, flagged TimeBoxed.

type GrantSpec

type GrantSpec struct {
	Name        string          `yaml:"name,omitempty"`
	Principal   string          `yaml:"principal"`
	Agent       string          `yaml:"agent"`
	Scopes      []string        `yaml:"scopes"`
	Audience    string          `yaml:"audience,omitempty"`
	TTL         string          `yaml:"ttl,omitempty"`
	Constraints *ConstraintSpec `yaml:"constraints,omitempty"`
	Delegate    []ChildSpec     `yaml:"delegate,omitempty"`
}

GrantSpec is one root delegation: principal -> agent, with constraints and any further attenuated hand-offs in Delegate.

type Issue

type Issue struct {
	Grant    string
	Severity string // "error" | "warn"
	Message  string
}

Issue is one lint finding. Error issues fail the lint (non-zero exit); Warn issues are advisory.

func (Issue) String

func (i Issue) String() string

type Match

type Match struct {
	Name       string
	Provenance string
	Audience   string
	// TimeBoxed is set when the grant only permits the action inside a time window
	// that is NOT open at the evaluation instant — so it is structurally capable but
	// blocked right now. The reviewer still needs to see it.
	TimeBoxed bool
}

Match is one grant that authorizes a queried action in WhoCan.

type Setup

type Setup struct {
	Dir      string
	Issuer   string
	Signer   *delegation.Signer
	Keys     map[string]*rsa.PublicKey
	FeedPath string
	// contains filtered or unexported fields
}

Setup is the offline trust material apply/mint operate on: a local signing key, its JWKS, and the signed revocation feed, all under one directory.

func EnsureSetup

func EnsureSetup(dir, issuer string, now time.Time) (*Setup, error)

EnsureSetup loads the local key/JWKS/feed from dir, creating them on first use. This is the general (non-coding-agent) analog of `legant guard init`: it makes a self-contained offline trust domain so apply/mint/show/revoke need no server or database. The private key is a LOCAL key — in a real deployment tokens come from a token-exchange against your Legant issuer and the JWKS/feed are its endpoints.

func (*Setup) Created

func (s *Setup) Created() bool

Created reports whether EnsureSetup just bootstrapped the directory.

func (*Setup) Prune

func (s *Setup) Prune(orphans []string, now time.Time) (int, error)

Prune deletes orphaned token files and revokes their token ids on the feed, so `apply --prune` makes the dir match the file exactly (removed grants are killed, not just abandoned).

type TimeWindowSpec

type TimeWindowSpec struct {
	Weekdays []int  `yaml:"weekdays,omitempty"` // 0=Sun..6=Sat; empty = any day
	Start    string `yaml:"start"`              // "09:00"
	End      string `yaml:"end"`                // "17:00"
	TZ       string `yaml:"tz,omitempty"`       // IANA name; empty = UTC
}

TimeWindowSpec is a friendlier surface over delegation.TimeWindow: HH:MM strings instead of minute-of-day integers.

Jump to

Keyboard shortcuts

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