Documentation
¶
Index ¶
- Constants
- Variables
- func ACLMiddleware(allowedUsers []string, localClient *LocalClient) func(http.Handler) http.Handler
- func AccessEventMiddleware(svc registry.Service, opts accesslog.Options, writer accesslog.Writer, ...) http.Handler
- func AccessLogMiddleware(serviceName string, identity *IdentityResolver, next http.Handler) http.Handler
- func AppAccessAt(reg *registry.Registry, svc registry.Service, login string, tags []string, ...) (bool, *time.Time)
- func CloseTSNetServer(name string, srv io.Closer)
- func MCPAuthMiddleware(allowedUsers []string, localClient *LocalClient, next http.Handler) http.Handler
- func MCPOriginMiddleware(next http.Handler) http.Handler
- func NewMCPControlPlaneHandler(cp *MCPControlPlane, localClient *LocalClient) http.Handler
- func NewProxyHandler(target string, identity *IdentityResolver) (http.Handler, error)
- func NewProxyHandlerWithOptions(target string, identity *IdentityResolver, options ProxyOptions) (http.Handler, error)
- func RequestLimitsAfterAccess(service registry.Service, report func(inspect.WarningView), ...) http.Handler
- func RequestLimitsMiddleware(service registry.Service, report func(inspect.WarningView), next http.Handler) http.Handler
- func ResourceBudgetMiddleware(next http.Handler) http.Handler
- func ValidateServiceForStartup(svc registry.Service) error
- type AppAccessDecision
- type AuthHandoff
- type AuthHandoffFunc
- type AuthKeyProvider
- type CleanupStaleNodesFunc
- type EnsureFunnelAttrFunc
- type EnsureTagsFunc
- type FileHandler
- type GatewayProxyOptions
- type IdentityResolver
- type LifecycleReconcileFunc
- type LocalClient
- type MCPCaller
- type MCPControlPlane
- type MCPEventPayload
- type PortalApp
- type PortalHandler
- type PortalRequests
- type ProxyOptions
- type Server
- func (s *Server) AccessLogWriter() accesslog.Writer
- func (s *Server) HoldsNodeState(name string) bool
- func (s *Server) MCPControlPlaneEnabled() bool
- func (s *Server) Run(ctx context.Context) error
- func (s *Server) SetAuthHandoffFunc(fn AuthHandoffFunc)
- func (s *Server) SetAuthKeyProvider(fn AuthKeyProvider)
- func (s *Server) SetAutoProvisionFunnel(enabled bool)
- func (s *Server) SetCleanupStaleNodesFn(fn CleanupStaleNodesFunc)
- func (s *Server) SetControlURLUnverified(unverified bool)
- func (s *Server) SetCredentialed(credentialed bool)
- func (s *Server) SetEnsureFunnelAttrFn(fn EnsureFunnelAttrFunc)
- func (s *Server) SetEnsureTagsFn(fn EnsureTagsFunc)
- func (s *Server) SetLifecycleReconcileFn(fn LifecycleReconcileFunc)
- func (s *Server) SetMCPControlPlane(cp *MCPControlPlane)
- func (s *Server) SetReadyFunc(fn func() error)
- func (s *Server) SetUserSuppliedAuthKey(userSupplied bool)
- func (s *Server) TryWithNodeStateLock(ctx context.Context, fn func() error) (bool, error)
- func (s *Server) WithNodeStateLock(fn func() error) error
- type ServiceNode
- type UpstreamDialer
Constants ¶
const ( MCPEventSnapshot = "snapshot" MCPEventUpdate = "update" MCPEventKeepalive = "keepalive" )
Event stream frame types. Every frame says what it is in two places: the SSE `event:` field and the payload's own "type". A client is never asked to infer whether it is holding a full state or a delta.
const ( // DefaultMCPEventsKeepalive is the heartbeat period when configuration // does not set one. // // The heartbeat is not optional. A tailnet long connection can go silent // without the socket reporting an error, and in that state "no events" and // "the connection is dead" produce byte-identical observations at the // client: nothing arrives either way. The heartbeat is what makes those two // distinguishable, so a client can reconnect on silence instead of showing // stale state forever. DefaultMCPEventsKeepalive = 20 * time.Second // MinMCPEventsKeepalive and MaxMCPEventsKeepalive bound the configured // value. Below the minimum the heartbeat becomes traffic rather than // liveness; above the maximum a dead connection outlives any UI that cares. MinMCPEventsKeepalive = 5 * time.Second MaxMCPEventsKeepalive = 5 * time.Minute )
const DefaultMCPNodeName = "tslink-mcp"
DefaultMCPNodeName is the hostname of the dedicated tsnet node that carries the remote MCP control plane.
The control plane deliberately does not travel as a registry service. A registry entry would appear in `tslink list`, would be a candidate for `tslink cleanup`, and could be given --funnel — which would publish the control plane to the public internet through an existing, already-audited code path. Keeping it off the registry means no code path in this program can make it publishable: the only listener it ever opens is ListenTLS on its own tsnet node.
const MCPControlPlanePath = "/mcp"
MCPControlPlanePath is the single HTTP endpoint the control plane serves.
const MCPEventsPath = "/events"
MCPEventsPath is the control plane's server-sent event stream.
It is a separate path from MCPControlPlanePath on purpose. The Streamable HTTP transport mounted there runs stateless, which makes the SDK answer GET with 405; sharing one path would mean deciding between the two by method, and the MCP transport — not this file — owns what GET means on /mcp.
const PortalRequestPath = "/access-requests"
PortalRequestPath accepts CSRF-protected requests from human tailnet members.
Variables ¶
var ErrMCPNoPrincipal = errors.New("mcp control plane is enabled but no principal is authorized; set mcp.allow (owner) or mcp.bindings (scoped) in config.json to login emails or tag:… entries, or disable the control plane")
ErrMCPNoPrincipal is returned when the control plane is enabled without an authorization principal. It is a refusal to start, not a warning: an endpoint that answers every tailnet peer is the failure this feature exists to avoid.
Functions ¶
func ACLMiddleware ¶
ACLMiddleware returns an HTTP middleware that enforces access control. allowedUsers is a list of email addresses and/or "tag:xxx" entries. If allowedUsers is empty, all requests are allowed. It uses the Tailscale LocalClient WhoIs to identify the caller. A refusal is answered like any other app refusal (writeAccessDenial); the legacy list has no gateway counterpart, so it names no app and links to no dashboard.
func AccessEventMiddleware ¶
func AccessEventMiddleware(svc registry.Service, opts accesslog.Options, writer accesslog.Writer, identity *IdentityResolver, now func() time.Time, next http.Handler) http.Handler
AccessEventMiddleware wraps authorization, limits and the app. Only actual request-body reads and response writes are counted, excluding HTTP framing.
func AccessLogMiddleware ¶
func AccessLogMiddleware(serviceName string, identity *IdentityResolver, next http.Handler) http.Handler
AccessLogMiddleware logs every HTTP request with structured fields via slog.
identity may be nil, and is nil for every node that cannot resolve a caller. The login and node fields are then empty, which is the same shape the record has for a caller whose identity could not be established -- one schema, so a reader parsing these lines never has to handle a missing key.
The two identity fields are the only ones sourced from outside the request itself, and they carry exactly what the local Tailscale daemon attested for the source address. Nothing here reads an X-Tailscale-* request header: those are attacker-controlled on the way in (the proxy strips them for that reason), and a log that repeated them would record a claim as a fact.
func AppAccessAt ¶
func AppAccessAt(reg *registry.Registry, svc registry.Service, login string, tags []string, now time.Time) (bool, *time.Time)
AppAccessAt exposes the listener decision from the same model as the portal.
func CloseTSNetServer ¶
CloseTSNetServer releases a tsnet node whose start may not have completed; every Close that can follow an incomplete Start or Up goes through it (service nodes, the MCP control-plane node, the CLI's client-secret validation node), while stopNodeLocked closes only nodes whose start completed. tsnet's Close dereferences state its start builds part-way through, so closing a node whose start failed early panics upstream (tailscale.com v1.102.4, tsnet.Server.close; reached for example when os.Executable fails because /proc/self/exe is unreadable). That panic must not take down the daemon, whose supervisor would restart it into the same failure, nor turn a CLI command's start error into a panic trace: it is logged, and the start error stays the reported outcome. The log line holds name and the panic value only, so name must never be a credential.
func MCPAuthMiddleware ¶
func MCPAuthMiddleware(allowedUsers []string, localClient *LocalClient, next http.Handler) http.Handler
MCPAuthMiddleware authorizes a caller against an explicit principal list, using the same Tailscale LocalClient WhoIs path the service proxy uses.
It is fail-closed in every direction. An empty principal list denies every caller rather than allowing every caller, which is the opposite of ACLMiddleware's per-service contract and is deliberate: a service with no --allow is a public page inside the tailnet, while a control plane with no principal is a mistake.
func MCPOriginMiddleware ¶
MCPOriginMiddleware implements the transport specification's Origin requirement. The 2026-07-28 Streamable HTTP transport states, under "Security Warning":
"Servers MUST validate the Origin header on all incoming connections to prevent DNS rebinding attacks. If the Origin header is present and invalid, servers MUST respond with HTTP 403 Forbidden. The HTTP response body MAY comprise a JSON-RPC error response that has no id."
A request with no Origin header is not a browser request and is allowed through; that is what every command-line MCP client sends. A request that does carry an Origin must have come from this endpoint's own https origin. Anything else — another host, a plain http origin, the opaque "null" origin a sandboxed document sends, or an unparseable value — is refused before the request reaches authorization or any tool.
func NewMCPControlPlaneHandler ¶
func NewMCPControlPlaneHandler(cp *MCPControlPlane, localClient *LocalClient) http.Handler
NewMCPControlPlaneHandler assembles the control plane's HTTP surface. It is the single assembly used in production and asserted by tests, so a test cannot pass against a chain the daemon does not actually serve.
Order is load-bearing. Origin validation is outermost after logging because the specification decides it before anything else and it must not be able to probe authorization. Authorization comes next, so an unauthorized request never reaches the routing mux and therefore never reaches a tool. Only the mounted MCP path exists on this node.
func NewProxyHandler ¶
func NewProxyHandler(target string, identity *IdentityResolver) (http.Handler, error)
NewProxyHandler returns the reverse proxy for one proxy service. identity may be nil, in which case no X-Tailscale-* identity is injected; it is shared with the node's access log so one caller costs one WhoIs.
func NewProxyHandlerWithOptions ¶
func NewProxyHandlerWithOptions(target string, identity *IdentityResolver, options ProxyOptions) (http.Handler, error)
NewProxyHandlerWithOptions configures per-service HTTP forwarding. The default constructor retains target Host rewriting for existing callers.
func RequestLimitsAfterAccess ¶ added in v0.3.0
func RequestLimitsAfterAccess(service registry.Service, report func(inspect.WarningView), access func(http.Handler) http.Handler, backend http.Handler) http.Handler
RequestLimitsAfterAccess is RequestLimitsMiddleware for a backend that sits behind an access check, so the caller is told about the limit only once the check lets them through. A caller who is refused gets the refusal, never the 413 and its app name and configured limit, whatever the size of the request: the gateway decides access before limits, and an app's own node must answer the same request the same way.
Everything that protects the connection is unchanged and still comes first: the connection and header budgets, full duplex, the read-idle budget and the bounded disposal of a body nobody read (which a refusal, like a 413, leaves unread). Only the answer to a declared oversize waits for the access check.
func RequestLimitsMiddleware ¶
func RequestLimitsMiddleware(service registry.Service, report func(inspect.WarningView), next http.Handler) http.Handler
RequestLimitsMiddleware streams the original body. Deadlines are renewed before each read, so backend backpressure is not counted as client idleness. No response writer wrapper is introduced: Flush, Hijack and 1xx remain intact. A request that declares a body over the limit is refused with 413 at once.
func ValidateServiceForStartup ¶
ValidateServiceForStartup validates a service definition before starting its node. It delegates to the canonical registry validator and adds startup context.
Types ¶
type AppAccessDecision ¶
type AppAccessDecision struct {
Allowed bool
IdentityEnforced bool
DirectoryVisible bool
ExpiresAt *time.Time
}
AppAccessDecision separates listener enforcement from directory disclosure. Raw TCP and Funnel have no TSLink per-identity gate; their cards are an owner/admin inventory and do not assert anything about visitor reachability.
type AuthHandoff ¶
type AuthHandoff struct {
Service string
AuthURL string
// State is pending, complete or cancelled. Empty means pending for older callers.
State string
}
AuthHandoff is emitted when a credential-free tsnet node needs the user to authorize it. AuthURL comes from local.Client.Status, the documented stable status API.
type AuthHandoffFunc ¶
type AuthHandoffFunc func(context.Context, AuthHandoff) error
AuthHandoffFunc receives credential-free interactive enrollment events.
type AuthKeyProvider ¶
AuthKeyProvider resolves auth material for a service immediately before its tsnet node starts.
type CleanupStaleNodesFunc ¶
type CleanupStaleNodesFunc func(ctx context.Context, targets []tailapi.CleanupTarget) (tailapi.CleanupResult, error)
CleanupStaleNodesFunc removes stale tailnet nodes for service targets before forced reauth.
type EnsureFunnelAttrFunc ¶
type EnsureFunnelAttrFunc func(ctx context.Context, request tailapi.FunnelPolicyRequest) (tailapi.PolicyMutationResult, error)
EnsureFunnelAttrFunc is the signature for the fused, lossless Funnel policy transaction. The request includes ordinary tags only when --manage-acl is enabled; the shared Funnel owner and nodeAttrs edits remain default-on.
type EnsureTagsFunc ¶
EnsureTagsFunc is the signature for ensuring ACL tags exist.
type FileHandler ¶
type FileHandler struct {
// contains filtered or unexported fields
}
FileHandler pins the directory object resolved at construction time. An initial symlink root is allowed and its then-current referent is pinned; replacing the path later cannot retarget requests. Close releases the one root descriptor held for this file node's lifetime.
func NewFileHandler ¶
func NewFileHandler(dir string) (*FileHandler, error)
NewFileHandler returns an HTTP handler that serves files from dir, confined to the directory subtree. Path opens are anchored at the OS root handle for dir, so a concurrent symlink swap cannot turn a prior path check into an open outside the served tree.
dir must be a non-empty absolute path. Empty or relative roots are rejected with an error rather than silently resolving to the process working directory (which for a Unix daemon is `/`). This is defense-in-depth: the canonical pre-side-effect validation already rejects empty/relative file paths before a node is constructed, but a direct caller of this constructor must not be able to serve cwd by mistake.
func NewSingleFileHandler ¶
func NewSingleFileHandler(dir, file string) (*FileHandler, error)
NewSingleFileHandler returns an HTTP handler that serves exactly one file out of dir: GET /<file> is the file, GET / redirects to it, and every other path is 404 with no directory listing and no access to any sibling.
It exists because a file share created from a regular file still has to anchor its opens somewhere, and the only anchor a file has is its parent directory. Serving that directory is what made `tslink share ./report.html` publish every sibling of report.html; the root here is still the directory, but the routing above is the whole reachable surface, so the sibling is not addressable rather than merely unlisted.
file must be a bare file name. The caller has already enforced that at the registry boundary; repeating the check here is defense-in-depth for a direct caller of this constructor, the same reason NewFileHandler re-checks its own root. It calls the registry's own predicate rather than restating it, because a restated copy is a copy that can drift -- and the first version of this function did drift, accepting and rejecting different sets of whitespace.
func (*FileHandler) Close ¶
func (h *FileHandler) Close() error
func (*FileHandler) ServeHTTP ¶
func (h *FileHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
type GatewayProxyOptions ¶ added in v0.2.0
type IdentityResolver ¶
type IdentityResolver struct {
// contains filtered or unexported fields
}
IdentityResolver answers "who is behind this request address" for every consumer on one service node, through a single shared cache.
One instance per node is the point. The proxy's X-Tailscale-* injection and the access log's login field both want the same answer for the same address in the same request, and before this type each consumer that wanted identity carried its own cache or none at all. Sharing one means adding a consumer costs no additional WhoIs calls.
Every method is fail-open and nil-safe: a nil resolver, an unavailable local client and a WhoIs error are all reported as "not known" rather than as an error, because the callers are observability and header enrichment, not authorization. Authorization lives in ACLMiddleware and MCPAuthMiddleware, which resolve identity themselves and fail closed. Routing those through this cache would let a revoked principal keep its access for the rest of a TTL, so they deliberately do not use it.
func NewIdentityResolver ¶
func NewIdentityResolver(provide func() (*LocalClient, error)) *IdentityResolver
NewIdentityResolver returns a resolver that acquires its local client on first use by calling provide. provide may be called more than once: an early failure is transient often enough that giving up forever would silently cost identity for the life of the process.
func NewStaticIdentityResolver ¶
func NewStaticIdentityResolver(client *LocalClient) *IdentityResolver
NewStaticIdentityResolver returns a resolver over an already-open local client. A nil client yields a nil resolver, so "no identity is available" has one representation rather than two.
func (*IdentityResolver) Principal ¶
func (r *IdentityResolver) Principal(ctx context.Context, remoteAddr string) (login, node string)
Principal returns the verified login and node name behind remoteAddr. Both are empty when identity cannot be established.
A tagged node reports an empty login rather than the pseudo-user Tailscale attributes tagged traffic to. The field exists to answer "which account did this", and a tag is a machine: filling it with a tag name would make a fleet of tagged nodes read as one very busy person. The node name still identifies the caller, so nothing is lost -- it is reported in the field that means "machine" instead of the one that means "account".
type LifecycleReconcileFunc ¶
LifecycleReconcileFunc persists wall-clock expiration and reconciles remote resources. The bool reports whether registry.json changed.
type LocalClient ¶
type MCPControlPlane ¶
type MCPControlPlane struct {
// NodeName is the tsnet hostname. Empty means DefaultMCPNodeName.
NodeName string
// Tags are advertised when the daemon runs with a stored credential.
Tags []string
// AllowedUsers is the authorization principal list. Empty is a start-time
// refusal, never "allow everyone".
AllowedUsers []string
Bindings []mcpscope.Binding
// Handler is the MCP transport handler mounted at MCPControlPlanePath.
Handler http.Handler
// EventsSnapshot builds the body of one event frame. A nil function
// disables the event stream entirely: MCPEventsPath is then not mounted,
// and the node answers it with the mux's own 404. That is the default for
// any caller that does not opt in, so the stream cannot appear by
// accident.
//
// This package deliberately does not know the payload's shape. The views a
// client needs are the CLI's own list and status results, which live in
// package cmd; building them here would mean a second copy of them, and
// the copy would be the one that rots.
EventsSnapshot func(context.Context) (any, error)
// EventsKeepalive is the heartbeat period. Zero means
// DefaultMCPEventsKeepalive; out-of-range values are clamped to
// [MinMCPEventsKeepalive, MaxMCPEventsKeepalive].
EventsKeepalive time.Duration
}
MCPControlPlane is the daemon-side configuration of the remote MCP transport. Handler carries the tool surface; this package never builds it, so the stdio and HTTP transports cannot drift into two tool registries.
func (*MCPControlPlane) Validate ¶
func (c *MCPControlPlane) Validate() error
Validate reports whether the control plane may start.
type MCPEventPayload ¶
type MCPEventPayload struct {
Type string `json:"type"`
// Instance is this daemon process's identity; see the contract above.
Instance string `json:"instance"`
// EventID mirrors the SSE id: field: monotonic across this instance,
// shared by every stream.
EventID uint64 `json:"event_id"`
// Sequence is the per-connection frame ordinal, starting at 1.
Sequence uint64 `json:"sequence"`
EmittedAt string `json:"emitted_at"`
Keepalive string `json:"keepalive_interval,omitempty"`
State any `json:"state,omitempty"`
StateError string `json:"state_error,omitempty"`
}
MCPEventPayload is the envelope every frame carries. State is the transport-independent body built by MCPControlPlane.EventsSnapshot; this package never inspects it.
Three numbering fields, three different scopes. They are not redundant, and reading one as the other is the mistake this comment exists to prevent:
- Instance names the daemon process. It is generated once at start and is the same on every frame of every stream until the daemon restarts. It is the only thing that lets a client tell "the server restarted, so EventID legitimately starts low again" from "I lost frames".
- EventID is monotonic over the whole instance and is what the SSE id: field carries. It is shared by every stream, so one stream sees gaps wherever another stream consumed a value: it orders frames and detects staleness across a reconnect, and it must not be used for per-stream gap detection.
- Sequence is this connection's own frame counter, starting at 1 for the snapshot. It is contiguous within one connection and resets on the next, which is what makes per-stream gap detection possible at all.
There is no Last-Event-ID replay. A reconnect is answered with a full snapshot, so a client that missed frames is made current by the first frame it receives rather than by asking for the ones it lost.
type PortalApp ¶
type PortalApp struct {
HostLabel string `json:"host_label,omitempty"`
OfflineSince *time.Time `json:"offline_since,omitempty"`
Name string `json:"name"`
URL string `json:"url"`
Health string `json:"health"`
ExpiresAt *time.Time `json:"expires_at,omitempty"`
ExpiryText string `json:"expiry"`
AccessNote string `json:"access_note,omitempty"`
}
func (PortalApp) BrowserLink ¶
BrowserLink keeps TCP connection addresses as text, matching tslink url.
type PortalHandler ¶
type PortalHandler struct {
RegistryPath string
LocalClient *LocalClient
CanonicalHost func() string
Apps func(*registry.Registry, time.Time) map[string]PortalApp
Now func() time.Time
Requests *PortalRequests
}
PortalHandler uses live, uncached WhoIs and the same decision as app nodes. Apps must return runtime observations only; authorization stays here.
func (PortalHandler) ServeHTTP ¶
func (h PortalHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
type PortalRequests ¶
type PortalRequests struct {
Notifier health.NotifierConfig
NotifierError string
Publish func()
Record func(registry.RequestEvent)
// contains filtered or unexported fields
}
type ProxyOptions ¶
type ProxyOptions struct {
Gateway *GatewayProxyOptions
// App names the app in the response a visitor gets when its backend fails
// on a direct app node. Through the gateway, Gateway.App does.
App string
Dialer UpstreamDialer
PreserveHost bool
// CanonicalHost reads the receiving node's trusted runtime name. It must
// never derive a name from a request, TLS SNI, or registry service name.
CanonicalHost func() string
}
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server manages multiple tsnet nodes, one per registered service.
func (*Server) AccessLogWriter ¶
AccessLogWriter returns the owning daemon's typed nonblocking writer. It is nil before Run has initialized storage or when local storage is unavailable.
func (*Server) HoldsNodeState ¶
HoldsNodeState reports whether this server runs a node for name, or is starting one, and therefore has a tsnet server holding that name's state directory open or about to write into it.
It exists for the lifecycle reconciliation, which decides whether the local state of an orphan service is safe to delete. That decision needs a fact only this process has, and the honest form of the fact is "I am holding it right now" rather than "nothing is holding it anywhere": a separate `tslink cleanup` process gets no answer from here and does not delete. The reconciler asks under the ownership ledger's lock, the lock a startup takes to reserve the directory, so a "no" stays true until its removal is done.
func (*Server) MCPControlPlaneEnabled ¶
MCPControlPlaneEnabled reports whether a control plane is configured.
func (*Server) SetAuthHandoffFunc ¶
func (s *Server) SetAuthHandoffFunc(fn AuthHandoffFunc)
SetAuthHandoffFunc sets the callback used to publish interactive login URLs.
func (*Server) SetAuthKeyProvider ¶
func (s *Server) SetAuthKeyProvider(fn AuthKeyProvider)
SetAuthKeyProvider sets the function used to resolve auth material per service. A provider is taken to mint its keys through the Tailscale API until SetUserSuppliedAuthKey says otherwise, so call that after this.
func (*Server) SetAutoProvisionFunnel ¶
SetAutoProvisionFunnel applies the daemon-wide kill switch. False wins over every service's default-on setting without mutating registry.json.
func (*Server) SetCleanupStaleNodesFn ¶
func (s *Server) SetCleanupStaleNodesFn(fn CleanupStaleNodesFunc)
SetCleanupStaleNodesFn sets the function used to remove stale tailnet nodes before forced reauth.
func (*Server) SetControlURLUnverified ¶
SetControlURLUnverified marks the server's control URL as a default the caller fell back to (config.json failed to load) rather than configuration. A service without its own control URL then has an unknown control server for identity comparison, so a control URL difference alone never resets its node.
func (*Server) SetCredentialed ¶
SetCredentialed records whether this process is running the stored- credential tier. The command layer resolves this independently from the per-service auth keys supplied by AuthKeyProvider.
func (*Server) SetEnsureFunnelAttrFn ¶
func (s *Server) SetEnsureFunnelAttrFn(fn EnsureFunnelAttrFunc)
SetEnsureFunnelAttrFn sets the function used for automatic Funnel policy provisioning.
func (*Server) SetEnsureTagsFn ¶
func (s *Server) SetEnsureTagsFn(fn EnsureTagsFunc)
SetEnsureTagsFn sets the function called to ensure ACL tags before starting nodes.
func (*Server) SetLifecycleReconcileFn ¶
func (s *Server) SetLifecycleReconcileFn(fn LifecycleReconcileFunc)
SetLifecycleReconcileFn configures the shared cleanup/expiration reconciler.
func (*Server) SetMCPControlPlane ¶
func (s *Server) SetMCPControlPlane(cp *MCPControlPlane)
SetMCPControlPlane installs the remote MCP control plane configuration. A nil argument leaves the daemon in its default state, where no control-plane listener is opened and no control-plane tsnet node is created.
func (*Server) SetReadyFunc ¶
SetReadyFunc sets a callback invoked after watcher setup and initial sync succeed.
func (*Server) SetUserSuppliedAuthKey ¶
SetUserSuppliedAuthKey records whether the auth-key provider returns a key the user supplied (the legacy authkey file) rather than one minted through the Tailscale API with a stored credential. Only a minted key is tied to Tailscale's control server; see mintedKeyControlURLError.
func (*Server) TryWithNodeStateLock ¶
TryWithNodeStateLock acquires the same startup gate without waiting. Optional local cleanup may skip a tick when startup is active or shutdown has begun.
func (*Server) WithNodeStateLock ¶
WithNodeStateLock excludes an in-progress sync from the final, local part of orphan cleanup. In particular, a node being started is not yet in nodes.
type ServiceNode ¶
type ServiceNode struct {
// contains filtered or unexported fields
}
ServiceNode represents a single tsnet node serving one service.
type UpstreamDialer ¶ added in v0.2.0
UpstreamDialer is shared by local gateway transports and the future tsnet adapter. Only the registry target is handed to the transport; request data never selects the destination. TLS remains verified by http.Transport.
Source Files
¶
- access_events.go
- access_pages.go
- access_tcp.go
- accesslog.go
- acl.go
- app_access.go
- credential_events.go
- fileserver.go
- gateway_enrollment.go
- gateway_gate.go
- gateway_node.go
- gateway_proxy.go
- gateway_remote.go
- guest.go
- guest_counter_status.go
- guest_lifecycle.go
- guest_pages.go
- health.go
- identity.go
- mcp_controlplane.go
- mcp_events.go
- node_identity.go
- people.go
- portal.go
- portal_node.go
- portal_requests.go
- proxy.go
- request_limits.go
- server.go
- tcpproxy.go