Documentation
¶
Overview ¶
Package bundle publishes and pulls signed release bundles as OCI artifacts.
Layout: artifactType application/vnd.halos.release.v1+tar; config blob is manifest.json; one layer is the bundle tar. The signature covers the tar digest and is stored in a manifest annotation. Immutable release tags are "v<version>". Which release a ring serves is decided by a signed ring pointer ("ring-<name>.pointer", see pointer.go); the plain "ring-<name>" tag is kept for humans and tooling only and is never trusted by halod.
Index ¶
- Constants
- Variables
- func DefaultPointerStatePath() (string, error)
- func ExecRunner(ctx context.Context, name string, args ...string) ([]byte, error)
- func GenerateKeyPair() (privPEM, pubPEM []byte, err error)
- func KeyID(v Verifier) string
- func MarshalPublicKeyPEM(v PublicKeyVerifier) ([]byte, error)
- func ParseRef(ref string) (repo, tag string)
- func PointerChannels(ctx context.Context, src oras.ReadOnlyTarget, v Verifier, p Pointer) ([]string, error)
- func PointerTag(ring string) string
- func Publish(ctx context.Context, dst oras.Target, rel *release.Release, s Signer, ...) (ocispec.Descriptor, error)
- func PublishChannel(ctx context.Context, dst oras.Target, v *release.Release, s Signer, ...) (string, error)
- func PublishRing(ctx context.Context, dst oras.Target, rel *release.Release, ...) (ocispec.Descriptor, []string, error)
- func Pull(ctx context.Context, src oras.ReadOnlyTarget, tag string, v Verifier) (*release.Release, error)
- func PullAttestations(ctx context.Context, src oras.ReadOnlyGraphTarget, tag string, v Verifier) (*release.Release, []*release.Attestations, error)
- func Repository(repoRef string, plainHTTP bool) (*remote.Repository, error)
- func RingTag(ring string) string
- func Stage(ctx context.Context, dst oras.Target, rel *release.Release, s Signer) (already bool, err error)
- func VerifyAs(ctx context.Context, v Verifier, alg, msg string, sig []byte) error
- func VersionTag(ver string) string
- type Cosign
- type ECDSASigner
- type ECDSAVerifier
- type Ed25519Signer
- type Ed25519Verifier
- type KeySigner
- type Keyring
- type MultiSigner
- type Pointer
- func PromoteRing(ctx context.Context, t oras.Target, from Source, ring string, s Signer, ...) ([]Pointer, error)
- func PullRing(ctx context.Context, src oras.ReadOnlyTarget, v Verifier, o RingOptions) (*release.Release, Pointer, error)
- func ReadPointer(ctx context.Context, src oras.ReadOnlyTarget, ring string, v Verifier) (p Pointer, found bool, err error)
- func RefreshRing(ctx context.Context, t oras.Target, ring string, s Signer, ...) ([]Pointer, error)
- func ServedPointer(ctx context.Context, src oras.ReadOnlyTarget, s Signer, ring string) (p Pointer, found bool, err error)
- type PointerMark
- type PointerOptions
- type PointerState
- type PublicKeyVerifier
- type RingOptions
- type Runner
- type Signer
- type Source
- type Verifier
Constants ¶
const ( ArtifactType = "application/vnd.halos.release.v1+tar" ConfigMediaType = "application/vnd.halos.release.config.v1+json" LayerMediaType = "application/vnd.halos.release.layer.v1.tar" AnnSignature = "dev.halos.signature" AnnSigType = "dev.halos.signature.type" )
const ( // AlgEd25519 is pure Ed25519 over the message bytes (RFC 8032). The // original algorithm: every release signed before ADR-0038 uses it. AlgEd25519 = "ed25519" // AlgECDSAP256 is ECDSA on P-256 over SHA-256(message), ASN.1 DER // encoded: what AWS KMS ECC_NIST_P256 keys and most HSMs produce. AlgECDSAP256 = "ecdsa-p256-sha256" )
Signature algorithms halod verifies natively (ADR-0038). The name is what a signature's AnnSigType annotation (and a kill envelope's alg) records; a verifier refuses any other name. Both are FIPS 186-5 approved and inside Go's FIPS 140-3 module.
const ( PointerArtifactType = "application/vnd.halos.pointer.v1" PointerConfigType = "application/vnd.halos.pointer.v1+json" DefaultPointerTTL = 7 * 24 * time.Hour DefaultMaxLayerSize = 256 << 20 )
Signed ring pointers (TUF-lite). A ring tag alone is unauthenticated: whoever controls the registry could retag it to an older signed release or to one built for another ring. The pointer is a signed statement {org, ring, digest, seq, issuedAt, expiresAt} stored as its own OCI artifact under tag "ring-<name>.pointer". halod trusts the pointer, never the ring tag: it checks org/ring against its config, refuses seq regressions (freshness), refuses expired pointers, and only then fetches the release it names. Rollback is a NEW pointer with a higher seq naming an older digest.
const AnnSigKeyID = "dev.halos.signature.keyid"
AnnSigKeyID records the KeyID of the key that made the primary signature. It is informational (audit, rotation tracking): verification never trusts it to pick a key, it tries every trusted key of the signature's algorithm.
const (
AttestationArtifactType = "application/vnd.halos.attestations.v1+json"
)
A release's AI-BOM and provenance (ADR-0028) are one OCI referrer of its release manifest: two layers, signed by the release signer over a domain-separated statement naming the release and both layer digests. They stay out of the release tar so its digest is content-only, and out of the release manifest so halods that predate them still pull it.
Variables ¶
var Algorithms = []string{AlgEd25519, AlgECDSAP256}
Algorithms lists the native signature algorithms.
Functions ¶
func DefaultPointerStatePath ¶
DefaultPointerStatePath is $XDG_STATE_HOME/halos/pointers.json (~/.local/state/halos/pointers.json when XDG_STATE_HOME is unset).
func ExecRunner ¶
ExecRunner is the real Runner.
func GenerateKeyPair ¶
GenerateKeyPair returns PKCS#8 private and PKIX public PEM.
func KeyID ¶ added in v0.5.0
KeyID is "sha256:<hex>" identifying v's key: over the raw 32 bytes for ed25519 (unchanged from halod's revokedKeys fingerprints) and over the PKIX DER for ECDSA. "" for a verifier without a key.
func MarshalPublicKeyPEM ¶ added in v0.5.0
func MarshalPublicKeyPEM(v PublicKeyVerifier) ([]byte, error)
MarshalPublicKeyPEM is v's key as PKIX PEM ("PUBLIC KEY").
func PointerChannels ¶
func PointerChannels(ctx context.Context, src oras.ReadOnlyTarget, v Verifier, p Pointer) ([]string, error)
PointerChannels fetches and verifies the release p names and returns the channels it routes p.Ring's devices to. Read-only (dashboards).
func PointerTag ¶
PointerTag is the tag holding ring's signed pointer.
func Publish ¶
func Publish(ctx context.Context, dst oras.Target, rel *release.Release, s Signer, rings ...string) (ocispec.Descriptor, error)
Publish pushes rel, signs it, tags it v<version> (immutable: an existing tag pointing elsewhere is an error), and writes a signed pointer (plus the informational ring-<name> tag) for each ring given. s must be ed25519 or a MultiSigner whose first signer is ed25519; further signers (cosign) are stored as co-signatures. Registries must also enforce tag immutability. Publish is stateless; PublishRing takes PointerOptions.
func PublishChannel ¶
func PublishChannel(ctx context.Context, dst oras.Target, v *release.Release, s Signer, opts *PointerOptions) (string, error)
PublishChannel publishes one client-axis variant release on its own channel (policy.ChannelName of its manifest) with the same preflight and signer-state checks as a ring pointer. No ring pointer moves: devices reach the channel only once a ring release lists the experiment (PublishRing), so this pre-stages a canary without exposing anyone. Returns the channel.
func PublishRing ¶
func PublishRing(ctx context.Context, dst oras.Target, rel *release.Release, variants []*release.Release, s Signer, ring string, opts *PointerOptions) (ocispec.Descriptor, []string, error)
PublishRing publishes a ring release and its variant releases (as built by release.BuildRing): every variant on its channel first, then the ring, so a device never sees a ring manifest naming a channel that is not yet published. Everything is checked before anything is pushed: the variants must be exactly the channels rel's manifest lists for ring. Returns the ring release's descriptor and the channels written.
func Pull ¶
func Pull(ctx context.Context, src oras.ReadOnlyTarget, tag string, v Verifier) (*release.Release, error)
Pull fetches tag and verifies the signature over the tar digest with v before downloading the layer (capped at DefaultMaxLayerSize). It proves authenticity only, not freshness or ring scope: halod must use PullRing. A nil verifier is refused so unverified content can never be returned.
func PullAttestations ¶ added in v0.4.0
func PullAttestations(ctx context.Context, src oras.ReadOnlyGraphTarget, tag string, v Verifier) (*release.Release, []*release.Attestations, error)
PullAttestations pulls tag's release (signature-verified with v, like Pull) and every attestation referrer of it whose signature verifies with v and whose content describes the release (release.VerifyAttestations). It fails when none does.
func Repository ¶
func Repository(repoRef string, plainHTTP bool) (*remote.Repository, error)
Repository opens a remote registry repository ("host/org/name") using docker-config credentials when available.
func Stage ¶
func Stage(ctx context.Context, dst oras.Target, rel *release.Release, s Signer) (already bool, err error)
Stage pushes, signs and tags rel as v<version> with no pointer. Re-staging the same release is a no-op (a new OCI manifest would carry a new creation time and trip tag immutability); a v-tag naming a different release is an error. It reports whether rel was already there.
func VerifyAs ¶ added in v0.5.0
VerifyAs checks sig over msg as a signature of algorithm alg (as recorded next to the signature). An algorithm v does not accept (including "") is refused, and only v's keys of that algorithm are tried.
func VersionTag ¶
Types ¶
type Cosign ¶
type Cosign struct {
Bin string // default "cosign"
Key string // signing key ref (Sign) or public key ref (Verify)
Keyless bool // opt in to transparency-log upload/verification
Runner Runner // default ExecRunner
}
Cosign signs/verifies the digest string as a blob using `cosign sign-blob` / `verify-blob` with a key reference (file, KMS URI, ...).
Cosign is a co-signer only: halod verifies ed25519 and never shells out to cosign as root, so Publish refuses a cosign-only signer (wrap it as MultiSigner{ed25519, Cosign}). Key-based signing does NOT upload to the public Rekor transparency log (release digests of a private org would otherwise be published) unless Keyless is set. Keyless (Fulcio + Rekor) verification needs certificate identity/issuer pinning and a --bundle transport, which is not implemented here.
type ECDSASigner ¶ added in v0.5.0
type ECDSASigner struct{ Key *ecdsa.PrivateKey }
ECDSASigner signs with a P-256 key held in memory (a file:// key; KMS and HSM keys sign through internal/signer).
func (ECDSASigner) Type ¶ added in v0.5.0
func (ECDSASigner) Type() string
func (ECDSASigner) Verifier ¶ added in v0.5.0
func (s ECDSASigner) Verifier() Verifier
Verifier is s's public half.
type ECDSAVerifier ¶ added in v0.5.0
ECDSAVerifier verifies ECDSA P-256 / SHA-256 ASN.1 signatures.
func (ECDSAVerifier) PublicKey ¶ added in v0.5.0
func (v ECDSAVerifier) PublicKey() crypto.PublicKey
PublicKey returns the ECDSA public key.
func (ECDSAVerifier) Type ¶ added in v0.5.0
func (ECDSAVerifier) Type() string
type Ed25519Signer ¶
type Ed25519Signer struct{ Key ed25519.PrivateKey }
Ed25519Signer signs with an ed25519 key.
func ParseEd25519Signer ¶
func ParseEd25519Signer(privPEM []byte) (Ed25519Signer, error)
ParseEd25519Signer parses a PKCS#8 PEM private key.
func (Ed25519Signer) Type ¶
func (Ed25519Signer) Type() string
func (Ed25519Signer) Verifier ¶ added in v0.5.0
func (s Ed25519Signer) Verifier() Verifier
Verifier is s's public half.
type Ed25519Verifier ¶
Ed25519Verifier verifies with an ed25519 public key.
func ParseEd25519Verifier ¶
func ParseEd25519Verifier(pubPEM []byte) (Ed25519Verifier, error)
ParseEd25519Verifier parses a PKIX PEM public key.
func (Ed25519Verifier) PublicKey ¶ added in v0.5.0
func (v Ed25519Verifier) PublicKey() crypto.PublicKey
PublicKey returns the ed25519 public key.
func (Ed25519Verifier) Type ¶
func (Ed25519Verifier) Type() string
type KeySigner ¶ added in v0.5.0
KeySigner is a Signer that knows the public key its signatures verify with: the native algorithms (ed25519, ECDSA P-256) held in a file, a KMS or an external signer (internal/signer). Only a KeySigner can be the primary signer of a release, because halod verifies the primary signature.
func ParseSigner ¶ added in v0.5.0
ParseSigner parses a PKCS#8 PEM private key: ed25519 or ECDSA P-256.
type Keyring ¶ added in v0.5.0
type Keyring []PublicKeyVerifier
Keyring accepts a signature made by any one of its keys, each under its own algorithm only: a key is bound to one algorithm by its type, so an ed25519 key never checks an ECDSA signature or the reverse.
func (Keyring) Algorithms ¶ added in v0.5.0
Algorithms are the distinct algorithms of the keyring's keys.
type MultiSigner ¶
type MultiSigner []Signer
MultiSigner signs with its first signer (the one halod verifies; must be a KeySigner: ed25519 or ECDSA P-256) and records every other signer as a co-signature on the release manifest: Cosign, or a second native key while rotating between keys or algorithms (a halod that only trusts the old key verifies the co-signature; ring pointers carry native co-signatures too).
func (MultiSigner) Type ¶
func (m MultiSigner) Type() string
type Pointer ¶
type Pointer struct {
Org string `json:"org"`
Ring string `json:"ring"`
Digest string `json:"digest"` // release tar digest (what the release signature covers)
// Manifest/ManifestSize address the release's OCI manifest by content, so
// halod never resolves a mutable tag to find it.
Manifest string `json:"manifest"`
ManifestSize int64 `json:"manifestSize"`
Seq uint64 `json:"seq"`
IssuedAt time.Time `json:"issuedAt"`
ExpiresAt time.Time `json:"expiresAt"`
}
Pointer is the signed ring -> release statement.
func PromoteRing ¶
func PromoteRing(ctx context.Context, t oras.Target, from Source, ring string, s Signer, opts *PointerOptions) ([]Pointer, error)
PromoteRing points ring at the release from names (verified with s's key) and, if that release was built for ring, points each of its channels at the variant release published with it (tag v<release.VariantVersion>, whose signed manifest must match). Every release is fetched and verified, and every pointer about to be replaced passes preflight, before any pointer is written. Rollback is PromoteRing to an older version: ring and channels return to that snapshot together, each with a higher seq. Returns the ring pointer first, then one per channel (Pointer.Ring is the channel).
func PullRing ¶
func PullRing(ctx context.Context, src oras.ReadOnlyTarget, v Verifier, o RingOptions) (*release.Release, Pointer, error)
PullRing is halod's entry point: verify the ring pointer (signature, org, ring, seq, expiry), then fetch the release it names by content address, verify its signature BEFORE downloading the layer, cap the layer size, and check the digest. Nothing unverified is returned.
func ReadPointer ¶
func ReadPointer(ctx context.Context, src oras.ReadOnlyTarget, ring string, v Verifier) (p Pointer, found bool, err error)
ReadPointer fetches and verifies ring's pointer. found=false (nil error) means no pointer exists yet.
func RefreshRing ¶
func RefreshRing(ctx context.Context, t oras.Target, ring string, s Signer, opts *PointerOptions) ([]Pointer, error)
RefreshRing re-signs ring's pointer and the pointer of every channel its current release routes to (same releases, higher seq, new expiry). Each served pointer must be unexpired and pass the state check (and the ring's must name opts.ExpectDigest, if set), and each release is re-verified, before anything is written. Returns the ring pointer first.
type PointerMark ¶
type PointerMark struct {
Seq uint64 `json:"seq"`
Digest string `json:"digest"`
IssuedAt time.Time `json:"issuedAt"`
}
PointerMark is the last pointer the signer wrote for one ring or channel.
type PointerOptions ¶
type PointerOptions struct {
// State remembers the pointers this signer wrote; a served pointer older
// than (or forked from) the recorded one is refused, and every write is recorded.
State *PointerState
// ExpectDigest, if set, is the release digest the operation must act on:
// the source release of a promote/rollback, the ring's current release on refresh.
ExpectDigest string
// AdoptExisting lets a signer whose State has no record for a ring take
// over the pointer the registry serves (first run with a new or evicted
// state file). Without it, or an ExpectDigest naming the served release,
// such a pointer is refused: a missing state file must not silently turn
// replay protection off.
AdoptExisting bool
// BeforeSign is called with each pointer, and the version of the release
// it names, just before it is signed.
BeforeSign func(p Pointer, version string)
// contains filtered or unexported fields
}
PointerOptions are the signer-side guards every pointer write runs. The zero value (or nil) is stateless: writes still refuse foreign-org pointers, and refresh/promote-from-ring still refuse expired pointers, but a replayed unexpired pointer is only caught by State or ExpectDigest.
type PointerState ¶
type PointerState struct {
// contains filtered or unexported fields
}
PointerState is the on-disk signer state (a JSON file; CI can cache it). Keys are "<registry repo>#<ring>". Not safe for concurrent writers: run one signer per state file.
func LoadPointerState ¶
func LoadPointerState(path, scope string) (*PointerState, error)
LoadPointerState reads path (a missing file is empty state) scoped to the registry repo scope.
func (*PointerState) Mark ¶
func (s *PointerState) Mark(ring string) (PointerMark, bool)
Mark returns the recorded pointer for ring.
type PublicKeyVerifier ¶ added in v0.5.0
PublicKeyVerifier is a Verifier bound to one public key.
func ParseVerifier ¶ added in v0.5.0
func ParseVerifier(pubPEM []byte) (PublicKeyVerifier, error)
ParseVerifier parses a PKIX PEM public key: ed25519 or ECDSA P-256.
func ParseVerifierDER ¶ added in v0.5.0
func ParseVerifierDER(der []byte) (PublicKeyVerifier, error)
ParseVerifierDER parses a DER SubjectPublicKeyInfo (what AWS KMS GetPublicKey returns).
func VerifierFor ¶ added in v0.5.0
func VerifierFor(k crypto.PublicKey) (PublicKeyVerifier, error)
VerifierFor wraps a public key in the verifier for its algorithm.
type RingOptions ¶
type RingOptions struct {
Org, Ring string
// MinSeq/LastDigest are the last applied pointer for this ring (zero on first run).
MinSeq uint64
LastDigest string
Now time.Time
// MaxLayerSize caps the release tar; 0 = DefaultMaxLayerSize.
MaxLayerSize int64
}
RingOptions are halod's freshness/scope requirements for PullRing.
type Signer ¶
Signer signs a release digest string (e.g. "sha256:ab..."). The digest is of the bundle tar, so it transitively covers manifest.json and every file.
type Source ¶
type Source struct {
Ring string // the release Ring's signed pointer names (promote)
Version string // tag v<Version> (rollback)
Manifest string // the release's OCI manifest digest (rollback by digest)
}
Source names the release a ring is pointed at. Set exactly one field. None of them trusts a mutable tag on its own: a ring source follows that ring's signed pointer, a version source must carry that version in its signed manifest, and a manifest source is a content address.