Documentation
¶
Overview ¶
Package hubca is the hub's own certificate authority, and the short-lived certificates it issues its two credential monitors (Task 20409).
Why the hub has one ¶
The git interception proxy and the Kubernetes access monitor exist so that a forge token and a cluster credential stay on the hub: the sandbox holds a session token instead, and a process it does not control decides every push and every API request. That token rides every request, so both listeners speak TLS — and until this package, their certificate was a pair of files the operator supplied. A self-signed pair needed its CA in every sandbox image, a public one needed a name the sandbox could reach and a CA would sign, and so most hubs ran neither and delivered the PAT itself into the sandbox. The security property was one the operator had to finish building.
With `tls: auto` on a monitor's section the hub builds it. A CA is minted once per control plane, its private key sealed under the hub's KEK registry (envelope encryption, the same as every brokered secret) and kept in the control-plane database, never as a loose file. So every hub-cluster member serves the same identity, a database backup carries it, and `cloop hub key rotate` rewraps it. Each monitor's serving certificate is a leaf issued from that CA for the names sandboxes reach it at — the advertise URL's host and the listen address's — valid for LeafLifetime and replaced by the cluster leader when less than RenewWithin remains.
What a sandbox trusts ¶
The CA, never the leaf, and only for the monitor's URL. The CA certificate travels to each sandbox (pkg/executor.GitCA) and git is told to trust it through http.<advertise_url>.sslCAInfo — not GIT_SSL_CAINFO, which replaces the trust store — so another https host whose certificate this CA signed is still verified against the image's own store and refused. A kubeconfig carries the CA as its cluster's certificate-authority-data. Because the sandbox trusts the CA, a leaf can be replaced under a running workload: a push in flight keeps its connection, and the next one verifies the new leaf against the same CA.
The CA's private key is opened only in the hub process, to sign leaves. It is in no Spec, no lease, no start frame and no log.
Index ¶
- Constants
- Variables
- func Covers(c *x509.Certificate, want []string) bool
- func Missing(c *x509.Certificate, want []string) []string
- func NormalizeSANs(in []string) ([]string, error)
- func ParseCertPEM(b []byte) (*x509.Certificate, error)
- func SANsFor(advertiseBase, listenAddr string) ([]string, error)
- type Authority
- type Issued
- type Leaf
- type LeafInfo
- type Monitor
- type Options
- type Sealer
- type Server
- func (s *Server) CAPEM() []byte
- func (s *Server) Current() *Leaf
- func (s *Server) GetCertificate(*tls.ClientHelloInfo) (*tls.Certificate, error)
- func (s *Server) Monitor() Monitor
- func (s *Server) Refresh() (bool, error)
- func (s *Server) Renew() (bool, error)
- func (s *Server) TLSConfig(minVersion string) (*tls.Config, error)
- func (s *Server) Want() []string
- type ServerConfig
- type Store
- func (s *Store) CountSealedByKey() (map[string]int, error)
- func (s *Store) ListSealedNotUnder(keyID string, limit int) ([]secretbroker.SealedRow, error)
- func (s *Store) ReadCA() (*x509.Certificate, []byte, error)
- func (s *Store) ReadLeaf(m Monitor) (LeafInfo, error)
- func (s *Store) ReplaceSealed(id string, expect, next secretbroker.Envelope) (bool, error)
- func (s *Store) SealedSetName() string
Constants ¶
const ( // CALifetime is how long the CA is valid. Long, because a CA that // expires is one every sandbox stops trusting at once, and replacing it // is a change to every workload's trust rather than to one listener's // certificate. Leaves are what is short-lived. CALifetime = 10 * 365 * 24 * time.Hour // LeafLifetime is how long a monitor's certificate is valid. LeafLifetime = 72 * time.Hour // RenewWithin: a leaf with less than this left is replaced. The 48-hour // overlap is what a renewal that failed has to be noticed and fixed in // before anything stops verifying. RenewWithin = 48 * time.Hour // NearExpiry is when `cloop hub doctor` fails a leaf: renewal should have // replaced it a day before this. NearExpiry = 24 * time.Hour // EmergencyWithin: a hub process that is not the cluster leader replaces // a leaf itself only this close to its expiry — a leader that has been // gone for two days, or a cluster whose leader cannot sign. EmergencyWithin = time.Hour // MaxSANs bounds the names one leaf carries. MaxSANs = 16 )
Variables ¶
var ErrNoKey = errors.New("hubca: the hub's certificate authority needs CLOOP_SECRET_KEY (or its key file, " +
"CLOOP_SECRET_KEY_FILE), which its private key is sealed under")
ErrNoKey is returned when the hub has no sealing key to keep the CA under.
var ErrNotMinted = errors.New("hubca: not issued yet")
ErrNotMinted is returned for a CA or a leaf that does not exist yet: a hub that has never started a monitor in auto mode.
Functions ¶
func Covers ¶
func Covers(c *x509.Certificate, want []string) bool
Covers reports whether a certificate carries every one of want's names.
func Missing ¶
func Missing(c *x509.Certificate, want []string) []string
Missing returns want's names a certificate does not carry.
func NormalizeSANs ¶
NormalizeSANs validates, lowercases, de-duplicates and sorts names.
func ParseCertPEM ¶
func ParseCertPEM(b []byte) (*x509.Certificate, error)
ParseCertPEM decodes exactly one certificate from PEM.
func SANsFor ¶
SANsFor returns the names a monitor's certificate must carry: the host of advertiseBase — the https URL sandboxes are pointed at — and the host of listenAddr when it names one, rather than every interface.
The listen address is there for the sandbox that shares the hub's network namespace and dials it directly, and for a hub whose advertise URL is the bound address; an unspecified bind (":8443", "0.0.0.0:8443") names nothing a client could dial, so it adds nothing.
Types ¶
type Authority ¶
type Authority struct {
// contains filtered or unexported fields
}
Authority is the hub's CA, opened for signing.
func OpenAuthority ¶
OpenAuthority returns the hub's CA, minting it on the first call any hub process makes against this control plane.
Two processes minting at the same moment cannot both win: the insert is ON CONFLICT DO NOTHING, and the loser opens the winner's CA instead of its own. A CA that exists is never replaced here — every sandbox already given it would refuse its successor.
func (*Authority) Certificate ¶
func (a *Authority) Certificate() *x509.Certificate
Certificate returns the CA certificate.
type Issued ¶
type Issued struct {
// CA is true for the CA itself, false for a monitor's leaf.
CA bool
// Monitor is the leaf's monitor; empty for the CA.
Monitor Monitor
Serial string
// Fingerprint is the SHA-256 of the certificate's DER, in hex: what an
// operator compares against the PEM a sandbox was given.
Fingerprint string
Names []string
NotAfter time.Time
// Replaced is the serial of the leaf this one replaced, if any.
Replaced string
}
Issued describes a certificate this process put in the store: the CA, or a monitor's leaf. Certificates only — nothing here is secret.
type Leaf ¶
type Leaf struct {
Monitor Monitor
Serial string
Cert *x509.Certificate
CertPEM []byte
IssuedBy string
// contains filtered or unexported fields
}
Leaf is a monitor's serving certificate, opened.
type LeafInfo ¶
type LeafInfo struct {
Monitor Monitor
Cert *x509.Certificate
SANs []string
Issuer string
IssuedBy string
IssuedAt time.Time
}
LeafInfo is what can be read about a monitor's leaf without opening its key.
type Monitor ¶
type Monitor string
Monitor names a listener whose certificate the hub issues.
func Monitors ¶
func Monitors() []Monitor
Monitors lists every monitor the hub issues for, in a stable order.
type Options ¶
type Options struct {
// Now is the clock; nil uses time.Now.
Now func() time.Time
// Actor names the hub process issuing, for the row's issued_by.
Actor string
// Audit receives one record per certificate this process minted or
// issued — never one it adopted from another process. Nil drops them.
Audit func(Issued)
}
Options adjusts an operation.
type Sealer ¶
type Sealer interface {
SealFor(aad string, plaintext []byte) (secretbroker.Envelope, error)
OpenEnvelope(aad string, env secretbroker.Envelope) ([]byte, error)
}
Sealer is the part of a *secretbroker.Keyring this package uses.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server is one monitor's certificate, as its listener serves it.
func NewServer ¶
func NewServer(cfg ServerConfig) (*Server, error)
NewServer settles the monitor's leaf — the stored one, or a new one when it is missing, near the end of its life, or misses a name — and returns a server for it.
func (*Server) GetCertificate ¶
func (s *Server) GetCertificate(*tls.ClientHelloInfo) (*tls.Certificate, error)
GetCertificate is tls.Config.GetCertificate.
func (*Server) Refresh ¶
Refresh starts serving the leaf the store holds now, replacing it only when it is within EmergencyWithin of expiry or misses a name this member needs. Every member runs it.
func (*Server) Renew ¶
Renew replaces the leaf when less than RenewWithin of its life remains. It is the cluster leader's duty, and reports whether a leaf was issued.
type ServerConfig ¶
type ServerConfig struct {
Monitor Monitor
Store *Store
Sealer Sealer
Authority *Authority
// Want are the names the monitor's sandboxes reach it at: SANsFor.
Want []string
Options Options
// Logf receives one line per certificate issued. Nil discards them.
Logf func(format string, args ...any)
}
ServerConfig describes one monitor's certificate.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is the hub_tls table. It is also the secretbroker.SealedSet through which `cloop hub key rotate` rewraps the keys it holds.
func NewStore ¶
NewStore adapts a control-plane database. Which database matters: the CA belongs to the hub, and a project's state.db is written by whoever can push to that project.
func (*Store) CountSealedByKey ¶
CountSealedByKey groups the rows by the key sealing them.
func (*Store) ListSealedNotUnder ¶
ListSealedNotUnder returns rows sealed under another key.
func (*Store) ReadCA ¶
func (s *Store) ReadCA() (*x509.Certificate, []byte, error)
ReadCA returns the CA certificate the control plane holds, without opening its key — what `cloop hub doctor` reads. ErrNotMinted when there is none.
func (*Store) ReadLeaf ¶
ReadLeaf returns a monitor's current leaf, without its key. ErrNotMinted when none has been issued.
func (*Store) ReplaceSealed ¶
ReplaceSealed swaps a row's key envelope under compare-and-swap.
func (*Store) SealedSetName ¶
SealedSetName identifies the set in rotation reports.