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 ¶
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 ¶
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.
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.
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 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) Healthy ¶
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.