grant

package
v1.11.1 Latest Latest
Warning

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

Go to latest
Published: Oct 29, 2026 License: Apache-2.0 Imports: 5 Imported by: 1

Documentation

Index

Constants

View Source
const Wildcard = "*"

Wildcard marks an unrestricted list: every value is allowed, or every value is blocked, depending on which kind of list carries it.

The lists a permit carries are schemas.WhiteList and schemas.BlackList, so what a list means travels on its type: a list holding only the wildcard is unrestricted, an empty list holds nothing, and membership ignores case. Key IDs are the one exception: they are identifiers, so whoever matches them does so exactly rather than through the type's membership methods (see composeKeyIDs), and only their wildcard is read off the type.

Variables

This section is empty.

Functions

func LimitsFrom

func LimitsFrom(limits []schemas.Limit, kinds ...LimitHolderKind) []schemas.Limit

LimitsFrom keeps the limits held by any of kinds, and is how a caller that enforces one holder's limits asks about exactly that holder.

It exists because "does anything govern this?" and "does this holder govern this?" are different questions, and a request's limits come from several holders at once. A check written against one holder, a key's budgets say, must gate on that holder's own limits, or it fires on a team's budget and then reports on the key's, which passes while the team is exhausted. Passing no kinds keeps nothing, since asking about no holder is asking about nothing.

func LimitsHeldBy

func LimitsHeldBy(kind LimitHolderKind, holderID, holderName, provider, model string, ids ...string) []schemas.Limit

LimitsHeldBy names the limits one holder imposes, from the identifiers of the records that enforce them. provider and model are recorded on each for description, empty when the limit is scoped to neither; see schemas.Limit for why nothing selects on them.

Identifiers rather than records, for the reason on schemas.Limit, and it is also what keeps this package out of the business of what a budget row looks like: whoever reads those rows already knows their shape, and all this needs from them is an identity. Records without one are skipped, since a limit that cannot be loaded cannot be enforced.

func NewCredential

func NewCredential(kind CredentialKind, value string) schemas.Credential

NewCredential is a schemas.Credential of the given kind.

Types

type Access

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

Access is the one implementation of schemas.Access: the fold of the permits a request carries.

Per attempt rather than per request, because a request that fails over resolves again for each provider it tries. Configuration can change between two slow calls, and the attempt running second has to answer to what is in force when it runs.

The caller's permits are a list because a caller can hold several at once, and they are read as one, as if they were a single permit holding all of their provider and MCP permits: what any of them permits, the caller may reach; any key any of them allows may serve the request; and every one of them that permits the pair pays for it (see PermitsForModel). Their order is the order the resolver gave them, and is the order candidates and refusals are reported in.

There is one scoping slot, not a list. A request is scoped by at most one thing at a time, and composing under one mode is what keeps every answer here decidable.

func NewAccess

func NewAccess(bases []schemas.Permit, scoping schemas.Permit, mode CompositionMode, matcher ModelMatcher) *Access

NewAccess folds the permits the caller holds together with the permit scoping this request, under one composition mode.

A request composes under exactly one mode. Choosing it belongs to whoever resolved the permits: that layer knows what it asked and what answered. Passing no scoping permit leaves the mode irrelevant and the answer is the caller's permits alone. Nil entries in bases are dropped, so a resolver that found nothing for one source does not have to say so twice.

func (*Access) AllowsEveryModel added in v1.9.0

func (a *Access) AllowsEveryModel(provider string) bool

AllowsEveryModel implements schemas.Access.

func (*Access) Bases

func (a *Access) Bases() []schemas.Permit

Bases implements schemas.Access.

func (*Access) DeniedPermitsForAgent added in v1.9.0

func (a *Access) DeniedPermitsForAgent(agentName string) []schemas.Permit

DeniedPermitsForAgent implements schemas.Access.

func (*Access) DeniedPermitsForMCPTool

func (a *Access) DeniedPermitsForMCPTool(toolPattern string) []schemas.Permit

DeniedPermitsForMCPTool implements schemas.Access.

func (*Access) DeniedPermitsForModel

func (a *Access) DeniedPermitsForModel(provider string, model string) []schemas.Permit

DeniedPermitsForModel implements schemas.Access.

What is named is whatever refused and whose permission would have changed the answer. The caller's permits are read as one, so when none of them permits the pair all of them refused and all are named: a caller holding two profiles is told that neither permits the model, not that one of them does not. Under an intersection, when one of them permits it and the scoping permit does not, the scoping permit alone stands in the way and alone is named. Under a union both sides must have refused, and both are named. A caller holding no permit at all has nothing to be named.

func (*Access) DeniedPermitsForRelay added in v1.11.0

func (a *Access) DeniedPermitsForRelay(endpointName string) []schemas.Permit

DeniedPermitsForRelay implements schemas.Access.

func (*Access) GrantedProvidersForModel

func (a *Access) GrantedProvidersForModel(model string) []string

GrantedProvidersForModel implements schemas.Access. Because this gate cannot ask ProvidersForModel anything, the composition it needs is re-derived by name below.

func (*Access) IsAgentAllowed added in v1.9.0

func (a *Access) IsAgentAllowed(agentName string) bool

IsAgentAllowed implements schemas.Access.

func (*Access) IsMCPToolAllowed

func (a *Access) IsMCPToolAllowed(toolPattern string) bool

IsMCPToolAllowed implements schemas.Access.

func (*Access) IsModelAllowed

func (a *Access) IsModelAllowed(provider string, model string) bool

IsModelAllowed implements schemas.Access.

func (*Access) IsProviderAllowed

func (a *Access) IsProviderAllowed(provider string) bool

IsProviderAllowed implements schemas.Access.

func (*Access) IsRelayAllowed added in v1.11.0

func (a *Access) IsRelayAllowed(endpointName string) bool

IsRelayAllowed implements schemas.Access.

func (*Access) IsScoped

func (a *Access) IsScoped() bool

IsScoped implements schemas.Access.

func (*Access) KeysForModel

func (a *Access) KeysForModel(provider string, model string) (keyIDs []string, restricted bool)

KeysForModel implements schemas.Access.

The caller's side is the union of every key restriction its permitting permits hold for the provider, across all of their provider permits for it: the caller's permits are read as one, and a request served by any of them may use any key any of them allows. That is composed with the scoping permit's restriction only where the scoping permit also authorizes the pair. A scoping permit that narrows a request to two of the provider's keys has to mean it, and taking the caller's list instead would hand the request a key the scope refused; but a permit the request is not proceeding on has no say, which is why the model is asked for.

The result is a plain []string rather than any list type of the permit's own: it is handed straight to consumers that type-assert []string, where a named slice type would fail the assertion silently and read as "no restriction at all".

func (*Access) MCPToolIncludeList

func (a *Access) MCPToolIncludeList() []string

MCPToolIncludeList implements schemas.Access.

The caller's permits are read as one, so their entries are merged in order before the scoping permit is composed on: a tool any of them grants, the caller may execute.

func (*Access) Mode

func (a *Access) Mode() string

Mode implements schemas.Access.

func (*Access) NarrowMCPToolIncludeList

func (a *Access) NarrowMCPToolIncludeList(requested []string) []string

NarrowMCPToolIncludeList implements schemas.Access.

A "<client>-*" wildcard is kept as a wildcard only when the access grants that client every tool, because downstream a wildcard reads as every tool of the client; otherwise it is replaced by the specific tools the access grants for that client, which may be none. A specific entry is kept when the access permits it. Empty entries and repeats are dropped. No access at all permits nothing.

func (*Access) PermitsForModel

func (a *Access) PermitsForModel(provider string, model string) []schemas.Permit

PermitsForModel implements schemas.Access.

func (*Access) ProvidersForModel

func (a *Access) ProvidersForModel(model string) []schemas.ProviderCandidate

ProvidersForModel implements schemas.Access. Model names resolve through the matcher here. Unlike GrantedProvidersForModel, this is the decision itself rather than a gate over one, so cross-provider naming and provider-prefixed entries must resolve exactly as the deployment resolves them.

func (*Access) Scoping

func (a *Access) Scoping() schemas.Permit

Scoping implements schemas.Access.

type CompositionMode

type CompositionMode string

CompositionMode is how a scoping permit combines with the permits the caller holds. These are the values a schemas.Access reports from Mode.

const (
	// Intersect permits what the caller's permits and the scoping permit both permit.
	Intersect CompositionMode = "intersect"
	// Union permits what either of them permits.
	Union CompositionMode = "union"
)

type CredentialKind

type CredentialKind string

CredentialKind names what a request presented to say who it is: one of the things this gateway identifies a caller by. These are the values a schemas.Credential carries in Kind. A raw provider key is not among them: it selects which key serves the request and says nothing about who made it.

const (
	// CredentialVirtualKey marks a virtual key.
	CredentialVirtualKey CredentialKind = "virtual_key"
	// CredentialSessionToken marks a dashboard session token.
	CredentialSessionToken CredentialKind = "session_token"
	// CredentialIdentityToken marks a token the identity provider issued for the user.
	CredentialIdentityToken CredentialKind = "idp_jwt"
	// CredentialMCPToken marks a token this deployment's own authorization server issued to an MCP
	// client.
	CredentialMCPToken CredentialKind = "mcp_jwt"
	// CredentialAPIKey marks a management API key.
	CredentialAPIKey CredentialKind = "api_key"
	// CredentialEdgeSession marks a Bifrost Edge agent's session token. Its value is the user's ID.
	CredentialEdgeSession CredentialKind = "edge_session"
)

type Grant

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

Grant is the envelope a request carries its sections in. See schemas.Grant for what each section is and when it settles.

Each section is held behind an atomic pointer and replaced whole. A consumer that read a section keeps the answer it read, so two readers of one attempt never see a torn or partially updated value, and a section being replaced cannot change a decision that was already made against it.

func New

func New() *Grant

New returns an empty grant.

func (*Grant) Access

func (g *Grant) Access() schemas.Access

Access implements schemas.Grant.

func (*Grant) Identity

func (g *Grant) Identity() schemas.Identity

Identity implements schemas.Grant.

func (*Grant) Limits

func (g *Grant) Limits() schemas.Limits

Limits implements schemas.Grant.

func (*Grant) SetAccess

func (g *Grant) SetAccess(access schemas.Access) bool

SetAccess implements schemas.Grant. Recording nothing is a no-op, as the interface says.

func (*Grant) SetIdentity

func (g *Grant) SetIdentity(identity schemas.Identity) bool

SetIdentity implements schemas.Grant. Recording nothing is a no-op, as the interface says.

func (*Grant) SetLimits

func (g *Grant) SetLimits(limits schemas.Limits) bool

SetLimits implements schemas.Grant. Recording nothing is a no-op, as the interface says.

type Identity

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

Identity is the one implementation of schemas.Identity. It is built whole and never edited: a layer that learns more about the caller builds the replacement from what the getters return plus what it learned, which is what lets a request's context hand a identity out without a lock.

func NewIdentity

func NewIdentity(
	credential schemas.Credential,
	user *schemas.UserRef,
	virtualKey *schemas.EntityRef,
	teams []schemas.EntityRef,
	customers []schemas.EntityRef,
	businessUnits []schemas.EntityRef,
	project *schemas.EntityRef,
) *Identity

NewIdentity builds a Identity. See schemas.Identity for what each value means. The lists are copied, so a caller that keeps its slices cannot alter what the request is attributed to.

func (*Identity) BusinessUnits

func (p *Identity) BusinessUnits() []schemas.EntityRef

BusinessUnits implements schemas.Identity.

func (*Identity) Credential

func (p *Identity) Credential() schemas.Credential

Credential implements schemas.Identity.

func (*Identity) Customers

func (p *Identity) Customers() []schemas.EntityRef

Customers implements schemas.Identity.

func (*Identity) Presented

func (p *Identity) Presented() bool

Presented implements schemas.Identity.

func (*Identity) Project

func (p *Identity) Project() *schemas.EntityRef

Project implements schemas.Identity.

func (*Identity) Teams

func (p *Identity) Teams() []schemas.EntityRef

Teams implements schemas.Identity.

func (*Identity) User

func (p *Identity) User() *schemas.UserRef

User implements schemas.Identity.

func (*Identity) VirtualKey

func (p *Identity) VirtualKey() *schemas.EntityRef

VirtualKey implements schemas.Identity.

type LimitHolderKind

type LimitHolderKind string

LimitHolderKind identifies what kind of entity a limit hangs off. These are the values a schemas.Limit carries in HolderKind. An open string, like PermitType, so a deployment resolving a holder of its own can name it without this package changing. The kinds that do exist are named below rather than wherever each is resolved, because telling one limit from another is a single vocabulary and a reader should not have to know which layer introduced a kind.

const (
	// Held by a virtual key.
	LimitHolderVirtualKey               LimitHolderKind = "vk"
	LimitHolderVirtualKeyProviderConfig LimitHolderKind = "vk_provider_config"
	LimitHolderVirtualKeyModelConfig    LimitHolderKind = "vk_model_config"

	// Held by an access profile attached to a user. Named for the user rather than generically,
	// because a profile attached to something else is a different holder: see the team, business
	// unit and customer profile kinds below, which exist for exactly the reason this comment once
	// anticipated - a refusal has to say whose profile it was.
	LimitHolderUserAccessProfile               LimitHolderKind = "user_access_profile"
	LimitHolderUserAccessProfileProviderConfig LimitHolderKind = "user_access_profile_provider_config"
	LimitHolderUserAccessProfileModelConfig    LimitHolderKind = "user_access_profile_model_config"

	// Held by an access profile attached to a team, business unit or customer. The money is the
	// entity's: it is charged on every request made under the entity - by a member or with a key the
	// entity owns - whichever permit admitted the model. One holder each, for the same reason the
	// permit kinds are separate: a caller told only that "an access profile is exhausted" cannot tell
	// whose.
	LimitHolderTeamAccessProfile                       LimitHolderKind = "team_access_profile"
	LimitHolderTeamAccessProfileProviderConfig         LimitHolderKind = "team_access_profile_provider_config"
	LimitHolderTeamAccessProfileModelConfig            LimitHolderKind = "team_access_profile_model_config"
	LimitHolderBusinessUnitAccessProfile               LimitHolderKind = "business_unit_access_profile"
	LimitHolderBusinessUnitAccessProfileProviderConfig LimitHolderKind = "business_unit_access_profile_provider_config"
	LimitHolderBusinessUnitAccessProfileModelConfig    LimitHolderKind = "business_unit_access_profile_model_config"
	LimitHolderCustomerAccessProfile                   LimitHolderKind = "customer_access_profile"
	LimitHolderCustomerAccessProfileProviderConfig     LimitHolderKind = "customer_access_profile_provider_config"
	LimitHolderCustomerAccessProfileModelConfig        LimitHolderKind = "customer_access_profile_model_config"

	// Held by the user a request is attributed to, directly — a per-model limit assigned straight
	// to the user rather than derived from an access profile they hold. Kept distinct from the
	// LimitHolderUserAccessProfile* kinds because a refusal has to say which one ran out, and from
	// LimitHolderVirtualKey because a caller can be told to stop counting a request against the key
	// that granted it while still counting it against the user who made it.
	LimitHolderUserModelConfig LimitHolderKind = "user_model_config"

	// Held by the organization above the caller. Spent by every request made under anything they
	// contain.
	LimitHolderTeam         LimitHolderKind = "team"
	LimitHolderBusinessUnit LimitHolderKind = "business_unit"
	LimitHolderCustomer     LimitHolderKind = "customer"

	// Held by a project, which is not part of the organization above the caller. A project is a
	// scope a request opts into rather than a container the caller sits inside, so what it funds
	// is spent by whoever names it and by nobody else: a caller's own limits and their
	// organization's are untouched by it, and the same caller reaching the same model outside the
	// project answers to none of these.
	LimitHolderProject               LimitHolderKind = "project"
	LimitHolderProjectProviderConfig LimitHolderKind = "project_provider_config"
	LimitHolderProjectModelConfig    LimitHolderKind = "project_model_config"

	// Held by no one in the organization. A provider's limits are the deployment's own, and a model
	// config's are scoped to a (model, provider) pattern. Neither is anybody's allowance, so a
	// refusal naming one of these is telling the caller about the deployment rather than about
	// themselves.
	LimitHolderProvider    LimitHolderKind = "provider"
	LimitHolderModelConfig LimitHolderKind = "model_config"
)

Every kind of limit holder, declared together and grouped by holder. Within a holder the kinds run from the widest scope to the narrowest: the holder's own limits, spent by every request it makes; its per-provider limits, spent only by requests that provider serves; and its per-model limits, spent only by requests for that model.

Once resolved for an attempt, a request's limits are one flat list, and these are what tell them apart afterwards: a refusal has to say whether it was your key's budget or your team's, and a caller looking at several limits of the same shape needs to know which is which.

Nothing registers or enumerates them. Enforcement covers every kind, so a limit carrying any of these is checked and charged by the same path, and a deployment that resolves a holder of its own declares its kind here and is enforced by construction.

type Limits

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

Limits is the one implementation of schemas.Limits.

Resolving them is the caller's to do and its answer is taken as given. Which holders are charged is a question about how a deployment is configured, and this package would have to learn what a project, a team or a model config is in order to have an opinion. So it holds the answer without deriving it.

One limit reached twice is still one limit, so what is held is deduplicated. That is not an opinion about which holders are charged; it is that charging one budget twice for a single request is never what a deployment meant, however its holders overlap. A team or customer reached through two permits, or a customer named directly as well as through its team, arrives twice and must not be billed twice.

func NewLimits

func NewLimits(budgets, rateLimits []schemas.Limit) *Limits

NewLimits holds the limits an attempt answers to. Both lists are copied as well as deduplicated, so a caller that keeps its slice cannot alter what the attempt is held to.

func (*Limits) Budgets

func (l *Limits) Budgets() []schemas.Limit

Budgets implements schemas.Limits. A copy, not the internal slice: enforcement, billing, and logging all read the same settled Limits for one attempt, and a caller sorting or overwriting what it got back must not change what the next reader sees.

func (*Limits) RateLimits

func (l *Limits) RateLimits() []schemas.Limit

RateLimits implements schemas.Limits. See Budgets for why this returns a copy.

type ModelMatcher

type ModelMatcher func(provider, model string, allowed []string) bool

ModelMatcher decides whether model, as the request names it, is one of allowed on provider. Whoever resolves a request's access supplies it, because deciding that needs the deployment's own view of the provider: how it names models, and which provider-prefixed or cross-provider entries in allowed stand for the same model. Without one, entries are matched by name.

type Permit

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

Permit is the one implementation of schemas.Permit.

It is a snapshot for one attempt. The provider permits carry a copy of the configuration they came from, so a source reloaded mid-attempt cannot change what that attempt has already resolved. A later attempt builds its own and picks the reload up.

func NewPermit

func NewPermit(
	permitType PermitType,
	id string,
	name string,
	isActive bool,
	isExpired bool,
	providerPermits []schemas.ProviderPermit,
	mcpPermits []schemas.MCPPermit,
	opts ...PermitOption,
) *Permit

NewPermit builds a Permit. See schemas.Permit for what each value means. The lists are deep copied, down to each entry's own slices and its Weight pointer, so a resolver that keeps its slices (or the value behind a Weight pointer) cannot alter what an attempt was admitted under.

func (*Permit) AgentPermits added in v1.9.0

func (p *Permit) AgentPermits() []string

AgentPermits implements schemas.Permit. See ProviderPermits for why this returns a copy.

func (*Permit) AllowsAllProviders

func (p *Permit) AllowsAllProviders() bool

AllowsAllProviders implements schemas.Permit.

func (*Permit) ID

func (p *Permit) ID() string

ID implements schemas.Permit.

func (*Permit) IsActive

func (p *Permit) IsActive() bool

IsActive implements schemas.Permit.

func (*Permit) IsExpired

func (p *Permit) IsExpired() bool

IsExpired implements schemas.Permit.

func (*Permit) MCPPermits

func (p *Permit) MCPPermits() []schemas.MCPPermit

MCPPermits implements schemas.Permit. See ProviderPermits for why this returns a copy.

func (*Permit) Name

func (p *Permit) Name() string

Name implements schemas.Permit.

func (*Permit) ProviderPermits

func (p *Permit) ProviderPermits() []schemas.ProviderPermit

ProviderPermits implements schemas.Permit. A copy, not the permit's own slice: the permit is a snapshot for the whole attempt, read by every consumer that asks what it permits, and a caller mutating what it got back must not change what the next reader sees.

func (*Permit) RelayPermits added in v1.11.0

func (p *Permit) RelayPermits() []string

RelayPermits implements schemas.Permit. See ProviderPermits for why this returns a copy.

func (*Permit) Type

func (p *Permit) Type() string

Type implements schemas.Permit.

type PermitOption

type PermitOption func(*Permit)

PermitOption configures a Permit at construction. Options keep NewPermit's required arguments stable while letting a source set the occasional extra, so a resolver that does not need one is unaffected.

func WithAgentPermits added in v1.9.0

func WithAgentPermits(agentNames []string) PermitOption

WithAgentPermits grants the named Agent Gateway registrations.

func WithAllowAllProviders

func WithAllowAllProviders(allow bool) PermitOption

WithAllowAllProviders grants every provider, including ones the permit holds no provider permit for: those are allowed with all models and all keys, while a provider it does hold a permit for still applies that permit's rules. See schemas.Permit.AllowsAllProviders.

func WithRelayPermits added in v1.11.0

func WithRelayPermits(endpointNames []string) PermitOption

WithRelayPermits grants the named relays.

type PermitType

type PermitType string

PermitType identifies what kind of source a permit's access comes from. These are the values a schemas.Permit reports from Type. An open string: kinds are declared by whoever resolves permits of that kind, so this package needs no list of them.

const (
	// PermitVirtualKey marks permits whose access comes from a virtual key.
	PermitVirtualKey PermitType = "vk"
	// PermitAccessProfile marks permits whose access comes from a profile attached to a caller.
	PermitAccessProfile PermitType = "access_profile"
	// PermitTeamAccessProfile, PermitBusinessUnitAccessProfile and PermitCustomerAccessProfile mark
	// permits whose access comes from a profile attached to a team, business unit or customer. Such a
	// profile applies to everything under the entity - its members' requests and the keys it owns -
	// and joins the caller's other permits under union, so it widens what may be reached. One kind
	// each rather than one shared kind, because a refusal has to say whose profile it named: "your
	// team's access profile" and "your business unit's" are different answers.
	PermitTeamAccessProfile         PermitType = "team_access_profile"
	PermitBusinessUnitAccessProfile PermitType = "business_unit_access_profile"
	PermitCustomerAccessProfile     PermitType = "customer_access_profile"
	// PermitProject marks permits whose access comes from a project a request names. A project is
	// not something the caller belongs to but something the request opts into, so it grants
	// alongside whatever the caller already holds rather than instead of it. PrettyString needs no
	// case for it: the identifier already reads as prose, which is the only reason the others have
	// one.
	PermitProject PermitType = "project"
)

The permit kinds this package names. The type stays open, so these are not the only kinds that can exist. They are the ones PrettyString can render, which is why they are declared here rather than wherever permits of each kind are resolved: a refusal has to read the same whichever kind it names.

func (PermitType) PrettyString

func (t PermitType) PrettyString() string

PrettyString is what a refusal calls this permit kind: "your team's access profile", not "team_access_profile". Every kind is named here because every kind is declared here - the labels are fixed words, so there is nothing for another build to supply. A kind with no case renders as its identifier, which is ugly but still says what ran out.

Jump to

Keyboard shortcuts

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