Help us shape the future of Go! Take the 2026 Go Developer Survey

enroll

package
v0.5.1 Latest Latest
Warning

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

Go to latest
Published: Oct 29, 2026 License: Apache-2.0 Imports: 16 Imported by: 0

Documentation

Overview

Package enroll is the enrollment domain: provisioning tokens and the exchange of a token plus a certificate signing request for a workload identity and credential, and the renewal of that credential by an already identified workload (ADR-0016).

A provisioning token is the only credential an installer needs. It is scoped to a label set, expires, is revocable, and may enroll many workloads within its scope. The control plane stores its hash, which is what a presented token is looked up by, and a short listing hint. The agent's private key never leaves its host; there is no trust-on-first-use window (ADR-0004).

Signing goes through ca.Authority; persistence goes through Store. This package knows nothing about how either is implemented.

Index

Constants

View Source
const DefaultLeafTTL = 24 * time.Hour

DefaultLeafTTL is the lifetime of an issued workload credential when the operator sets none. Short lifetimes replace revocation machinery: a credential that is not renewed simply stops working (ADR-0016).

View Source
const DefaultRenewalGrace = 72 * time.Hour

DefaultRenewalGrace is how long after its expiry a workload credential may still renew when the operator sets no grace. It is sized to outlast a control-plane outage of a long weekend: a fleet whose credentials lapsed while nothing could renew them comes back by itself, with the same identities, once the control plane returns (ADR-0016 as amended).

View Source
const DefaultTokenTTL = 30 * 24 * time.Hour

DefaultTokenTTL is the lifetime of a token when the operator sets none.

View Source
const ReenrollRequired = "re-enrollment with a new provisioning token is required"

ReenrollRequired is the remedy a credential expired beyond the grace is told, and the phrase the agent logs for the same state.

View Source
const TokenPrefix = "iw_"

TokenPrefix marks a provisioning token in transit. A fixed prefix on an otherwise random secret lets secret scanners recognize a leaked token without any knowledge of the deployment that minted it.

Variables

View Source
var (
	ErrTokenMalformed = errors.New("enroll: provisioning token is malformed")
	ErrTokenUnknown   = errors.New("enroll: provisioning token is not recognized")
	ErrTokenExpired   = errors.New("enroll: provisioning token has expired")
	ErrTokenRevoked   = errors.New("enroll: provisioning token has been revoked")
	// ErrTokenLabelsInvalid refuses a token whose labels are outside the
	// label grammar (ADR-0022): one minted before the grammar was
	// admitted would hand every workload it enrolls a label no selector
	// can match. Mint a new token.
	ErrTokenLabelsInvalid = errors.New("enroll: provisioning token assigns labels outside the label grammar; mint a new token")
)

Errors a token check can produce. They are distinct so an operator sees exactly why an enrollment failed; none of them reveals whether a token with a different value would have succeeded.

View Source
var ErrCredentialExpired = errors.New("workload credential has expired")

ErrCredentialExpired is wrapped by every refusal of an expired workload credential: on any method other than renewal, and on renewal once the grace is spent. The message beyond the grace names re-enrollment, in the words the agent uses for the same state.

View Source
var ErrCredentialNotYetValid = errors.New("workload credential is not yet valid")

ErrCredentialNotYetValid is returned for a credential whose validity has not begun on the control plane's clock.

View Source
var ErrWorkloadUnknown = errors.New("enroll: workload is not registered")

ErrWorkloadUnknown is returned when a credential names a workload the registry does not hold.

Functions

func CredentialSerial added in v0.5.0

func CredentialSerial(cert *x509.Certificate) string

CredentialSerial is a credential's serial as the control plane records and names it: the certificate's serial number in lowercase hex. What issuance stores and what the sync path records a stream presenting are both written by it, so the two compare as text.

func HashEqual

func HashEqual(a, b []byte) bool

HashEqual compares two token hashes in constant time.

func HashToken

func HashToken(plaintext string) ([]byte, error)

HashToken validates the shape of a plaintext token and returns its SHA-256 digest. Shape validation happens before hashing so a malformed value is reported as malformed rather than as unknown. The digest is what is looked up; a store never sees the plaintext.

func ListingHint

func ListingHint(plaintext string) string

ListingHint is the part of a plaintext token kept for listing: the fixed prefix and the first characters of the random body.

func NewToken

func NewToken() (plaintext string, hash []byte, err error)

NewToken draws a fresh token. The plaintext is returned once, for display to the operator; the hash is what gets stored.

Types

type Credential

type Credential struct {
	CertificatePEM []byte
	BundlePEM      []byte
	Serial         string
	ExpiresAt      time.Time
	// LateBy is how long the credential a renewal replaced had been
	// expired; zero for an enrollment and for a renewal on time.
	LateBy time.Duration
}

Credential is what an enrollment or renewal hands back.

type Label

type Label struct {
	Key   string
	Value string
}

Label is one key/value pair assigned to a workload.

type LateRenewal added in v0.2.0

type LateRenewal struct {
	RenewedAt time.Time
	ExpiredAt time.Time
}

LateRenewal records a renewal made with a credential that had already expired: when it happened, and when the credential it replaced had expired. The difference is how late it was.

type Result

type Result struct {
	Workload   Workload
	Credential Credential
}

Result is the outcome of a successful enrollment.

type Service

type Service struct {
	Store     Store
	Authority ca.Authority
	// LeafTTL is the lifetime of issued credentials; DefaultLeafTTL if zero.
	LeafTTL time.Duration
	// RenewalGrace is how long after its expiry a credential may still
	// renew, and do nothing else; DefaultRenewalGrace if zero.
	RenewalGrace time.Duration
	// Now is the clock; time.Now if nil.
	Now func() time.Time
}

Service performs enrollment and renewal.

func (*Service) AdmitCredential added in v0.2.0

func (s *Service) AdmitCredential(notBefore, notAfter time.Time, renewal bool) error

AdmitCredential decides whether a verified workload credential valid from notBefore to notAfter may make a call now. A credential within its validity may make any call. An expired one may do exactly one thing, renew, and only within the grace: it still proves possession of a key the authority bound to the identity, and refusing it would turn a control-plane outage longer than the renewal window into per-host re-enrollment, a new identity, and an orphaned history. Everything else (the sync stream, flow reports) keeps requiring a valid credential, because what those carry is acted on as coming from the identity now. Beyond the grace the refusal names re-enrollment.

func (*Service) CheckNotRetired added in v0.5.0

func (s *Service) CheckNotRetired(ctx context.Context, id identity.WorkloadID) error

CheckNotRetired refuses an identity whose workload the operator has retired, with a *registry.RetiredError naming the instant. It is the rule every agent-service call is admitted by beside the credential's validity (ADR-0023); a workload the registry does not hold passes here and is refused by whatever needs its record, as before.

func (*Service) Enroll

func (s *Service) Enroll(ctx context.Context, tokenPlaintext string, csrPEM []byte, hostname string) (*Result, error)

Enroll exchanges a provisioning token and a CSR for an identity and a credential. The order is deliberate: the token is checked before the CSR is even parsed, so an unauthenticated caller learns nothing about what the signing boundary accepts.

func (*Service) Grace added in v0.2.0

func (s *Service) Grace() time.Duration

Grace returns the renewal grace in force.

func (*Service) ListTokens

func (s *Service) ListTokens(ctx context.Context) ([]Token, error)

ListTokens returns every provisioning token, newest first: hashes and metadata, never a plaintext.

func (*Service) MintToken

func (s *Service) MintToken(ctx context.Context, name string, labels []Label, ttl time.Duration) (plaintext string, tok Token, err error)

MintToken creates a token scoped to labels with the given lifetime (DefaultTokenTTL if zero) and returns the plaintext exactly once.

func (*Service) Renew

func (s *Service) Renew(ctx context.Context, id identity.WorkloadID, presentedFrom, presentedUntil time.Time, csrPEM []byte) (*Credential, error)

Renew reissues the credential for id, which the caller has already derived from a verified connection credential valid from presentedFrom to presentedUntil. The same identity is bound; the CSR contributes only its (possibly new) public key. A presented credential that has expired renews within the grace and is recorded on the workload as a late renewal; beyond it, for a workload no longer registered, or for one the operator retired (a *registry.RetiredError), nothing is issued. The retirement is checked before anything is signed, and again by the conditioned write, which keeps a credential signed meanwhile from being recorded or returned.

func (*Service) RevokeToken

func (s *Service) RevokeToken(ctx context.Context, id uuid.UUID) error

RevokeToken revokes a token as of now. Future enrollments with it are refused; workloads it already enrolled are untouched (ADR-0016).

type Standing added in v0.5.0

type Standing int

Standing is where a credential stands against its expiry and the renewal grace: what the control plane will still accept from it.

const (
	// StandingValid: not yet expired; it may make any call.
	StandingValid Standing = iota
	// StandingInGrace: expired within the grace; it may renew, and
	// renewing recovers the workload with the same identity.
	StandingInGrace
	// StandingPastGrace: expired beyond the grace; nothing accepts it,
	// and the workload returns only by enrolling again with a new
	// provisioning token.
	StandingPastGrace
)

Credential standings.

func StandingAt added in v0.5.0

func StandingAt(notAfter, now time.Time, grace time.Duration) Standing

StandingAt is the standing, at now, of a credential that expires at notAfter under the given renewal grace. It is the rule AdmitCredential applies to a presented credential, and the one the operator surface reports a workload's stored expiry by, so the two cannot disagree.

type Store

type Store interface {
	// CreateToken persists a minted token (hash only).
	CreateToken(ctx context.Context, t Token) error
	// FindTokenByHash returns ErrTokenUnknown when no token has that hash.
	FindTokenByHash(ctx context.Context, hash []byte) (*Token, error)
	// ListTokens returns every token, newest first.
	ListTokens(ctx context.Context) ([]Token, error)
	// RevokeToken marks a token revoked at instant now. It returns
	// ErrTokenUnknown for an unknown id and ErrTokenRevoked if it already was.
	RevokeToken(ctx context.Context, id uuid.UUID, now time.Time) error

	// CreateWorkload persists a newly enrolled workload with its labels and
	// records the use against its token, atomically.
	CreateWorkload(ctx context.Context, w Workload, now time.Time) error
	// GetWorkload returns ErrWorkloadUnknown when the id is not registered.
	GetWorkload(ctx context.Context, id identity.WorkloadID) (*Workload, error)
	// RecordRenewal stores the serial and expiry of a renewed credential,
	// and, when late is non-nil, records it as the workload's latest late
	// renewal in the same write. It returns ErrWorkloadUnknown when the id
	// is not registered and a *registry.RetiredError when the workload was
	// retired, however recently: the write is conditioned on it.
	RecordRenewal(ctx context.Context, id identity.WorkloadID, serial string, expiresAt, now time.Time, late *LateRenewal) error
}

Store is the persistence the enrollment domain needs. Every method is implemented with hand-written SQL in internal/store (ADR-0006).

type Token

type Token struct {
	ID         uuid.UUID
	Hash       []byte
	Prefix     *string
	Name       string
	Labels     []Label
	CreatedAt  time.Time
	ExpiresAt  time.Time
	RevokedAt  *time.Time
	UseCount   int64
	LastUsedAt *time.Time
}

Token is the stored form of a provisioning token. It never carries the plaintext: Hash is what is looked up, and Prefix, the listing hint, is what is listed. Prefix is nil for a token minted before hints were kept, since none can be recovered from a digest.

func (*Token) Check

func (t *Token) Check(now time.Time) error

Check reports whether the token may enroll a workload at instant now. Revocation is checked before expiry so that a revoked token reports as revoked even after it would have expired anyway; the operator's action is the more specific fact.

type Workload

type Workload struct {
	ID                  identity.WorkloadID
	TokenID             uuid.UUID
	Hostname            string
	Labels              []Label
	EnrolledAt          time.Time
	CredentialSerial    string
	CredentialExpiresAt time.Time
	LastRenewedAt       *time.Time
	// LateRenewal is the latest renewal made with an already-expired
	// credential, within the grace; nil when there has been none.
	LateRenewal *LateRenewal
	// RetiredAt is when the operator retired the workload; nil while it
	// is active. A retired identity renews nothing (ADR-0023).
	RetiredAt *time.Time
}

Workload is a registry row: the identity granted at enrollment plus the attributes that live beside it rather than in the credential.

Directories

Path Synopsis
Package enrolltest holds test doubles for the enrollment domain.
Package enrolltest holds test doubles for the enrollment domain.

Jump to

Keyboard shortcuts

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