Documentation
¶
Overview ¶
Package agents is the server side of myownpki-agent (docs/PHASE3.md): join tokens, enrollment with proof of control (ADR-18), the agents' 7-day certificates from the agent CA, their names, disable and revoke.
Everything an agent sends is attacker input. Before it is admitted, an agent can only create a pending enrollment – limited in number, expiring, never an agent – and nothing it reports is ever used as a path or a command here.
Index ¶
- Constants
- Variables
- func CheckDNSName(h string) error
- func CheckHostname(s string) (string, error)
- func CheckPattern(s string) (string, error)
- func Matches(pattern, name string) bool
- func MatchesAny(patterns []string, name string) bool
- type Agent
- type Caller
- type Checker
- type Deployment
- type DeploymentReport
- type DesiredDeployment
- type DesiredState
- type EnrollInput
- type EnrollOutput
- type Enrollment
- type InstallFacts
- type InstallSource
- type JoinToken
- type Name
- type NewToken
- type Package
- type PassHost
- type ReportInput
- type Service
- func (s *Service) AddName(ctx context.Context, who keys.Who, id, pattern string) error
- func (s *Service) Approve(ctx context.Context, who keys.Who, enrollmentID string) error
- func (s *Service) ClientCAs(ctx context.Context, now time.Time) (*x509.CertPool, int, error)
- func (s *Service) CloseWatches()
- func (s *Service) CreateEnrollmentPass(ctx context.Context, who keys.Who, label string, patterns []string, ...) (string, *JoinToken, error)
- func (s *Service) CreateJoinToken(ctx context.Context, who keys.Who, label string, patterns []string, ...) (string, *JoinToken, error)
- func (s *Service) CreateToken(ctx context.Context, who keys.Who, in NewToken) (string, *JoinToken, error)
- func (s *Service) DeleteJoinToken(ctx context.Context, who keys.Who, id string) error
- func (s *Service) Deny(ctx context.Context, who keys.Who, enrollmentID string) error
- func (s *Service) Enroll(ctx context.Context, in EnrollInput) (*EnrollOutput, error)
- func (s *Service) EnrollStatus(ctx context.Context, in StatusInput) (*Status, error)
- func (s *Service) GetAgent(ctx context.Context, who keys.Who, id string) (*Agent, error)
- func (s *Service) Handler() http.Handler
- func (s *Service) Identify(ctx context.Context, leaf *x509.Certificate, sourceIP string) (*Caller, error)
- func (s *Service) InstallFacts(ctx context.Context, who keys.Who) (*InstallFacts, error)
- func (s *Service) ListAgents(ctx context.Context, who keys.Who) ([]Agent, error)
- func (s *Service) ListEnrollments(ctx context.Context, who keys.Who, openOnly bool) ([]Enrollment, error)
- func (s *Service) ListJoinTokens(ctx context.Context, who keys.Who) ([]JoinToken, error)
- func (s *Service) RegisterACMEAccount(ctx context.Context, c *Caller, thumbprint string) error
- func (s *Service) RemoveName(ctx context.Context, who keys.Who, id, pattern string) error
- func (s *Service) RenewCertificate(ctx context.Context, c *Caller, csrDER []byte) (der []byte, chain [][]byte, err error)
- func (s *Service) Report(ctx context.Context, caller *Caller, in ReportInput) error
- func (s *Service) RequestRenewal(ctx context.Context, who keys.Who, id, name string, ...) error
- func (s *Service) Revoke(ctx context.Context, who keys.Who, id string) error
- func (s *Service) RevokeJoinToken(ctx context.Context, who keys.Who, id string) error
- func (s *Service) SetDisabled(ctx context.Context, who keys.Who, id string, disabled bool) error
- func (s *Service) SetPredecessorPolicy(ctx context.Context, who keys.Who, id, deployment string, ...) error
- func (s *Service) WatchDesiredState(ctx context.Context, c *Caller, known int64) (*DesiredState, error)
- func (s *Service) WithdrawRenewal(ctx context.Context, who keys.Who, id, name string) error
- type Status
- type StatusInput
- type TokenState
- type Trust
Constants ¶
const ( ActionEnroll = "agent.enroll" // an enrollment arrived (anonymous, with the source address) ActionAdmit = "agent.admit" // admitted by the mode or an admin ActionDeny = "agent.deny" // refused by an admin ActionDisable = "agent.disable" // and agent.enable ActionEnable = "agent.enable" // ActionRevoke = "agent.revoke" // its certificates on the CRL, cut off ActionNames = "agent.names" // a name added or removed ActionTokenCreate = "agent.token.create" // a join token or pass made (shown once) ActionTokenDelete = "agent.token.delete" // an unused join token withdrawn ActionTokenRevoke = "agent.token.revoke" // #nosec G101 -- an action name (ends a join token or pass at once), not a credential )
Audit actions.
const ( // CertificateDays is the validity of an agent certificate; the agent // renews it at 2/3. CertificateDays = 7 // MaxOpenWithoutToken: pending enrollments without a valid join token // per tenant. An enrollment with a valid token is never refused by it. MaxOpenWithoutToken = 100 // ShortExpiry: pending without a valid token and without a passed check. ShortExpiry = 24 * time.Hour // LongExpiry: every other pending enrollment. LongExpiry = 7 * 24 * time.Hour // MaxTokenTTL and DefaultTokenTTL bound a join token's life. MaxTokenTTL = 30 * 24 * time.Hour DefaultTokenTTL = 24 * time.Hour )
Limits (docs/PHASE3.md §6.1).
const ( StatusActive = "active" StatusDisabled = "disabled" StatusRevoked = "revoked" )
Agent states.
const ( StateWaitingForCheck = "waiting_for_check" StatePending = "pending_approval" StateAdmitted = "admitted" StateDenied = "denied" StateExpired = "expired" StateWaitingForUnseal = "waiting_for_unseal" )
Enrollment states (agent_enrollments.state); StateWaitingForUnseal is only reported, never stored: the enrollment is admitted, the certificate follows the unseal.
const ( MethodStandalone = "standalone" MethodWebroot = "webroot" )
Check methods: how the agent answers the http-01 check.
const ( // MaxDeployments bounds the configured inventory per agent. MaxDeployments = 100 // MaxReportDeployments also admits one removed result to drain per report. MaxReportDeployments = MaxDeployments + 1 )
const ( TokenPrefix = "mjt_" PassPrefix = "mjp_" )
Prefixes of the secrets, so secret scanners and people can tell one when it ends up somewhere it shouldn't, and tell a pass from a token.
const ( KindToken = "token" KindPass = "pass" )
Kinds of join token (join_tokens.kind): a plain token admits one host, an enrollment pass admits up to MaxPassUses hosts (docs/PHASE3.md section 11.2).
const ( MinPassUses = 2 MaxPassUses = 25 DefaultPassUses = 10 MaxPassPatterns = 5 MinPassTTL = 5 * time.Minute MaxPassTTL = 7 * 24 * time.Hour DefaultPassTTL = 24 * time.Hour )
Limits of an enrollment pass (docs/PHASE3.md section 11.2). They are constants, not configuration: a leaked pass admits at most MaxPassUses hosts matching at most MaxPassPatterns patterns until it expires, and a larger fleet takes two passes.
const ActionPredecessor = "agent.predecessor"
const MaxWatches = 1024
const PackagesTTL = time.Minute
PackagesTTL: the packages of a release are immutable, but hashing six of them is not free and any viewer, API tokens included, may ask.
Variables ¶
var ( ErrNotFound = ca.ErrNotFound ErrInvalid = errors.New("invalid request") ErrQueueFull = errors.New("too many enrollments wait for approval – ask an admin for a join token") ErrRateLimited = errors.New("too many requests from this address – try again later") ErrUnknownAgent = errors.New("this certificate belongs to no active agent") ErrConflict = errors.New("conflict") )
Errors the transport maps to its codes.
var ( ErrWatchClosed = errors.New("the agent service is shutting down") ErrWatchReplaced = errors.New("a newer watch replaced this agent's watch") )
Functions ¶
func CheckDNSName ¶ added in v0.3.4
CheckDNSName checks a host name that is already lower case and has no trailing dot, by the rules of CheckHostname: a fully qualified DNS name, letters, digits and hyphens, no wildcard, no IP address. The TLS scan (internal/tlsscan) shares it so that both accept the same names.
func CheckHostname ¶
CheckHostname checks a host name an agent claims: a fully qualified DNS name (at least two labels), lower case, letters, digits and hyphens, no wildcard, no IP address. It returns the name in lower case.
func CheckPattern ¶
CheckPattern checks an entry of a name list or of a join token: an exact name or "*.suffix" – every name below suffix.
func Matches ¶
Matches reports whether name is covered by pattern: the same name, or below the suffix of "*.suffix". A wildcard name ("*.x") is only covered by the identical pattern – an agent never gets a wildcard by accident.
func MatchesAny ¶
MatchesAny reports whether one of the patterns covers name.
Types ¶
type Agent ¶
type Agent struct {
ID string
Hostname string
Status string // active | disabled | revoked
EnrollmentMode string
Proof string // JSON
CertificateID string
CertSerial string
OS, Arch string
Version string
PackageFormat string
LastSeenAt time.Time
CreatedAt time.Time
ApprovedAt time.Time
ApprovedBy string
Predecessor *predecessor.Policy
EffectivePolicy predecessor.Policy
Names []Name
Deployments []Deployment
}
Agent is an admitted agent.
type Checker ¶
Checker proves control of host: it fetches http://host/.well-known/acme-challenge/<token> and compares the answer with want, under the address policy of the domain (acme.HTTP01Checker).
type Deployment ¶
type Deployment struct {
Name, Profile string
Names []string
Challenge, State, Serial, CertificateID, Fingerprint, LastError string
NotAfter, DeployedAt, LastReportAt, RenewRequestedAt time.Time
RenewRequestedBy string
RenewalVersion int64
Removed bool
Predecessor *predecessor.Policy
EffectivePolicy predecessor.Policy
RevocationPlannedAt *time.Time
}
Deployment is the current reported state and an optional renewal request.
type DeploymentReport ¶
type DesiredDeployment ¶
type DesiredState ¶
type DesiredState struct {
Version int64
Changed bool
AllowedNames []string
Deployments []DesiredDeployment
}
type EnrollInput ¶
type EnrollInput struct {
Hostname, Token, Method, OS, Arch, Version, SourceIP string
CSRDER []byte
}
EnrollInput is an enrollment as the transport received it.
type EnrollOutput ¶
type EnrollOutput struct {
ID, Handle, CheckToken string
}
EnrollOutput is what the agent needs for the check and the polls.
type Enrollment ¶
type Enrollment struct {
ID, Hostname, State string
AgentID string // set when admitted, and for a known agent
CheckMethod, CheckState string
CheckError string
TokenValid bool
TokenProblem string
KnownAgent bool
OS, Arch, Version, SourceIP string
CreatedAt, ExpiresAt time.Time
DecidedAt time.Time // zero = open
DecidedBy string
}
Enrollment is an enrollment as the approval queue shows it.
type InstallFacts ¶ added in v0.3.5
type InstallFacts struct {
ServerURL string
ACMEDirectory string // "" = ACME is off
Version string // the agent release that matches this server
Root Trust
Packages []Package
}
InstallFacts is everything a script needs to install the agent on a host without a browser: the independent values of the guided installation.
type InstallSource ¶ added in v0.3.5
type InstallSource struct {
Dist *agentdist.Store
BaseURL string // agent.base_url
TLSIssuer string // agent.tls_issuer
// ACMEDirectory returns the directory URL of the ACME server, "" while ACME is off.
ACMEDirectory func() string
Clock clock.Clock
// contains filtered or unexported fields
}
InstallSource holds what the install facts are made of: the server's agent address, the packages of this release, the agent TLS issuer and the ACME directory. Everything in it is already visible to a viewer (the packages and the root are served without a login); the facts only gather it.
type JoinToken ¶
type JoinToken struct {
ID string
Label string
Kind string // KindToken | KindPass
Patterns []string
MaxUses int
UseCount int
ExpiresAt time.Time
UsedAt time.Time // the last use was taken; zero = uses left
UsedBy string // the enrollment of the last use
RevokedAt time.Time
RevokedBy string
CreatedBy string
CreatedAt time.Time
// Hosts are the enrollments a pass admitted or queued, oldest first
// (ListJoinTokens fills them for passes only).
Hosts []PassHost
}
JoinToken is a join token or enrollment pass without its secret (security.md "Agent trust": short-lived, scoped to name patterns, stored hashed; a plain token is single-use, a pass has a bounded number of uses).
type Name ¶
type Name struct {
Pattern string
Source string // hostname | token | admin
AddedBy string
AddedAt time.Time
}
Name is one entry of an agent's name list.
type NewToken ¶ added in v0.3.5
type NewToken struct {
Kind string // "" = KindToken
Label string
Patterns []string
TTL time.Duration // 0 = the default of the kind
MaxUses int // pass only; 0 = DefaultPassUses
}
NewToken is a join token or pass to create.
type Package ¶ added in v0.3.5
type Package struct {
Name, Format, Arch string // Format "checksums" for checksums.txt
Size int64
SHA256 string // hex
URL string
}
Package is one file of the agent distribution.
type PassHost ¶ added in v0.3.5
type PassHost struct {
Enrollment string
Hostname string
State string // the enrollment state
AgentID string // set once admitted
At time.Time
}
PassHost is one use of a pass: the enrollment it started and where that stands now.
type ReportInput ¶
type ReportInput struct {
Version, OS, Arch string
PackageFormat string
Deployments []DeploymentReport
}
ReportInput is display data, with identity supplied only by mTLS.
type Service ¶
type Service struct {
PKI *sql.DB // pki.db writer
Tenant string // the tenant these agents belong to
Engine *ca.Engine
// CA is agent.ca: the CA that signs the agents' certificates.
CA string
// Mode returns the tenant's enrollment mode (tenant.Store).
Mode func(ctx context.Context) (string, error)
// AgentPredecessor returns the tenant default for agent deployments.
AgentPredecessor func(ctx context.Context) (predecessor.Policy, error)
Checker Checker
// Install serves the install facts (InstallFacts); nil = no agent distribution.
Install *InstallSource
Clock clock.Clock
Log *slog.Logger
Wake func()
Rand io.Reader // nil = crypto/rand
Sleep func(context.Context, time.Duration) error // nil = clock.Sleep
// contains filtered or unexported fields
}
Service is the agent service of one tenant.
func (*Service) ClientCAs ¶
ClientCAs are the certificates of the agent CA – every generation that is valid at now: the only anchors of the listener's client authentication.
func (*Service) CloseWatches ¶
func (s *Service) CloseWatches()
CloseWatches is called before HTTP shutdown, so long polls release the request budget immediately. A service closed this way is never reused.
func (*Service) CreateEnrollmentPass ¶ added in v0.3.5
func (s *Service) CreateEnrollmentPass(ctx context.Context, who keys.Who, label string, patterns []string, maxUses int, ttl time.Duration) (string, *JoinToken, error)
CreateEnrollmentPass makes a pass that admits up to maxUses hosts whose name matches one of patterns, valid for ttl (0 = DefaultPassTTL). It needs the same fresh second factor as a join token and, unlike one, only names that a domain covers.
func (*Service) CreateJoinToken ¶
func (s *Service) CreateJoinToken(ctx context.Context, who keys.Who, label string, patterns []string, ttl time.Duration) (string, *JoinToken, error)
CreateJoinToken makes a single-use token for hosts whose name matches one of patterns, valid for ttl (0 = DefaultTokenTTL). The secret is returned once; only its SHA-256 is stored, and the audit entry never holds it.
func (*Service) CreateToken ¶ added in v0.3.5
func (s *Service) CreateToken(ctx context.Context, who keys.Who, in NewToken) (string, *JoinToken, error)
CreateToken creates a join token or an enrollment pass; the secret is returned once.
func (*Service) DeleteJoinToken ¶
DeleteJoinToken withdraws a token or pass nobody has used; a used one stays as a record.
func (*Service) Deny ¶
Deny refuses an enrollment from the queue; the agent learns it at its next poll.
func (*Service) Enroll ¶
func (s *Service) Enroll(ctx context.Context, in EnrollInput) (*EnrollOutput, error)
Enroll stores a pending enrollment. It needs no certificate: nothing but an enrollment that waits, limited in number and expiring, comes of it.
func (*Service) EnrollStatus ¶
EnrollStatus reports an enrollment; with CheckReady it first runs the check (once) and decides. An admitted agent gets its certificate here.
func (*Service) Handler ¶
Handler returns the Connect-RPC handler of the agent listener: only the AgentService path, every other path 404. The peer's address and certificate come from the TLS connection – never from a header.
func (*Service) Identify ¶
func (s *Service) Identify(ctx context.Context, leaf *x509.Certificate, sourceIP string) (*Caller, error)
Identify maps the client certificate of a request to its agent – per request, not per handshake, so a revocation or a "disable" takes effect at the next call even on a resumed TLS session. Only the agent's current certificate and the one before it count (renewal overlap), and only as exactly the stored certificate.
func (*Service) InstallFacts ¶ added in v0.3.5
InstallFacts returns the install facts for anyone who may read the PKI (viewer and up, API tokens included): they hold no secret, only what the agent distribution and the public root already show.
func (*Service) ListAgents ¶
ListAgents returns every agent, by hostname.
func (*Service) ListEnrollments ¶
func (s *Service) ListEnrollments(ctx context.Context, who keys.Who, openOnly bool) ([]Enrollment, error)
ListEnrollments returns the open enrollments (the approval queue), or the last 200 of any state.
func (*Service) ListJoinTokens ¶
ListJoinTokens returns every token and pass, newest first, without secrets. A pass comes with the enrollments it admitted or queued.
func (*Service) RegisterACMEAccount ¶
RegisterACMEAccount reserves the account key for an authenticated agent. The ACME request itself later proves possession of that key and binds the account. A reservation never transfers another agent's account.
func (*Service) RemoveName ¶
RemoveName takes a name or pattern from an agent's list.
func (*Service) RenewCertificate ¶
func (s *Service) RenewCertificate(ctx context.Context, c *Caller, csrDER []byte) (der []byte, chain [][]byte, err error)
RenewCertificate gives an agent a new 7-day certificate for a new key (ADR-11: the same key twice is refused). The current certificate becomes the previous one and stays accepted until it expires.
func (*Service) Report ¶
Report replaces only this agent's inventory. No agent ID, path or hook can be provided by the sender. Heartbeats do not fill the audit log.
func (*Service) RequestRenewal ¶
func (s *Service) RequestRenewal(ctx context.Context, who keys.Who, id, name string, overrides ...predecessor.Policy) error
RequestRenewal records an operator's request and snapshots its predecessor policy, never executable input. An omitted override uses the effective policy.
func (*Service) Revoke ¶
Revoke cuts an agent off for good: the status first (it takes effect at the agent's next call), then its agent certificates go on the agent CA's CRL. The certificates it deployed stay valid – revoking them would take services down; that is a separate step (owner 2026-10-24).
func (*Service) RevokeJoinToken ¶ added in v0.3.5
RevokeJoinToken ends the use of a token or pass at once. It does not touch the enrollments and agents the token already produced: they stay, listed on the pass, for an admin to refuse or disable (docs/PHASE3.md section 11.2).
func (*Service) SetDisabled ¶
SetDisabled disables an active agent or enables a disabled one. A disabled agent is cut off at its next call; its certificates stay valid.
func (*Service) SetPredecessorPolicy ¶ added in v0.3.1
func (s *Service) SetPredecessorPolicy(ctx context.Context, who keys.Who, id, deployment string, policy *predecessor.Policy) error
SetPredecessorPolicy sets or clears an agent or deployment override. An empty deployment selects the agent-wide override.
func (*Service) WatchDesiredState ¶
func (s *Service) WatchDesiredState(ctx context.Context, c *Caller, known int64) (*DesiredState, error)
WatchDesiredState subscribes before reading the database: a commit between the read and the wait can never be lost.
type Status ¶
type Status struct {
State string
Message string
AgentID string
TokenValid bool
CheckPassed bool
CertDER []byte
Chain [][]byte // issuing CA first, without the root
}
Status is where an enrollment stands, for the agent.
type StatusInput ¶
StatusInput is a poll of EnrollStatus.
type TokenState ¶ added in v0.3.5
type TokenState string
TokenState is where a join token or pass stands at a given time.
const ( TokenActive TokenState = "active" TokenUsedUp TokenState = "used_up" TokenExpired TokenState = "expired" TokenRevoked TokenState = "revoked" )
Token states. A revoked token reports revoked whatever else is true, a used up one used_up before it reports expired.
type Trust ¶ added in v0.3.5
type Trust struct {
SHA256, SHA1, URL string
}
Trust is the root the agents are told to trust before anything else: its DER fingerprints and the plain-HTTP URL of its PEM. Only SHA-256 authorizes an installation; SHA-1 is for comparing with the Windows certificate dialog.
func TLSTrust ¶ added in v0.3.5
func TLSTrust(ctx context.Context, engine *ca.Engine, who keys.Who, issuerName string) (Trust, error)
TLSTrust finds the root of the agent TLS issuer (agent.tls_issuer) among the CAs who may read. The guided installation (web) and the install facts (API) share it, so both name the same root.