overlaykey

package
v0.14.30-dev Latest Latest
Warning

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

Go to latest
Published: Aug 29, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package overlaykey fetches a fresh single-use overlay (Tailscale/ Headscale) pre-auth key from cloudbox when this host's tailnet registration is no longer valid.

Why this exists: overlay credentials used to arrive only at pairing and during reattach, and reattach runs at daemon BOOT only. So anything that invalidated the registration — most commonly a cloudbox deploy, which resets the embedded Headscale — left the node unable to rejoin until a human restarted the daemon. The credentials were always re-issuable; the node simply had no way to ask.

The key we ask with is the cloudbox access token. It is signed with cloudbox's JWT secret and has nothing to do with the tailnet, so it keeps working across exactly the failures that break overlay auth. Every fetch is a fresh SINGLE-USE key: nothing replayable is stored on disk, and the alternative (a long-lived reusable key held forever) would be a standing credential to join the tailnet, which is what reaches the pod network.

Index

Constants

View Source
const DefaultInterval = 60 * time.Second

DefaultInterval is how often the refresher checks overlay health.

The failure it repairs is rare (a cloudbox deploy) but leaves the node unusable for cluster networking until fixed, so a minute of extra downtime is worse than a cheap poll. The check itself is one exec into a container when healthy.

Variables

View Source
var ErrOverlayDisabled = fmt.Errorf("overlaykey: overlay not enabled on cloudbox")

ErrOverlayDisabled means cloudbox is not running the overlay at all.

Distinct from a transient failure on purpose: a caller that cannot tell "the feature is off" from "the mint failed" will retry forever against something that is never going to answer.

View Source
var ErrThrottled = fmt.Errorf("overlaykey: throttled by cloudbox")

ErrThrottled means cloudbox refused because we asked too recently.

Functions

This section is empty.

Types

type Client

type Client struct {
	// BaseURL is the cloudbox base (e.g. https://ai.dhnt.io).
	BaseURL string
	// AccessToken is this host's cloudbox bearer token.
	AccessToken string
	// AgentName is this host's registered name.
	AgentName string
	// HTTP is optional; a 30s-timeout client is used when nil.
	HTTP *http.Client
}

Client fetches overlay credentials for one paired host.

func (*Client) Fetch

func (c *Client) Fetch(ctx context.Context) (*Credentials, error)

Fetch requests a fresh pre-auth key.

type Credentials

type Credentials struct {
	LoginServer      string `json:"overlay_login_server"`
	AuthKey          string `json:"overlay_auth_key"`
	PodCIDR          string `json:"overlay_pod_cidr"`
	ExpiresInSeconds int    `json:"expires_in_seconds"`
}

Credentials is one issuance from cloudbox.

type ExecFunc

type ExecFunc func(ctx context.Context, args ...string) ([]byte, error)

ExecFunc runs a command inside the runtime container.

type Refresher

type Refresher struct {
	Client   *Client
	Exec     ExecFunc
	PodCIDR  string
	Interval time.Duration
	Log      *slog.Logger

	// IgnoreCredentialPodCIDR, when true, makes Heal ignore a fetched
	// Credentials.PodCIDR entirely and advertise only r.PodCIDR (which may
	// be empty, in which case Heal advertises nothing).
	//
	// Set this for a peer-hosted-plane worker (PeerFlannel). Under that
	// mode the pod CIDR comes from flannel reading Node.spec.podCIDR, which
	// the PEER's k3s controller-manager allocates
	// (docs/adr-peer-dks-pod-network.md, Option A). The fetched
	// Credentials.PodCIDR is cloudbox's cloudbox-cluster carve and has no
	// authority over the peer's pod network — honoring it on every Heal
	// would hand the container a CIDR to advertise routes for and
	// reintroduce exactly the competing allocator that ADR removed. Left
	// false (the default), Heal keeps its original behavior byte-for-byte:
	// a fetched Credentials.PodCIDR wins, falling back to r.PodCIDR.
	IgnoreCredentialPodCIDR bool

	// ControlURL, when set, overrides the login server returned by
	// cloudbox. It exists because the public overlay URL is fronted by
	// Cloudflare, which strips the Upgrade header on the ts2021 control
	// POST — so the client must instead reach Headscale over the
	// frp-tunnelled loopback endpoint (http://127.0.0.1:<port>/overlay/
	// headscale). Heal execs inside the runtime container, where that
	// visitor is bound, so the loopback URL resolves.
	ControlURL string
}

Refresher keeps this host registered on the overlay.

It exists because overlay credentials used to be a boot-time-only affair: pairing and reattach handed them over, and reattach runs when the daemon starts. Anything that invalidated the tailnet registration afterwards — a cloudbox deploy resetting Headscale being the common case — left the node off the pod network until a human restarted the daemon. Nothing was broken that could not be re-issued; the node simply never asked.

func (*Refresher) Heal

func (r *Refresher) Heal(ctx context.Context) error

Heal fetches a fresh single-use key and re-registers this node.

func (*Refresher) Healthy

func (r *Refresher) Healthy(ctx context.Context) (bool, error)

Healthy reports whether tailscaled is logged in AND actually in the network map.

BackendState=="Running" alone is NOT sufficient: after a cloudbox deploy resets Headscale's node DB, tailscaled keeps reporting "Running" from cached state while every /machine/map poll gets a 404 "node not found" — the node is stranded (logged out at the control plane) yet self-reports Running. A Running-only check then leaves it stranded forever: the refresher never heals what looks healthy, so a deploy takes the whole fleet's overlay down until each node is restarted by hand. Self.InNetworkMap is the honest signal — false in exactly that stranded state — so requiring BOTH is what makes the refresher actually re-register after a deploy.

A failure to ASK is deliberately not "unhealthy": the container may be gone, tailscale may not be installed, or the overlay may simply be off on this host. Treating "could not determine" as "broken" would have the refresher mint keys and run `tailscale up` against hosts that never wanted an overlay — acting on the absence of evidence.

func (*Refresher) Run

func (r *Refresher) Run(ctx context.Context) error

Run polls until ctx is done, healing when the overlay is not Running.

Jump to

Keyboard shortcuts

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