Documentation
¶
Overview ¶
Package config locates and manages flue's on-disk configuration, including the loopback authentication token.
Index ¶
- func DeleteCloudflare() error
- func Dir() (string, error)
- func LoadOrCreateToken() (string, error)
- func MachineIDTag(secret, slug string) string
- func MintMachineID(hostname, secret string, r io.Reader) string
- func SaveCloudflare(c Cloudflare) error
- func SaveRelay(r Relay) error
- type Cloudflare
- type Relay
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func DeleteCloudflare ¶
func DeleteCloudflare() error
DeleteCloudflare removes the stored credential; absent is success.
func LoadOrCreateToken ¶
LoadOrCreateToken returns the daemon's loopback token, generating and persisting one at mode 0600 on first use.
func MachineIDTag ¶ added in v0.2.0
MachineIDTag is the self-certifying suffix of a machine id: the first 8 lowercase hex characters of HMAC-SHA256(secret, "flue-machine-id/"+slug).
The Worker recomputes exactly this before it lets any id pick a Durable Object (relay/src/index.ts, machineTag), which is what closes the open id namespace: an id whose tag does not verify is answered with the same 404 a malformed id gets, and no object wakes. The two implementations are pinned to each other by testdata/relay/machine-ids.json.
func MintMachineID ¶
MintMachineID makes the id a machine joins the relay under: `<hostname sanitized, truncated to 24>-<4 lowercase hex>-<8 hex tag>`.
The hostname part is for the human reading a machine list; the random hex is for the relay, where the id is the routing key and a collision means the second machine evicts the first from its hub; the tag is MachineIDTag over everything before it, and is what makes the id verifiable by the Worker without any registry. secret is the relay's DAEMON_SECRET — both minting commands (`flue relay setup`, `flue relay join`) hold it at mint time, and an id minted under any other string is unroutable. r is crypto/rand.Reader everywhere but tests, which inject fixed bytes to pin the format.
func SaveCloudflare ¶
func SaveCloudflare(c Cloudflare) error
SaveCloudflare writes cloudflare.json at 0600, replacing whatever was there — the same fresh-inode rename dance SaveRelay does, for the same concurrent-reader reasons.
func SaveRelay ¶
SaveRelay writes relay.json at 0600, replacing whatever was there.
It goes through the same write-temp-then-rename path as the token, and for the same two reasons: the new secret is never visible at a mode the old file happened to carry, and a crash mid-write cannot leave a truncated file behind where a complete one was. See writeSecretAtomically.
Types ¶
type Cloudflare ¶
type Cloudflare struct {
// Token is the API token setup verified. Never logged, never echoed into
// an error, never carried by any endpoint response — reads of this file
// hand it to the Cloudflare API and nowhere else.
Token string `json:"token"`
// AccountID and AccountName are the account the relay was deployed into,
// recorded so the UI can say where the relay lives and so an update needs
// no account picker.
AccountID string `json:"account_id"`
AccountName string `json:"account_name,omitempty"`
}
Cloudflare is the stored Cloudflare credential and the account it deploys into: what makes "update the relay" a click instead of a trip to the token page.
Stored by explicit product decision: the token is an account-level Workers credential and flue keeps it the way it keeps the relay secret — one 0600 file in the config directory, owner-readable and nothing else. The user is doing this on their own machine for their own account; the file is theirs to delete, and everything that reads it works (by asking again) when it is gone.
func LoadCloudflare ¶
func LoadCloudflare() (Cloudflare, bool, error)
LoadCloudflare reads cloudflare.json. ok is false when nothing is stored.
type Relay ¶
type Relay struct {
// URL is the bare wss:// address of the relay — host only, no path. The
// transport appends /daemon/<machine id> itself, so the file never holds a
// path that could disagree with the id beside it.
URL string `json:"url"`
Origin string `json:"origin"`
// Secret is the relay's shared DAEMON_SECRET: the deploy set it on the
// user's own Worker, and the daemon presents it on every dial. Never
// logged, and the reason this file is 0600. It does ride argv exactly
// once — `flue relay join --secret`, the hand-off line setup prints —
// and that is deliberate: argv and the shell history that keeps it are
// the accepted cost of moving the secret to the next machine
// (docs/RELAY.md says so where the line is printed).
Secret string `json:"secret,omitempty"`
// FleetSeed is the fleet key's Ed25519 seed, unpadded base64url —
// exactly the value `flue relay join --fleet` was given, minted by
// `flue relay setup` alongside the secret (spec/fleet-trust.md and
// internal/fleet own its meaning; this package only stores the string).
// It is the one credential in this file the Worker never holds: the
// secret's other copy lives on Cloudflare, the fleet key's copies live
// only in the fleet's relay.json files and the join line that carried
// it between them. Never logged, never sent anywhere, and the second
// reason this file is 0600 — a leaked seed signs device certs every
// machine in the fleet honours.
//
// It rides argv exactly once, beside the secret and for the same
// reason: `flue relay join --fleet` is the deliberate hand-off. The
// cost is the same shell history, and the consequence is larger, which
// is why docs/RELAY.md states it where it teaches the line rather than
// leaving it to be inferred from the secret's warning.
FleetSeed string `json:"fleet_seed,omitempty"`
// MachineCert is this machine's fleet machine certificate — the signed
// blob internal/fleet produces over {id, name, this daemon's Noise
// static public key, iat} — minted by whichever command wrote this file
// (`flue relay setup`, `flue relay join`, the Remote screen's deploy) and
// published to the relay's fleet directory by the daemon when it
// connects. It is a public artifact: it is signed, not secret, and every
// browser in the fleet reads it out of the directory to learn which Noise
// key to pin for this machine (spec/fleet-trust.md).
//
// It is stored rather than minted at startup because the directory is
// content-addressed: a cert re-minted on every boot would carry a fresh
// `iat`, hash to a fresh key, and spend one of the directory's 512
// entries per daemon restart. Stored, it is one entry forever.
//
// Empty means "this machine publishes no machine cert" — a relay.json
// written before this field, or one whose fleet key could not sign. The
// daemon says so once and carries on: a machine missing from the
// directory is one browsers cannot auto-discover, not one that stops
// working for the devices paired to it directly.
MachineCert []byte `json:"machine_cert,omitempty"`
// MachineID is the slot this machine holds on the relay: the <id> in the
// /daemon/<id> leg the daemon dials and the /client/<id> URLs browsers
// open. Minted by `flue relay setup` and `flue relay join` (MintMachineID),
// never typed by hand — which is why it is a slug and the name below is
// not.
MachineID string `json:"machine_id"`
// MachineName is the human label for this machine — free text, shown in
// machine lists; it rides the pairing link's query (`n=`, so the pairing
// browser can label the machine), never any path the relay routes on.
MachineName string `json:"machine_name"`
// Worker is the Cloudflare script name the relay was deployed under —
// `flue relay setup --worker`, or the default. `flue relay update` reads
// it back so a redeploy names the same script without re-deriving it from
// the URL. Empty in files written by `flue relay join` (join deploys
// nothing and does not know) and in files from before the field existed;
// readers must treat empty as "derive or ask", never as a name.
Worker string `json:"worker,omitempty"`
}
Relay is how this daemon reaches a deployed relay: where to dial, what authenticates the dial, and the origin the relay serves browsers on.
It is written by `flue relay setup` and read by `flue serve` at startup. This package deliberately does not decide whether a file is *complete* — see transport/relay.New, which is where that lives — because a file that exists says the user meant to have a relay, and telling them which field is missing is a better answer than pretending they never configured one.
func LoadRelay ¶
LoadRelay reads relay.json. ok is false when there is no relay configured at all, which is the ordinary state and not an error.
An unreadable or unparseable file *is* an error, and the distinction matters: "there is no relay" and "there is a relay and I cannot read it" lead to the same daemon — one serving loopback only — but only the second is something the operator has to be told about, and reporting it as absence would hide a typo in a file they just edited.
The file's mode is not policed the way the token's is. A loose mode on the token is evidence the secret leaked *and* a reason to mint a new one, which costs one browser session; the relay secret's other copy lives in a deployed Worker, so this process cannot regenerate it and refusing to read it would only take remote access away without making anything safer. SaveRelay writes 0600, and rotating a secret that has been exposed is the operator's move.