Documentation
¶
Overview ¶
Package server implements the rift QUIC tunnel server.
Index ¶
- Constants
- Variables
- func DevTLSConfig(domain string) (*tls.Config, error)
- func ProdTLSConfig(domain, cacheDir string, log *zap.Logger) (*tls.Config, http.Handler)
- type AdminSecretIssuer
- func (a *AdminSecretIssuer) Match(r *http.Request) bool
- func (a *AdminSecretIssuer) ServeHTTP(w http.ResponseWriter, r *http.Request)
- func (a *AdminSecretIssuer) SetAuthRL(rl *rateLimiter)
- func (a *AdminSecretIssuer) SetRevokes(r *RevokeRegistry)
- func (a *AdminSecretIssuer) SetTrustProxyHeaders(trust bool)
- type Registry
- func (r *Registry) ByID(id uint32) *Tunnel
- func (r *Registry) BySubdomain(subdomain string) *Tunnel
- func (r *Registry) RegisterHTTP(subdomain string, conn *quic.Conn) (*Tunnel, error)
- func (r *Registry) RegisterTCP(conn *quic.Conn) (*Tunnel, error)
- func (r *Registry) RegisterWT(subdomain string, conn *quic.Conn, allowedOrigins, allowedWTProtocols []string) (*Tunnel, error)
- func (r *Registry) Unregister(id uint32)
- type RevokeRegistry
- type Server
- type TokenIssuer
- type Tunnel
- func (t *Tunnel) AddWTSession(s any) uint32
- func (t *Tunnel) GetWTSession(id uint32) any
- func (t *Tunnel) OpenDataStream(ctx context.Context) (*quic.Stream, error)
- func (t *Tunnel) OpenDataUniStream(ctx context.Context) (*quic.SendStream, error)
- func (t *Tunnel) RemoveWTSession(id uint32)
- func (t *Tunnel) TryAddVisitor() bool
- func (t *Tunnel) VisitorDone()
Constants ¶
const DefaultMaxVisitorsPerTunnel int64 = 50
DefaultMaxVisitorsPerTunnel is the fallback when ServerConfig leaves the limit at zero. Picked to bound per-tunnel goroutines without choking on typical short-lived HTTP storms.
Variables ¶
var ErrAuthFailed = errors.New("rift: authentication failed")
ErrAuthFailed indicates the client's auth frame was rejected: missing, malformed, or carrying an invalid/unknown token. Wrapped with %w by producer sites so callers can match it with errors.Is.
var ErrIPBlocked = errors.New("rift: source IP blocked by rate limiter")
ErrIPBlocked indicates the source IP was rejected by the per-IP rate limiter for excessive failed auth attempts.
var ErrPortsExhausted = errors.New("rift: no TCP ports available in configured range")
ErrPortsExhausted is returned by RegisterTCP when no free port can be found in the configured TCP port range.
var ErrSubdomainTaken = errors.New("rift: subdomain already in use")
ErrSubdomainTaken is returned by RegisterHTTP when the subdomain is already claimed by an active tunnel.
var ErrTokenExpired = errors.New("rift: token expired")
ErrTokenExpired indicates the connection was closed because the token's expiry time passed. Distinct from ErrAuthFailed so callers can react (e.g. refresh a token) without retry-looping on rejected credentials.
var ErrTokenRevoked = errors.New("rift: token revoked")
ErrTokenRevoked indicates the connection was closed because an operator revoked the token via DELETE /_admin/tokens/:name. Distinct from ErrTokenExpired and ErrAuthFailed so callers can surface the right remediation (provision a new token rather than refresh or retry).
var ErrTunnelGone = errors.New("rift: tunnel connection closed")
ErrTunnelGone is returned by Tunnel.OpenDataStream when the underlying rift-client QUIC connection has already closed.
Functions ¶
func DevTLSConfig ¶
DevTLSConfig generates a self-signed wildcard ECDSA P-256 certificate covering domain and *.domain. For development only.
func ProdTLSConfig ¶
ProdTLSConfig returns a *tls.Config and an HTTP handler for Let's Encrypt.
The TLS config uses TLS-ALPN-01 to issue per-subdomain certificates (wildcard certs require DNS-01 which needs a DNS provider integration). It accepts the apex domain and any single-level subdomain (e.g. foo.tunnel.example.com).
Mount the returned http.Handler on port 80 to enable HTTP-01 as a fallback challenge method — this is faster and more reliable than TLS-ALPN-01 alone.
cacheDir is created with mode 0700 if it does not exist. If the directory already exists with looser permissions, a warning is logged but the open proceeds so existing deployments are not broken. log may be nil.
Types ¶
type AdminSecretIssuer ¶
type AdminSecretIssuer struct {
// contains filtered or unexported fields
}
AdminSecretIssuer provisions tunnel tokens via a bearer-secret protected HTTP endpoint. It is the v1 TokenIssuer implementation.
Endpoint: POST /_admin/tokens?name=<name>[&ttl=24h] Header: Authorization: Bearer <secret> Response: {"name":"<name>","token":"rift_...","ttl":"24h0m0s"}
Access is restricted to loopback addresses only. Requests are rate-limited to 5 per minute per IP to prevent brute-force.
func NewAdminSecretIssuer ¶
func NewAdminSecretIssuer(secret string, ts store.TokenStore, defaultTTL time.Duration, log *zap.Logger) *AdminSecretIssuer
NewAdminSecretIssuer returns a new AdminSecretIssuer. defaultTTL is applied to every token unless overridden by the ?ttl= query param; 0 = no expiry.
func (*AdminSecretIssuer) Match ¶
func (a *AdminSecretIssuer) Match(r *http.Request) bool
Match returns true for issue (POST /_admin/tokens) and revoke (DELETE /_admin/tokens/:name) requests.
func (*AdminSecretIssuer) ServeHTTP ¶
func (a *AdminSecretIssuer) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP gates by IP allowlist + rate limit + bearer secret, then dispatches POST → handleIssue, DELETE → handleRevoke.
func (*AdminSecretIssuer) SetAuthRL ¶ added in v0.1.0
func (a *AdminSecretIssuer) SetAuthRL(rl *rateLimiter)
SetAuthRL installs the per-IP auth-failure tracker. Repeated bad bearers now escalate to the same block that QUIC auth failures trigger, so a loopback-spoofing attacker cannot brute-force forever.
func (*AdminSecretIssuer) SetRevokes ¶ added in v0.1.0
func (a *AdminSecretIssuer) SetRevokes(r *RevokeRegistry)
SetRevokes installs the registry used to fire revoke callbacks on DELETE /_admin/tokens/:name. Server.SetTokenIssuer calls this automatically for *AdminSecretIssuer values.
func (*AdminSecretIssuer) SetTrustProxyHeaders ¶ added in v0.1.0
func (a *AdminSecretIssuer) SetTrustProxyHeaders(trust bool)
SetTrustProxyHeaders configures whether X-Forwarded-For / X-Real-IP on the admin endpoint is tolerated. Defaults to false; rift terminates TLS itself in the documented deployment shape.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry is a thread-safe store of active tunnels.
func NewRegistry ¶
NewRegistry returns an empty Registry using the given TCP port range and per-tunnel visitor cap. portMin and portMax are inclusive (0 → 10000/65535 defaults). maxVisitors of 0 falls back to DefaultMaxVisitorsPerTunnel.
func (*Registry) BySubdomain ¶
BySubdomain returns the tunnel for subdomain, or nil.
func (*Registry) RegisterHTTP ¶
RegisterHTTP registers an HTTP tunnel. Returns ErrSubdomainTaken if the subdomain is already active, preventing tunnel hijacking by re-registration.
func (*Registry) RegisterTCP ¶
RegisterTCP registers a TCP tunnel with a unique random port in [10000, 65535]. Returns ErrPortsExhausted if no free port can be found.
func (*Registry) RegisterWT ¶ added in v0.1.0
func (r *Registry) RegisterWT(subdomain string, conn *quic.Conn, allowedOrigins, allowedWTProtocols []string) (*Tunnel, error)
RegisterWT registers a WebTransport tunnel under subdomain. Shares the subdomain namespace with HTTP tunnels — ErrSubdomainTaken on collision. allowedOrigins is the per-tunnel cross-origin allow-list; empty means reject all cross-origin requests, "*" matches any. allowedWTProtocols is the optional subprotocol allow-list echoed via WT-Protocol.
func (*Registry) Unregister ¶
Unregister removes the tunnel with id.
type RevokeRegistry ¶ added in v0.1.0
type RevokeRegistry struct {
// contains filtered or unexported fields
}
RevokeRegistry maps a token name to a set of side-effect callbacks fired when the token is revoked.
Callers (typically connection handlers) Register a callback after a successful authentication. Revoke fires every callback for the supplied name and clears the entry. The unregister func returned from Register is idempotent and removes that single registration.
func NewRevokeRegistry ¶ added in v0.1.0
func NewRevokeRegistry() *RevokeRegistry
func (*RevokeRegistry) Count ¶ added in v0.1.0
func (r *RevokeRegistry) Count(name string) int
Count returns the number of callbacks currently registered under name. Useful for tests.
func (*RevokeRegistry) Register ¶ added in v0.1.0
func (r *RevokeRegistry) Register(name string, fn func()) func()
Register stores fn under name and returns an unregister func. A blank name is rejected with a no-op unregister so a race that loses the name cannot register a callback under an unkillable empty bucket.
func (*RevokeRegistry) Revoke ¶ added in v0.1.0
func (r *RevokeRegistry) Revoke(name string) int
Revoke fires every callback registered under name and clears the entry. Returns the number of callbacks invoked. Callbacks are invoked outside the registry lock so they may safely touch the registry themselves.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server is the rift tunnel server.
func New ¶
func New(cfg config.ServerConfig, ts store.TokenStore, tlsCfg *tls.Config, acmeHandler http.Handler, log *zap.Logger) *Server
New constructs a Server. ts may be nil when cfg.Dev is true. acmeHandler may be nil; when set it is served on cfg.ACMEAddr for HTTP-01 challenges.
func (*Server) Addr ¶ added in v0.1.0
Addr returns the bound UDP listener address, or nil before Run binds it. Safe to call concurrently.
func (*Server) Revokes ¶ added in v0.1.0
func (s *Server) Revokes() *RevokeRegistry
Revokes returns the revocation registry. The admin handler uses this to fire revoke callbacks for connections currently using a deleted token.
func (*Server) SetTokenIssuer ¶ added in v0.1.0
func (s *Server) SetTokenIssuer(iss TokenIssuer)
SetTokenIssuer stores the issuer that will be served before tunnel routing once Run starts the HTTPS listener. Must be called before Run; later calls take effect on the next Run cycle.
When iss is an *AdminSecretIssuer, the server's revoke registry is wired into it so DELETE /_admin/tokens/:name fires active-connection callbacks.
type TokenIssuer ¶
type TokenIssuer interface {
// Match returns true if this issuer wants to handle the request.
// Called before any tunnel routing logic.
Match(r *http.Request) bool
// ServeHTTP handles the matched request.
ServeHTTP(w http.ResponseWriter, r *http.Request)
}
TokenIssuer handles HTTP routes for token provisioning.
Implementations:
- AdminSecretIssuer — bearer-secret protected endpoint (v1)
- OAuthIssuer — provider-based device flow (future)
To add a new issuer: implement this interface and wire it in server.go. No changes to httpHandler or tunnel routing are needed.
type Tunnel ¶
type Tunnel struct {
ID uint32
Subdomain string // set for HTTP tunnels
Port uint16 // set for TCP tunnels
Proto string // "http", "tcp", or "wt"
Conn *quic.Conn
AllowedOrigins []string // WT tunnels: permitted Origin header values; "*" = any
AllowedWTProtocols []string // WT tunnels: subprotocols echoed via WT-Protocol; empty = accept any
// contains filtered or unexported fields
}
Tunnel holds the metadata for a registered tunnel.
func (*Tunnel) AddWTSession ¶ added in v0.1.0
AddWTSession registers an active WebTransport session against the tunnel and returns the allocated session ID. The ID is non-zero, unique within the tunnel for the lifetime of this Server.Run, and carried in the datagram wire envelope so client-side routing is 1:1.
func (*Tunnel) GetWTSession ¶ added in v0.1.0
GetWTSession returns the session previously registered under id, or nil if no session exists with that ID.
func (*Tunnel) OpenDataStream ¶ added in v0.1.0
OpenDataStream opens a QUIC bidi stream on the rift-client side of the tunnel. Returns ErrTunnelGone if the underlying connection has already been closed — callers should treat this as a recoverable "tunnel unregistered" case rather than a generic stream-open failure.
func (*Tunnel) OpenDataUniStream ¶ added in v0.1.0
OpenDataUniStream opens a QUIC unidirectional stream from the server to the rift client. Used for WebTransport uni-streams from visitors: the visitor writes bytes the local service should receive, with no return path. Returns ErrTunnelGone when the connection is already closed.
func (*Tunnel) RemoveWTSession ¶ added in v0.1.0
RemoveWTSession removes a previously registered WT session by ID.
func (*Tunnel) TryAddVisitor ¶
TryAddVisitor increments the visitor count if below the per-tunnel cap. Returns false if the tunnel is at capacity — caller must NOT call VisitorDone.
func (*Tunnel) VisitorDone ¶
func (t *Tunnel) VisitorDone()
VisitorDone decrements the visitor count. Must be called exactly once per successful TryAddVisitor call, typically via defer.