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 BundleIngress
- type CA
- type CertCache
- type Credentials
- type Destination
- type Dialer
- type DialerFunc
- type EgressPolicy
- type Gateway
- type IngressManager
- type Route
- type RouteKind
- type Router
- type SOCKS5Server
- 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 SOCKS5 reply code. 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"`
// Ingress is what this agent is permitted to serve, not what it is
// currently serving. The agent says when it is ready, and on which port,
// through the gateway's ingress endpoint; the platform decides only which
// names it may claim.
Ingress []BundleIngress `yaml:"ingress"`
// 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
// Ingress serves what the agent is permitted to advertise. Nil means the
// agent may serve nothing, which is the case for a sandbox that only calls
// out.
Ingress *IngressManager
// 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 (socks5.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 BundleIngress ¶
BundleIngress is one mesh service the agent may advertise.
type CA ¶
type CA struct {
CertBytes []byte
Certificate *x509.Certificate
PrivateKey *rsa.PrivateKey
}
func GenerateEphemeralCA ¶
type CertCache ¶
type CertCache struct {
// contains filtered or unexported fields
}
func NewCertCache ¶
func (*CertCache) GetCertificate ¶
type Credentials ¶
Credentials are the RFC 1929 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 ¶
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 tun2socks with virtual DNS; a literal address arrives when the sandbox 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 {
// SidecarSocket is the node's API socket, where registrations are made.
SidecarSocket string
// Allowed is what the bundle permits this agent to serve. Empty means the
// agent may serve nothing.
Allowed []BundleIngress
// 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 registers what an agent serves, and forwards what arrives.
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) Announce ¶
func (m *IngressManager) Announce(ctx context.Context, decl ingressDeclaration) error
Announce validates a declaration, starts routing to the agent, and registers the service with the node.
func (*IngressManager) Close ¶
func (m *IngressManager) Close(ctx context.Context)
Close stops serving and withdraws everything this gateway advertised, so a detached sandbox stops being routed to instead of lingering in discovery.
func (*IngressManager) Handler ¶
func (m *IngressManager) Handler() http.Handler
Handler serves the gateway's own ingress endpoint. It is never forwarded to the node.
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 SOCKS5Server ¶
type SOCKS5Server struct {
// Dialer is required.
Dialer Dialer
// Authenticate, when set, makes RFC 1929 username/password the only
// acceptable method: a client that offers no credentials is rejected rather
// than silently downgraded to an anonymous flow.
Authenticate func(Credentials) error
}
SOCKS5Server serves the sandbox-facing side of the boundary.
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.