Documentation
¶
Overview ¶
Package hubaccess implements the hub-access half of the provider contract: the hub REST capabilities a provider may exercise with an admitted caller context, supplied either by a delegated user token or a short-lived action proof minted at the authenticated kcp front door.
A provider requests capabilities in its CatalogEntry (spec.hub.access) from a closed set this package owns; it never names routes. A tenant accepts them when enabling the provider, which records a Grant for the provider. The Gate admits a delegated or proof-backed call only for a route that maps to a capability the provider both declares and was granted in that workspace, and marks the request with the accepted limits (tenant.DelegatedCall). The person the caller context represents is still authorized by the tenant middleware and handlers exactly as if they had called: a provider never exceeds that person.
Index ¶
- Constants
- func Allowed(grant *tenancyv1alpha1.Grant, declared providersv1alpha1.ProviderHubAccess, ...) (tenancyv1alpha1.GrantedCapability, bool, bool)
- func ComposeAllowed(grant *tenancyv1alpha1.Grant, composition CompositionRequirement, ...) (allowed, byDefault bool)
- func ComposeCapability(group, resource string) string
- func Declared(declared []providersv1alpha1.ProviderHubAccess, req Requirement) (providersv1alpha1.ProviderHubAccess, bool)
- func DelegatedUserResolver(human tenant.UserResolver) tenant.UserResolver
- func FromDeclaration(d providersv1alpha1.ProviderHubAccess) tenancyv1alpha1.GrantedCapability
- func GrantName(orgUUID, wsUUID, provider, providerOrgUUID string) string
- func Granted(grant *tenancyv1alpha1.Grant, req Requirement) (tenancyv1alpha1.GrantedCapability, bool)
- func GrantedComposition(composition CompositionRequirement) tenancyv1alpha1.GrantedCapability
- func MayDecide(scope providersv1alpha1.ProviderHubAccessScope, workspaceRole, orgRole string) bool
- func MayDecideComposition(workspaceRole, orgRole string) bool
- func ParseComposeCapability(capability string) (group, resource string, ok bool)
- type Caller
- type CompositionRequirement
- type Decision
- type Gate
- type GrantKey
- type GrantReader
- type Limits
- type MergeFunc
- type ProviderLookup
- type Requirement
- type Store
- func (s *Store) Delete(ctx context.Context, key GrantKey) error
- func (s *Store) Get(ctx context.Context, key GrantKey) (*tenancyv1alpha1.Grant, error)
- func (s *Store) Put(ctx context.Context, key GrantKey, ...) (*tenancyv1alpha1.Grant, error)
- func (s *Store) Record(ctx context.Context, key GrantKey, merge MergeFunc, decidedBy string) (*tenancyv1alpha1.Grant, error)
- type VerifyFunc
Constants ¶
const ComposeCapabilityPrefix = "compose:"
ComposeCapabilityPrefix marks a composition capability inside a Grant.
const DefaultGrantCacheTTL = 15 * time.Second
DefaultGrantCacheTTL bounds how long a replica serves a cached grant (or the absence of one). Enable and Disable on the same replica update the cache immediately; another replica sees the change within this window.
const DefaultInvitesPerHour = 60
DefaultInvitesPerHour bounds memberships.invite calls per provider per Organization on one replica.
Variables ¶
This section is empty.
Functions ¶
func Allowed ¶
func Allowed(grant *tenancyv1alpha1.Grant, declared providersv1alpha1.ProviderHubAccess, platformProvider, platformDefault bool) (tenancyv1alpha1.GrantedCapability, bool, bool)
Allowed is the gate's answer for one declared capability: accepted capabilities are allowed, declined ones are not, and an undecided one is allowed only for a platform provider under the platform default. The returned entry carries the limits to combine with the declaration.
func ComposeAllowed ¶ added in v0.2.7
func ComposeAllowed(grant *tenancyv1alpha1.Grant, composition CompositionRequirement, platformProvider, platformDefault bool) (allowed, byDefault bool)
ComposeAllowed is the policy's answer for one declared composition: accepted is allowed, declined is not, and undecided is allowed only for a platform provider under the platform default — exactly the rule hub access uses, for the same reason. Platform providers are operator-installed and were composing these kinds before consent existed; an org-owned provider always needs an explicit acceptance. The second return says the decision came from the default rather than from a person, which the enabled listing surfaces so an admin can see what nobody has actually agreed to.
func ComposeCapability ¶ added in v0.2.7
ComposeCapability is the capability name recorded for one composed kind.
func Declared ¶
func Declared(declared []providersv1alpha1.ProviderHubAccess, req Requirement) (providersv1alpha1.ProviderHubAccess, bool)
Declared returns the provider's declaration for req, if any.
func DelegatedUserResolver ¶
func DelegatedUserResolver(human tenant.UserResolver) tenant.UserResolver
DelegatedUserResolver wraps the human resolver for tenant.Middleware: a request the gate admitted resolves to the person the delegated token stands for; anything else is the human resolver's decision.
func FromDeclaration ¶
func FromDeclaration(d providersv1alpha1.ProviderHubAccess) tenancyv1alpha1.GrantedCapability
FromDeclaration is the grant entry recorded when a tenant accepts a declared capability.
func GrantName ¶
GrantName is the deterministic Grant name for one provider in one workspace. The subject kind and the provider owner are part of it, so an org-owned provider and the platform provider it shadows never share a grant, and neither collides with a grant for another kind of subject.
func Granted ¶
func Granted(grant *tenancyv1alpha1.Grant, req Requirement) (tenancyv1alpha1.GrantedCapability, bool)
Granted returns the grant entry for req, if any.
func GrantedComposition ¶ added in v0.2.7
func GrantedComposition(composition CompositionRequirement) tenancyv1alpha1.GrantedCapability
GrantedComposition is the grant entry recorded when a tenant accepts a composition. It carries no limits: the verbs come from the declaration, read fresh on every mint.
func MayDecide ¶
func MayDecide(scope providersv1alpha1.ProviderHubAccessScope, workspaceRole, orgRole string) bool
MayDecide reports whether a person with these roles may accept or decline a capability at scope: org scope needs an org admin, workspace scope a workspace or org admin.
func MayDecideComposition ¶ added in v0.2.7
MayDecideComposition reports whether a person with these roles may accept a composition. A composition applies to exactly one workspace, so it is a workspace decision: a workspace admin or an org admin.
func ParseComposeCapability ¶ added in v0.2.7
ParseComposeCapability splits a composition capability back into its (group, resource). It reports false for any other capability, so a caller iterating a grant can tell compositions from hub access without a second list.
The resource half may itself be a "<resource>/<verb>" coordinate: a requirement on another provider names either a kind or one verb on a kind (ProviderRequiredResource), and both are recorded as compositions. The group is therefore the first segment and everything after it is the resource, so a coordinate round-trips instead of failing to parse and disappearing from the Enable dialog's composition list.
Types ¶
type Caller ¶
type Caller struct {
// User is the person a delegated token stands for.
User string
OrgUUID string
WorkspaceUUID string
// Delegated is true for a delegated user token; Provider and Legacy are
// meaningful only then.
Delegated bool
Provider string
// ProviderOrgUUID is the provider's owner org ("" = platform), covered by
// the hub's proof unless Legacy.
ProviderOrgUUID string
// Legacy marks a token whose proof predates the owner org; the owner is
// then resolved the way the proxy resolved it when issuing.
Legacy bool
}
Caller is a verified workload identity presented with the tenant headers.
type CompositionRequirement ¶ added in v0.2.7
type CompositionRequirement struct {
// Dependency is the provider whose kind is composed (the OWNER of the
// group), not the provider the grant is for.
Dependency string
Group string
Resource string
}
CompositionRequirement is one composed kind at the point of decision: which dependency declared it, and which kind it is.
func DeclaredCompositions ¶ added in v0.2.7
func DeclaredCompositions(requires []providersv1alpha1.ProviderRequirement) []CompositionRequirement
DeclaredCompositions flattens a provider's requirements into the list an Enable dialog offers and an Enable request is checked against.
Only a requirement that NAMES a provider produces one. A requirement on a platform builtin — authorization.k8s.io, the core group — belongs to no provider, so there is no "this provider manages that provider's kinds here" consent to ask for; it is an ordinary permission claim the tenant ticks in the same dialog.
func (CompositionRequirement) Ref ¶ added in v0.2.7
func (c CompositionRequirement) Ref() tenancyv1alpha1.CapabilityRef
Ref is the capability reference this composition is recorded under.
type Decision ¶
type Decision int
Decision is what a grant says about one capability.
func Decide ¶
func Decide(grant *tenancyv1alpha1.Grant, req Requirement) (Decision, tenancyv1alpha1.GrantedCapability)
Decide returns the grant's decision for req, and the accepted entry when the decision is Accepted.
func DecideComposition ¶ added in v0.2.7
func DecideComposition(grant *tenancyv1alpha1.Grant, composition CompositionRequirement) Decision
DecideComposition returns the grant's decision for one composition.
type Gate ¶
type Gate struct {
// Human resolves the caller's own credential (OIDC, static token, kcp
// ServiceAccount). When it succeeds the request is not a delegated call.
Human tenant.UserResolver
Verify VerifyFunc
// ActionProof verifies a request-scoped proof minted at the authenticated
// kcp front door. Unlike a delegated ServiceAccount token, it is accepted
// only for the App Studio membership flows bound to the original action.
ActionProof *actionproof.Verifier
ActionProofUses actionproof.UseStore
Providers ProviderLookup
Grants GrantReader
// PlatformDefault grants a platform provider the capabilities it declares
// in a workspace where no decision was recorded yet (no grant object).
// Platform providers are operator-installed and, under the default
// --provider-delegated-tokens=off, already hold the caller's own bearer;
// this keeps them working across the upgrade. Org-owned providers always
// need an explicit grant.
PlatformDefault bool
// InvitesPerHour bounds memberships.invite per (provider, org); 0 uses
// DefaultInvitesPerHour.
InvitesPerHour int
// contains filtered or unexported fields
}
Gate admits provider calls made with a delegated user token or a narrow hub-signed action proof to the REST routes the provider contract allows.
func (*Gate) Middleware ¶
Middleware wraps the tenant-scoped routes. It runs before tenant.Middleware. Action proofs are verified and constrained first; requests without one use the existing delegated-token path or pass through for the tenant middleware.
type GrantKey ¶
type GrantKey struct {
OrgUUID, WorkspaceUUID, Provider, ProviderOrgUUID string
}
GrantKey identifies one provider in one workspace.
type GrantReader ¶
type GrantReader interface {
Get(ctx context.Context, key GrantKey) (*tenancyv1alpha1.Grant, error)
}
GrantReader reads recorded grants.
type Limits ¶
Limits are the constraints in force for one admitted capability.
func Effective ¶
func Effective(declared providersv1alpha1.ProviderHubAccess, granted tenancyv1alpha1.GrantedCapability) Limits
Effective combines a declaration with its grant: the narrower of the two wins, so neither a catalog update nor an old grant can widen access. A role cap is always "member" — admin is never grantable through a provider.
type MergeFunc ¶
type MergeFunc func(prev *tenancyv1alpha1.Grant) (accepted []tenancyv1alpha1.GrantedCapability, declined []tenancyv1alpha1.CapabilityRef)
MergeFunc computes the decisions to record from the stored grant (nil when none exists).
type ProviderLookup ¶
type ProviderLookup interface {
Get(name string) (providers.Provider, bool)
GetForOrg(orgUUID, name string) (providers.Provider, bool)
}
ProviderLookup is the part of the provider registry the gate needs.
type Requirement ¶
type Requirement struct {
Capability providersv1alpha1.ProviderHubCapability
Scope providersv1alpha1.ProviderHubAccessScope
}
Requirement is the capability a route needs, at a scope.
func Match ¶
func Match(method, urlPath string) (Requirement, bool)
Match maps a hub REST request to the capability it requires. Only the routes listed here are reachable with a delegated token; everything else (role changes, removals, workspace membership writes, org and workspace management) is not part of the closed set and is refused.
GET /api/orgs/{org}/memberships memberships.read org
GET /api/orgs/{org}/workspaces/{ws}/memberships memberships.read workspace
POST /api/orgs/{org}/memberships memberships.invite org
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store reads and writes provider Grants in root:railgrid:system:tenants, with a short cache on the read path the gate uses per request.
func NewStore ¶
func NewStore(client *railgridclient.Client) *Store
NewStore returns a Store over the hub's tenancy client (the one bound to root:railgrid:system:tenants).
func (*Store) Delete ¶
Delete removes the grant for key. Deleting a grant that does not exist is not an error.
func (*Store) Put ¶
func (s *Store) Put(ctx context.Context, key GrantKey, accepted []tenancyv1alpha1.GrantedCapability, declined []tenancyv1alpha1.CapabilityRef, decidedBy string) (*tenancyv1alpha1.Grant, error)
Put records exactly these decisions for key, replacing any earlier ones.
func (*Store) Record ¶
func (s *Store) Record(ctx context.Context, key GrantKey, merge MergeFunc, decidedBy string) (*tenancyv1alpha1.Grant, error)
Record reads the stored grant for key straight from the API (not the cache), merges the new decisions into it with merge, and writes the result. When there is no stored grant and merge decides nothing, nothing is written: every capability stays undecided.