sambox

package
v0.1.0-alpha.9 Latest Latest
Warning

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

Go to latest
Published: Sep 7, 2026 License: Apache-2.0 Imports: 37 Imported by: 0

Documentation

Index

Constants

View Source
const BundleVersion = "v1"

BundleVersion is the only bundle version this gateway understands.

Variables

View Source
var (
	// ErrNotAllowed is a policy denial: the destination is not permitted.
	ErrNotAllowed = errors.New("connection not allowed by ruleset")

	// ErrHostUnreachable means the destination could not be resolved or routed.
	ErrHostUnreachable = errors.New("host unreachable")

	// ErrConnectionRefused means the destination actively refused the flow.
	ErrConnectionRefused = errors.New("connection refused")
)

Errors a Dialer returns to select a refusal status. Anything else becomes a general failure, which is the right default: an unrecognised failure must not be reported to a sandbox as a precise diagnostic.

Functions

func GenerateLeafCert

func GenerateLeafCert(sni string, ca *CA, priv *rsa.PrivateKey) (*tls.Certificate, error)

func ListenSandboxSocket

func ListenSandboxSocket(path string) (net.Listener, error)

ListenSandboxSocket binds the sandbox-facing socket. A socket left behind by a crashed gateway is replaced; one a live gateway is still answering on is not.

The socket is created 0600. For a microVM that is exactly right, since firecracker connects to it as the same user. For a container whose sandbox runs as a different uid, the platform has to align ownership when it creates the sandbox — which is where per-agent sockets will be created once admission exists, and the only place that knows which uid to use.

func ServeMetrics

func ServeMetrics(ctx context.Context, addr string) (*http.Server, error)

ServeMetrics exposes this boundary's counters on addr until ctx ends.

It is off unless an operator asks for it. The boundary sits between a sandbox and the mesh, so any listener it opens is one more thing reachable from wherever addr is bound; an experiment wants the numbers, a production sandbox usually does not. Nothing here is authenticated, which is why the caller has to name the address rather than get one by default.

Types

type AgentBundle

type AgentBundle struct {
	Version string        `yaml:"version"`
	Agent   AgentIdentity `yaml:"agent"`
	Egress  BundleEgress  `yaml:"egress"`

	// Serves is the one mesh service this agent provides: itself, as an A2A
	// agent. The name is the platform's grant and the port is its contract
	// with the agent (like $PORT on a serverless runtime); the agent binds it
	// when ready, and everything else about serving -- capabilities, skills,
	// negotiation -- lives on the agent's own card, inside the A2A protocol.
	// Tools (mcp://) and models (inference://) are operator workloads declared
	// in a node's configuration, never agent ingress.
	Serves *BundleServes `yaml:"serves,omitempty"`
	// contains filtered or unexported fields
}

AgentBundle is the parsed form of that file.

func LoadAgentBundle

func LoadAgentBundle(path string) (*AgentBundle, error)

LoadAgentBundle reads and validates a bundle.

func (*AgentBundle) EgressPolicy

func (b *AgentBundle) EgressPolicy() *EgressPolicy

EgressPolicy returns the compiled allowlist.

type AgentDialer

type AgentDialer struct {
	// Router classifies destinations. Required.
	Router *Router

	// SidecarSocket is the Unix socket of the sam-node this sandbox is attached
	// to. sam-box is the node's only consumer here: an agent never reaches the
	// socket, only the curated surface built on top of it (entrypoint.go).
	SidecarSocket string

	// AgentID is the principal this boundary serves, asserted to the node on
	// every request (api.HeaderSamAgent). Empty means the sandbox is
	// unidentified, and mesh policy sees only the node it came through.
	AgentID string

	// DialContext opens external destinations. Nil uses a plain net.Dialer;
	// tests and future egress interception replace it.
	DialContext func(ctx context.Context, network, address string) (net.Conn, error)
}

AgentDialer opens whatever a Route calls for. It is the only place in the sandbox boundary that touches the network, which keeps the routing decision (route.go) and the protocol (connect.go) free of I/O.

func (*AgentDialer) DialDestination

func (d *AgentDialer) DialDestination(ctx context.Context, _ *Credentials, dst Destination) (net.Conn, error)

DialDestination implements Dialer.

type AgentIdentity

type AgentIdentity struct {
	// ID is the canonical mesh identifier, without the "agent:" prefix.
	ID string `yaml:"id"`

	// ExternalID is the platform's own identifier, kept verbatim: the
	// translation into ID is not always reversible, and an auditor needs the
	// value the platform actually issued. When credentials are verified, it is
	// also the subject the credential has to attest.
	ExternalID string `yaml:"external_id"`

	// Credential is the path to the credential the platform issued this
	// workload, such as a projected Kubernetes service-account token. It backs
	// the claim the rest of this file makes; see credential.go.
	Credential string `yaml:"credential"`
}

AgentIdentity names the principal the gateway asserts for this sandbox.

type BundleEgress

type BundleEgress struct {
	Allow []string `yaml:"allow"`
}

BundleEgress is the agent's allowance outside the mesh. Absent means none.

type BundleServes

type BundleServes struct {
	Name string `yaml:"name"`
	Port int    `yaml:"port"`
}

BundleServes contracts the agent's own a2a service: its mesh name and the sandbox port it must bind.

type CA

type CA struct {
	CertBytes   []byte
	Certificate *x509.Certificate
	PrivateKey  *rsa.PrivateKey
}

func GenerateEphemeralCA

func GenerateEphemeralCA() (*CA, error)

type CapsuleStream

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

CapsuleStream frames HTTP Datagrams on a reliable stream. It is exported so tests and non-tun2connect clients can speak the boundary's UDP form.

func NewCapsuleStream

func NewCapsuleStream(rw io.ReadWriter) *CapsuleStream

func (*CapsuleStream) ReadDatagram

func (s *CapsuleStream) ReadDatagram() ([]byte, error)

ReadDatagram returns the next UDP payload, skipping capsule types and datagram contexts it does not understand, as RFC 9297 requires.

func (*CapsuleStream) WriteDatagram

func (s *CapsuleStream) WriteDatagram(p []byte) error

WriteDatagram sends one UDP payload as a DATAGRAM capsule with context ID 0. Safe for concurrent writers.

type CertCache

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

func NewCertCache

func NewCertCache() (*CertCache, error)

func (*CertCache) GetCertificate

func (c *CertCache) GetCertificate(sni string, ca *CA) (*tls.Certificate, error)

type ConnectServer

type ConnectServer struct {
	// Dialer is required.
	Dialer Dialer

	// Authenticate, when set, makes Proxy-Authorization Basic credentials the
	// only acceptable greeting: a client that offers none is answered 407
	// rather than silently downgraded to an anonymous flow.
	Authenticate func(Credentials) error
}

ConnectServer serves the sandbox-facing side of the boundary.

func (*ConnectServer) Serve

func (s *ConnectServer) Serve(ctx context.Context, l net.Listener) error

Serve accepts connections until the listener fails or ctx is cancelled.

type Credentials

type Credentials struct {
	Username string
	Password string
}

Credentials are the Proxy-Authorization Basic username and password. When one sam-box multiplexes several agents over a single socket, this is how a flow says which agent it belongs to; the password is never logged.

type Destination

type Destination struct {
	Name   string
	Port   uint16
	IsName bool

	// Network is "tcp" for a CONNECT tunnel and "udp" for a connect-udp
	// session. Empty means "tcp", so the zero value stays the common case.
	Network string
}

Destination is a requested target exactly as it arrived on the sandbox boundary. Name is a domain when the client sent one, which is the case for every flow that came through tun2connect's virtual DNS; a literal address arrives when a client dialled an IP directly, and IsName says which.

func (Destination) Address

func (d Destination) Address() string

Address renders the destination as a dial target.

func (Destination) String

func (d Destination) String() string

type Dialer

type Dialer interface {
	DialDestination(ctx context.Context, creds *Credentials, dst Destination) (net.Conn, error)
}

Dialer decides whether a requested destination may be reached and opens it. It is the single policy enforcement point on the sandbox boundary.

type DialerFunc

type DialerFunc func(ctx context.Context, creds *Credentials, dst Destination) (net.Conn, error)

DialerFunc adapts a function to Dialer.

func (DialerFunc) DialDestination

func (f DialerFunc) DialDestination(ctx context.Context, creds *Credentials, dst Destination) (net.Conn, error)

type EgressPolicy

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

EgressPolicy is the allowlist for destinations outside the mesh. A nil policy allows nothing: a sandbox with no configured egress must reach nothing, so the zero value has to be the safe one.

func NewEgressPolicy

func NewEgressPolicy(allow []string) (*EgressPolicy, error)

NewEgressPolicy compiles an allowlist. Entries are either an exact host ("api.github.com") or a leading-label wildcard ("*.pypi.org"). Any other use of "*" is rejected rather than quietly treated as a literal, because an allowlist entry that silently means something other than what it looks like is how allowlists leak.

func (*EgressPolicy) Allows

func (p *EgressPolicy) Allows(host string) bool

Allows reports whether host may be reached. A wildcard covers subdomains only, never the parent, matching how every other wildcard in this system and in TLS behaves.

type Gateway

type Gateway struct {
	CA              *CA
	CertCache       *CertCache
	SecretStore     map[string]SecretConfig
	Transport       http.RoundTripper
	InterceptorsDir string
	// contains filtered or unexported fields
}

func NewGateway

func NewGateway(secretStore map[string]SecretConfig, transport http.RoundTripper, interceptorsDir string) (*Gateway, error)

func (*Gateway) Serve

func (g *Gateway) Serve(listener net.Listener) error

type IngressManager

type IngressManager struct {
	// ListenAddr is where this gateway's ingress listens, e.g.
	// "127.0.0.1:7080". It must be stable: the node's configuration names it
	// as the declared services' backend. Empty picks an ephemeral port,
	// which only tests can meaningfully consume via Addr.
	ListenAddr string

	// Serves is the bundle's contract: the agent's a2a service name and the
	// sandbox port it binds. An agent serves at most itself; tools and models
	// are operator workloads, not agent ingress.
	Serves BundleServes

	// AgentSocket is the sandbox's reverse channel: a Unix socket nano-init
	// listens on from inside the sandbox. It is how an isolated agent is
	// reached at all, because every sandbox has a network namespace of its own
	// and the gateway's 127.0.0.1 is therefore not the agent's. A pathname
	// socket crosses that boundary for the same reason the egress one does: it
	// is a filesystem object, and network namespaces do not apply to it.
	//
	// Empty means the agent shares this process's network namespace and can be
	// dialled directly, which is true of no sandboxed profile.
	AgentSocket string

	// AgentAddr resolves where the agent listens inside its sandbox. Setting it
	// overrides both of the above, which is how tests point the forwarder at a
	// server of their own.
	AgentAddr func(port int) string
	// contains filtered or unexported fields
}

IngressManager forwards what the mesh delivers to the ports the platform contracted the agent to serve.

func (*IngressManager) AgentTransport

func (m *IngressManager) AgentTransport() http.RoundTripper

AgentTransport reaches the sandbox over its reverse channel when there is one, and returns nil when the agent can be dialled directly.

The address the forwarder writes is still 127.0.0.1:<port>, because that is what the port means where it is going. Only the dialling changes: the port is carried in the handshake and the connection is made by the process inside the sandbox, which is the one that can.

func (*IngressManager) Close

func (m *IngressManager) Close()

Close stops serving, so a detached sandbox stops being routed to: the node's backend probe starts failing and withholds the name from discovery.

func (*IngressManager) Start

func (m *IngressManager) Start() (string, error)

Start validates that the sandbox can be reached, builds the routes the bundle contracts, and serves the ingress. It returns the bound address, which is what the node's configuration must name as the services' backend.

type Route

type Route struct {
	Kind RouteKind

	// ServiceURI is the canonical mesh identity for RouteMeshService, e.g.
	// "inference://openrouter". It is the same string policy is written
	// against, so a routing decision and an authorization decision can never
	// disagree about what was asked for.
	ServiceURI string

	Destination Destination
}

Route is the outcome of classifying a destination.

type RouteKind

type RouteKind int

RouteKind is the destination class a flow was resolved to.

const (
	// RouteMeshEntrypoint is the gateway's own agent-facing surface: the mesh
	// services an agent may consume, with the provider chosen by policy. It is
	// not the node's sidecar API, which an agent never reaches.
	RouteMeshEntrypoint RouteKind = iota

	// RouteMeshService is a service provided by some peer in the mesh. Which
	// peer is a discovery decision, deliberately not encoded in the name.
	RouteMeshService

	// RouteExternal is a destination outside the mesh, permitted by policy.
	RouteExternal
)

func (RouteKind) String

func (k RouteKind) String() string

type Router

type Router struct {
	// Egress is the allowlist for destinations outside the mesh. Nil denies
	// every external destination.
	Egress *EgressPolicy
}

Router classifies destinations arriving on the sandbox boundary.

func (*Router) Route

func (r *Router) Route(dst Destination) (Route, error)

Route classifies a destination, or returns ErrNotAllowed. Mesh names that do not name a service are denied rather than reported as unreachable: to a sandbox, "not permitted" and "does not exist" must look the same, or the boundary becomes a discovery oracle for the mesh's contents.

type SecretConfig

type SecretConfig struct {
	Kind       SecretKind `yaml:"kind" json:"kind"`
	HeaderName string     `yaml:"header_name" json:"header_name"`
	Value      string     `yaml:"value" json:"value"`
}

type SecretKind

type SecretKind string
const (
	SecretKindBearer       SecretKind = "bearer"
	SecretKindCustomHeader SecretKind = "customheader"
	SecretKindBasicAuth    SecretKind = "basicauth"
)

type WorkloadVerifier

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

WorkloadVerifier checks the credential a platform issued to a sandbox.

func NewWorkloadVerifier

func NewWorkloadVerifier(ctx context.Context, issuer, audience string) (*WorkloadVerifier, error)

NewWorkloadVerifier resolves the issuer, which requires reaching its discovery endpoint, so a misconfigured issuer fails at startup rather than on the first agent.

func (*WorkloadVerifier) Verify

func (v *WorkloadVerifier) Verify(ctx context.Context, bundle *AgentBundle) error

Verify reports whether the bundle's credential attests the identity the bundle claims.

The check that matters is the last one: the credential's subject must be the external identity the bundle declares. Verifying the signature alone would only prove the sandbox holds *a* valid credential, which every sandbox on the platform does, and any of them could then claim to be any other.

Jump to

Keyboard shortcuts

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