Documentation
¶
Overview ¶
Package cpbackup manages control plane self-backups: verified SQLite snapshots kept in a directory under the data dir.
Index ¶
- Constants
- Variables
- func DataKey(installID string, ts time.Time) string
- func LatestKey(ctx context.Context, b Bucket, prefix string) (string, error)
- func ManifestKey(dataKey string) string
- func ParseIdentityFile(path string) ([]age.Identity, error)
- func ParseKey(key string) (installID string, ts time.Time, err error)
- func ParseRecipients(keys []string) ([]age.Recipient, error)
- func RecipientOf(id age.Identity) (string, bool)
- func ValidName(name string) bool
- type AlertSource
- type Bucket
- type Check
- type Checklist
- type ConfigUpdate
- type Destinations
- type DrillResult
- type DrillStatus
- type Effective
- type Entry
- type EscrowBundle
- type EscrowInput
- type EscrowMaterial
- type EscrowPayload
- type Identity
- type Info
- type Location
- type Manager
- func (m *Manager) Create(ctx context.Context) (Info, error)
- func (m *Manager) CreateScheduled(ctx context.Context) (Info, error)
- func (m *Manager) Delete(name string) error
- func (m *Manager) List() ([]Info, error)
- func (m *Manager) Newest() (time.Time, bool, error)
- func (m *Manager) Open(name string) (*os.File, Info, error)
- func (m *Manager) Prune(retain int) (int, error)
- func (m *Manager) Run(ctx context.Context, interval time.Duration, retain int, logger *slog.Logger)
- func (m *Manager) Verify(ctx context.Context, name string) (VerifyResult, error)
- type Manifest
- type Options
- type Remote
- type ResolverDestinations
- type RestoreOptions
- type RestoreReport
- type Retention
- type Service
- func (s *Service) AckEscrow(ctx context.Context) error
- func (s *Service) BackupRunning() bool
- func (s *Service) BuildEscrow(ctx context.Context, in EscrowInput) (EscrowBundle, error)
- func (s *Service) CleanTemp()
- func (s *Service) DRProblem(now time.Time) string
- func (s *Service) DrillRunning() bool
- func (s *Service) ListRemote(ctx context.Context) ([]Remote, error)
- func (s *Service) Problem(now time.Time) string
- func (s *Service) Run(ctx context.Context)
- func (s *Service) RunBackup(ctx context.Context, slot time.Time) (Manifest, error)
- func (s *Service) RunDrill(ctx context.Context) (DrillResult, error)
- func (s *Service) StartBackup() error
- func (s *Service) StartDrill() error
- func (s *Service) Status(ctx context.Context) (Status, error)
- func (s *Service) Tick(ctx context.Context)
- func (s *Service) UpdateConfig(ctx context.Context, u ConfigUpdate) error
- type SettingsStore
- type Snapshotter
- type Source
- type Status
- type VerifyResult
- type Warning
Constants ¶
const ( EnvOffboxSchedule = "APP_CONTROL_PLANE_OFFBOX_SCHEDULE" EnvRetainDaily = "APP_CONTROL_PLANE_OFFBOX_RETAIN_DAILY" EnvRetainWeekly = "APP_CONTROL_PLANE_OFFBOX_RETAIN_WEEKLY" EnvRetainMonthly = "APP_CONTROL_PLANE_OFFBOX_RETAIN_MONTHLY" EnvMaxPrune = "APP_CONTROL_PLANE_OFFBOX_MAX_PRUNE_PER_RUN" EnvTick = "APP_CONTROL_PLANE_OFFBOX_TICK" EnvRetryAfter = "APP_CONTROL_PLANE_OFFBOX_RETRY_AFTER" EnvDrillSchedule = "APP_CONTROL_PLANE_DRILL_SCHEDULE" EnvDrillIdentityFile = "APP_CONTROL_PLANE_DRILL_IDENTITY_FILE" EnvGrace = "APP_CONTROL_PLANE_DR_GRACE" )
Env vars tuning off-box backups; unset or invalid values use the defaults.
const DirName = "control-plane-backups"
DirName is the backup directory's name under the data dir.
const EscrowPrefix = "cp-escrow"
EscrowPrefix is the key prefix for escrow bundles uploaded on explicit request.
const Prefix = "cp-backups"
Prefix is the top-level key prefix for off-box control plane backups.
Variables ¶
var ( // ErrInvalidName means the name does not match the backup file pattern. ErrInvalidName = errors.New("invalid backup name") // ErrNotFound means no backup with that name exists. ErrNotFound = errors.New("backup not found") )
var ( ErrChecksumMismatch = errors.New("downloaded backup does not match its manifest checksum") ErrNewerSchema = errors.New("backup schema is newer than this binary supports") ErrInstallMismatch = errors.New("backup belongs to a different install") )
Errors a restore can end with.
var ( ErrBusy = errors.New("a run is already in progress") ErrNotConfigured = errors.New("off-box backups are not configured: choose a destination and at least one recipient") ErrNoBackups = errors.New("no complete backup exists at the destination yet") )
Errors callers map to responses.
var ErrBadManifest = errors.New("invalid backup manifest")
ErrBadManifest means a manifest is unreadable or self-inconsistent.
var ErrEscrowSameBucket = errors.New("the escrow destination is the same bucket as the backup destination, which defeats the point of escrow: choose a separate destination")
ErrEscrowSameBucket means the escrow upload would land beside the backups it protects.
var ErrInvalid = errors.New("invalid configuration")
ErrInvalid marks a configuration the operator must fix.
var ErrNoRecipients = errors.New("at least one age recipient (public key) is required")
ErrNoRecipients means an encryption was asked for without any recipient.
var ErrWrongIdentity = errors.New("none of the supplied identities can decrypt this backup")
ErrWrongIdentity means no supplied identity can decrypt the file.
Functions ¶
func ManifestKey ¶
ManifestKey is the manifest object key beside a ciphertext key.
func ParseIdentityFile ¶
ParseIdentityFile parses an age identity file (one key per line, comments allowed).
func ParseRecipients ¶
ParseRecipients parses age public keys, X25519 (age1...) or hybrid (age1pq1...).
func RecipientOf ¶
RecipientOf derives the public recipient string from a parsed identity, when it has one.
Types ¶
type AlertSource ¶
type AlertSource struct {
Local *Manager
Svc *Service
// LocalScheduled is false when scheduled local snapshots are switched off,
// so their absence is not staleness.
LocalScheduled bool
}
AlertSource adapts the local snapshot manager and the off-box service to the stale-backup alert rule: when off-box backups are enabled their age counts.
type Bucket ¶
type Bucket interface {
Put(ctx context.Context, key string, body io.ReadSeeker, contentType, contentEncoding string) error
Get(ctx context.Context, key string) (io.ReadCloser, int64, error)
Delete(ctx context.Context, key string) error
List(ctx context.Context, prefix, token string, limit int32) (objectstore.ListPage, error)
}
Bucket is the object store surface off-box backups use; *objectstore.Client satisfies it.
type Checklist ¶
type Checklist struct {
DestinationChosen bool `json:"destination_chosen"`
RecipientSet bool `json:"recipient_set"`
EscrowAcknowledged bool `json:"escrow_acknowledged"`
DrillPassed bool `json:"drill_passed"`
}
Checklist is the guided setup progress.
type ConfigUpdate ¶
type ConfigUpdate struct {
Enabled bool
TargetID string
Recipients []string
Schedule string
DrillSchedule string
Retention Retention
EscrowTargetID string
}
ConfigUpdate is the operator-editable configuration.
type Destinations ¶
type Destinations interface {
Open(ctx context.Context, targetID string) (Bucket, Location, error)
}
Destinations resolves a stored storage destination to a bucket client.
type DrillResult ¶
type DrillResult struct {
At time.Time `json:"at"`
OK bool `json:"ok"`
Partial bool `json:"partial"`
Detail string `json:"detail"`
BackupKey string `json:"backup_key,omitempty"`
Duration time.Duration `json:"duration_ns"`
Checks []Check `json:"checks"`
}
DrillResult is the outcome of one restore drill.
type DrillStatus ¶
type DrillStatus struct {
At *time.Time `json:"at,omitempty"`
OK bool `json:"ok"`
Partial bool `json:"partial"`
Detail string `json:"detail"`
DurationMs int64 `json:"duration_ms"`
}
DrillStatus is the last drill outcome.
type Effective ¶
type Effective struct {
Schedule string `json:"schedule"`
DrillSchedule string `json:"drill_schedule"`
Retention Retention `json:"-"`
}
Effective is the configuration in force after applying env defaults.
type EscrowBundle ¶
type EscrowBundle struct {
Armored string `json:"armored"`
Instructions string `json:"instructions"`
Fingerprint string `json:"fingerprint"`
RecipientCount int `json:"recipient_count"`
CreatedAt time.Time `json:"created_at"`
UploadedKey string `json:"uploaded_key,omitempty"`
}
EscrowBundle is a generated escrow file: armored age ciphertext plus a plain-text instruction sheet to store beside it.
type EscrowInput ¶
type EscrowInput struct {
EscrowMaterial
Recipients []string
Upload bool
}
EscrowInput describes one escrow bundle to build. Recipients, when non-empty, replace the configured ones for this bundle.
type EscrowMaterial ¶
EscrowMaterial is what an escrow bundle protects.
type EscrowPayload ¶
type EscrowPayload struct {
Version int `json:"version"`
CreatedAt time.Time `json:"created_at"`
InstallID string `json:"install_id"`
BinaryVersion string `json:"binary_version"`
// MasterKey is the serialized master key identity. Never log it.
MasterKey string `json:"master_key"`
MasterRecipient string `json:"master_recipient"`
// Files holds other recovery material by file name, such as the agent CA
// key that lives outside the database. Never log its values.
Files map[string]string `json:"files,omitempty"`
Instructions string `json:"instructions"`
}
EscrowPayload is what an escrow bundle decrypts to.
func OpenEscrow ¶
func OpenEscrow(bundle []byte, identities []age.Identity) (EscrowPayload, error)
OpenEscrow decrypts an escrow bundle with an identity that matches one of its recipients.
type Identity ¶
type Identity struct {
// Recipient is the public key, safe to store and share.
Recipient string
// Secret is the private key line. Never log it.
Secret string
}
Identity is a freshly generated age keypair.
func GenerateIdentity ¶
GenerateIdentity makes an X25519 keypair, or a hybrid post-quantum one when hybrid is set.
type Info ¶
type Info struct {
Name string `json:"name"`
SizeBytes int64 `json:"size_bytes"`
CreatedAt time.Time `json:"created_at"`
SHA256 string `json:"sha256"`
// VerifiedAt and VerifiedOK describe the last verification, if any.
VerifiedAt *time.Time `json:"verified_at,omitempty"`
VerifiedOK *bool `json:"verified_ok,omitempty"`
}
Info describes one backup.
type Location ¶
type Location struct {
TargetID string `json:"target_id"`
Name string `json:"name"`
Endpoint string `json:"endpoint"`
Bucket string `json:"bucket"`
}
Location identifies a bucket well enough to tell whether two destinations are the same one.
func (Location) SameBucket ¶
SameBucket reports whether two locations point at one bucket.
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager creates, lists, and prunes backups in one directory.
func NewManager ¶
func NewManager(db Snapshotter, dataDir string) *Manager
NewManager returns a Manager storing backups under dataDir/DirName.
func (*Manager) CreateScheduled ¶
CreateScheduled takes a snapshot that Prune may later remove.
func (*Manager) Newest ¶
Newest returns the creation time of the most recent backup, read from file names alone (no checksumming), and false when there is none.
func (*Manager) Prune ¶
Prune deletes the oldest scheduled backups beyond retain. Manual backups are never touched.
type Manifest ¶
type Manifest struct {
ManifestVersion int `json:"manifest_version"`
InstallID string `json:"install_id"`
Key string `json:"key"`
CreatedAt time.Time `json:"created_at"`
BinaryVersion string `json:"binary_version"`
SchemaVersion int `json:"schema_version"`
// MigrationsApplied is the row count of the snapshot's schema_migrations.
MigrationsApplied int `json:"migrations_applied"`
SHA256 string `json:"sha256"`
SizeBytes int64 `json:"size_bytes"`
PlainSHA256 string `json:"plain_sha256"`
PlainSizeBytes int64 `json:"plain_size_bytes"`
Compression string `json:"compression"`
Encryption string `json:"encryption"`
RecipientCount int `json:"recipient_count"`
// ContainsWrappedSecrets is true: the database holds secret ciphertexts and
// wrapped data keys. IncludesMasterKey is always false.
ContainsWrappedSecrets bool `json:"contains_wrapped_secrets"`
IncludesMasterKey bool `json:"includes_master_key"`
}
Manifest describes one off-box backup. It is stored unencrypted beside the ciphertext and holds no key material.
func FetchManifest ¶
FetchManifest downloads and parses the manifest for a ciphertext key.
func ParseManifest ¶
ParseManifest decodes and sanity-checks a manifest.
type Options ¶
type Options struct {
Schedule string
DrillSchedule string
Retention Retention
MaxPrune int
Tick time.Duration
RetryAfter time.Duration
// Grace is how late a scheduled run may be before it counts as overdue.
Grace time.Duration
// OrphanAge is how old an object without its partner must be before pruning removes it.
OrphanAge time.Duration
}
Options tunes off-box backups and drills.
func DefaultOptions ¶
func DefaultOptions() Options
DefaultOptions returns the built-in tuning: daily backups at 02:00, weekly drills.
type Remote ¶
type Remote struct {
Key string `json:"key"`
ManifestKey string `json:"manifest_key"`
CreatedAt time.Time `json:"created_at"`
SizeBytes int64 `json:"size_bytes"`
// Complete is true when both the ciphertext and its manifest exist.
Complete bool `json:"complete"`
// Modified is the newest object modification time, used to age out orphans.
Modified time.Time `json:"-"`
}
Remote is one backup found at the destination.
type ResolverDestinations ¶
type ResolverDestinations struct{ Resolver *objectstore.Resolver }
ResolverDestinations adapts an objectstore.Resolver to Destinations.
type RestoreOptions ¶
type RestoreOptions struct {
// LivePath is the control plane database file to replace.
LivePath string
Identities []age.Identity
// DryRun verifies everything and leaves the live database untouched.
DryRun bool
// ForceInstallID accepts a backup taken by a different install.
ForceInstallID bool
Now func() time.Time
// BeforeInstall runs just before the live database is replaced; an error aborts the restore.
BeforeInstall func() error
// contains filtered or unexported fields
}
RestoreOptions control one restore.
type RestoreReport ¶
type RestoreReport struct {
Source string `json:"source"`
Manifest Manifest `json:"manifest"`
SchemaVersion int `json:"schema_version"`
Checks []Check `json:"checks"`
DryRun bool `json:"dry_run"`
PreRestore string `json:"pre_restore,omitempty"`
}
RestoreReport is what a restore checked and did.
func Restore ¶
func Restore(ctx context.Context, src Source, opts RestoreOptions) (RestoreReport, error)
Restore downloads, verifies and decrypts a backup, then swaps it in atomically. The live database file is replaced by one rename, so an interruption at any earlier point leaves the old database untouched.
type Retention ¶
type Retention struct {
Daily, Weekly, Monthly int
}
Retention is how many backups to keep per calendar bucket. A backup is kept if it is the newest of any day, ISO week or month still inside its count.
type Service ¶
type Service struct {
DB Snapshotter
Store SettingsStore
Dest Destinations
DataDir string
BinaryVersion string
Opts Options
Logger *slog.Logger
Now func() time.Time
// DrillIdentities, when set, decrypt backups during drills. Their public
// keys are added as recipients of every backup.
DrillIdentities []age.Identity
// contains filtered or unexported fields
}
Service runs encrypted off-box control plane backups, restore drills and escrow.
func NewService ¶
func NewService(db Snapshotter, st SettingsStore, dest Destinations, dataDir, binaryVersion string, opts Options, logger *slog.Logger) *Service
NewService builds a Service with the wall clock.
func (*Service) BackupRunning ¶
BackupRunning reports whether a backup is in progress.
func (*Service) BuildEscrow ¶
func (s *Service) BuildEscrow(ctx context.Context, in EscrowInput) (EscrowBundle, error)
BuildEscrow encrypts the master key and other recovery files to the operator's recipients. The drill identity is never a recipient of escrow.
func (*Service) CleanTemp ¶
func (s *Service) CleanTemp()
CleanTemp removes work directories a crashed run left behind. They can hold a plaintext snapshot, so it runs at startup when no run can be in progress.
func (*Service) DRProblem ¶
DRProblem lets the stale-backup alert rule fire on off-box failures and failed drills.
func (*Service) DrillRunning ¶
DrillRunning reports whether a drill is in progress.
func (*Service) ListRemote ¶
ListRemote lists the backups one install has at the destination, newest first.
func (*Service) Problem ¶
Problem returns a one-line description of what is wrong with the running off-box backups or drills, or "" when healthy or not enabled.
func (*Service) RunBackup ¶
RunBackup snapshots the database, encrypts it to the configured recipients and uploads it with a manifest. slot names the schedule tick (zero for a manual run): a slot whose manifest already exists is skipped, and one whose upload was cut short is redone over the same keys.
func (*Service) RunDrill ¶
func (s *Service) RunDrill(ctx context.Context) (DrillResult, error)
RunDrill downloads the newest complete backup and proves it can be restored, into a temporary directory, never the live database. With a drill identity configured it decrypts and integrity-checks; without one it verifies only the ciphertext checksum and manifest and reports itself as partial.
func (*Service) StartBackup ¶
StartBackup begins a manual backup in the background and returns at once. ErrBusy means one is already running.
func (*Service) StartDrill ¶
StartDrill begins a drill in the background and returns at once. ErrBusy means one is already running.
func (*Service) Tick ¶
Tick runs whatever is due right now: at most one backup, then at most one drill.
func (*Service) UpdateConfig ¶
func (s *Service) UpdateConfig(ctx context.Context, u ConfigUpdate) error
UpdateConfig validates and stores the configuration.
type SettingsStore ¶
type SettingsStore interface {
GetCPDRSettings(ctx context.Context) (store.CPDRSettings, error)
UpdateCPDRConfig(ctx context.Context, s store.CPDRSettings, now time.Time) error
SetCPDRInstallID(ctx context.Context, id string) error
RecordCPDRBackup(ctx context.Context, at time.Time, key, runErr string) error
RecordCPDRDrill(ctx context.Context, at time.Time, ok, partial bool, detail string, ms int64) error
RecordCPDREscrow(ctx context.Context, at time.Time) error
AckCPDREscrow(ctx context.Context, at time.Time) error
}
SettingsStore is the persistence surface the service needs.
type Snapshotter ¶
Snapshotter is the slice of *store.DB the manager needs.
type Source ¶
type Source interface {
Describe() string
ReadManifest(ctx context.Context) (Manifest, error)
Fetch(ctx context.Context, dst string, maxBytes int64) error
}
Source is where a restore reads an encrypted backup from.
func NewBucketSource ¶
NewBucketSource reads the backup at key from a bucket.
func NewFileSource ¶
NewFileSource reads an encrypted backup from disk. Its manifest is the sibling file with the .json suffix.
type Status ¶
type Status struct {
Enabled bool `json:"enabled"`
Configured bool `json:"configured"`
TargetID string `json:"target_id"`
TargetName string `json:"target_name,omitempty"`
InstallID string `json:"install_id"`
Recipients []string `json:"recipients"`
Schedule string `json:"schedule"`
DrillSchedule string `json:"drill_schedule"`
RetainDaily int `json:"retain_daily"`
RetainWeekly int `json:"retain_weekly"`
RetainMonthly int `json:"retain_monthly"`
EscrowTargetID string `json:"escrow_target_id"`
EscrowGeneratedAt *time.Time `json:"escrow_generated_at,omitempty"`
EscrowAckedAt *time.Time `json:"escrow_acked_at,omitempty"`
NextBackupAt *time.Time `json:"next_backup_at,omitempty"`
NextDrillAt *time.Time `json:"next_drill_at,omitempty"`
LastBackupAt *time.Time `json:"last_backup_at,omitempty"`
LastBackupKey string `json:"last_backup_key,omitempty"`
LastBackupError string `json:"last_backup_error,omitempty"`
LastAttemptAt *time.Time `json:"last_attempt_at,omitempty"`
LastDrill DrillStatus `json:"last_drill"`
DrillIdentityConfigured bool `json:"drill_identity_configured"`
BackupRunning bool `json:"backup_running"`
DrillRunning bool `json:"drill_running"`
Checklist Checklist `json:"checklist"`
Warnings []Warning `json:"warnings"`
}
Status is the disaster recovery state shown in the UI, CLI and doctor. It carries public recipients only, never key material.