inventory

package
v0.41.0 Latest Latest
Warning

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

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

Documentation

Overview

Package inventory owns managed host facts, enrollment, patterns, and storage.

Index

Constants

View Source
const DefaultPageLimit = 100
View Source
const MaxPageLimit = 1000

Variables

View Source
var (
	ErrStorage  = errors.New("managed inventory storage unavailable")
	ErrConflict = errors.New("inventory conflict")
	ErrNotFound = errors.New("inventory record not found")
	ErrToken    = errors.New("invalid, expired, revoked, or used enrollment token")
	ErrRevision = errors.New("record changed; reload before retrying")
)

Functions

func DecodeYAML

func DecodeYAML(data []byte, dst any) error

Types

type AuditEvent

type AuditEvent struct {
	Sequence AuditSequence `yaml:"sequence"`
	At       time.Time     `yaml:"at"`
	Actor    string        `yaml:"actor"`
	Action   string        `yaml:"action"`
	Resource string        `yaml:"resource"`
}

func (AuditEvent) ControlEvent

func (e AuditEvent) ControlEvent() facts.HostAuditEvent

ControlEvent projects the audit event onto the control API.

type AuditSequence

type AuditSequence uint64

AuditSequence identifies one committed event independently of its timestamp. Sequences increase and are never reused, including after record deletion.

type EnrollmentToken

type EnrollmentToken struct {
	ID        string    `yaml:"id"`
	ExpiresAt time.Time `yaml:"expires-at"`
	UsedBy    string    `yaml:"used-by,omitempty"`
	Revoked   bool      `yaml:"revoked"`
}

EnrollmentToken's ID is also its secret. UsedBy is the reserved host ID after redemption; unused tokens retain an empty UsedBy value.

func (EnrollmentToken) ControlToken

func (t EnrollmentToken) ControlToken() facts.EnrollmentToken

ControlToken projects the token onto the control API.

type Host

type Host struct {
	Names    []string
	Labels   map[string]string
	Accounts []string
}

Host contains authorization attributes, separate from principal metadata.

type HostRecord

type HostRecord struct {
	ID        string    `yaml:"id"`
	Revision  uint64    `yaml:"revision"`
	Status    string    `yaml:"status"`
	Proposal  Proposal  `yaml:"host,omitempty"`
	CreatedAt time.Time `yaml:"created-at"`
	UpdatedAt time.Time `yaml:"updated-at"`
}

HostRecord includes the editable proposal and server-owned admission metadata. IDs are 64 lowercase hexadecimal characters; revisions increase on mutations.

func (HostRecord) ControlRecord

func (r HostRecord) ControlRecord() facts.HostRecord

ControlRecord projects the record onto the control API.

type Hosts

type Hosts interface {
	LookupHost(context.Context, string) (*ResolvedHost, string, error)
}

Hosts resolves machines independently of the user directory. LookupHost returns the host and the revision of the inventory used to resolve it from one coherent read. A missing host returns nil with a nonempty revision; lookup failures return an error.

type PrincipalMode

type PrincipalMode string

PrincipalMode selects how an allowed account@host tuple is represented in an issued SSH certificate.

const (
	// AccountNamePrincipals places the literal requested account name in the
	// certificate. It is compatible with ordinary OpenSSH CA configuration but
	// is not destination-bound.
	AccountNamePrincipals PrincipalMode = "account-name"

	// EpithetPrincipalV1 derives a destination-bound v1 principal from the
	// principal realm and requested account name.
	EpithetPrincipalV1 PrincipalMode = principal.SchemeV1
)

func (PrincipalMode) Effective

func (m PrincipalMode) Effective() PrincipalMode

Effective returns the concrete mode represented by m.

func (PrincipalMode) Validate

func (m PrincipalMode) Validate() error

Validate rejects unknown non-empty principal modes. Empty is the zero-value spelling of AccountNamePrincipals for compatibility with custom inventory implementations constructed in Go.

type Proposal

type Proposal struct {
	Names         []string          `yaml:"names"`
	Pattern       string            `yaml:"pattern,omitempty"`
	Labels        map[string]string `yaml:"labels"`
	Accounts      []string          `yaml:"accounts"`
	PrincipalMode PrincipalMode     `yaml:"principal-mode"`
	Realm         string            `yaml:"realm,omitempty"`
}

Proposal is the entire editable authorization record. Admission and ownership are server-owned metadata, never fields a host can approve for itself.

func ParseProposal

func ParseProposal(data []byte) (Proposal, error)

ParseProposal distinguishes an explicit null (ungrounded) account set from a missing field, and refuses trailing YAML documents and unknown fields.

func ProposalFromControl

func ProposalFromControl(p facts.Proposal) Proposal

ProposalFromControl adopts a control API proposal without validating it; callers validate through Validate or the store operation that consumes it.

func (Proposal) ControlProposal

func (p Proposal) ControlProposal() facts.Proposal

ControlProposal projects the proposal onto the control API. Slices and maps are shared, not cloned, so nil (unrestricted accounts) and empty (no accounts) survive.

func (Proposal) MarshalYAML

func (p Proposal) MarshalYAML() (any, error)

MarshalYAML preserves the distinction between unrestricted accounts (null) and an explicit empty account set ([]) in the editor.

func (*Proposal) UnmarshalYAML

func (p *Proposal) UnmarshalYAML(node *yaml.Node) error

UnmarshalYAML requires explicit accounts in editor proposals. Accidentally deleting accounts must not broaden access.

func (*Proposal) Validate

func (p *Proposal) Validate() error

type ResolvedHost

type ResolvedHost struct {
	Policy        Host
	PrincipalMode PrincipalMode
	Realm         principal.Realm
}

ResolvedHost carries both the authorization resource consumed by Writ and issuance metadata that must remain outside the pure policy model. Policy.Names always contains the host's names, independent of its principal mode or realm.

type Store

type Store interface {
	Hosts

	// Enroll validates a proposal and creates a pending record without a token.
	// A valid token atomically activates its reserved record and consumes the
	// preapproval. Expired, revoked, or used tokens return ErrToken.
	Enroll(ctx context.Context, proposal Proposal, token string) (*HostRecord, error)
	// AddPattern validates and admits an active pattern as one audited mutation.
	AddPattern(ctx context.Context, actor string, proposal Proposal) (*HostRecord, error)
	// Change accepts edit, approve, deny, or remove. The expected record revision
	// must be nonzero and current, otherwise ErrRevision leaves state unchanged.
	// Edit requires a proposal; approve and deny require a pending record. Moving
	// a record out of pending revokes unused preapproval. Remove returns nil and
	// deletes the record, its token, and its audit history.
	Change(ctx context.Context, actor, action, id string, revision uint64, proposal *Proposal) (*HostRecord, error)
	Get(ctx context.Context, id string) (*HostRecord, error)
	// List returns at most limit records after the exclusive full-ID cursor,
	// ordered by ID. pending restricts results before applying the limit.
	// Empty after starts at the beginning. Zero limit uses DefaultPageLimit;
	// negative limits or limits above MaxPageLimit are invalid. An empty page
	// ends enumeration. A short page means no further matching records exist in
	// that snapshot. Each page is coherent; separate pages may see changes.
	List(ctx context.Context, after string, limit int, pending bool) ([]HostRecord, error)
	// CreateToken reserves an empty pending record for single-use preapproval.
	// Lifetime must be positive and no more than 24 hours. The token's ID is the
	// reserved host ID; redemption preserves its creation time.
	CreateToken(ctx context.Context, actor string, lifetime time.Duration) (EnrollmentToken, error)
	// Tokens includes used, revoked, and expired tokens, ordered by ID, with
	// the same exclusive full-ID cursor and page limits as List.
	Tokens(ctx context.Context, after string, limit int) ([]EnrollmentToken, error)
	RevokeToken(ctx context.Context, actor, id string) error
	// Audit returns at most limit surviving events after the exclusive sequence
	// cursor, in sequence order. Zero after starts at the beginning. Page limits
	// are the same as List; an empty page means the reader is caught up.
	Audit(ctx context.Context, after AuditSequence, limit int) ([]AuditEvent, error)
	Close() error
}

Store owns managed inventory persistence and admission. Each mutation commits the record, token changes, audit event, and inventory revision atomically. Active exact names and patterns are unique; shared named realms require equal labels and account sets, and generated realms belong to one host. Pending and denied records never contribute lookup facts or reserve authorization names. Implementations enforce these rules in storage, without relying on coordination between service processes. Database handles and transaction steps stay private.

Reads return independent, coherent snapshots. LookupHost returns an opaque revision from the same snapshot as its facts; exact names take precedence, and multiple matching active patterns return ErrConflict. Storage failures return ErrStorage, never missing records. Contexts bound both reads and mutations. The caller owns the store's lifetime; HTTP handlers never close it.

Directories

Path Synopsis
Package sqlitestore implements managed inventory using embedded SQLite.
Package sqlitestore implements managed inventory using embedded SQLite.

Jump to

Keyboard shortcuts

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