sandbox

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Oct 26, 2026 License: MIT Imports: 37 Imported by: 0

Documentation

Overview

Package sandbox runs every AAuth role from aauth-go together for development: an agent provider, a person server, an access server and two demo resources, on real HTTPS origins under a development certificate authority, with consent pages, a message trace, and a signing helper for HTTP clients that cannot sign (Postman, curl).

The sandbox is a development tool. Its keys, certificate authority and automatic consent are for local use only.

Index

Constants

View Source
const (
	RoleAP       = "ap"
	RolePS       = "ps"
	RoleAS       = "as"
	RoleResource = "resource"
	RoleDocs     = "docs"
)

Role names.

View Source
const (
	// ConsentAuto approves every interaction as soon as it is created.
	ConsentAuto = "auto"
	// ConsentManual waits for a person at the interaction page, or for
	// a headless approval at /sandbox/approve.
	ConsentManual = "manual"
)

Consent modes.

View Source
const (
	PathAgentToken  = "/agent-token" // jkt-jwt two-key refresh (enrollment in the walkthrough)
	PathEnroll      = "/enroll"      // hwk enrollment and single-key refresh
	PathPersonToken = "/person"
	PathToken       = "/token"
	PathPending     = "/pending/"
	PathInteraction = "/interaction"
	PathJWKS        = "/jwks.json"
	PathApprove     = "/sandbox/approve"
	PathTrace       = "/sandbox/trace"
)

Paths the sandbox serves, matching the AAuth walkthrough examples.

View Source
const (
	// HeaderPresent selects how the helper signs: agent (default),
	// person, auth, hwk, jkt-jwt, or none.
	HeaderPresent = "X-Sandbox-Present"
	// HeaderToken is the token to present: required for person and
	// auth; for agent it replaces the helper's own agent token.
	HeaderToken = "X-Sandbox-Token"
	// HeaderCapabilitiesOverride replaces the capabilities the helper
	// declares to resources (default: interaction); empty declares none.
	HeaderCapabilitiesOverride = "X-Sandbox-Capabilities"
)

Signing helper control headers. The helper removes them before forwarding a request.

View Source
const (
	PresentAgent  = "agent"
	PresentPerson = "person"
	PresentAuth   = "auth"
	PresentHWK    = "hwk"
	PresentJKTJWT = "jkt-jwt"
	PresentNone   = "none"
)

Presentation modes for HeaderPresent.

View Source
const AgentName = "assistant"

AgentName is the local part of the test agent's identifier.

View Source
const (
	CACertFile = "ca.pem"
)

CA file names in the state directory.

View Source
const DefaultDomain = "aauth.test"

DefaultDomain is the parent domain of the default role host names. The .test top-level domain is reserved for testing (RFC 6761).

View Source
const DefaultListenAddr = "127.0.0.1:443"

DefaultListenAddr is the roles' default TLS listen address: loopback only, since the roles hand out tokens and approve consent on their own.

View Source
const DefaultPerson = "alice"

DefaultPerson is the person every approval acts for.

View Source
const DefaultSignerAddr = "127.0.0.1:8099"

DefaultSignerAddr is the signing helper's default listen address.

View Source
const HeaderFrom = "X-Sandbox-From"

HeaderFrom names the role that sent a request inside the sandbox. The sandbox's own clients set it; role handlers remove it before serving, and it is never covered by a signature.

Variables

Roles lists the roles in display order.

Functions

func IsLoopback

func IsLoopback(addr string) bool

IsLoopback reports whether the listen address addr (host:port) is a loopback address or localhost. An empty host (":443") listens on every interface and is not loopback.

func NewClient

func NewClient(cfg Config, ca *CA, addr string) *http.Client

NewClient returns a client for a running sandbox listening at addr: it trusts the sandbox CA and dials the local role host names at addr, so no hosts entries are needed. cfg must have defaults applied.

Types

type CA

type CA struct {
	Cert    *x509.Certificate
	CertPEM []byte

	// Replaced says why LoadOrCreateCA replaced the CA that was in the
	// state directory (it had no name constraints, was about to expire,
	// or did not cover a domain), or is empty. Clients that trusted the
	// old CA must trust the new one.
	Replaced string
	// contains filtered or unexported fields
}

CA is the sandbox's development certificate authority. It is name constrained: it can issue certificates only for its Domains (and their subdomains) and the loopback addresses, so trusting it does not let its key vouch for any other site.

func LoadOrCreateCA

func LoadOrCreateCA(dir string, domains ...string) (*CA, error)

LoadOrCreateCA reads the CA from dir, creating it on first use, and replacing one that has no name constraints, expires within 30 days, or does not cover one of domains. domains are the DNS names the CA may issue for, with their subdomains (default: DefaultDomain); localhost and the loopback addresses are always permitted. The private key is written with mode 0600.

func (*CA) IssueServerCert

func (ca *CA) IssueServerCert(names []string) (tls.Certificate, error)

IssueServerCert issues a TLS certificate for names, plus localhost and the loopback addresses, valid for a year or until the CA expires. The names must sit under the CA's domains.

func (*CA) Pool

func (ca *CA) Pool() *x509.CertPool

Pool returns a certificate pool holding only the CA.

type Config

type Config struct {
	// StateDir holds the certificate authority and role keys (default
	// ~/.aauth-sandbox).
	StateDir string
	// Domain is the parent domain of the role host names (default
	// aauth.test); role hosts are <role>.<Domain> unless Hosts sets one.
	Domain string
	// Hosts overrides a role's host name.
	Hosts map[string]string
	// Remote maps a role to an external implementation's https origin;
	// the sandbox does not serve that role and calls the URL instead.
	Remote map[string]string
	// Upstreams maps an extra host name to the http or https origin of a
	// server outside the sandbox, such as a resource under development.
	// The sandbox serves the host over TLS with its CA, records its
	// traffic in the trace and passes requests through with their Host
	// header, so the server's identifier is https://<host>. A name
	// without a dot is under Domain (petstore: petstore.aauth.test).
	Upstreams map[string]string
	// Consent is ConsentAuto (default) or ConsentManual.
	Consent string
	// Person is the person approvals act for (default alice).
	Person string
	// TraceSize bounds the trace (default 2000 exchanges).
	TraceSize int
}

Config configures a Sandbox. Zero values take defaults.

func Init

func Init(cfg Config) (Config, error)

Init creates the certificate authority and every key the sandbox uses in cfg's state directory, if they do not exist. Run it once before starting several sandbox processes that share a state directory, such as one container per role.

func (Config) CADomains

func (c Config) CADomains() []string

CADomains returns the DNS names the sandbox CA is constrained to: Domain, and each local host name that is not under Domain (a Hosts override or an upstream named outside it).

func (Config) HostsLine

func (c Config) HostsLine() string

HostsLine returns the /etc/hosts line that points the local role names at this machine.

func (Config) Local

func (c Config) Local(role string) bool

Local reports whether the sandbox serves role.

func (Config) LocalHosts

func (c Config) LocalHosts() []string

LocalHosts returns the host names the sandbox serves: its local roles' in role order, then its upstreams' in name order.

func (Config) URL

func (c Config) URL(role string) string

URL returns a role's origin: the remote URL, or https://<host>.

func (Config) UpstreamHosts

func (c Config) UpstreamHosts() []string

UpstreamHosts returns the upstream host names in name order.

func (Config) WithDefaults

func (c Config) WithDefaults() (Config, error)

WithDefaults returns cfg with defaults applied and checked, as New applies them.

type Exchange

type Exchange struct {
	Time     time.Time `json:"time"`
	From     string    `json:"from"`
	To       string    `json:"to"`
	Request  Message   `json:"request"`
	Response Message   `json:"response"`
}

Exchange is one request and its response, between two roles.

type Message

type Message struct {
	Method  string          `json:"method,omitempty"`
	URL     string          `json:"url,omitempty"`
	Status  int             `json:"status,omitempty"`
	Headers [][2]string     `json:"headers,omitempty"`
	Body    json.RawMessage `json:"body,omitempty"`
}

Message is an HTTP request or response. Headers keep their order; Body is JSON when the body is JSON, and a JSON string otherwise.

type Sandbox

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

Sandbox runs the local roles behind one TLS listener, routed by host.

func New

func New(cfg Config, logger *slog.Logger) (*Sandbox, error)

New builds a sandbox: it loads or creates the CA and role keys in cfg.StateDir and configures every local role. Call Serve to start it.

func (*Sandbox) CA

func (s *Sandbox) CA() *CA

CA returns the sandbox certificate authority.

func (*Sandbox) Client

func (s *Sandbox) Client(from string) *http.Client

Client returns the HTTP client a role (or "agent") sends requests with: it trusts the sandbox CA and reaches local roles by name.

func (*Sandbox) Config

func (s *Sandbox) Config() Config

Config returns the configuration with defaults applied.

func (*Sandbox) Handler

func (s *Sandbox) Handler() http.Handler

Handler routes requests to local roles by host, and serves the trace on every role host to clients other than web pages.

func (*Sandbox) Serve

func (s *Sandbox) Serve(ctx context.Context, ln net.Listener) error

Serve serves the roles on ln over TLS until ctx ends.

func (*Sandbox) SetAddr

func (s *Sandbox) SetAddr(addr string)

SetAddr sets the address that local role host names are dialed at; Serve sets it from its listener.

func (*Sandbox) Signer

func (s *Sandbox) Signer() *Signer

Signer returns the test agent and signing helper.

func (*Sandbox) TLSConfig

func (s *Sandbox) TLSConfig() *tls.Config

TLSConfig returns the listener's TLS configuration.

func (*Sandbox) Trace

func (s *Sandbox) Trace() *Trace

Trace returns the message trace.

type Signer

type Signer struct {

	// AnyInterface lets ListenAndServe listen on a non-loopback address.
	// Set it only inside a container whose port is published to the
	// host's loopback address: the helper signs what it is sent.
	AnyInterface bool

	// AnyTarget lets the helper sign and forward requests for any https
	// host. By default it signs only for the sandbox's own role hosts,
	// its upstreams and its remote role origins.
	AnyTarget bool

	// Hosts are extra Host header names the helper answers to, besides
	// localhost and the loopback addresses (and the address it listens
	// on). Requests naming any other host are refused, so a web page
	// cannot reach the helper by pointing a DNS name at 127.0.0.1.
	Hosts []string
	// contains filtered or unexported fields
}

Signer is the sandbox's test agent and its signing helper: HTTP clients that cannot sign send requests through it, and it signs them as the agent (RFC 9421 with a Signature-Key) and forwards them.

The agent has a durable key and a current (ephemeral) key. It holds an agent token from the agent provider bound to the current key, obtained by a two-key refresh: a naming JWT signed by the durable key delegating to the current key. An agent token a client obtains through the helper with jkt-jwt is bound to the same current key, so the client can pass it back in X-Sandbox-Token.

func (*Signer) AgentToken

func (sg *Signer) AgentToken(ctx context.Context) (string, error)

AgentToken returns a current agent token, refreshing it at the agent provider when it is missing or expires within five minutes.

func (*Signer) ListenAndServe

func (sg *Signer) ListenAndServe(ctx context.Context, addr string) error

ListenAndServe serves the helper on addr, which must be a loopback address unless AnyInterface is set: the helper signs anything it is sent.

func (*Signer) ServeHTTP

func (sg *Signer) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP serves the helper:

  • GET / describes the helper and the agent;
  • POST /sign returns the headers that sign a described request;
  • any request to /<https URL> is signed and forwarded to that URL, and the response is returned unchanged.

It answers only requests whose Host is a loopback name (or one of Hosts), and refuses requests a browser sends on behalf of a web page (those with Origin, or Sec-Fetch-Site naming another site). OPTIONS is answered without forwarding.

func (*Signer) Sign

func (sg *Signer) Sign(ctx context.Context, req *http.Request, present, token string) error

Sign signs req as the agent. present is one of the Present* modes; token is the token to present (required for person and auth). Any signature headers already on req are replaced.

type Trace

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

Trace keeps the most recent exchanges.

func NewTrace

func NewTrace(size int) *Trace

NewTrace returns a trace keeping at most size exchanges.

func (*Trace) Add

func (t *Trace) Add(e Exchange)

Add records an exchange, dropping the oldest when full.

func (*Trace) Clear

func (t *Trace) Clear()

Clear removes every exchange.

func (*Trace) Exchanges

func (t *Trace) Exchanges() []Exchange

Exchanges returns a copy of the recorded exchanges, oldest first.

Jump to

Keyboard shortcuts

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