Documentation
¶
Overview ¶
Package agentpki issues the mTLS client certificates of AnixOps Agents (node-ops-service.md, section 5.3).
A certificate names one node, spiffe://anixops/<cluster>/agent/proxy-<id> or .../agent/forward-<id>, and is signed by the module PKI's CA (internal/modulepki) with its current/next rotation. Agents get their first certificate with a bootstrap credential: the node credential they already have (a proxy node's API key or a forward node's token), or a one-time anixagt_ enrollment credential bound to a node. They renew with the current certificate at two thirds of its lifetime.
Every certificate is recorded in v4_kernel_agent_certificate and every way in in v4_kernel_agent_enrollment. Revoking, replacing or disabling a node's credentials, or deleting the node, revokes its certificates (RevokeNode), and the agent listener refuses revoked serials through a cache of at most RevocationCacheTTL.
The package also runs the forward link CA (link.go, owner decision H28): a separate root that issues nodes whose Agent negotiated forward.v1 the link certificates their forward engines present to each other, recorded in v4_kernel_forward_link_certificate and revoked with the node's Agent credentials.
Index ¶
- Constants
- Variables
- func CheckNodeEnabled(ctx context.Context, db *gorm.DB, node agentcontrol.AgentNode) error
- func ForgetRevocations()
- func RevokeNode(ctx context.Context, db *gorm.DB, node agentcontrol.AgentNode, reason string) error
- type ActiveCredentials
- type Bootstrap
- type EnrollRequest
- type Issued
- type LinkAuthority
- func (a *LinkAuthority) CAs(ctx context.Context) ([]model.ForwardLinkCA, error)
- func (a *LinkAuthority) Cluster() string
- func (a *LinkAuthority) Ensure(ctx context.Context) error
- func (a *LinkAuthority) Lifetime() time.Duration
- func (a *LinkAuthority) Maintain(ctx context.Context) error
- func (a *LinkAuthority) Rotate(ctx context.Context) (*model.ForwardLinkCA, error)
- func (a *LinkAuthority) TrustBundle(ctx context.Context) ([]*x509.Certificate, error)
- type LinkAuthorityOptions
- type LinkIssued
- type Options
- type RotateRequest
- type Rotation
- type Service
- func (s *Service) Cluster() string
- func (s *Service) CreateEnrollmentToken(ctx context.Context, request TokenRequest) (string, model.AgentEnrollment, error)
- func (s *Service) CreateEnrollmentTokenTx(tx *gorm.DB, request TokenRequest) (string, model.AgentEnrollment, error)
- func (s *Service) Enroll(ctx context.Context, request EnrollRequest) (Issued, error)
- func (s *Service) IsRevoked(ctx context.Context, serial string, node agentcontrol.AgentNode) (bool, error)
- func (s *Service) IssueLinkCertificate(ctx context.Context, peer *x509.Certificate, csrDER []byte, remoteAddr string) (LinkIssued, error)
- func (s *Service) Lifetime() time.Duration
- func (s *Service) Link() *LinkAuthority
- func (s *Service) LinkTrustBundle(ctx context.Context) ([]*x509.Certificate, error)
- func (s *Service) Prune(ctx context.Context, cutoff time.Time) error
- func (s *Service) Renew(ctx context.Context, peer *x509.Certificate, csrDER []byte) (Issued, error)
- func (s *Service) Roots(ctx context.Context) (*x509.CertPool, error)
- func (s *Service) RotateCredentials(ctx context.Context, req RotateRequest) (Rotation, error)
- func (s *Service) TrustBundle(ctx context.Context) ([]*x509.Certificate, error)
- func (s *Service) VerifyPeer(ctx context.Context, rawCerts [][]byte) (agentcontrol.AgentIdentity, *x509.Certificate, error)
- type TokenRequest
Constants ¶
const ( // DefaultCertificateLifetime is the lifetime of agent certificates // (decision D9): nodes can be offline longer than modules. DefaultCertificateLifetime = 7 * 24 * time.Hour // MaxEnrollmentCredentialTTL bounds a one-time enrollment credential. MaxEnrollmentCredentialTTL = 7 * 24 * time.Hour // DefaultEnrollmentCredentialTTL is used when a request names none. DefaultEnrollmentCredentialTTL = 24 * time.Hour // RevocationCacheTTL is the longest the listener trusts a cached // revocation answer. RevocationCacheTTL = 30 * time.Second )
const ( AuditActionTokenIssue = "agent_enrollment_token_issue" AuditActionEnroll = "agent_enroll" )
Audit actions written to the operation log (v2_operation_log).
const ( RevokeReasonNodeDisabled = "node_disabled" RevokeReasonNodeDeleted = "node_deleted" RevokeReasonCredentialsReplaced = "credentials_replaced" RevokeReasonCredentialsRevoked = "credentials_revoked" RevokeReasonCredentialsRotated = "credentials_rotated" )
Revocation reasons recorded with revoked enrollments and certificates.
const AuditActionCredentialsRotate = "agent_credentials_rotate" // #nosec G101 -- names an operation log action, not a credential.
AuditActionCredentialsRotate is the operation log action of a credential rotation (RotateCredentials). Its entry carries the node, the reason and what was revoked; never a credential.
const AuditActionLinkIssue = "forward_link_certificate_issue"
AuditActionLinkIssue is the operation log action of an issued link certificate.
const ( // DefaultLinkCertificateLifetime is the lifetime of link certificates: // the Agent certificate's. DefaultLinkCertificateLifetime = DefaultCertificateLifetime )
const MaxRotationReasonLength = 200
MaxRotationReasonLength bounds the free-text reason an administrator gives for a rotation.
Variables ¶
var ( // ErrDisabled means the kernel does not run the built-in CA, which signs // agent certificates. It needs module_runtime.ca_kek with pki builtin; // the module listener (module_runtime.enabled) need not run. ErrDisabled = errors.New("agent enrollment needs the built-in CA (module_runtime.ca_kek with module_runtime.pki: builtin)") // ErrExternalPKI is ErrDisabled with module_runtime.pki external: the // kernel holds no CA key, so it cannot issue agent certificates. ErrExternalPKI = fmt.Errorf("%w: module_runtime.pki is external, so the kernel holds no CA key to issue agent certificates", ErrDisabled) // ErrEnrollmentRejected is the single answer to any unusable bootstrap // credential, so credentials cannot be probed. ErrEnrollmentRejected = errors.New("agent enrollment rejected") // ErrCertificateRevoked means the certificate, its enrollment or its // node's credentials were revoked, or the node is gone or disabled. ErrCertificateRevoked = errors.New("agent certificate revoked") // ErrInvalidCertificate reports a client certificate that does not // authenticate an agent of this cluster: unparsable, not chaining to the // trust bundle, expired, or carrying another identity. ErrInvalidCertificate = errors.New("invalid agent client certificate") // ErrCertificateExpired is the ErrInvalidCertificate of an agent // certificate of this CA whose not_after has passed. ErrCertificateExpired = fmt.Errorf("%w: the certificate has expired", ErrInvalidCertificate) // ErrCertificateWrongCluster is the ErrInvalidCertificate of an agent // certificate of another cluster. ErrCertificateWrongCluster = fmt.Errorf("%w: the certificate belongs to another cluster", ErrInvalidCertificate) // ErrInvalidNode reports an enrollment credential request for a node // that does not exist or is disabled. ErrInvalidNode = errors.New("agent node not found or disabled") )
var ( // ErrLinkDisabled means the kernel does not run the forward link CA. It // needs the built-in CA's module_runtime.ca_kek with pki builtin; an // external PKI holds no key in the kernel, so it cannot issue link // certificates either. ErrLinkDisabled = errors.New("forward link certificates need the built-in CA (module_runtime.ca_kek with module_runtime.pki: builtin)") // ErrNoLinkAuthority means the cluster has no current link CA yet. ErrNoLinkAuthority = errors.New("the forward link PKI has no current CA") // ErrLinkNotNegotiated means the node's Agent did not negotiate // forward.v1 in its last Hello. ErrLinkNotNegotiated = errors.New("the node's Agent has not negotiated forward.v1") // ErrInvalidLinkRequest reports an unacceptable link certificate // request: malformed, signed with an unsupported key or with the Agent's // own key, or asking for a name other than the node's. ErrInvalidLinkRequest = errors.New("invalid link certificate request") )
var ErrNodeDisabled = errors.New("agent node is disabled")
ErrNodeDisabled reports a rotation of a node an administrator disabled: disabling already revoked its Agent credentials, and a disabled node is not enrolled until it is enabled again.
Functions ¶
func CheckNodeEnabled ¶
CheckNodeEnabled fails with ErrInvalidNode unless node exists and is not disabled.
func ForgetRevocations ¶ added in v4.2.0
func ForgetRevocations()
ForgetRevocations drops every cached revocation answer of this process. RevokeNode does it too, but inside the caller's transaction: a reader between it and the commit caches "not revoked". A caller that revoked in a transaction calls this after the commit, so open Agent streams see the revocation at their next heartbeat instead of after the cache lifetime.
func RevokeNode ¶
RevokeNode revokes every certificate and every enrollment (including unused enrollment credentials) of node, and its forward link certificates, in db, which may be a transaction. It is the hook of the kernel paths that revoke, replace or disable a node's credentials or delete the node. A database without the agent PKI tables has nothing to revoke.
Types ¶
type ActiveCredentials ¶ added in v4.2.0
type ActiveCredentials struct {
Certificates int64 `json:"certificates"`
Enrollments int64 `json:"enrollments"`
LinkCertificates int64 `json:"link_certificates"`
}
ActiveCredentials counts what a revocation of a node's Agent credentials would revoke: certificates, enrollments (including unused enrollment credentials) and forward link certificates that are not revoked yet.
func CountActive ¶ added in v4.2.0
func CountActive(ctx context.Context, db *gorm.DB, node agentcontrol.AgentNode) (ActiveCredentials, error)
CountActive counts node's unrevoked Agent credentials in db, which may be a transaction. A database without the agent PKI tables has none.
type Bootstrap ¶
type Bootstrap struct {
// Method is one of model.AgentEnrollmentMethod*.
Method string
// Node is the node the agent claims: required with a node API key or a
// forward token; with an enrollment credential it must match the
// credential's node when set.
Node agentcontrol.AgentNode
// Secret is the API key, the forward token or the enrollment
// credential. It is never stored or logged.
Secret string
}
Bootstrap is the credential an agent enrolls with.
type EnrollRequest ¶
type EnrollRequest struct {
Bootstrap Bootstrap
CSRDER []byte
AgentVersion string
InstanceID string
// RemoteAddr is recorded in the audit entry.
RemoteAddr string
}
EnrollRequest is an agent's request for its first certificate.
type Issued ¶
type Issued struct {
modulepki.SignedLeaf
Identity agentcontrol.AgentIdentity
EnrollmentID string
}
Issued is a signed agent certificate.
type LinkAuthority ¶ added in v4.2.0
type LinkAuthority struct {
// contains filtered or unexported fields
}
LinkAuthority is the forward link CA of one cluster.
func LinkAuthorityFromConfig ¶ added in v4.2.0
LinkAuthorityFromConfig returns the forward link CA of the built-in CA's configuration: module_runtime.ca_kek seals its keys and the module runtime's cluster names it. ErrLinkDisabled without the built-in CA or with an external PKI.
func NewLinkAuthority ¶ added in v4.2.0
func NewLinkAuthority(opts LinkAuthorityOptions) (*LinkAuthority, error)
NewLinkAuthority returns the forward link CA of opts.Cluster.
func (*LinkAuthority) CAs ¶ added in v4.2.0
func (a *LinkAuthority) CAs(ctx context.Context) ([]model.ForwardLinkCA, error)
CAs returns the cluster's link CAs, oldest first, without their keys' plaintext (SealedKey stays sealed and is not serialized).
func (*LinkAuthority) Cluster ¶ added in v4.2.0
func (a *LinkAuthority) Cluster() string
Cluster returns the cluster the link CA serves.
func (*LinkAuthority) Ensure ¶ added in v4.2.0
func (a *LinkAuthority) Ensure(ctx context.Context) error
Ensure creates the cluster's first link CA when it has none. Concurrent kernels call it under the bootstrap lock.
func (*LinkAuthority) Lifetime ¶ added in v4.2.0
func (a *LinkAuthority) Lifetime() time.Duration
Lifetime returns the lifetime of link certificates, which is also the rotation overlap.
func (*LinkAuthority) Maintain ¶ added in v4.2.0
func (a *LinkAuthority) Maintain(ctx context.Context) error
Maintain promotes a next link CA that has been in the trust bundle for one link certificate lifetime and retires the current one.
func (*LinkAuthority) Rotate ¶ added in v4.2.0
func (a *LinkAuthority) Rotate(ctx context.Context) (*model.ForwardLinkCA, error)
Rotate creates the next link CA. It joins the trust bundle at once and starts signing one link certificate lifetime later (Maintain), so every node that renewed meanwhile trusts it before any peer presents a certificate it signed. Rotating while a next CA exists returns it.
func (*LinkAuthority) TrustBundle ¶ added in v4.2.0
func (a *LinkAuthority) TrustBundle(ctx context.Context) ([]*x509.Certificate, error)
TrustBundle returns every link CA a node must trust to verify its peers' link certificates: the current and next CAs and retired CAs whose link certificates may still be valid.
type LinkAuthorityOptions ¶ added in v4.2.0
type LinkAuthorityOptions struct {
DB *gorm.DB
Cluster string
// KEK seals the CA keys: module_runtime.ca_kek (modulepki.ParseKEK).
KEK []byte
// Lifetime of link certificates; DefaultLinkCertificateLifetime when
// zero.
Lifetime time.Duration
// Now defaults to time.Now.
Now func() time.Time
}
LinkAuthorityOptions configures a LinkAuthority.
type LinkIssued ¶ added in v4.2.0
type LinkIssued struct {
CertificateDER []byte
// TrustBundleDER is the link trust bundle at issuance.
TrustBundleDER [][]byte
Identity agentcontrol.AgentIdentity
// DNSName is the certificate's only DNS name: the node's identity name.
DNSName string
Serial string
NotAfter time.Time
RenewAfter time.Time
IssuerKeyID string
}
LinkIssued is a signed link certificate.
type Options ¶
type Options struct {
DB *gorm.DB
Authority *modulepki.Authority
// Lifetime of issued certificates; DefaultCertificateLifetime when zero.
Lifetime time.Duration
// RevocationCacheTTL defaults to RevocationCacheTTL and may not exceed it.
RevocationCacheTTL time.Duration
// Now defaults to time.Now.
Now func() time.Time
// Link is the forward link CA (link.go); nil serves no link
// certificates (ErrLinkDisabled).
Link *LinkAuthority
}
Options configures a Service.
type RotateRequest ¶ added in v4.2.0
type RotateRequest struct {
Node agentcontrol.AgentNode
// TTL is the new enrollment credential's lifetime; as TokenRequest.
TTL time.Duration
// CreatedBy is the administrator's user id; Actor and IP describe the
// caller in the audit entries.
CreatedBy uint
Actor string
IP string
// Reason is recorded in the audit entry (at most MaxRotationReasonLength
// bytes, checked by the caller).
Reason string
// ReplaceNodeKey, when set, runs first in the rotation's transaction and
// replaces the node's own credential (a proxy node's API key). It must
// not revoke Agent credentials itself: the rotation does that, once, with
// its own reason. Its failure rolls the whole rotation back.
ReplaceNodeKey func(tx *gorm.DB) error
}
RotateRequest rotates a node's Agent credentials.
type Rotation ¶ added in v4.2.0
type Rotation struct {
// Credential is the new one-time enrollment credential. It is shown
// once: only its SHA-256 is stored.
Credential string
Enrollment model.AgentEnrollment
// Revoked counts the Agent credentials the rotation revoked.
Revoked ActiveCredentials
APIKeyRotated bool
}
Rotation is what RotateCredentials did.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service is the agent PKI of one cluster.
func FromConfig ¶
FromConfig returns the agent PKI of the built-in CA, or ErrDisabled (ErrExternalPKI with an external PKI). It does not need the module runtime: module_runtime.ca_kek alone configures the CA.
func (*Service) CreateEnrollmentToken ¶
func (s *Service) CreateEnrollmentToken(ctx context.Context, request TokenRequest) (string, model.AgentEnrollment, error)
CreateEnrollmentToken stores a one-time enrollment credential bound to a node and returns it. The credential is shown only here; the kernel keeps its SHA-256.
func (*Service) CreateEnrollmentTokenTx ¶ added in v4.2.0
func (s *Service) CreateEnrollmentTokenTx(tx *gorm.DB, request TokenRequest) (string, model.AgentEnrollment, error)
CreateEnrollmentTokenTx is CreateEnrollmentToken in the caller's transaction: the credential, its row and its audit entry commit with whatever else the caller changed (the credential rotation revokes the node's old credentials in the same transaction).
func (*Service) Enroll ¶
Enroll checks a bootstrap credential and issues the authenticated node's first certificate. The CSR's subject and SANs are ignored. A one-time credential is consumed atomically; a failed issuance leaves it unused.
func (*Service) IsRevoked ¶
func (s *Service) IsRevoked(ctx context.Context, serial string, node agentcontrol.AgentNode) (bool, error)
IsRevoked reports whether the agent certificate serial of node may no longer authenticate. A serial without a record, or recorded for another node, counts as revoked: every agent certificate is recorded when it is issued, and records are pruned only after the certificate expired. Answers are cached for at most the revocation cache lifetime.
func (*Service) IssueLinkCertificate ¶ added in v4.2.0
func (s *Service) IssueLinkCertificate(ctx context.Context, peer *x509.Certificate, csrDER []byte, remoteAddr string) (LinkIssued, error)
IssueLinkCertificate issues the node of the verified Agent certificate peer (VerifyPeer) a link certificate for the key of csrDER. The Agent certificate's record must be unrevoked, the node enabled, and its Agent must have negotiated forward.v1 in its last Hello.
func (*Service) Link ¶ added in v4.2.0
func (s *Service) Link() *LinkAuthority
Link returns the forward link CA, nil when the agent PKI runs without it.
func (*Service) LinkTrustBundle ¶ added in v4.2.0
LinkTrustBundle returns the link trust bundle: ErrLinkDisabled without the link CA.
func (*Service) Prune ¶
Prune deletes the records of certificates (Agent and, with the link CA, forward link certificates) that expired before cutoff and of enrollment credentials that expired unused before it.
func (*Service) Renew ¶
Renew issues a new certificate to the holder of a valid agent certificate. peer must already be verified (VerifyPeer).
func (*Service) Roots ¶
Roots returns TrustBundle as a pool, reloaded at most every minute; a failed reload keeps the last good pool.
func (*Service) RotateCredentials ¶ added in v4.2.0
RotateCredentials rotates a node's Agent credentials in one transaction: it optionally replaces the node's API key (req.ReplaceNodeKey), revokes every Agent certificate, enrollment (unused enrollment credentials included) and forward link certificate of the node (RevokeNode, reason credentials_rotated), and then issues a fresh one-time enrollment credential, the same code path as the enrollment token administration (CreateEnrollmentTokenTx). The revocation comes first because it also revokes unused enrollment credentials, and the new one must survive it.
It never dials the node: an offline node is rotated like any other, and its Agent finds out when it next presents the revoked certificate. Open Agent streams end at their next heartbeat. The node row is locked, so concurrent rotations of one node run one after the other, and a repeated rotation leaves exactly one valid enrollment credential: the last. ErrInvalidNode and ErrNodeDisabled report a node that is gone or disabled; nothing is changed then.
func (*Service) TrustBundle ¶
TrustBundle returns every CA that may have signed a valid agent certificate.
func (*Service) VerifyPeer ¶
func (s *Service) VerifyPeer(ctx context.Context, rawCerts [][]byte) (agentcontrol.AgentIdentity, *x509.Certificate, error)
VerifyPeer verifies a client certificate chain presented on the agent listener: it must chain to the trust bundle, be valid now, carry the agent identity of a node of this cluster, and not be revoked. Module and kernel certificates of the same CA are refused.
type TokenRequest ¶
type TokenRequest struct {
Node agentcontrol.AgentNode
// TTL defaults to DefaultEnrollmentCredentialTTL and may not exceed
// MaxEnrollmentCredentialTTL.
TTL time.Duration
// CreatedBy is the administrator's user id; 0 for the command line.
CreatedBy uint
// Actor and IP describe the caller in the audit entry.
Actor string
IP string
}
TokenRequest describes a new one-time enrollment credential.