Documentation
¶
Overview ¶
Package identitybroker is the in-process core of the agent identity broker.
Service is the contract for the whole identity broker: Mecatl calls it, and only it, whether the broker runs in process or as its own service. Core is the broker side of that contract, with the authenticated workload on every call; NewInProcess joins the two through an Authenticator. Broker is the Core, and package conformance holds the contract tests every implementation passes.
New validates explicitly supplied configuration, an ECDSA P-256 (ES256) signing capability, and already-loaded definition records, and returns a Broker holding an independently owned snapshot of them, and no broker sessions. The broker signs agent credentials (JWT-SVIDs) with the signer and verifies them itself, for Mecatl's calls and for AS-1. Every agent has the shared agent identity: one ag_ identifier generated per process, and a SPIFFE ID under one shared path that differs only by incarnation. The snapshot holds only definitions the broker loads itself, operator-loaded global definitions and system built-ins; repository definitions never enter it. The package performs no file, environment, or network access; source loading and transport belong in adapters that call it.
Index ¶
- Constants
- Variables
- type AgentRef
- type Authenticator
- type Authorization
- type AuthorizeCallRequest
- type Broker
- func (b *Broker) AuthorizeCall(_ context.Context, workload Workload, req AuthorizeCallRequest) (Authorization, error)
- func (b *Broker) CloseSession(_ context.Context, workload Workload, req CloseSessionRequest) error
- func (b *Broker) Definition(ref DefinitionRef) (DefinitionRecord, bool)
- func (b *Broker) FindSession(_ context.Context, workload Workload, req FindSessionRequest) (Session, bool, error)
- func (b *Broker) OpenSession(_ context.Context, workload Workload, req OpenSessionRequest) (Session, error)
- func (b *Broker) RegisterAgent(_ context.Context, workload Workload, req RegisterAgentRequest) (Registration, error)
- func (b *Broker) VerifyCredential(_ context.Context, req VerifyCredentialRequest) (VerifiedAgent, error)
- type CloseSessionRequest
- type Code
- type Config
- type Core
- type CredentialVerifier
- type DefinitionRecord
- type DefinitionRef
- type Digest
- type Error
- type FindSessionRequest
- type OpenSessionRequest
- type Owner
- type Provenance
- type Reason
- type RegisterAgentRequest
- type Registration
- type Service
- type Session
- type Token
- type UnverifiedAuthenticator
- type VerifiedAgent
- type VerifyCredentialRequest
- type Workload
Constants ¶
const DigestSHA256 = "sha256"
DigestSHA256 is the only digest algorithm this package accepts.
const MaxCredentialLifetime = time.Hour
MaxCredentialLifetime bounds Config.CredentialLifetime. Agent credentials are short-lived bearer tokens; the bound limits how long a stolen one stays usable and rejects unit mistakes such as hours entered for minutes. The value is an interim choice: the agent identity design does not set one.
const MaxSegmentBytes = 63
MaxSegmentBytes bounds DefinitionRef.Source and DefinitionRef.Name. Both become segments of agent SPIFFE IDs, and the cap keeps the longest planned ID within the 2,048 bytes SPIFFE-ID section 2.3 advises against exceeding.
Variables ¶
var ( ErrInvalidConfig = errors.New("identitybroker: invalid configuration") ErrInvalidSigner = errors.New("identitybroker: invalid signer") ErrInvalidDefinition = errors.New("identitybroker: invalid definition record") ErrConflictingDefinition = errors.New("identitybroker: conflicting definition records") )
Errors returned by New and DefinitionRecord.Validate. Each returned error wraps exactly one of them, so a transport adapter can map failures with errors.Is.
Functions ¶
This section is empty.
Types ¶
type AgentRef ¶
type AgentRef struct {
Subject string
}
AgentRef refers to a registered agent instance by its canonical subject, which the caller checks matches the instance it registered. The zero value refers to no agent.
type Authenticator ¶
Authenticator decides which workload is calling. A failure is an *Error, normally with CodeUnauthenticated or CodePermissionDenied.
type Authorization ¶
type Authorization struct {
// GatewayToken is the token the call is dispatched with.
GatewayToken Token
}
Authorization is an allowed MCP call.
type AuthorizeCallRequest ¶
type AuthorizeCallRequest struct {
SessionID string
// Credential is the acting agent's credential from RegisterAgent. The
// broker identifies the acting agent from it.
Credential Token
// OperationID identifies this call.
OperationID string
// Target is the gateway or backend the call is routed to.
Target string
Tool string
// GatewayRejectedToken reports that the gateway rejected the token
// returned for this operation, so the broker obtains a fresh one. A caller
// sets it on at most one retry.
GatewayRejectedToken bool
}
AuthorizeCallRequest asks whether an agent may make one MCP call.
func (AuthorizeCallRequest) Validate ¶
func (r AuthorizeCallRequest) Validate() error
Validate reports whether r is well formed. The error is an *Error with CodeInvalidArgument.
type Broker ¶
type Broker struct {
// contains filtered or unexported fields
}
Broker is the identity broker core. Its startup state is immutable after New; its broker sessions are guarded by a mutex. Its methods are safe for concurrent use.
func New ¶
New validates its inputs and returns a ready Broker, or an error wrapping one of the package's sentinel errors. Inputs are copied before validation, so later caller mutations cannot affect the broker. Records that repeat a reference with identical provenance collapse to one; records that repeat a reference with different provenance are rejected. An empty definition set is valid. New rejects the whole set if any record is invalid; a loader that skips bad definitions filters them first with DefinitionRecord.Validate.
The signer must have an ECDSA P-256 public key, the ES256 algorithm that JWT-SVID permits. A raw *ecdsa.PrivateKey is copied; any other signer, such as a KMS handle, is retained as supplied.
func (*Broker) AuthorizeCall ¶
func (b *Broker) AuthorizeCall(_ context.Context, workload Workload, req AuthorizeCallRequest) (Authorization, error)
AuthorizeCall implements Core. It applies the session checks, then denies every call: no call is authorized until per-call grants exist.
func (*Broker) CloseSession ¶
CloseSession implements Core. Calls already in progress may finish.
func (*Broker) Definition ¶
func (b *Broker) Definition(ref DefinitionRef) (DefinitionRecord, bool)
Definition returns a copy of the loaded record for ref, and whether one exists. Mutating the returned record does not affect the broker.
func (*Broker) FindSession ¶
func (b *Broker) FindSession(_ context.Context, workload Workload, req FindSessionRequest) (Session, bool, error)
FindSession implements Core.
func (*Broker) OpenSession ¶
func (b *Broker) OpenSession(_ context.Context, workload Workload, req OpenSessionRequest) (Session, error)
OpenSession implements Core. Opens run one at a time, so concurrent opens for one host session return the same broker session.
func (*Broker) RegisterAgent ¶
func (b *Broker) RegisterAgent(_ context.Context, workload Workload, req RegisterAgentRequest) (Registration, error)
RegisterAgent implements Core. It checks the session, the owner, the root host session or parent, and an earlier registration of the instance, issues the credential, and only then records the instance in the session, so a failure changes nothing. The definition is accepted as reported, without a root eligibility check, because the broker loads no definitions yet.
func (*Broker) VerifyCredential ¶
func (b *Broker) VerifyCredential(_ context.Context, req VerifyCredentialRequest) (VerifiedAgent, error)
VerifyCredential implements CredentialVerifier. After the credential's own checks (see credentialIssuer.verify), its subject must be a registered instance, and that instance must be registered to the request's session, and that session must be open, so closing a session ends its credentials' use at once rather than at their expiry. Only a credential issued since the instance moved to this session verifies, so reopening a session does not revive credentials issued before it closed. Per-instance subjects are what bind a credential to one session, with no session claim. It does not check which workload opened the session: AS-1 checks that from its refresh token's record.
type CloseSessionRequest ¶
type CloseSessionRequest struct {
SessionID string
// Reason is the closing reason later calls on the session fail with.
// ReasonSessionClosed is the only closing reason Mecatl supplies today.
Reason Reason
}
CloseSessionRequest closes a broker session.
func (CloseSessionRequest) Validate ¶
func (r CloseSessionRequest) Validate() error
Validate reports whether r is well formed. The error is an *Error with CodeInvalidArgument.
type Code ¶
type Code string
Code classifies a Service error. The values mirror the gRPC status codes the agent identity design assigns, without importing gRPC.
const ( CodeUnauthenticated Code = "UNAUTHENTICATED" CodePermissionDenied Code = "PERMISSION_DENIED" CodeInvalidArgument Code = "INVALID_ARGUMENT" CodeFailedPrecondition Code = "FAILED_PRECONDITION" CodeAlreadyExists Code = "ALREADY_EXISTS" CodeAborted Code = "ABORTED" CodeInternal Code = "INTERNAL" )
Codes a Service error can carry.
type Config ¶
type Config struct {
// TrustDomain is the SPIFFE trust domain name for agent identities, such as
// "agents.example.com", without the spiffe:// scheme.
TrustDomain string
// Audience is the single audience every agent credential names: the
// broker's, such as "https://broker.example.com". JWT-SVID recommends one
// audience per token, and the broker's verifiers reject more than one.
Audience string
// CredentialLifetime is the validity period of an agent credential. It must
// be positive and at most MaxCredentialLifetime.
CredentialLifetime time.Duration
}
Config is the operator-supplied identity configuration. The caller resolves it from whatever configuration source it uses; New validates it. Config holds only immutable values, so New's copy is independent of the caller's.
type Core ¶
type Core interface {
OpenSession(ctx context.Context, workload Workload, req OpenSessionRequest) (Session, error)
FindSession(ctx context.Context, workload Workload, req FindSessionRequest) (Session, bool, error)
CloseSession(ctx context.Context, workload Workload, req CloseSessionRequest) error
RegisterAgent(ctx context.Context, workload Workload, req RegisterAgentRequest) (Registration, error)
AuthorizeCall(ctx context.Context, workload Workload, req AuthorizeCallRequest) (Authorization, error)
}
Core is the broker side of the contract: Service's calls, each with the workload the authenticator determined. Implementations provide the behavior documented on Service, and also:
- A workload that fails Validate is refused with CodeUnauthenticated, before any other check.
- A broker session is bound to the whole Workload that opened it, Verified included, so a verified and an unverified workload with the same name are different workloads.
- Another workload cannot tell another workload's broker session from none: closing, registering into, or calling through it fails exactly as for an unknown session ID (CodePermissionDenied, before any closing reason), and FindSession reports none open. Opening its host session is refused with CodePermissionDenied.
type CredentialVerifier ¶
type CredentialVerifier interface {
// VerifyCredential checks an agent credential for use in a broker
// session: the design's credential checks, in order, then that the agent
// is registered to that session, the credential was issued since the
// agent moved there, and the session is open. It returns the agent, its
// ag_ identifier, and the credential's expiry. A malformed request fails
// with CodeInvalidArgument; an expired credential with
// CodeUnauthenticated and ReasonSVIDExpired; a closed session with its
// closing reason, such as ReasonSessionClosed; every other failure with
// CodePermissionDenied and ReasonDenied.
VerifyCredential(ctx context.Context, req VerifyCredentialRequest) (VerifiedAgent, error)
}
CredentialVerifier is the check AS-1 makes on each grant. AS-1 is a module of the broker and calls it in process, so it takes no workload, and Mecatl never calls it. It is apart from Core so that a transport serving Core cannot publish an unauthenticated credential check by accident.
type DefinitionRecord ¶
type DefinitionRecord struct {
Ref DefinitionRef
Provenance Provenance
}
DefinitionRecord is one loaded definition as the identity core sees it: the reference and its provenance. It deliberately carries no instructions, tools, or execution settings.
func (DefinitionRecord) Validate ¶
func (r DefinitionRecord) Validate() error
Validate reports whether r is a well-formed definition record, applying the same rules as New. The returned error wraps ErrInvalidDefinition. A loader that skips bad definitions, rather than failing startup, uses it to filter records before calling New.
type DefinitionRef ¶
type DefinitionRef struct {
// Source identifies where the definition came from, such as "global" for
// operator-loaded definitions or "system" for built-ins. It becomes the
// definition category segment of agent SPIFFE IDs.
Source string
// Name is the definition name within the source, such as "code-reviewer".
Name string
// Revision is the source's own revision identifier, such as a Git commit or
// an object revision. It is opaque to the broker and is not a content digest.
Revision string
}
DefinitionRef identifies one agent definition: the configured source it was loaded from, its name within that source, and the selected source revision. Two references are equal only when all three fields are byte-identical, so the same name from different sources, or at different revisions, names different definitions. DefinitionRef is comparable and is the snapshot key.
type Digest ¶
Digest is a content digest in canonical form: a supported algorithm and the lowercase hexadecimal digest value. The canonical form makes value equality mean digest equality.
type Error ¶
Error is the error every Service call returns. Detail is for diagnostics; it never contains credentials.
func ReasonError ¶
ReasonError returns an *Error for reason with the code the design assigns.
type FindSessionRequest ¶
type FindSessionRequest struct {
RootHostSessionID string
}
FindSessionRequest finds the open broker session for a root host session.
func (FindSessionRequest) Validate ¶
func (r FindSessionRequest) Validate() error
Validate reports whether r is well formed. The error is an *Error with CodeInvalidArgument.
type OpenSessionRequest ¶
type OpenSessionRequest struct {
HostSessionID string
// Incarnation is the host session's incarnation, which its main agent
// registers with, so it must be a Mecatl incarnation (see
// RegisterAgentRequest.Incarnation).
Incarnation string
Owner Owner
// AccessToken is the user's AS-1 access token, when the request has one.
AccessToken Token
}
OpenSessionRequest opens the broker session for a root host session.
func (OpenSessionRequest) Validate ¶
func (r OpenSessionRequest) Validate() error
Validate reports whether r is well formed. The error is an *Error with CodeInvalidArgument.
type Owner ¶
Owner is a session owner: the user's company identity, such as issuer "https://login.example.com" and subject "alice-204".
type Provenance ¶
type Provenance struct {
// Profile identifies the provenance profile, such as "git/v1".
Profile string
// Metadata holds profile-specific facts, such as a repository and path.
// Keys must be non-empty printable text without spaces; values must be
// valid UTF-8. A nil and an empty map are equivalent.
Metadata map[string]string
// SourceDigest is the digest of the bytes read from the source.
SourceDigest Digest
// DefinitionDigest is the digest of the normalized definition the loader
// accepted. It differs from SourceDigest whenever parsing normalizes input.
DefinitionDigest Digest
}
Provenance records where a loaded definition came from and what was accepted. The profile names the source kind and versions the meaning of Metadata; this package requires a profile but does not interpret it.
type Reason ¶
type Reason string
Reason is a design-defined reason code that tells the caller what to do, such as opening a new broker session. Reasons are lowercase letters and underscores.
const ( // ReasonSessionClosed: the broker session is closed; open a new one on the // user's next request. ReasonSessionClosed Reason = "session_closed" // ReasonSessionOwned: the host session's broker session belongs to another // owner. ReasonSessionOwned Reason = "session_owned" // ReasonDenied: the call is not allowed. ReasonDenied Reason = "denied" // ReasonSVIDExpired: the agent credential has expired; get a new one and // retry. ReasonSVIDExpired Reason = "svid_expired" )
Reasons a Service error can carry. Later work adds the design's other reasons as it returns them.
type RegisterAgentRequest ¶
type RegisterAgentRequest struct {
SessionID string
// HostSessionID is the agent's own host session. With no Parent it must
// equal the broker session's root host session. A child agent's differs,
// so it is not compared; checking that it belongs to the session's tree
// will be addressed later.
HostSessionID string
// Owner must equal the broker session's owner.
Owner Owner
// Incarnation is the agent instance's incarnation, which the harness
// allocates and persists: its host session's incarnation. With no Parent
// it must equal the broker session's incarnation. It becomes a segment of
// the agent's SPIFFE ID, so it must be a Mecatl incarnation: inc_ and 26
// lowercase base32 characters, or legacy_ and 64 hex characters.
Incarnation string
// Definition is the definition the instance runs. Name is required. Source
// and Revision are checked when set but may be empty for now, because
// Mecatl sessions know their definition only by name; requiring them will
// be addressed later.
Definition DefinitionRef
// Parent is the registered instance that created this one; zero for a
// session's main agent.
Parent AgentRef
}
RegisterAgentRequest registers an agent instance in a broker session.
func (RegisterAgentRequest) Validate ¶
func (r RegisterAgentRequest) Validate() error
Validate reports whether r is well formed. The error is an *Error with CodeInvalidArgument.
type Registration ¶
type Registration struct {
Agent AgentRef
Credential Token
// ExpiresAt is when the credential expires. Register again before then
// for a fresh one.
ExpiresAt time.Time
}
Registration is a registered agent instance and its credential. Mecatl's trusted harness code holds the credential and presents it on each call; it never reaches the engine or the model.
type Service ¶
type Service interface {
// OpenSession opens the broker session for a root host session, or returns
// the one already open for it.
OpenSession(ctx context.Context, req OpenSessionRequest) (Session, error)
// FindSession returns the open broker session for a root host session, and
// whether one is open.
FindSession(ctx context.Context, req FindSessionRequest) (Session, bool, error)
// CloseSession closes a broker session with a closing reason. Closing an
// already closed session succeeds and keeps its first reason.
CloseSession(ctx context.Context, req CloseSessionRequest) error
// RegisterAgent registers an agent instance in an open broker session and
// returns its reference and credential.
RegisterAgent(ctx context.Context, req RegisterAgentRequest) (Registration, error)
// AuthorizeCall decides whether the agent presenting the credential may
// make an MCP call, and returns the gateway token when it may. A denial is
// an error, so a caller that ignores the result cannot dispatch.
AuthorizeCall(ctx context.Context, req AuthorizeCallRequest) (Authorization, error)
}
Service is the contract between Mecatl and the identity broker: every call Mecatl makes goes through it, whether the broker runs in process or as its own service. Requests and results are plain values owned by this package, and every error is an *Error carrying a Code and, where the design defines one, a Reason.
Behavior every implementation provides:
- OpenSession for a host session that already has an open broker session returns that session when the incarnation and owner match. A different owner fails with CodePermissionDenied and ReasonSessionOwned; a different incarnation fails with CodeAlreadyExists.
- FindSession finds the open broker session from its root host session.
- A closed broker session is never reopened. It refuses registration and calls with its closing reason, such as ReasonSessionClosed with CodeFailedPrecondition, and a later OpenSession for the same host session opens a new broker session with a new ID.
- RegisterAgent refuses a request whose owner differs from the broker session's owner with CodePermissionDenied: one session tree has one owner. It also refuses, with CodePermissionDenied, an agent with no parent whose host session or incarnation is not the broker session's root host session or incarnation, and an agent whose parent is not registered to the session.
- Registering the same incarnation again returns the same agent with a fresh credential, so a lost response or an expiring credential is handled by registering again. Registering it into a new broker session moves it there, keeping its identity. A registration of the same incarnation with a different owner, host session, or parent fails with CodeAlreadyExists and changes nothing.
- A failed registration leaves any earlier registration as it was.
- Agents with different incarnations are different agents. An agent's subject is a SPIFFE ID ending in "/inst/" and its incarnation, so the caller can check that the agent returned is the one it registered.
- A broker session ID the broker never issued is refused with CodePermissionDenied.
- Invalid requests fail with CodeInvalidArgument.
No request names the calling workload: the broker authenticates it itself, and Mecatl gets a Service from NewInProcess. Implementations provide this behavior by implementing Core.
func NewInProcess ¶
func NewInProcess(authn Authenticator, core Core) Service
NewInProcess returns the Service Mecatl calls when the broker runs in its process. Each call asks authn which workload is calling, then passes it to core, so the in-process path takes the same authentication step as a remote one.
type Session ¶
type Session struct {
// ID is the broker session ID. The broker allocates it and never reuses it.
ID string
HostSessionID string
Incarnation string
Owner Owner
}
Session is a broker session.
type Token ¶
type Token struct {
// contains filtered or unexported fields
}
Token is a bearer credential: an AS-1 access token, an agent credential, or a gateway token. Its value is only available through Value; formatting a Token with any verb prints a redacted placeholder.
type UnverifiedAuthenticator ¶
type UnverifiedAuthenticator struct {
Name string
}
UnverifiedAuthenticator returns the same unverified workload for every call. It stands in until the broker authenticates the workload's credential, which will be addressed later.
func (UnverifiedAuthenticator) Authenticate ¶
func (a UnverifiedAuthenticator) Authenticate(context.Context) (Workload, error)
Authenticate implements Authenticator. The workload is never verified.
type VerifiedAgent ¶
type VerifiedAgent struct {
Agent AgentRef
// AgentID is the agent's opaque ag_ identifier, the value AS-1 names the
// acting agent by. It never contains a SPIFFE path or definition name.
AgentID string
// ExpiresAt is when the credential expires.
ExpiresAt time.Time
}
VerifiedAgent is the agent a valid credential identifies.
type VerifyCredentialRequest ¶
VerifyCredentialRequest asks whether an agent credential is valid for use in a broker session.
func (VerifyCredentialRequest) Validate ¶
func (r VerifyCredentialRequest) Validate() error
Validate reports whether r is well formed. The error is an *Error with CodeInvalidArgument.
type Workload ¶
type Workload struct {
// Name identifies the calling service, such as a Kubernetes
// ServiceAccount.
Name string
// Verified reports that an authenticator verified the workload's
// credential. The zero value is false, so only an authenticator that
// verifies a credential sets it. When false, Name is a claim nobody
// proved: implementations must never treat it as the verified workload of
// the same name, and records must not present it as verified.
Verified bool
}
Workload is the service making a call, as the broker's authenticator determined it. It is never a request field: Mecatl presents no identity of its own, and the broker decides who is calling.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package conformance provides the contract tests every identity broker implementation, an identitybroker.Core and identitybroker.CredentialVerifier, passes.
|
Package conformance provides the contract tests every identity broker implementation, an identitybroker.Core and identitybroker.CredentialVerifier, passes. |