Documentation
¶
Index ¶
- Constants
- Variables
- func GenerateLeafCert(sni string, ca *CA, priv *rsa.PrivateKey) (*tls.Certificate, error)
- func ListenSandboxSocket(path string) (net.Listener, error)
- func ServeMetrics(ctx context.Context, addr string) (*http.Server, error)
- type AgentBundle
- type AgentDialer
- type AgentIdentity
- type BundleEgress
- type BundleServes
- type CA
- type CapsuleStream
- type CertCache
- type ConnectServer
- type Credentials
- type Destination
- type Dialer
- type DialerFunc
- type EgressPolicy
- type Gateway
- type IngressManager
- type Route
- type RouteKind
- type Router
- type SecretConfig
- type SecretKind
- type WorkloadVerifier
Constants ¶
const BundleVersion = "v1"
BundleVersion is the only bundle version this gateway understands.
Variables ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 (*CertCache) GetCertificate ¶
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.
type Credentials ¶
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)
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 )
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.