hubca

package
v0.0.6 Latest Latest
Warning

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

Go to latest
Published: Oct 16, 2026 License: MIT Imports: 26 Imported by: 0

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

View Source
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

View Source
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.

View Source
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

func NormalizeSANs(in []string) ([]string, error)

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

func SANsFor(advertiseBase, listenAddr string) ([]string, error)

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

func OpenAuthority(s *Store, sealer Sealer, o Options) (*Authority, error)

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) CertPEM

func (a *Authority) CertPEM() []byte

CertPEM returns a copy of the CA certificate, PEM-encoded. Public.

func (*Authority) Certificate

func (a *Authority) Certificate() *x509.Certificate

Certificate returns the CA certificate.

func (*Authority) Serial

func (a *Authority) Serial() string

Serial is the CA's serial in lowercase hex.

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.

func (*Leaf) SANs

func (l *Leaf) SANs() []string

SANs lists the names the leaf carries.

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.

const (
	// GitProxy is the git interception proxy (executors.git_proxy).
	GitProxy Monitor = "git_proxy"
	// KubeGuard is the Kubernetes access monitor (executors.kube_guard).
	KubeGuard Monitor = "kube_guard"
)

func Monitors

func Monitors() []Monitor

Monitors lists every monitor the hub issues for, in a stable order.

func (Monitor) ConfigKey

func (m Monitor) ConfigKey() string

ConfigKey is the configuration section the monitor is configured by.

func (Monitor) Label

func (m Monitor) Label() string

Label is how a message names the monitor.

func (Monitor) Valid

func (m Monitor) Valid() bool

Valid reports whether m is a monitor this package issues for.

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) CAPEM

func (s *Server) CAPEM() []byte

CAPEM returns the CA certificate sandboxes are told to trust. Public.

func (*Server) Current

func (s *Server) Current() *Leaf

Current returns the leaf being served.

func (*Server) GetCertificate

func (s *Server) GetCertificate(*tls.ClientHelloInfo) (*tls.Certificate, error)

GetCertificate is tls.Config.GetCertificate.

func (*Server) Monitor

func (s *Server) Monitor() Monitor

Monitor names what this server's certificate is for.

func (*Server) Refresh

func (s *Server) Refresh() (bool, error)

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

func (s *Server) Renew() (bool, error)

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.

func (*Server) TLSConfig

func (s *Server) TLSConfig(minVersion string) (*tls.Config, error)

TLSConfig is the listener's configuration: the hub's TLS parameters around this server's certificate.

func (*Server) Want

func (s *Server) Want() []string

Want returns the names this member asked for.

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

func NewStore(db *statedb.DB) (*Store, error)

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

func (s *Store) CountSealedByKey() (map[string]int, error)

CountSealedByKey groups the rows by the key sealing them.

func (*Store) ListSealedNotUnder

func (s *Store) ListSealedNotUnder(keyID string, limit int) ([]secretbroker.SealedRow, error)

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

func (s *Store) ReadLeaf(m Monitor) (LeafInfo, error)

ReadLeaf returns a monitor's current leaf, without its key. ErrNotMinted when none has been issued.

func (*Store) ReplaceSealed

func (s *Store) ReplaceSealed(id string, expect, next secretbroker.Envelope) (bool, error)

ReplaceSealed swaps a row's key envelope under compare-and-swap.

func (*Store) SealedSetName

func (s *Store) SealedSetName() string

SealedSetName identifies the set in rotation reports.

Jump to

Keyboard shortcuts

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