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
- Variables
- func CredentialSerial(cert *x509.Certificate) string
- func HashEqual(a, b []byte) bool
- func HashToken(plaintext string) ([]byte, error)
- func ListingHint(plaintext string) string
- func NewToken() (plaintext string, hash []byte, err error)
- type Credential
- type Label
- type LateRenewal
- type Result
- type Service
- func (s *Service) AdmitCredential(notBefore, notAfter time.Time, renewal bool) error
- func (s *Service) CheckNotRetired(ctx context.Context, id identity.WorkloadID) error
- func (s *Service) Enroll(ctx context.Context, tokenPlaintext string, csrPEM []byte, hostname string) (*Result, error)
- func (s *Service) Grace() time.Duration
- func (s *Service) ListTokens(ctx context.Context) ([]Token, error)
- func (s *Service) MintToken(ctx context.Context, name string, labels []Label, ttl time.Duration) (plaintext string, tok Token, err error)
- func (s *Service) Renew(ctx context.Context, id identity.WorkloadID, ...) (*Credential, error)
- func (s *Service) RevokeToken(ctx context.Context, id uuid.UUID) error
- type Standing
- type Store
- type Token
- type Workload
Constants ¶
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).
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).
const DefaultTokenTTL = 30 * 24 * time.Hour
DefaultTokenTTL is the lifetime of a token when the operator sets none.
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.
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 ¶
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.
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.
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.
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 HashToken ¶
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 ¶
ListingHint is the part of a plaintext token kept for listing: the fixed prefix and the first characters of the random body.
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 LateRenewal ¶ added in v0.2.0
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
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
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) ListTokens ¶
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.
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
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.
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. |