Documentation
¶
Overview ¶
Package update finds, downloads and verifies new releases of Regulus. The hub drives it (see the design in the README): it asks the release source what is newest on the chosen channel and keeps a verified copy in a local cache, ready for whoever applies it.
Index ¶
- Constants
- Variables
- func ApplyDir(dataDir string) string
- func ArchiveName(v Version, goos, goarch string) string
- func IsUpstream(err error) bool
- func WriteConnected(dataDir string, c Connected) error
- func WriteRequest(dir string, req ApplyRequest) error
- func WriteResult(dir string, res ApplyResult) error
- type ApplyRequest
- type ApplyResult
- type Asset
- type Auditor
- type Channel
- type Compat
- type Connected
- type Fetcher
- type GitHubSource
- type Info
- type Installer
- type Manager
- func (m *Manager) Apply(ctx context.Context, by string) (Status, error)
- func (m *Manager) Check(ctx context.Context) (Status, error)
- func (m *Manager) Close()
- func (m *Manager) Download(ctx context.Context, by string) (Status, error)
- func (m *Manager) EnsureRelease(ctx context.Context, version string, plats []Platform) error
- func (m *Manager) Files() *Stager
- func (m *Manager) Run(ctx context.Context)
- func (m *Manager) SetSettings(ctx context.Context, by string, s Settings) (Status, error)
- func (m *Manager) Settings(ctx context.Context) (Settings, error)
- func (m *Manager) Status(ctx context.Context) (Status, error)
- type Mode
- type Options
- type Platform
- type Release
- type Runner
- type Settings
- type SigstoreVerifier
- type Source
- type Staged
- type Stager
- func (s *Stager) Current() (Staged, bool)
- func (s *Stager) HasArchives(version string, plats ...Platform) bool
- func (s *Stager) Offer(version string, p Platform) (name, sha string, size int64, err error)
- func (s *Stager) Open(version, name string) (*os.File, int64, error)
- func (s *Stager) Stage(ctx context.Context, rel Release, goos, goarch string) (Staged, error)
- func (s *Stager) StageFetched(ctx context.Context, v Version, plat Platform, wantSHA string, fetch Fetcher) (Staged, error)
- func (s *Stager) StagePlatforms(ctx context.Context, rel Release, plats ...Platform) (Staged, error)
- func (s *Stager) Verify(st Staged) error
- type Status
- type TrustSource
- type Verifier
- type Version
Constants ¶
const ( // OutcomeInstalling is written when the helper starts and replaced when it // ends. OutcomeInstalling = "installing" OutcomeApplied = "applied" // OutcomeRolledBack: the new version was installed but did not come up // healthy, so the previous one was put back. OutcomeRolledBack = "rolled_back" // OutcomeFailed: nothing was changed. OutcomeFailed = "failed" )
Outcomes of an installation.
const ( FileChecksums = checksumsName FileBundle = bundleName )
Names of the files of a release, for Fetcher.
const ( EventSettings = "update_settings_changed" EventDownloaded = "update_downloaded" EventDownloadFailed = "update_download_failed" EventApplyRequested = "update_apply_requested" EventApplied = "update_applied" EventRolledBack = "update_rolled_back" EventApplyFailed = "update_apply_failed" )
Audit events recorded by the manager.
const ConnectedName = "connected.json"
ConnectedName is the file an agent writes when the hub accepted it.
const ( // DefaultCheckEvery is how often the hub asks the release server. DefaultCheckEvery = 6 * time.Hour )
const SystemActor = "system"
SystemActor is the actor of what the hub does by itself.
const ( // VerifiedChecksum: the archive matches the SHA-256 in the release's // checksums.txt. That catches a corrupted or truncated download, but both // files come from the same place, so it does not prove who made them. // A Stager without a Verifier stops here. VerifiedChecksum = "checksum" )
How far a staged release was checked.
const VerifiedSignature = "signature"
VerifiedSignature: the checksums file carries a valid Sigstore signature made by the release workflow of this repository for this very tag, and the archive matches it.
Variables ¶
var ( // ErrInvalidSettings wraps the explanation of settings that cannot be saved. ErrInvalidSettings = errors.New("invalid update settings") ErrChecksOff = errors.New("update checks are turned off (updates.check in the configuration)") ErrBusy = errors.New("an update check or download is already running") ErrTooSoon = errors.New("the release server was asked a moment ago; wait a little before asking again") ErrNothingToFetch = errors.New("there is no newer release to download") ErrNotReleaseBuild = errors.New("this build is not a release, so it cannot tell whether a release is newer") ErrApplyDisabled = errors.New("installing updates from the dashboard is turned off (updates.apply); it needs the regulus-update.path unit the installer sets up") ErrNothingToApply = errors.New("there is no downloaded update to install") ErrNotVerified = errors.New("the downloaded update was not verified by its signature, so it will not be installed") ErrApplying = errors.New("an update is already being installed") )
Errors returned by the manager.
var ErrNoRequest = errors.New("there is no update request")
ErrNoRequest is returned by the installer when the hub left nothing to do.
Functions ¶
func ApplyDir ¶
ApplyDir is the directory, inside the hub's data directory, that the hub and the helper share. systemd watches the request file in it.
func ArchiveName ¶
ArchiveName is the name GoReleaser gives the archive of a platform.
func IsUpstream ¶
IsUpstream reports whether err is a failure of the release server or of the way to it, not of the hub.
func WriteConnected ¶
WriteConnected records that the agent of dataDir is connected.
func WriteRequest ¶
func WriteRequest(dir string, req ApplyRequest) error
WriteRequest leaves a request for the helper. The file appears all at once.
func WriteResult ¶
func WriteResult(dir string, res ApplyResult) error
WriteResult records the outcome. It is readable by the hub's user.
Types ¶
type ApplyRequest ¶
type ApplyRequest struct {
Version string `json:"version"`
RequestedBy string `json:"requested_by"`
RequestedAt time.Time `json:"requested_at"`
}
ApplyRequest asks the helper to install a staged release.
func ReadRequest ¶
func ReadRequest(dir string) (ApplyRequest, bool)
ReadRequest returns the request waiting for the helper, if any.
type ApplyResult ¶
type ApplyResult struct {
Version string `json:"version"`
From string `json:"from,omitempty"`
Outcome string `json:"outcome"`
Message string `json:"message,omitempty"`
RequestedBy string `json:"requested_by,omitempty"`
At time.Time `json:"at"`
}
ApplyResult is what the helper reports.
func ReadResult ¶
func ReadResult(dir string) (ApplyResult, bool)
ReadResult returns the last outcome the helper wrote, if any.
type Channel ¶
type Channel string
Channel decides which releases are offered.
type Compat ¶
type Compat string
Compat tells how the release of an agent relates to the hub's.
const ( // CompatCurrent: the same release. CompatCurrent Compat = "current" // CompatBehind: older than the hub but still supported (the previous // minor release, or any release of the previous major). It connects; the // dashboard suggests updating it. CompatBehind Compat = "behind" // CompatAhead: newer than the hub. The hub is updated first, so this // means the hub is the one to update. It connects if the protocol allows. CompatAhead Compat = "ahead" // CompatTooOld: further behind than the hub supports. It is refused. CompatTooOld Compat = "too_old" // CompatUnknown: one of the two is not a release build (a development // build, or a version that cannot be read). It connects. CompatUnknown Compat = "unknown" )
func CheckCompat ¶
CheckCompat applies the rule "the hub accepts agents of its own release series and the previous one": the same major and minor, one minor behind, or any release of the previous major. The protocol version is negotiated separately and can still refuse an agent this accepts.
type Connected ¶
Connected is written by an agent each time the hub accepts it. After an update the helper reads it: an agent that connected again with the new version is a healthy one.
func ReadConnected ¶
ReadConnected returns what the agent of dataDir last wrote.
type Fetcher ¶
Fetcher writes one file of a release to w, from wherever the release is offered. file is FileChecksums, FileBundle or the name of the archive.
type GitHubSource ¶
type GitHubSource struct {
// Repo is "owner/name".
Repo string
// BaseURL is the API address; empty means https://api.github.com.
BaseURL string
// UserAgent identifies the hub to GitHub.
UserAgent string
Client *http.Client
// contains filtered or unexported fields
}
GitHubSource reads the releases of a repository from the GitHub API. The repository is public, so no token is needed; the unauthenticated limit (60 requests an hour per address) is respected with conditional requests and by the caller checking only every few hours.
type Info ¶
type Info struct {
Version string `json:"version"`
Tag string `json:"tag"`
Prerelease bool `json:"prerelease"`
Channel Channel `json:"channel"`
URL string `json:"url"`
Notes string `json:"notes,omitempty"`
PublishedAt time.Time `json:"published_at"`
}
Info is a release as shown to the administrator.
type Installer ¶
type Installer struct {
// DataDir is the hub's data directory; the staged release and the request
// are in it.
DataDir string
// StateDir belongs to root: the copy of the previous binary and the cache
// of the signature trust root.
StateDir string
// Binary is the executable to replace, for example /usr/bin/regulus.
Binary string
// Unit is the service to restart, for example regulus.service.
Unit string
Verifier Verifier
Run Runner
// Agent is true on a server that runs the agent: its health is that it
// connected to the hub again (see WriteConnected), not the hub's local
// status socket.
Agent bool
// Probe returns the version of the service that is running, or an error
// while it is not up. Only a process that started at or after since counts:
// the one that was there before the restart does not.
Probe func(ctx context.Context, since time.Time) (string, error)
// HealthTimeout is how long the new version has to come up; it must then
// keep running for Stable. Poll is how often it is looked at.
HealthTimeout, Stable, Poll time.Duration
GOOS, GOARCH string
Now func() time.Time
Log *slog.Logger
}
Installer is the helper that runs as root. Nothing it is given by the hub's user is trusted: the request only says which version, and the archive is checked again here, signature included, before a byte of it is installed.
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager looks for new releases and keeps the newest one downloaded. It does not apply anything.
func (*Manager) Apply ¶
Apply asks the helper that runs as root to install the downloaded release, and returns at once. The hub restarts when the helper replaces it; the outcome is in Status afterwards, and in the audit log.
func (*Manager) Check ¶
Check asks the release server now, for an administrator who pressed the button. It refuses to do so more than once in a short while.
func (*Manager) Close ¶
func (m *Manager) Close()
Close waits for a download in progress to end. Cancel the context given to Run first.
func (*Manager) Download ¶
Download starts downloading the newest release on the channel in the background and returns at once; Status shows the progress. The download is checked against the release's checksums before it is kept.
func (*Manager) EnsureRelease ¶
EnsureRelease makes sure the cache holds the release of this very version, signature checked, with the archive of every platform given, so the hub can serve it to the agents it manages. It downloads what is missing.
func (*Manager) Run ¶
Run checks for updates now and then until ctx ends. It returns at once when checks are off.
func (*Manager) SetSettings ¶
SetSettings saves the choices. Changing the channel makes the last answer stale, so a new check starts at once.
type Mode ¶
type Mode string
Mode decides what the hub does when a newer release exists.
const ( // ModeManual only tells the administrator that a new version exists. ModeManual Mode = "manual" // ModeDownload also downloads and checks it, then waits to be told to // apply it. ModeDownload Mode = "download" // ModeAutomatic also installs it, by itself, inside the maintenance // window. A version that was rolled back is not tried again. ModeAutomatic Mode = "automatic" )
type Options ¶
type Options struct {
DB *sql.DB
Log *slog.Logger
Source Source
Stager *Stager
Audit Auditor
// Version is the version the binary was built with.
Version string
GOOS, GOARCH string
// ApplyDir is the directory shared with the root helper (see ApplyDir);
// empty when installing from the dashboard is turned off.
ApplyDir string
// Enabled is false when the hub must not contact the release server.
Enabled bool
CheckEvery time.Duration
// Now may be replaced in tests.
Now func() time.Time
}
Options configure a Manager.
type Platform ¶
type Platform struct{ OS, Arch string }
Platform is an operating system and architecture a release is built for.
type Release ¶
type Release struct {
Version Version
Tag string
URL string // page of the release, for the release notes
Notes string
PublishedAt time.Time
Assets []Asset
}
Release is a published version of the core.
type Settings ¶
type Settings struct {
Mode Mode `json:"mode"`
Channel Channel `json:"channel"`
// WindowStart and WindowEnd ("15:04", the hub's local time) bound when the
// automatic mode may install. They may cross midnight.
WindowStart string `json:"window_start,omitempty"`
WindowEnd string `json:"window_end,omitempty"`
}
Settings are the update choices an administrator makes in the dashboard.
type SigstoreVerifier ¶
type SigstoreVerifier struct {
// Trust provides the trusted root; nil uses PublicTrust with no cache.
Trust TrustSource
// Signer is the expected identity (the certificate's subject alternative
// name) for a tag; nil expects the release workflow of this repository.
Signer func(tag string) string
// Issuer is the expected OIDC issuer; empty means GitHub Actions.
Issuer string
// contains filtered or unexported fields
}
SigstoreVerifier verifies bundles made by cosign's keyless signing.
type Staged ¶
type Staged struct {
Version string `json:"version"`
Tag string `json:"tag"`
Archive string `json:"archive"`
SHA256 string `json:"sha256"`
Size int64 `json:"size"`
Verified string `json:"verified"`
StagedAt time.Time `json:"staged_at"`
// Signed is true when the signature bundle of checksums.txt was published
// and kept next to it. Verified says whether it was checked.
Signed bool `json:"signed"`
}
Staged describes a downloaded release kept in the cache.
type Stager ¶
type Stager struct {
// Dir is the cache; it is created when needed.
Dir string
Client *http.Client
Now func() time.Time
// Verifier, if set, checks the signature of checksums.txt before anything
// else is downloaded; a release without a valid signature is refused.
Verifier Verifier
// Keep, if set, is a version that is never pruned: the one this server runs,
// whose files the hub serves to the agents it manages.
Keep string
}
Stager downloads releases into a cache directory.
func (*Stager) Current ¶
Current returns the staged release in the cache, if there is one and its files are still intact.
func (*Stager) HasArchives ¶
HasArchives reports whether the staged release of a version holds the archive of every platform given, and has its signature bundle.
func (*Stager) Offer ¶
Offer describes the archive of a platform in the staged release of a version, from the signed checksum list: its name, SHA-256 and size.
func (*Stager) Open ¶
Open opens a file of a staged release so it can be served to an agent. The name must be the checksum list, the signature bundle or an archive of that release; nothing else in the cache is reachable.
func (*Stager) Stage ¶
Stage downloads the archive of the release for a platform, checks it against the release's checksums.txt and keeps it, with the checksums and the signature bundle, in Dir/<version>/. Any other staged version is removed. Nothing reaches the final place unless the checksum matches.
func (*Stager) StageFetched ¶
func (s *Stager) StageFetched(ctx context.Context, v Version, plat Platform, wantSHA string, fetch Fetcher) (Staged, error)
StageFetched keeps a release that was offered by a hub rather than found on GitHub: the files are fetched, the archive is compared with the checksum list (and with wantSHA, the value the hub named), and the signature of the list is verified when the Stager has a Verifier. The result is laid out like Stage's, so the helper that runs as root installs it the same way.
func (*Stager) StagePlatforms ¶
func (s *Stager) StagePlatforms(ctx context.Context, rel Release, plats ...Platform) (Staged, error)
StagePlatforms is Stage for several platforms at once: the hub also serves the archives of the servers it manages. The first platform is the one described by the returned Staged.
type Status ¶
type Status struct {
// CurrentVersion is the version of this hub as it was built.
CurrentVersion string `json:"current_version"`
// ReleaseBuild is false for a development build, which cannot compare
// itself with releases: no update is ever reported for it.
ReleaseBuild bool `json:"release_build"`
// ChecksEnabled is false when updates.check is off: the hub never contacts
// the release server.
ChecksEnabled bool `json:"checks_enabled"`
Mode Mode `json:"mode"`
Channel Channel `json:"channel"`
WindowStart string `json:"window_start,omitempty"`
WindowEnd string `json:"window_end,omitempty"`
// Platform is the build of the release this hub would download.
Platform string `json:"platform"`
CheckedAt *time.Time `json:"checked_at"`
CheckError string `json:"check_error,omitempty"`
Checking bool `json:"checking"`
// Latest is the newest release on the channel at the last check.
Latest *Info `json:"latest"`
UpdateAvailable bool `json:"update_available"`
// Staged is the downloaded and checked release waiting in the cache.
Staged *Staged `json:"staged"`
Downloading bool `json:"downloading"`
DownloadError string `json:"download_error,omitempty"`
// ApplyEnabled is true when this hub can have an update installed (the
// helper is set up; see updates.apply). ApplyAvailable adds that a
// verified release is waiting. Applying is true from the request until the
// helper has finished, and ApplyStalled when nothing picked the request up.
ApplyEnabled bool `json:"apply_enabled"`
ApplyAvailable bool `json:"apply_available"`
Applying bool `json:"applying"`
ApplyStalled bool `json:"apply_stalled,omitempty"`
// LastApply is the outcome of the last installation.
LastApply *ApplyResult `json:"last_apply"`
}
Status is what the hub knows about updates, as the API reports it.
type TrustSource ¶
type TrustSource func(ctx context.Context) (root.TrustedMaterial, error)
TrustSource provides the Sigstore trusted material.
func PublicTrust ¶
func PublicTrust(cacheDir string) TrustSource
PublicTrust returns the trust source for the public Sigstore instance. It asks the Sigstore TUF repository for the current trusted root (so a key rotation reaches a hub that nobody updated), keeping the answer in cacheDir (empty: not cached on disk), and falls back to the snapshot embedded in the binary when the repository cannot be reached.
type Verifier ¶
type Verifier interface {
// Verify checks the Sigstore bundle of checksums for the release at tag.
Verify(ctx context.Context, tag string, checksums, bundleJSON []byte) error
}
Verifier checks the signature of a release's checksums.txt.
type Version ¶
type Version struct {
Major, Minor, Patch uint64
// Pre holds the dot-separated pre-release identifiers, such as ["beta", "1"].
Pre []string
}
Version is a SemVer 2.0 version.
func ParseBuildVersion ¶
ParseBuildVersion reads the version a binary was built with (-ldflags "-X main.version=..."). It reports ok = false for builds that are not a release: "dev", or the output of `git describe` for a commit after a tag. Such a build cannot tell whether a release is newer.
func ParseVersion ¶
ParseVersion reads a version such as "1.4.0", "v1.4.0" or "v1.5.0-beta.1". Build metadata (+...) is accepted and ignored, as SemVer says it must be when comparing.
func (Version) Channel ¶
Channel is the channel a release of this version belongs to: Stable for a release, Beta for a pre-release named beta or rc (1.5.0-beta.1, 1.5.0-rc.1), and Alpha for any other pre-release (1.5.0-alpha.1), so an unknown kind is never offered to more cautious channels.
func (Version) Compare ¶
Compare returns -1, 0 or 1 as v is older than, equal to or newer than o, following SemVer precedence: a pre-release is older than its release.
func (Version) Prerelease ¶
Prerelease reports whether the version is a pre-release (beta or alpha).