Documentation
¶
Overview ¶
Package supervision implements the wire values of AAuth Supervision, the interface between a Person Server and a supervision server (SS) that decides on the person's behalf, and the Person Server's client for it.
A Person Server with an SS configured for an Agent makes no supervision decision of its own for that Agent. It puts every mission proposal, update and completion, and every person-token, auth-token and permission request, to the Agent's deciding SS; it acts on the SS's allow, deny or ask; and it tells the SS what happened through events. The SS never calls the Person Server: everything it has to say is a response.
This package owns the metadata document, the signed request envelope and its bodies, decisions, deferred and clarification responses, events, errors, and the HTTP Message Signature profile of a signed decision. Client makes the Person Server's signed requests and verifies what comes back. The personserver package composes it, and the supervisionserver package implements the SS role. Neither role imports the other.
Index ¶
- Constants
- func AcceptSignature(hasBody bool) string
- func DecisionSignOptions(request *http.Request) signaturekey.ResponseSignOptions
- func DecodeMissionAction(s256 string, exact []byte) (*Request, *Report, error)
- func EncodePending(sdi string, clarification *Clarification) ([]byte, error)
- func ForwardedMembers(body []byte) map[string]json.RawMessage
- func ParseScope(scope string) ([]string, error)
- func SignDecision(header http.Header, status int, body []byte, request *http.Request, ...) error
- func VerifyDecision(ctx context.Context, status int, header http.Header, body []byte, ...) (signaturekey.VerifiedResponse, error)
- func VerifyExchange(ctx context.Context, exchange Exchange, resolver signaturekey.KeyResolver) (signaturekey.VerifiedResponse, error)
- type Answer
- type ApplyOption
- type Clarification
- type ClarificationResponse
- type Client
- func (client *Client) AnswerClarification(ctx context.Context, supervisor string, request Request, location string, ...) (Outcome, error)
- func (client *Client) Cancel(ctx context.Context, supervisor, location string) (Outcome, error)
- func (client *Client) Connect(ctx context.Context, supervisor string, request ConnectRequest) (Outcome, error)
- func (client *Client) Metadata(ctx context.Context, supervisor string) (Metadata, error)
- func (client *Client) Poll(ctx context.Context, supervisor string, request Request, location string) (Outcome, error)
- func (client *Client) PollConnect(ctx context.Context, supervisor, location string) (Outcome, error)
- func (client *Client) PostEvent(ctx context.Context, supervisor string, event Event) (Outcome, error)
- func (client *Client) PostReport(ctx context.Context, supervisor string, report Report) (Outcome, error)
- func (client *Client) Supervise(ctx context.Context, supervisor string, request Request) (Outcome, error)
- type ClientConfig
- type ConnectAction
- type ConnectInteraction
- type ConnectRequest
- type Decision
- type ErrorCode
- type Event
- type EventName
- type Exchange
- type Kind
- type Metadata
- type MissionTerms
- type Outcome
- type PendingAnswer
- type Problem
- type RecordedRequest
- type RecordedResponse
- type Report
- type ReportAction
- type Request
- type RequestError
- type TranscriptEntry
- type UpdatedRequest
- type Verdict
Constants ¶
const ( ApplyOnce = "once" ApplyMission = "mission" ApplyAlways = "always" )
The fixed reaches a Person Server may offer when an ask carries no apply_options (Reach of an Answer).
const ( FromPerson = "person" FromAgent = "agent" )
The two speakers of a transcript.
const DefaultWait = 30 * time.Second
DefaultWait is the Prefer: wait a Client sends on every supervision request and connect when ClientConfig.Wait is zero. The draft RECOMMENDS 30 seconds, so an SS that decides within it never holds a pending record.
const DiscoveryDocument = "aauth-supervisor.json"
DiscoveryDocument is the well-known metadata document of a supervision server, and the dwk a signed decision names in its Signature-Key.
const MaxBodyBytes = 1 << 20
MaxBodyBytes bounds every supervision request and response body.
const MaxClarificationRounds = 5
MaxClarificationRounds is the clarification limit a Person Server enforces on one supervision request, counting the SS's questions and the person's. The draft RECOMMENDS five.
const MaxMarkdownBytes = 64 << 10
MaxMarkdownBytes bounds one Markdown member: a description, summary, justification, reason, question or answer.
const SignatureLabel = "sig"
SignatureLabel is the label of a signed decision and of the Person Server's Accept-Signature request for one.
SupervisorUnavailable is the supervisor member of a decision.resolved event for an act the Person Server put to the person because it could not reach the SS.
Variables ¶
This section is empty.
Functions ¶
func AcceptSignature ¶
AcceptSignature returns the Accept-Signature value a Person Server sends on a supervision request. A request with a body asks for its content-digest to be covered under req; a poll, which has none, asks for @authority and @path only.
func DecisionSignOptions ¶
func DecisionSignOptions(request *http.Request) signaturekey.ResponseSignOptions
DecisionSignOptions is the coverage of a signed decision answering request. It always covers @status, content-type and content-digest. When request carries Accept-Signature, it then covers each further component the request names under SignatureLabel that the SS can produce, in the order named: signature-key, and under req @method, @authority, @path, @query when the request has a query, and request fields the request carries. Without Accept-Signature, it binds the decision to request by its content-digest when it has one, and by @authority and @path.
func DecodeMissionAction ¶
DecodeMissionAction decodes a body posted to {mission_endpoint}/{s256}. It returns a Request for an update or completion and a Report for approved or terminated; exactly one is set. s256 is the path segment.
func EncodePending ¶
func EncodePending(sdi string, clarification *Clarification) ([]byte, error)
EncodePending returns the body of a 202 for a pending request: sdi and status pending, and for a clarification, decision clarify with the question. sdi is omitted when empty.
func ForwardedMembers ¶
func ForwardedMembers(body []byte) map[string]json.RawMessage
ForwardedMembers returns the members of an Agent's request body that a supervision request does not define, exactly as sent, for Request.Extra. A body that is not one JSON object forwards nothing.
func ParseScope ¶
ParseScope splits a space-delimited scope into distinct tokens.
func SignDecision ¶
func SignDecision(header http.Header, status int, body []byte, request *http.Request, key jose.SigningKey, issuer string, clock aauth.Clock) error
SignDecision signs a terminal decision response under the SS's jwks_uri key, naming issuer and DiscoveryDocument in Signature-Key, and sets the signature and Content-Digest fields in header. Signature-Input carries created and the key's kid as keyid, as the draft's example does. body is the exact response body and request the request it answers.
func VerifyDecision ¶
func VerifyDecision( ctx context.Context, status int, header http.Header, body []byte, request *http.Request, supervisor string, resolver signaturekey.KeyResolver, clock aauth.Clock, ) (signaturekey.VerifiedResponse, error)
VerifyDecision verifies a signed decision: the signature must cover @status, content-type and content-digest, verify under a key the supervisor's metadata names, and name supervisor as its Signature-Key id with DiscoveryDocument. Any covered req component is checked against request. A signature's created time has no lower bound: a repeated decision keeps its first signature, and one reached later at a pending URL is signed when polled. One more than the signature window ahead of clock is refused.
func VerifyExchange ¶
func VerifyExchange(ctx context.Context, exchange Exchange, resolver signaturekey.KeyResolver) (signaturekey.VerifiedResponse, error)
VerifyExchange verifies a recorded signed decision again, for a party who reads the log entry later and was not on the connection. The response identifies its signer in itself, so the check needs only the record and a key resolver; it does not depend on which SS was configured at the time. Verification uses the recorded receipt time.
Types ¶
type Answer ¶
type Answer string
Answer is what the person decided after an ask, or when the SS was unavailable.
type ApplyOption ¶
ApplyOption is one reach an SS offers the person for their answer: a value the SS recognizes and a Markdown label the Person Server shows.
type Clarification ¶
Clarification is a question an SS asks the Agent before it decides (Clarification). Timeout, when positive, is how long the Agent has to answer.
func (Clarification) Validate ¶
func (clarification Clarification) Validate() error
Validate checks the question, timeout and options. The wire carries the timeout in whole seconds, so Timeout must be zero or a whole number of seconds: a positive Timeout under one second is refused, never sent as 0.
type ClarificationResponse ¶
type ClarificationResponse struct {
Answer string
Updated *UpdatedRequest
}
ClarificationResponse is the answer a Person Server relays to an SS pending URL, as the Agent sent it: the Agent's Markdown answer, posted with action clarification_response, or for an auth-token request a replacement resource token and presented token, posted with action updated_request. Exactly one of Answer and Updated is set.
func DecodeClarificationResponse ¶
func DecodeClarificationResponse(exact []byte) (ClarificationResponse, error)
DecodeClarificationResponse decodes a POST to an SS pending URL. A missing or unrecognized action is invalid_request.
func (ClarificationResponse) Encode ¶
func (response ClarificationResponse) Encode() ([]byte, error)
Encode returns the clarification_response or updated_request body.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client makes a Person Server's signed requests to supervision servers. Each method sends one request and never sleeps or retries. Methods return an error only when no usable answer arrived: a transport failure, a malformed response, or a decision whose signature fails; the Person Server then treats the SS as unavailable for the act. An error response the draft defines is returned as Outcome.Problem with a nil error, so the caller can connect, replay or wait as it directs.
func NewClient ¶
func NewClient(config ClientConfig) (*Client, error)
NewClient validates config and returns a Client over a copy of its HTTP client that follows no redirect and sends no cookies.
func (*Client) AnswerClarification ¶
func (client *Client) AnswerClarification(ctx context.Context, supervisor string, request Request, location string, answer ClarificationResponse) (Outcome, error)
AnswerClarification relays the Agent's answer to an SS clarification, its Markdown answer or its updated auth-token request, with a signed POST to the pending URL. The SS answers it as it answers a poll.
func (*Client) Cancel ¶
Cancel withdraws a pending supervision request with a signed DELETE, as when the Agent cancels.
func (*Client) Connect ¶
func (client *Client) Connect(ctx context.Context, supervisor string, request ConnectRequest) (Outcome, error)
Connect posts a connect or disconnect to the supervisor's connect_endpoint. A 200 is Connected; a 202 with requirement=interaction is Interaction.
func (*Client) Metadata ¶
Metadata returns the supervisor's metadata, fetched under the egress rules and cached for five minutes.
func (*Client) Poll ¶
func (client *Client) Poll(ctx context.Context, supervisor string, request Request, location string) (Outcome, error)
Poll fetches an SS pending URL with a signed, bodyless GET under the same identity that made request, with Prefer: wait.
func (*Client) PollConnect ¶
func (client *Client) PollConnect(ctx context.Context, supervisor, location string) (Outcome, error)
PollConnect polls a connect that waits for the person, with a signed GET.
func (*Client) PostEvent ¶
func (client *Client) PostEvent(ctx context.Context, supervisor string, event Event) (Outcome, error)
PostEvent reports one event to event_endpoint. 201 is Accepted.
func (*Client) PostReport ¶
func (client *Client) PostReport(ctx context.Context, supervisor string, report Report) (Outcome, error)
PostReport reports a mission approval or termination to {mission_endpoint}/{s256}. 201 is Accepted.
func (*Client) Supervise ¶
func (client *Client) Supervise(ctx context.Context, supervisor string, request Request) (Outcome, error)
Supervise puts one act to the supervisor: a POST to mission_endpoint, to {mission_endpoint}/{s256}, or to decision_endpoint, with Prefer: wait and Accept-Signature. A decision must echo the request's sdi and, from an SS that publishes a jwks_uri, carry a signature that verifies.
type ClientConfig ¶
type ClientConfig struct {
PersonServer string
SigningKey jose.SigningKey
KeyResolver signaturekey.KeyResolver
HTTPClient *http.Client
Admit signaturekey.EgressAdmitter
Clock aauth.Clock
// Wait is sent as Prefer: wait on supervision requests, their polls,
// connect and its polls. Zero selects DefaultWait; it is rounded up to
// whole seconds.
Wait time.Duration
}
ClientConfig supplies a Person Server's identity, keys and egress policy. The Client signs as PersonServer under the jwks_uri scheme with the Person Server metadata document, and verifies signed decisions with KeyResolver. Admit decides, before each request, whether a metadata, endpoint or pending URL may be contacted: the person names the SS, and anyone can run one.
type ConnectAction ¶
type ConnectAction string
ConnectAction is the action of a connect request.
const ( ActionConnect ConnectAction = "connect" ActionDisconnect ConnectAction = "disconnect" )
type ConnectInteraction ¶
ConnectInteraction is a connect the SS completes only after the person confirms the Person Server at {URL}?code={Code}. The Person Server polls Location until the SS answers 200. A poll answered 202 without a requirement, as once the person has reached the interaction page, is Outcome.Pending instead: keep polling.
type ConnectRequest ¶
type ConnectRequest struct {
Action ConnectAction
ID string
IssuedAt int64
Subject string
Tenant string
}
ConnectRequest establishes, or ends, that a Person Server speaks for Subject at the SS. Tenant is the person's tenant when the Person Server asserts one.
func DecodeConnectRequest ¶
func DecodeConnectRequest(exact []byte) (ConnectRequest, error)
DecodeConnectRequest decodes a body posted to connect_endpoint.
func (ConnectRequest) Encode ¶
func (request ConnectRequest) Encode() ([]byte, error)
Encode returns the connect body.
func (ConnectRequest) Validate ¶
func (request ConnectRequest) Validate() error
Validate checks the members.
type Decision ¶
type Decision struct {
SDI string
Verdict Verdict
// Scope narrows an allowed auth-token request: a space-delimited subset
// of the scope the resource token requested.
Scope string
// ExpiresAt bounds an allowed person or auth token: its exp is no later.
ExpiresAt int64
// Mission is the approved mission an allow of a mission proposal carries.
Mission *MissionTerms
// Reason is the Markdown reason a deny may carry.
Reason string
// Summary, Recommendation, Proposed and ApplyOptions belong to an ask.
Summary string
Recommendation Verdict
Proposed *MissionTerms
ApplyOptions []ApplyOption
}
Decision is a terminal answer to a supervision request: allow, deny or ask. The members other than SDI and Verdict depend on the verdict and on the request it answers.
func DecodeDecision ¶
DecodeDecision decodes and validates a 200 decision body answering a request of kind.
type ErrorCode ¶
type ErrorCode string
ErrorCode is a supervision error member (Errors).
const ( // ErrorInvalidRequest is a malformed body, a missing member, or an // unrecognized action, act or event. A Person Server treats the SS as // unavailable for the act; retrying will not help. ErrorInvalidRequest ErrorCode = "invalid_request" // ErrorUnsupportedIss means the SS does not accept requests from this // Person Server. ErrorUnsupportedIss ErrorCode = "unsupported_iss" // ErrorUnknownSub means the SS accepts the Person Server but has no // connection for the sub. The Person Server connects and resends. ErrorUnknownSub ErrorCode = "unknown_sub" // ErrorUnknownMission means the SS has no record of mission_s256. The // Person Server replays the mission and resends. ErrorUnknownMission ErrorCode = "unknown_mission" // ErrorUnknownPending names a pending URL the SS does not hold, or a // poller that is not the requester. ErrorUnknownPending ErrorCode = "unknown_pending" // ErrorRateLimited carries a required Retry-After. ErrorRateLimited ErrorCode = "rate_limited" // ErrorServerError is an internal failure. ErrorServerError ErrorCode = "server_error" )
type Event ¶
type Event struct {
Name EventName
ID string
SDI string
IssuedAt int64
Subject string
Agent string
MissionS256 string
// JTI, Audience and ExpiresAt name an issued token, or a revoked one.
JTI string
Audience string
ExpiresAt int64
// Scope and Issuer belong to auth_token.issued; Issuer is the Person
// Server or the Access Server that issued the token.
Scope string
Issuer string
// Answer through Reason belong to decision.resolved.
Answer Answer
Apply string
Recommendation Verdict
Proposed *MissionTerms
Transcript []TranscriptEntry
Supervisor string
Reason string
// Action, Description, Parameters and Result belong to audit.recorded,
// as the Agent sent them.
Action string
Description string
Parameters json.RawMessage
Result json.RawMessage
// Type and URL belong to interaction.requested. The interaction code is
// never forwarded.
Type string
URL string
// AgentIssuer and AgentSubject belong to agent.bound.
AgentIssuer string
AgentSubject string
// TokenType and TokenIssuer belong to revocation.received, with JTI and
// ExpiresAt. TokenType is agent_token or resource_token.
TokenType string
TokenIssuer string
}
Event is one report posted to event_endpoint. The SS records it and answers 201; it does not decide it. The members other than the envelope depend on Name.
func DecodeEvent ¶
DecodeEvent decodes a body posted to event_endpoint. An unrecognized event is invalid_request.
type EventName ¶
type EventName string
EventName is the event member of a body posted to event_endpoint.
const ( EventPersonTokenIssued EventName = "person_token.issued" EventAuthTokenIssued EventName = "auth_token.issued" EventDecisionResolved EventName = "decision.resolved" EventAuditRecorded EventName = "audit.recorded" EventInteractionRequested EventName = "interaction.requested" EventAgentBound EventName = "agent.bound" EventRevocationReceived EventName = "revocation.received" )
type Exchange ¶
type Exchange struct {
Supervisor string
Request RecordedRequest
Response *RecordedResponse
SentAt time.Time
ReceivedAt time.Time
Signed bool
}
Exchange is one request to an SS and the response to it, kept as the Person Server's log entry for a decision. Response is nil when no response arrived. Signed reports that the response carried a signature that verified.
type Kind ¶
type Kind string
Kind names one supervision request: an act the Person Server puts to the SS for decision. Mission kinds go to mission_endpoint; the others go to decision_endpoint with the matching act.
func (Kind) MissionEndpoint ¶
MissionEndpoint reports whether the kind is sent to mission_endpoint.
type Metadata ¶
type Metadata struct {
Issuer string `json:"issuer"`
Name string `json:"name,omitempty"`
ConnectEndpoint string `json:"connect_endpoint"`
MissionEndpoint string `json:"mission_endpoint"`
DecisionEndpoint string `json:"decision_endpoint"`
EventEndpoint string `json:"event_endpoint"`
JWKSURI string `json:"jwks_uri,omitempty"`
Description string `json:"description,omitempty"`
LogoURI string `json:"logo_uri,omitempty"`
DocumentationURI string `json:"documentation_uri,omitempty"`
TOSURI string `json:"tos_uri,omitempty"`
PolicyURI string `json:"policy_uri,omitempty"`
}
Metadata is a supervision server's metadata document (Supervisor Metadata). JWKSURI is optional: an SS that publishes one signs the terminal response to every supervision request, and one that does not is a plain JSON responder.
func DecodeMetadata ¶
DecodeMetadata validates one exact metadata document fetched for expectedIssuer. The document must name that issuer exactly.
func (Metadata) Signs ¶
Signs reports whether the SS publishes a key set, and therefore signs its decisions.
func (Metadata) Validate ¶
Validate checks the required members and that every endpoint and the key set are HTTPS URLs. The draft puts them on the issuer's origin or one the SS controls; a Person Server cannot establish the second from the document, so which origins it contacts is left to its egress admission.
type MissionTerms ¶
type MissionTerms struct {
Description string `json:"description"`
ApprovedTools []aauth.MissionTool `json:"approved_tools,omitempty"`
ApprovedResources []string `json:"approved_resources,omitempty"`
ExpiresAt *time.Time `json:"expires_at,omitempty"`
}
MissionTerms are the mission members an SS approves or proposes for a mission proposal: the approved description and, optionally, tools, resources and expiry. The Person Server builds the mission blob from them.
func (MissionTerms) MarshalJSON ¶
func (terms MissionTerms) MarshalJSON() ([]byte, error)
MarshalJSON writes the terms with expires_at in whole seconds.
func (MissionTerms) Validate ¶
func (terms MissionTerms) Validate() error
Validate checks the terms against the proposal they answer: a description is required, approved tools are named, and approved resources are server identifiers.
type Outcome ¶
type Outcome struct {
Decision *Decision
Pending *PendingAnswer
Connected bool
Interaction *ConnectInteraction
// Accepted reports a 201 to an event or report, or a 200 or 204 to a
// cancellation.
Accepted bool
Problem *Problem
Exchange Exchange
}
Outcome is one SS answer. Exactly one of Decision, Pending, Connected, Interaction, Accepted or Problem describes it. Exchange is the request as signed and the response as received.
type PendingAnswer ¶
type PendingAnswer struct {
Location string
RetryAfter time.Duration
Clarification *Clarification
}
PendingAnswer is a 202 from an SS: the pending URL to poll, the interval, and a clarification when the SS asks the Agent a question.
type RecordedRequest ¶
RecordedRequest is a request as the Person Server signed and sent it.
type RecordedResponse ¶
RecordedResponse is a response as the Person Server received it.
type Report ¶
type Report struct {
Action ReportAction
ID string
SDI string
IssuedAt int64
Subject string
Agent string
MissionS256 string
// Mission is the exact mission blob an approved report carries; its
// SHA-256 is MissionS256.
Mission []byte
// Reason is the termination reason a terminated report carries.
Reason string
}
Report is a mission report posted to {mission_endpoint}/{s256}. The SS answers 201 and does not decide it.
func (Report) Encode ¶
Encode returns the report body. An approved report carries s256 and the base64url mission blob; a terminated report carries its reason, and the sdi of the completion that ended it when a decision did. The mission is the path; neither names it as mission_s256.
type ReportAction ¶
type ReportAction string
ReportAction is the action of a mission report.
const ( // ReportApproved tells the SS the Person Server built and recorded a // mission. ReportApproved ReportAction = actionApproved // ReportTerminated tells the SS a mission ended, for any reason. ReportTerminated ReportAction = actionTerminated )
type Request ¶
type Request struct {
Kind Kind
SDI string
IssuedAt int64
Subject string
Agent string
MissionS256 string
// AgentToken is required on a mission proposal, a person-token request
// and an auth-token request.
AgentToken string
// Description is required on a mission proposal and update, and optional
// on a permission request.
Description string
Tools []aauth.MissionTool
Resources []string
// Summary is required on a mission completion.
Summary string
// Resource is required on a person-token request.
Resource string
// ResourceToken and PresentedToken are required on an auth-token request.
ResourceToken string
PresentedToken string
UpstreamToken string
SubagentToken string
Justification string
// Action is the action named in a permission request; it is required
// there.
Action string
// Parameters is the permission request's JSON object, exactly as sent.
Parameters json.RawMessage
// Extra holds the other members of the Agent's request body, forwarded
// as the Agent sent them, such as capabilities or login_hint. A name this
// package defines, or one of the envelope, never appears here.
Extra map[string]json.RawMessage
}
Request is one supervision request: the envelope (Envelope) and the members its Kind defines. Members that the Kind does not define must be empty.
Forwarded tokens and Agent-authored members are copied as the Agent sent them, so the SS sees what the Person Server saw. Agent-asserted content is carried only in members the Agent authored: Description, Summary, Justification, Action and Parameters. Resource-asserted content arrives in the forwarded resource token.
func DecodeDecisionRequest ¶
DecodeDecisionRequest decodes a body posted to decision_endpoint.
func DecodeMissionProposal ¶
DecodeMissionProposal decodes a body posted to mission_endpoint itself.
func (Request) Encode ¶
Encode returns the request body. Members are written in a fixed order and the result is the exact bytes the Person Server signs and retains.
type RequestError ¶
type RequestError struct {
// contains filtered or unexported fields
}
RequestError reports a request body an SS rejects with invalid_request.
func (*RequestError) Error ¶
func (failure *RequestError) Error() string
func (*RequestError) Unwrap ¶
func (failure *RequestError) Unwrap() error
type TranscriptEntry ¶
TranscriptEntry is one message of the clarification chat between the person and the Agent at the Person Server.
type UpdatedRequest ¶
UpdatedRequest is an auth-token request the Agent replaced while answering a clarification. The Person Server has verified the pair as the protocol requires before relaying it.
type Verdict ¶
type Verdict string
Verdict is the decision member of a terminal answer.
const ( // VerdictAllow means the Person Server proceeds, narrowed as the decision // says. VerdictAllow Verdict = "allow" // VerdictDeny means the Person Server refuses the act. VerdictDeny Verdict = "deny" // VerdictAsk means the SS does not decide, and the Person Server puts the // act to the person. VerdictAsk Verdict = "ask" )