Documentation
¶
Overview ¶
Package update implements `redoubt update`: opt-in, never silent, cosign-verified updates of the platform images (plan §3, golden guardrail "auto-update is an attack vector").
Verification is Sigstore keyless: the release workflow signs every image with a short-lived Fulcio certificate bound to its GitHub OIDC identity and records the signature in the Rekor transparency log. This package re-implements the verification cosign performs with the standard library only (golden rule 7 — no new runtime dependency):
- the ECDSA/Ed25519/RSA signature over the signed payload verifies with the certificate's public key;
- the certificate chains to the embedded Fulcio roots (the intermediate ships in the signature) and was valid at the time Rekor integrated the entry;
- the certificate's SubjectAlternativeName URI matches the expected identity (the release workflow of this repository at a release tag) and its OIDC-issuer extension matches the expected issuer (GitHub Actions);
- the Rekor bundle's signed entry timestamp verifies with the embedded Rekor public key and its body is the hashedrekord of exactly this payload, signature and certificate.
The trust roots are the ones published by the Sigstore public-good instance, vendored in trust/; an operator (or a demo/test) can point at a different root directory.
Index ¶
- Constants
- Variables
- func Apply(o Options, plan *Plan) (trigger string, err error)
- func AutoUpdateFlag(etcDir string) string
- func EnvKey(name string) string
- func GetEnv(path, key string) (string, error)
- func LatestVersion(ctx context.Context, hc *http.Client, repo string) (string, error)
- func PayloadDigest(payload []byte) (string, error)
- func SetEnv(path string, values map[string]string) error
- type Actor
- type Event
- type Identity
- type Options
- type Plan
- type Policy
- type Recorder
- type Ref
- type Registry
- type Rekor
- type RekorBundle
- type Signature
- type TrustRoot
- type Verified
Constants ¶
const ( DefaultIssuer = "https://token.actions.githubusercontent.com" DefaultIdentityRe = `^https://github\.com/BetV3/redoubt/\.github/workflows/release\.yml@refs/tags/v[0-9]+\.[0-9]+\.[0-9]+` DefaultRegistry = "ghcr.io/betv3/redoubt" DefaultRekorURL = "https://rekor.sigstore.dev" DefaultRepo = "BetV3/redoubt" )
Default policy: releases of this repository signed by GitHub Actions.
const PermSettingsManage = "settings.manage"
PermSettingsManage is the permission that may record update events (Owner / Admin).
Variables ¶
var ErrForbidden = errors.New("update: forbidden")
ErrForbidden is returned when the actor lacks settings.manage.
var ErrInvalid = errors.New("update: invalid event")
ErrInvalid is returned for malformed events.
var ErrNotInstalled = errors.New("no Redoubt installation found (missing .env)")
ErrNotInstalled is returned when the bundle directory has no .env.
var ErrVerification = errors.New("signature verification failed")
ErrVerification wraps every reason a signature is rejected.
var Images = []string{"platformd", "socket-proxy"}
Images the bundle runs from the release registry.
Functions ¶
func Apply ¶
Apply pins the verified digests in .env and drops the apply-update trigger that the systemd path unit (install/systemd/redoubt-apply-update.path) turns into `docker compose pull && up -d`. It never restarts anything itself.
func AutoUpdateFlag ¶
AutoUpdateFlag is the file whose presence enables the timer-driven `redoubt update --yes`.
func LatestVersion ¶
LatestVersion asks the GitHub releases API for the newest tag.
func PayloadDigest ¶
PayloadDigest extracts the image digest a cosign simple-signing payload attests to.
Types ¶
type Event ¶
type Event struct {
// Kind is "update" (images pinned to verified digests) or "auto_update" (timer toggled).
Kind string `json:"kind"`
Version string `json:"version,omitempty"`
Images map[string]string `json:"images,omitempty"` // name -> pinned reference
Enabled bool `json:"enabled,omitempty"`
Signer string `json:"signer,omitempty"` // verified certificate identity
}
Event is what the host-side CLI reports to the control plane so the audit chain carries it.
type Identity ¶
type Identity struct {
Subject string // the SAN URI (workflow identity)
Issuer string
IntegratedTime time.Time
LogIndex int64
}
Identity is what a verified signature proves.
type Options ¶
type Options struct {
Version string // "" = latest GitHub release
Registry string // e.g. ghcr.io/betv3/redoubt
Repo string // GitHub owner/name for the releases API
Trust TrustRoot
Policy Policy
Reg *Registry
Rekor *Rekor
HTTP *http.Client
// EtcDir holds .env (and the apply-update trigger); "" = /etc/redoubt.
EtcDir string
}
Options drive one update check.
type Plan ¶
type Plan struct {
Version string `json:"version"`
Images []Verified `json:"images"`
// Current holds the .env values before the update (by image name).
Current map[string]string `json:"current"`
// Changed is false when every image is already pinned to the verified digest.
Changed bool `json:"changed"`
}
Plan is the outcome of a check: what would change and the proof it is genuine.
type Recorder ¶
Recorder is the service that turns update events into audit records (RBAC lives here, golden rule 6; every state change audited, golden rule 5).
func (*Recorder) RecordVersionChange ¶
RecordVersionChange audits a control-plane restart under a different version than the previous run (the applied side of an update, whoever triggered it). prev is "" on first start.
type Registry ¶
type Registry struct {
HTTP *http.Client
// PlainHTTP allows http:// for loopback registries (demos and tests only).
PlainHTTP bool
// contains filtered or unexported fields
}
Registry is a minimal OCI distribution client (stdlib): resolve a tag to a digest and fetch cosign's signature manifest for it. Anonymous token auth (GHCR, Docker Hub) is handled.
type RekorBundle ¶
type RekorBundle struct {
SignedEntryTimestamp string `json:"SignedEntryTimestamp"`
Payload struct {
Body string `json:"body"`
IntegratedTime int64 `json:"integratedTime"`
LogIndex int64 `json:"logIndex"`
LogID string `json:"logID"`
} `json:"Payload"`
}
RekorBundle is cosign's offline transparency-log proof (the `dev.sigstore.cosign/bundle` annotation / the `--bundle` file): the entry Rekor integrated plus its signed timestamp.
func ParseBundle ¶
func ParseBundle(data []byte) (*RekorBundle, error)
ParseBundle parses a cosign bundle JSON document.
type Signature ¶
type Signature struct {
Payload []byte
Signature []byte // DER (ECDSA) / raw (Ed25519) / PKCS#1 v1.5 (RSA)
CertPEM []byte
ChainPEM []byte
Bundle *RekorBundle
}
Signature is one cosign signature: the payload that was signed, the raw signature, the signing certificate, its chain and the Rekor bundle.
type TrustRoot ¶
TrustRoot holds the certificate roots and the transparency-log key signatures are checked against.
func DefaultTrustRoot ¶
DefaultTrustRoot returns the embedded Sigstore public-good trust root.
func LoadTrustRoot ¶
LoadTrustRoot reads fulcio.pem and rekor.pub from dir (demo / private Sigstore instances).
func ParseTrustRoot ¶
ParseTrustRoot parses PEM certificate(s) and a PEM public key.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package sigtest is a miniature Sigstore for tests and demos: a Fulcio-like CA that issues short-lived code-signing certificates carrying the OIDC identity extensions, a Rekor-like key that signs entry timestamps, and an in-memory OCI registry that serves images and their cosign signature manifests.
|
Package sigtest is a miniature Sigstore for tests and demos: a Fulcio-like CA that issues short-lived code-signing certificates carrying the OIDC identity extensions, a Rekor-like key that signs entry timestamps, and an in-memory OCI registry that serves images and their cosign signature manifests. |
|
demosign
command
demosign signs images in a local (plain-HTTP, loopback) registry with a throwaway Sigstore (sigtest) and writes the matching trust root, so `make demo-phase3` can exercise `redoubt update` end to end without GitHub OIDC.
|
demosign signs images in a local (plain-HTTP, loopback) registry with a throwaway Sigstore (sigtest) and writes the matching trust root, so `make demo-phase3` can exercise `redoubt update` end to end without GitHub OIDC. |