Documentation
¶
Overview ¶
Package service holds Headmesh's use cases — enrollment, grants, API keys, bootstrap — on top of the store and the cell hub. HTTP handlers in package api are thin wrappers around it.
Index ¶
- Constants
- Variables
- type APIKey
- type BootstrapRequest
- type BootstrapResult
- type CompleteRequest
- type Device
- type EnrollRequest
- type EnrollResponse
- type Grant
- type GrantRequest
- type Hub
- type Principal
- type RecoveryHub
- type Service
- func (s *Service) Authenticate(ctx context.Context, rawKey string) (Principal, error)
- func (s *Service) AwaitCellsCurrent(ctx context.Context, hub RecoveryHub, log *slog.Logger) error
- func (s *Service) Bootstrap(ctx context.Context, req BootstrapRequest) (*BootstrapResult, error)
- func (s *Service) CompleteEnrollment(ctx context.Context, req CompleteRequest) (*Device, error)
- func (s *Service) CreateAPIKey(ctx context.Context, p Principal, name string, scopes []string, ...) (*APIKey, error)
- func (s *Service) CreateEnrollmentToken(ctx context.Context, p Principal, req TokenRequest) (*TokenResponse, error)
- func (s *Service) CreateGrant(ctx context.Context, p Principal, req GrantRequest) (*Grant, error)
- func (s *Service) Enroll(ctx context.Context, req EnrollRequest) (*EnrollResponse, error)
- func (s *Service) ListDevices(ctx context.Context, p Principal) ([]*Device, error)
- func (s *Service) ListGrants(ctx context.Context, p Principal) ([]*Grant, error)
- func (s *Service) RevokeGrant(ctx context.Context, p Principal, grantID string) (*Grant, error)
- func (s *Service) SweepExpiredGrants(ctx context.Context) (int, error)
- type TokenRequest
- type TokenResponse
Constants ¶
const ( PrefixEnrollmentToken = "hmj_" PrefixAPIKey = "hmk_" PrefixCompletion = "hmc_" )
Secret prefixes make leaked credentials recognisable to secret scanners and humans.
const ( ScopeAdmin = "admin" ScopeDevicesRead = "devices:read" ScopeEnrollWrite = "enroll:write" ScopeGrantsWrite = "grants:write" ScopeGrantsRead = "grants:read" ScopeAPIKeysWrite = "apikeys:write" )
Scopes.
Variables ¶
var ( ErrNotFound = errors.New("not found") ErrForbidden = errors.New("forbidden") ErrInvalid = errors.New("invalid request") ErrConflict = errors.New("conflict") ErrTokenRejected = errors.New("enrollment token is invalid, expired, revoked or used up") )
Errors returned to callers; the API maps them to HTTP status codes.
Functions ¶
This section is empty.
Types ¶
type APIKey ¶
type APIKey struct {
ID string
Key string // only set on creation
Prefix string
Name string
Scopes []string
ExpiresAt *time.Time
}
APIKey is returned once, at creation.
type BootstrapRequest ¶
type BootstrapRequest struct {
CellName, ServerURL, BaseDomain, CellMode string
OrgSlug, OrgName, Template string
Platform bool
ParentSlug string
HeadscaleUsers []string
}
BootstrapRequest creates a cell (if missing), an org in it and an admin API key. It is how an operator sets up a fresh installation from the command line before any user accounts exist.
type BootstrapResult ¶
BootstrapResult reports what was created. AdminKey is shown once.
type CompleteRequest ¶
CompleteRequest binds the registered Headscale node to the enrollment.
type Device ¶
type Device struct {
ID string
ShortID string
Name string
GivenName string
Kind string
Roles []string
IPv4, IPv6 string
Online bool
State string
}
Device is the API view of a device.
type EnrollRequest ¶
type EnrollRequest struct {
Token string
Hostname string
OS string
Arch string
ClientIP *netip.Addr
}
EnrollRequest is what a joining device sends. The token is its only credential.
type EnrollResponse ¶
type EnrollResponse struct {
EnrollmentID string
DeviceID string
LoginServer string
AuthKey string
Hostname string
Tags []string
ExpiresAt time.Time
CompletionSecret string
}
EnrollResponse is everything the client needs to run `tailscale up`.
type Grant ¶
type Grant struct {
ID string
GrantorOrg string
GranteeOrg string
DstDeviceID string
Ports []string
StartsAt time.Time
ExpiresAt time.Time
Revoked bool
ExternalRef string
// Enforced is true when the cell confirmed the resulting policy is live.
Enforced bool
}
Grant is the API view of a grant.
type GrantRequest ¶
type GrantRequest struct {
GranteeOrg string // short id of the org receiving access
Src store.SelectorJSON
DstDeviceID string
Ports []string
StartsAt *time.Time
ExpiresAt time.Time
Reason string
ExternalRef string // idempotency key, for example a rental id
}
GrantRequest gives devices of another org time-limited access to one device of the principal's org.
type Hub ¶
type Hub interface {
Refresh(ctx context.Context, cell string) error
RefreshAndWait(ctx context.Context, cell string) error
Do(ctx context.Context, cell string, op *cellv1.Op) (*cellv1.OpResult, error)
}
Hub is the part of the cell hub services use.
type RecoveryHub ¶
type RecoveryHub interface {
Connected(cell string) bool
RefreshAndWait(ctx context.Context, cell string) error
}
RecoveryHub is the part of the hub the startup gate uses.
type Service ¶
type Service struct {
Store *store.Store
Hub Hub
Now func() time.Time
// MaxGrantTTL caps grant lifetimes unless an org setting is lower (default 30 days).
MaxGrantTTL time.Duration
// PublicURL is the API's external base URL, used in join links (for example https://api.headmesh.dev).
PublicURL string
// InstallURL is where the install script is served (for example https://get.headmesh.dev).
InstallURL string
}
Service bundles the use cases.
func (*Service) Authenticate ¶
Authenticate resolves an API key to a principal. Lookup runs in the system scope because the key is what establishes the tenant scope; nothing else is read there.
func (*Service) AwaitCellsCurrent ¶
AwaitCellsCurrent blocks until every enforce-mode cell is connected and has applied a policy compiled now, so grants that expired while the control plane was down are closed before the API accepts new work (plan §13.4). Observe-mode cells never block. It returns an error if ctx ends first.
func (*Service) Bootstrap ¶
func (s *Service) Bootstrap(ctx context.Context, req BootstrapRequest) (*BootstrapResult, error)
Bootstrap runs in the system scope; it is only reachable from the server binary, never over HTTP.
func (*Service) CompleteEnrollment ¶
CompleteEnrollment checks the caller holds the completion secret, asks the cell which pre-auth key the node registered with, and only then binds the node to the device and gives it its MagicDNS name.
func (*Service) CreateAPIKey ¶
func (s *Service) CreateAPIKey(ctx context.Context, p Principal, name string, scopes []string, ttl time.Duration) (*APIKey, error)
CreateAPIKey issues a key for the principal's org. A key can never grant scopes its creator lacks.
func (*Service) CreateEnrollmentToken ¶
func (s *Service) CreateEnrollmentToken(ctx context.Context, p Principal, req TokenRequest) (*TokenResponse, error)
CreateEnrollmentToken mints an invite for devices joining the principal's org.
func (*Service) CreateGrant ¶
CreateGrant creates (or, with the same external ref, returns) a grant and pushes the new policy to the cell.
func (*Service) Enroll ¶
func (s *Service) Enroll(ctx context.Context, req EnrollRequest) (*EnrollResponse, error)
Enroll spends one use of an enrollment token, creates the device, makes sure the cell's live policy already declares the device's tags, and returns a single-use 5-minute pre-auth key carrying them.
func (*Service) ListDevices ¶
ListDevices returns the principal's org devices.
func (*Service) ListGrants ¶
ListGrants returns active grants the principal's org issued or received.
func (*Service) RevokeGrant ¶
RevokeGrant revokes a grant the principal's org issued and waits until the cell enforces it.
func (*Service) SweepExpiredGrants ¶
SweepExpiredGrants writes one grant.expired audit entry for every grant whose expiry has passed and has not been audited, recording how late the sweep noticed it. Access itself already ended on time — cells apply precomputed policy revisions at each expiry — so this is bookkeeping, not enforcement. It returns how many grants it audited.