api

package
v0.5.0 Latest Latest
Warning

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

Go to latest
Published: Oct 6, 2026 License: AGPL-3.0 Imports: 36 Imported by: 0

Documentation

Overview

Package api is the HTTP interface every Wegweiser client speaks.

The web interface and the weg command line are both clients of it, and neither reaches the database any other way (architecture invariant 1). What the API accepts is described in openapi.yaml, which is the source of truth: the models, the server interface and the client in gen/ are all generated from it, so an endpoint that is not written down there does not exist.

Index

Constants

View Source
const BootstrapName = "bootstrap"

BootstrapName is the name the first token carries, so that an operator listing tokens can see which one came from the installer rather than from a person.

View Source
const TokenPrefix = "weg_"

TokenPrefix marks a secret as one of ours, so that a token pasted into the wrong field is recognisable in a log and a secret scanner has something to match on.

Variables

View Source
var ErrTokenInvalid = errors.New("api: the token is not valid")

ErrTokenInvalid is what every failed authentication returns.

Unknown, revoked and expired are one error on purpose: three would let an attacker sort guesses into "wrong" and "used to be right" (D5).

Functions

func EnsureBootstrapToken

func EnsureBootstrapToken(
	ctx context.Context, s store.Store, a *apply.Applier, now time.Time,
) (secret string, err error)

EnsureBootstrapToken creates the first admin token if the database has none, and returns the secret exactly once.

An empty secret means tokens already existed and nothing was created. There is no way to ask for the secret again: what is stored is a hash, so a lost bootstrap token is replaced rather than recovered (docs/decisions/ D5).

func MintToken

func MintToken(name string, scopes []Scope, now time.Time) (secret string, tok store.Token, err error)

MintToken creates a new token and the record that stores it.

Types

type Cluster added in v0.5.0

type Cluster interface {
	// Init starts a cluster with this node as its only member. The API runs
	// it with no write in flight, which is what it asks of its caller.
	Init(ctx context.Context) error
	// Member is the identifier this node is a member by, and the address the
	// others reach it at.
	Member() (id, addr string)
	// Status is what this member knows of the cluster and of itself.
	Status(ctx context.Context) (cluster.Status, error)
	// Replicating reports whether this node is a member of a cluster yet.
	Replicating() bool
	// IsLeader reports whether this member leads.
	IsLeader() bool
	// Leader names the member leading, empty while none is.
	Leader() (id, addr string)
	// Remove takes a member out of the cluster. Only the leader can.
	Remove(id string) error
	// Left says why this member no longer takes part in the cluster, and is
	// nil while it does.
	Left() error
	// DialForward opens a stream to the member at addr that carries a
	// forwarded write.
	DialForward(ctx context.Context, addr string) (net.Conn, error)
}

Cluster is this node's member of a cluster, as far as the API acts on it. A *cluster.Node is one.

Of the cluster itself the API learns two things, whether this member leads and where the leader is, which is what forwarding a write takes (docs/decisions/d40-a-write-reaches-the-leader.md).

type Config

type Config struct {
	// Store is the source of truth. It is read directly and written only
	// through the applier.
	Store store.Store

	// Applier is the write path. Everything this server stores goes through
	// it, zones and the tokens, keys and settings beside them, so that there
	// is one place a change is decided (architecture invariant 4).
	Applier *apply.Applier

	// Snapshots is what the query path answers from, read for /healthz and
	// for the version a notification announces. A nil one means the API is
	// running without a query path, which is what a test does.
	Snapshots Snapshots

	// Secondaries is where each secondary stands, for the status endpoint.
	// Optional; without it there is nothing to report.
	Secondaries Secondaries

	// Notifier tells the secondaries a zone changed, once the snapshot saying
	// so has been published (docs/decisions/d27-notify.md). May be nil, and then
	// nobody is told and a secondary waits out its refresh timer.
	Notifier Notifier

	// Metrics is what /metrics exports. It is required rather than optional:
	// an endpoint that answers with an empty registry when the process forgot
	// to build one would report a server that answers no queries, which is
	// worse than no endpoint at all.
	Metrics *metrics.Metrics

	// Stream is what the live query stream subscribes to. Required for the
	// same reason as Metrics: an endpoint that opens a stream nothing feeds
	// looks like a server nobody is querying.
	Stream *stream.Hub

	// Cluster is nil when the configuration file has no cluster section, and
	// then this node is a single server and every cluster endpoint says so.
	Cluster Cluster

	// UI decides whether the embedded web interface is served alongside the
	// API. False serves only the API and answers everything else with a
	// problem document saying so (docs/decisions/ D16).
	UI bool

	// OnError is called for failures the client is not told the detail of, so
	// that they reach an operator instead of nobody. It may be nil.
	OnError func(error)

	// Now supplies the current time. Nil picks [time.Now]; tests set it.
	Now func() time.Time
}

Config is what a Server needs.

type Notifier added in v0.2.0

type Notifier interface {
	Notify(snap *dns.Snapshot, apex zone.Name)
}

Notifier tells the secondaries that a zone has a new version. A *dns.Notifier is one. Who they are is copied from the store beside everything else the query path holds, not by the API.

type Scope

type Scope string

Scope is a permission a token carries.

const (
	ScopeRead  Scope = "read"
	ScopeWrite Scope = "write"
	ScopeAdmin Scope = "admin"
)

The scopes a token may carry. They are ordered: admin allows everything write allows, and write allows everything read allows. Three levels rather than a permission per endpoint, because a permission model nobody can hold in their head is one that gets granted wholesale.

type Secondaries added in v0.3.0

type Secondaries interface {
	Standing() []dns.ProbeStanding
}

Secondaries reports where each secondary stands on each zone, as the probes have found it. A *dns.Prober is one.

It is optional. A server running without one knows nothing about any secondary and says so by having nothing to report, which is the same answer a server whose notify list is empty gives.

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server implements the generated API.

func New

func New(cfg Config) (*Server, http.Handler, error)

New returns a server and the handler that serves it.

func (*Server) CheckZone added in v0.2.0

CheckZone reports what is wrong with a zone as it stands.

func (*Server) Close

func (s *Server) Close() error

Close stops the background work the server does and writes out what it was holding.

It is safe to call more than once, and a server that is closed still serves: the handler does not depend on any of this, so a request arriving during shutdown is answered rather than dropped.

func (*Server) CreateRecord

CreateRecord adds a record to a zone.

What adding it caused comes back with it. An address record may generate a PTR, may find that the address already answers with another name, or may find no reverse zone to put anything in, and the last two are not errors. They are decisions for a person, so they travel as data (docs/decisions/ D3 and D6).

func (*Server) CreateSession

CreateSession exchanges a token for a browser session.

func (*Server) CreateTSIGKey added in v0.2.0

CreateTSIGKey stores a key and returns its secret.

func (*Server) CreateToken

CreateToken mints a token and shows its secret for the only time.

func (*Server) CreateZone

CreateZone brings a zone into existence.

func (*Server) DeleteRecord

DeleteRecord removes a record, and whatever was generated from it.

func (*Server) DeleteSession

DeleteSession ends the session, if the request came with one.

func (*Server) DeleteZone

DeleteZone removes a zone and everything in it.

func (*Server) DetachRecord

DetachRecord turns a generated record into an authored one.

This is the way out of "a generated record cannot be edited" (D4). A record that is already authored comes back unchanged rather than as an error, which makes calling this twice harmless.

func (*Server) ExportZone

ExportZone writes a zone out in the format every other authoritative server reads.

func (*Server) Forwarded added in v0.5.0

func (s *Server) Forwarded() http.Handler

Forwarded is what the cluster port serves writes forwarded to this member with. The member that forwarded one authenticated its caller and says who it was, and the cluster port is reached only by members holding the secret, which is what makes saying so enough (D40, D43). A write is never forwarded on from here: one that arrives after leadership moved is refused, and the member it came from answers with that.

func (*Server) GetCluster added in v0.5.0

GetCluster says who the members are, as this node knows them, and how far each has got (docs/decisions/d42-membership-lives-in-the-log.md). The others are asked as it answers, unless another member is the one asking (docs/decisions/d47-status-asks-every-member.md).

func (*Server) GetCommit

GetCommit returns one commit with everything it changed.

func (*Server) GetHealth

GetHealth reports whether the server is fit to answer queries.

func (*Server) GetMetrics

GetMetrics writes the current values in the Prometheus text exposition format.

func (*Server) GetRecord

GetRecord returns one record.

func (*Server) GetSecondaryConfig added in v0.2.0

GetSecondaryConfig writes the configuration the other end of a transfer needs, and says what about the arrangement will stop it working.

func (*Server) GetSecondaryStatus added in v0.3.0

GetSecondaryStatus says where each secondary stands on each zone.

A plain read: it carries no secret, only addresses that are already on the notify list, zone names this server is authoritative for, and serials it answers with.

func (*Server) GetSession

GetSession reports who the request is authenticated as.

func (*Server) GetSettings

GetSettings reports the defaults a zone that says nothing inherits.

func (*Server) GetZone

GetZone returns one zone.

func (*Server) ImportZone

ImportZone brings a whole zone in from a file in the format of RFC 1035 §5.

func (*Server) InitCluster added in v0.5.0

InitCluster starts a cluster with this node as its first member (docs/decisions/d44-starting-and-joining.md).

func (*Server) ListCommits

ListCommits returns one page of history, newest first.

func (*Server) ListRecords

ListRecords returns one page of a zone's records.

func (*Server) ListTSIGKeys added in v0.2.0

ListTSIGKeys returns every key, revoked ones included.

func (*Server) ListTokens

ListTokens returns every token, revoked and expired ones included.

func (*Server) ListZones

ListZones returns one page of zones.

func (*Server) MakeRecordCanonical added in v0.2.0

MakeRecordCanonical hands the reverse entry for an address to this record.

It is the answer to a conflict that D3 asked for and D33 put here: the check says which name an address reverses to, and this is how somebody changes it.

func (*Server) ReadTSIGKeySecret added in v0.2.0

ReadTSIGKeySecret returns the secret of one key.

func (*Server) ReconcileZone added in v0.2.0

ReconcileZone writes the reverse entries a zone's records imply and it does not have.

func (*Server) RemoveClusterMember added in v0.5.0

RemoveClusterMember takes a member out of the cluster. The request reaches the leader wherever it was sent (docs/decisions/d44-starting-and-joining.md).

func (*Server) ReplaceRRsets

ReplaceRRsets makes the named RRsets exactly what the client sent.

func (*Server) RevokeTSIGKey added in v0.2.0

RevokeTSIGKey withdraws a key and clears its secret.

func (*Server) RevokeToken

RevokeToken withdraws a token.

func (*Server) RollbackZone

RollbackZone restores a zone to the state it had at a serial.

func (*Server) StreamQueries

StreamQueries carries a live tail of the exchanges matching the filter, as Server-Sent Events.

func (*Server) UpdateRecord

UpdateRecord changes a record, keeping its identity.

func (*Server) UpdateSettings

UpdateSettings changes them. A field the request leaves out is left alone, so a client may send only what it means to change.

func (*Server) UpdateZone

UpdateZone changes a zone's own settings, leaving its records alone.

type Snapshots

type Snapshots interface {
	Snapshot() *dns.Snapshot
}

Snapshots is the data plane, as far as the API needs to know about it: what is being answered from. A *dns.Server is one.

The API only reads it. What is answered from next is published by whatever applied the change, on every node, and a handler is not that (docs/decisions/d41-what-follows-applying-a-batch.md).

Directories

Path Synopsis
Package gen provides primitives to interact with the openapi HTTP API.
Package gen provides primitives to interact with the openapi HTTP API.

Jump to

Keyboard shortcuts

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