Documentation
¶
Index ¶
- Constants
- func DecryptPrivateKey(encryptedPEM []byte, password []byte) (*rsa.PrivateKey, error)
- func DiscoverInterfaceIPs() ([]net.IP, error)
- func DistributeCertificateViaJetStream(kv KeyValueStore, nodeID string, certType CertificateType, certPEM []byte) (uint64, error)
- func EncryptPrivateKey(privateKey *rsa.PrivateKey, password []byte) ([]byte, error)
- func EnsureCertDir(dir string) error
- func GenerateAllCSRs(customSANs []string) (map[CertificateType]*CSRResult, error)
- func ReadFileBytes(path string) ([]byte, error)
- func SignCSR(csr *x509.CertificateRequest, certType CertificateType, opts SigningOptions) ([]byte, error)
- func ValidateCSR(csrPEM []byte, minKeyBits int) (*x509.CertificateRequest, error)
- func WriteAllCAs(certDir string, cas []RootCA) error
- func WriteAllCertificates(certDir string, certs map[CertificateType][]byte, ...) error
- func WriteCertificateAtomic(certPath, keyPath string, certPEM, keyPEM []byte) error
- type CAKeyStorage
- type CAOptions
- type CAType
- type CRLManager
- type CRLStore
- type CSROptions
- type CSRResult
- type CertPaths
- type CertificateType
- type DistributeCARequest
- type DistributeCAResponse
- type KeyValueStore
- type Manager
- func (m *Manager) GenerateAndDistribute(ctx context.Context, peers []string) error
- func (m *Manager) GetCAPrivateKeys() map[CertificateType][]byte
- func (m *Manager) GetCAs() []RootCA
- func (m *Manager) GetCRLManager() CRLManager
- func (m *Manager) GetEncryptedJWTKey() []byte
- func (m *Manager) HandleCertificateRequest(nodeID string, csrMap map[CertificateType][]byte, isFollowerRequest bool) (map[CertificateType][]byte, error)
- func (m *Manager) SetCRLManager(crlManager CRLManager)
- func (m *Manager) SetEncryptedJWTKey(encryptedKey []byte)
- func (m *Manager) WaitForAllCSRs(ctx context.Context) error
- type NATSCAKeyStorage
- type RootCA
- type SigningOptions
- type Transport
Constants ¶
const ( // DefaultValidity is the default certificate lifetime (10 years) DefaultValidity = 10 * 365 * 24 * time.Hour // DefaultBackdateDuration moves NotBefore into the past for clock skew tolerance (10 minutes) DefaultBackdateDuration = 10 * time.Minute )
Default CA configuration values
const (
// CAKeyBucketName is the NATS KV bucket for CA private keys
CAKeyBucketName = "neuwerk-ca-keys"
)
KV bucket and key names for CA storage
Variables ¶
This section is empty.
Functions ¶
func DecryptPrivateKey ¶
func DecryptPrivateKey(encryptedPEM []byte, password []byte) (*rsa.PrivateKey, error)
DecryptPrivateKey decrypts a PKCS#8 encrypted RSA private key The password parameter should match the one used in EncryptPrivateKey Returns error if PEM format is invalid or decryption fails
func DiscoverInterfaceIPs ¶
DiscoverInterfaceIPs enumerates all network interface IP addresses. Returns deduplicated list including localhost (127.0.0.1, ::1).
func DistributeCertificateViaJetStream ¶
func DistributeCertificateViaJetStream(kv KeyValueStore, nodeID string, certType CertificateType, certPEM []byte) (uint64, error)
DistributeCertificateViaJetStream stores certificate in JetStream KV for cluster-wide access. Key format: "neuwerk.certs.{nodeID}.{certType}" Example: "neuwerk.certs.node-1.nats", "neuwerk.certs.node-1.api-server"
Only certificates are stored (NOT private keys - security requirement). Certificates are public data and can be safely distributed. Returns revision number and error.
func EncryptPrivateKey ¶
func EncryptPrivateKey(privateKey *rsa.PrivateKey, password []byte) ([]byte, error)
EncryptPrivateKey encrypts an RSA private key using PKCS#8 with AES-256-GCM The password parameter receives output from token.DeriveCAEncryptionKey(). PKCS#8 library runs PBKDF2 internally - this provides layered security:
- Outer layer: Argon2id (token derivation)
- Inner layer: PBKDF2 (PKCS#8 encryption)
Returns PEM-encoded "ENCRYPTED PRIVATE KEY" block
func EnsureCertDir ¶
EnsureCertDir creates the certificate directory if it doesn't exist. Idempotent - safe to call multiple times. Directory is created with 0755 permissions (rwxr-xr-x).
func GenerateAllCSRs ¶
func GenerateAllCSRs(customSANs []string) (map[CertificateType]*CSRResult, error)
GenerateAllCSRs generates all 3 CSRs (NATS, API Server, API Client) in parallel. Returns map of CertificateType to CSRResult.
func ReadFileBytes ¶ added in v1.6.1
ReadFileBytes reads a file and returns its contents as bytes. Helper function to avoid importing os in places that use this package.
func SignCSR ¶
func SignCSR(csr *x509.CertificateRequest, certType CertificateType, opts SigningOptions) ([]byte, error)
SignCSR signs a CSR to create an X.509 certificate. The certificate inherits SANs from CSR (no modification by CA). Extended Key Usage is set based on certType.
Returns PEM-encoded certificate or error.
func ValidateCSR ¶
func ValidateCSR(csrPEM []byte, minKeyBits int) (*x509.CertificateRequest, error)
ValidateCSR validates CSR structure, signature, and key strength. Returns parsed *x509.CertificateRequest if valid, error otherwise.
Validation checks:
- PEM format: Type must be "CERTIFICATE REQUEST"
- Signature: CheckSignature() proves private key possession
- Key strength: RSA >= minKeyBits (4096), ECDSA >= P-384, Ed25519 accepted
- Subject: CommonName must be non-empty
- SANs: At least one DNS name or IP address required
func WriteAllCAs ¶
WriteAllCAs persists all 3 CA certificates to filesystem. Called by bootstrap manager after CA generation completes. Parameters:
- certDir: Base directory for certificate storage
- cas: Slice of RootCA containing PEM-encoded CA certificates
func WriteAllCertificates ¶
func WriteAllCertificates(certDir string, certs map[CertificateType][]byte, keys map[CertificateType][]byte, kv KeyValueStore, nodeID string) error
WriteAllCertificates persists all 3 certificate types to filesystem and JetStream KV. Writes certificates atomically to prevent partial writes on crash. Distributes certificates to JetStream KV for cluster-wide availability.
Parameters:
- certDir: Base directory for certificate storage (e.g., "/var/lib/neuwerk/certs")
- certs: Map of CertificateType to PEM-encoded certificate
- keys: Map of CertificateType to PEM-encoded private key
- kv: JetStream KeyValue bucket (may be nil for single-node deployments)
- nodeID: Node identifier for JetStream key naming (hostname)
If any write fails, returns error immediately (fail-fast).
func WriteCertificateAtomic ¶
WriteCertificateAtomic writes certificate and key files atomically using temp file + rename. Private keys are written with 0600 permissions (owner-only read/write). Certificates are written with 0644 permissions (world-readable).
Atomicity:
- Creates temp files (certPath.tmp, keyPath.tmp)
- Writes content to temp files
- Renames temp files to final paths (atomic on POSIX)
- Cleans up temp files if any step fails
This prevents partial writes if the process crashes mid-operation.
Types ¶
type CAKeyStorage ¶ added in v1.6.1
type CAKeyStorage interface {
// StoreCAKeys stores encrypted CA private keys to persistent storage.
// Keys are encrypted with AES-256-GCM using the bootstrap token.
StoreCAKeys(ctx context.Context, keys map[CertificateType][]byte) error
// LoadCAKeys retrieves and decrypts CA private keys from storage.
// Returns an error if keys are not found or decryption fails.
LoadCAKeys(ctx context.Context) (map[CertificateType][]byte, error)
// HasCAKeys checks if CA keys exist in storage without loading them.
HasCAKeys(ctx context.Context) (bool, error)
}
CAKeyStorage defines the interface for storing and retrieving CA private keys. This enables late joiners to sign certificates even when the original bootstrap leader is no longer available.
type CAOptions ¶
type CAOptions struct {
// Validity is the certificate lifetime (default: 10 years)
Validity time.Duration
// BackdateDuration moves NotBefore into the past for clock skew tolerance (default: 10 minutes)
BackdateDuration time.Duration
// EncryptionKey is derived from bootstrap token via DeriveCAEncryptionKey()
// Must be 32 bytes for AES-256-GCM encryption
EncryptionKey []byte
}
CAOptions configures certificate authority generation
func DefaultCAOptions ¶
DefaultCAOptions returns default CA generation options The encryptionKey should be derived from bootstrap token via token.DeriveCAEncryptionKey()
type CRLManager ¶
type CRLManager interface {
GenerateInitialCRL(ctx context.Context, caType CAType) ([]byte, error)
RefreshCRL(ctx context.Context, caType CAType) ([]byte, error)
}
CRLManager defines interface for CRL generation (avoids import cycle with crl package)
type CRLStore ¶
type CRLStore interface {
StoreCRL(ctx context.Context, caType CAType, crlBytes []byte) error
FetchCRL(ctx context.Context, caType CAType) ([]byte, error)
IncrementCRLNumber(ctx context.Context, caType CAType) (int64, error)
}
CRLStore defines interface for CRL persistence (avoids import cycle with crl package)
type CSROptions ¶
type CSROptions struct {
// CertType identifies the certificate purpose
CertType CertificateType
// CommonName is the certificate subject CN (hostname or generic name)
CommonName string
// CustomSANs are additional SANs from --san CLI flag
CustomSANs []string
// KeySize is the RSA key size (default 4096)
KeySize int
}
CSROptions configures CSR generation
type CSRResult ¶
type CSRResult struct {
// CertType identifies the certificate purpose
CertType CertificateType
// CSRPEM is PEM-encoded certificate signing request
CSRPEM []byte
// PrivateKeyPEM is PEM-encoded private key (PKCS#8 format)
PrivateKeyPEM []byte
// PublicKey is the public key from the generated key pair
PublicKey interface{}
}
CSRResult contains generated CSR and private key
func GenerateCSR ¶
func GenerateCSR(opts CSROptions) (*CSRResult, error)
GenerateCSR generates a single CSR with key pair. Auto-discovers IPs via DiscoverInterfaceIPs() and includes hostname, localhost in SANs.
type CertPaths ¶
type CertPaths struct {
// BaseDir is the root directory for certificates (e.g., "/var/lib/neuwerk/certs")
BaseDir string
// NATS cluster mTLS certificate and key
NATSCert string
NATSKey string
// API server HTTPS certificate and key
APIServerCert string
APIServerKey string
// API client authentication certificate and key
APIClientCert string
APIClientKey string
// CA certificates for verification
ClusterCA string // NATS cluster CA (e.g., /var/lib/neuwerk/certs/nats-ca.crt)
APIServerCA string // API server CA (e.g., /var/lib/neuwerk/certs/api-server-ca.crt)
APIClientCA string // API client CA (e.g., /var/lib/neuwerk/certs/api-client-ca.crt)
}
CertPaths holds standard paths for certificate files.
func DefaultCertPaths ¶
DefaultCertPaths builds standard certificate paths from base directory. Example: baseDir="/var/lib/neuwerk/certs" → NATSCert="/var/lib/neuwerk/certs/nats-server.crt"
type CertificateType ¶
type CertificateType int
CertificateType identifies the purpose of a certificate
const ( // CertTypeNATS is for NATS cluster mTLS CertTypeNATS CertificateType = iota // CertTypeAPIServer is for API server HTTPS CertTypeAPIServer // CertTypeAPIClient is for API client authentication CertTypeAPIClient )
func (CertificateType) String ¶
func (t CertificateType) String() string
String returns human-readable certificate type name
type DistributeCARequest ¶
type DistributeCARequest struct {
CAs []RootCA // All three CAs (NATS, API Server, API Client)
EncryptedJWTKey []byte // Encrypted JWT signing key (encrypted with bootstrap token)
Timestamp time.Time // For logging/debugging
}
DistributeCARequest is sent by the leader to distribute CAs to all followers
type DistributeCAResponse ¶
DistributeCAResponse is the response from followers after receiving CAs
type KeyValueStore ¶
KeyValueStore is an interface for JetStream KV operations (enables testing).
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager orchestrates CA generation and distribution across the cluster. It coordinates the bootstrap leader's CA generation and distribution to followers via the PSK-encrypted Raft transport.
func NewManager ¶
func NewManager(bootstrapToken token.SecureToken, transport Transport, logger logr.Logger) (*Manager, error)
NewManager creates a new CA manager instance. The transport callback is set to receive CAs from leader during distribution. For single-node deployments, transport may be nil (no distribution needed). The crlManager is optional and will be set later via SetCRLManager.
func NewManagerFromKeys ¶ added in v1.6.1
func NewManagerFromKeys(keys map[CertificateType][]byte, certDir string) (*Manager, error)
NewManagerFromKeys creates a CA manager initialized with CA private keys loaded from storage. This is used by late joiner support to create a manager that can sign certificates using CA keys loaded from NATS KV storage.
The keys map contains encrypted private keys indexed by CertificateType. The certDir is used to load CA certificates from disk for signing.
func (*Manager) GenerateAndDistribute ¶
GenerateAndDistribute generates all three CAs and distributes them to peers. Called by the bootstrap leader after achieving leadership stability.
Steps:
- Derive encryption key from bootstrap token
- Generate all three CAs (NATS, API Server, API Client)
- Store CAs locally
- Distribute to all peers in parallel
- Log distribution results (failures are logged but don't block)
func (*Manager) GetCAPrivateKeys ¶ added in v1.6.1
func (m *Manager) GetCAPrivateKeys() map[CertificateType][]byte
GetCAPrivateKeys returns the CA private keys (encrypted) for storage. Used by controller to store CA keys in NATS KV for late joiner support. Returns a map of CertificateType to encrypted private key PEM bytes.
func (*Manager) GetCAs ¶
GetCAs returns the generated or received CAs (thread-safe). Returns empty slice if CAs haven't been generated or received yet.
func (*Manager) GetCRLManager ¶
func (m *Manager) GetCRLManager() CRLManager
GetCRLManager returns the CRL manager for downstream use
func (*Manager) GetEncryptedJWTKey ¶
GetEncryptedJWTKey returns the encrypted JWT signing key received from leader. Called by bootstrap manager to save the JWT key after CA distribution.
func (*Manager) HandleCertificateRequest ¶
func (m *Manager) HandleCertificateRequest(nodeID string, csrMap map[CertificateType][]byte, isFollowerRequest bool) (map[CertificateType][]byte, error)
HandleCertificateRequest validates and signs CSRs from a follower node. Called by the bootstrap leader when handling RequestCertificate RPC.
Steps:
- Validate CAs are available (generated by leader)
- Derive encryption key from bootstrap token
- Validate each CSR (signature, key strength, SANs)
- Sign each CSR with matching CA (NATS CSR → NATS CA, etc.)
- Return map of signed certificates or error
Returns error if:
- CAs not yet generated
- CSR validation fails (weak key, invalid signature, etc.)
- Certificate signing fails
The isFollowerRequest parameter indicates whether this is a follower CSR submission (via RPC) that should be counted for the WaitForAllCSRs tracking. Leader's own certificate signing should pass false to avoid being counted.
func (*Manager) SetCRLManager ¶
func (m *Manager) SetCRLManager(crlManager CRLManager)
SetCRLManager sets the CRL manager for initial CRL generation Called after CA manager creation to avoid circular dependency
func (*Manager) SetEncryptedJWTKey ¶
SetEncryptedJWTKey sets the encrypted JWT signing key to be distributed with CAs. Called by bootstrap manager after generating the JWT key.
func (*Manager) WaitForAllCSRs ¶
WaitForAllCSRs blocks until all expected peers have submitted their CSRs. Called by the bootstrap leader after CA distribution to ensure all followers complete certificate generation before the leader shuts down the Raft transport. Returns error if context is cancelled or times out.
type NATSCAKeyStorage ¶ added in v1.6.1
type NATSCAKeyStorage struct {
// contains filtered or unexported fields
}
NATSCAKeyStorage implements CAKeyStorage using NATS JetStream KV. CA private keys are encrypted with AES-256-GCM before storage.
func NewNATSCAKeyStorage ¶ added in v1.6.1
func NewNATSCAKeyStorage(js jetstream.JetStream, encryptionKey []byte) (*NATSCAKeyStorage, error)
NewNATSCAKeyStorage creates a new NATS-backed CA key storage. The encryptionKey must be 32 bytes (derived from bootstrap token via DeriveCAEncryptionKey).
func (*NATSCAKeyStorage) HasCAKeys ¶ added in v1.6.1
func (s *NATSCAKeyStorage) HasCAKeys(ctx context.Context) (bool, error)
HasCAKeys checks if all CA keys exist in storage without loading them.
func (*NATSCAKeyStorage) LoadCAKeys ¶ added in v1.6.1
func (s *NATSCAKeyStorage) LoadCAKeys(ctx context.Context) (map[CertificateType][]byte, error)
LoadCAKeys retrieves and decrypts CA private keys from NATS KV.
func (*NATSCAKeyStorage) StoreCAKeys ¶ added in v1.6.1
func (s *NATSCAKeyStorage) StoreCAKeys(ctx context.Context, keys map[CertificateType][]byte) error
StoreCAKeys stores encrypted CA private keys to NATS KV. Each key is encrypted separately with AES-256-GCM.
type RootCA ¶
type RootCA struct {
// Type identifies the CA's purpose
Type CAType
// CertificatePEM is PEM-encoded X.509 certificate
CertificatePEM []byte
// EncryptedKeyPEM is PEM-encoded PKCS#8 encrypted private key
EncryptedKeyPEM []byte
// NotBefore is the certificate validity start time
NotBefore time.Time
// NotAfter is the certificate validity end time
NotAfter time.Time
// SerialNumber is the certificate serial number
SerialNumber *big.Int
}
RootCA represents a generated certificate authority
func GenerateRootCA ¶
GenerateRootCA generates a self-signed root certificate authority The CA private key is encrypted with AES-256-GCM before being returned
Parameters:
- caType: identifies the CA's purpose (NATS, API Server, or API Client)
- opts: configuration options (validity period, backdate duration, encryption key)
Returns:
- *RootCA containing PEM-encoded certificate and encrypted private key
- error if key generation, certificate creation, or encryption fails
type SigningOptions ¶
type SigningOptions struct {
// CA is the root CA to sign with
CA *RootCA
// EncryptionKey for decrypting CA private key (from token.DeriveCAEncryptionKey)
EncryptionKey []byte
// Validity is certificate lifetime (default: 90 days)
Validity time.Duration
// BackdateDuration moves NotBefore into past for clock skew tolerance (default: 10 minutes)
BackdateDuration time.Duration
}
SigningOptions configures certificate signing
func DefaultSigningOptions ¶
func DefaultSigningOptions(ca *RootCA, encryptionKey []byte) SigningOptions
DefaultSigningOptions returns default signing configuration