verify

package
v1.3.0 Latest Latest
Warning

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

Go to latest
Published: Aug 26, 2026 License: Apache-2.0 Imports: 19 Imported by: 0

Documentation

Index

Constants

View Source
const BuiltinKeyName = "iac-bazaar"

BuiltinKeyName is the name reported for the compiled-in IaC Bazaar key.

View Source
const PinnedPublicKeyPEM = `` /* 177-byte string literal not displayed */

PinnedPublicKeyPEM is the IaC Bazaar cosign public key, compiled into the binary on purpose.

The whole value of a signature check is that the key came from somewhere the artifact's publisher cannot change at request time. Fetching it from the same catalog that serves the proof would make the check circular: an attacker who can rewrite the proof can rewrite the key. It mirrors the key pinned in the main app at src/lib/provenance.ts - if that rotates, this must be rebuilt.

Variables

This section is empty.

Functions

func BundleDigest

func BundleDigest(raw []byte) (string, error)

BundleDigest returns the hex SHA-256 the bundle claims to sign. It performs no verification - callers that need proof must use VerifyBundle.

func CatalogSlugs added in v1.1.0

func CatalogSlugs(decisions []Decision) []string

CatalogSlugs collects the iacbazaar:// modules a set of decisions covers, in first-seen order and without repeats, so a tree can be checked in one request.

func DefaultTrustedKeyDir added in v1.1.0

func DefaultTrustedKeyDir() string

DefaultTrustedKeyDir is where registered keys live when nothing says otherwise.

func KnownStatus added in v1.2.0

func KnownStatus(s string) bool

KnownStatus reports whether a min_status names a real rung on the ladder.

Exported so `hcl validate` can catch a typo at validate time. Today a misspelled min_status is caught by verification, fail-closed, in the middle of a run - correct, but the wrong moment to learn it.

func KnownStatuses added in v1.2.0

func KnownStatuses() string

KnownStatuses is the list to print when refusing one.

func ParseSource

func ParseSource(src string) (slug, version string, ok bool)

ParseSource extracts (slug, version) from an iacbazaar:// source.

func StatusMeets added in v1.1.0

func StatusMeets(got, min string) bool

StatusMeets reports whether an evidence level clears a required floor.

Exported so `vizier admit` judges evidence with the SAME ladder verification uses. A second implementation would eventually disagree with the first, and the one that drifts is always the one nobody is looking at.

Fails closed twice over: an unknown floor is satisfied by nothing, and an unknown status satisfies nothing. Both are the safe direction - the caller is asking "is this good enough", and "I do not recognise it" must never be yes.

func TlogIndex

func TlogIndex(raw []byte) string

TlogIndex returns the Rekor transparency-log index, for the run receipt. It is evidence a third party can look up, not something we verify inclusion of.

func TrustPathsFromEnv added in v1.1.0

func TrustPathsFromEnv() []string

TrustPathsFromEnv reads VIZIER_TRUSTED_KEYS, an OS-separated path list.

func VerifyBundle

func VerifyBundle(raw []byte, expectedSHA256 string) error

VerifyBundle checks that the bundle carries a signature made by the pinned key over expectedSHA256.

Two independent things are asserted, and both matter:

  1. the signature is valid for the digest the bundle carries (authenticity), and
  2. that digest IS the checksum the catalog advertised for this module (binding).

Without (2), a genuine signature over some other artifact would satisfy the check - which is how a valid signature gets reused as proof for the wrong file.

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

func NewClient

func NewClient(baseURL string) *Client

func (*Client) DownloadURL

func (c *Client) DownloadURL(slug, system, version string) (string, error)

DownloadURL resolves the module archive through the registry protocol.

The X-Terraform-Get header carries Terraform's `//<subdir>` selector, which is NOT part of the HTTP URL, while the signed access token lives in the query string AFTER it. Truncating at the selector - the obvious way to strip it - takes the token with it and yields a 403 whose tiny error body then "fails" the checksum comparison in a thoroughly confusing way.

func (*Client) Fetch

func (c *Client) Fetch(slug, version string) (*ModuleProof, error)

func (*Client) Identity added in v1.1.0

func (c *Client) Identity(slugs []string) (*Identity, error)

Identity asks the catalog about the caller, optionally about specific slugs.

A transport failure is an error the CALLER may choose to ignore: an offline laptop and a revoked token need completely different fixes, and reporting the first as the second sends people to re-mint a credential that was fine.

func (*Client) Modules added in v1.2.0

func (c *Client) Modules() ([]Module, error)

Modules lists the catalog.

Read-only and anonymous: browsing what exists needs no credential, and the console proxies this call rather than fetching it from the page, because the site sends no CORS headers and should not start.

func (*Client) Token added in v1.1.0

func (c *Client) Token() string

Token exposes the credential so the archive fetch can carry it too.

func (*Client) WithToken added in v1.1.0

func (c *Client) WithToken(token string) *Client

WithToken returns a client that authenticates as the caller.

type Decision

type Decision struct {
	// Unit is the display name, filled in by the caller (the orchestrator knows
	// it; verification only sees the config).
	Unit string
	// Dir is the unit's Key - its cleaned directory path - and is what anything
	// matching a decision back to a unit must use. Basenames repeat across
	// environments, so matching on Unit picked whichever prod/stg unit came
	// first and could fetch and run the wrong module.
	Dir     string
	Allowed bool
	Reason  string

	// Evidence behind the verdict, carried so a run receipt can record WHY a
	// module was admitted rather than just that it was. Empty for units whose
	// source is not an iacbazaar:// module.
	Slug              string
	Version           string
	Status            string
	System            string
	SHA256            string
	SignatureVerified bool
	// VerifiedBy names the trusted key that accepted the signature. Recorded
	// because "signed" is not one fact once a team registers its own publishers:
	// a receipt that does not say WHICH key vouched for the code has not said
	// much.
	VerifiedBy string
	TlogIndex  string
	// SourceVerified records that the module was DOWNLOADED and its digest
	// checked against SHA256 before the engine ran, so SHA256 describes the code
	// that executed. False means SHA256 describes the artifact the CATALOG
	// published while the engine ran whatever was already in the unit directory
	// - very likely the same thing, but nothing checked it.
	SourceVerified bool
	// FromLock records that the proof was read from vizier.lock rather than
	// confirmed against the catalog in this run.
	FromLock bool

	// Source is the raw source string for a NON-catalog module.
	Source string
	// ExpectSHA256 is the client's `source_sha256` pin, checked against the
	// digest of what was actually fetched.
	ExpectSHA256 string
	// Unpinned marks a client source admitted with no provenance at all, so a
	// receipt can say so instead of looking like every other allowed unit.
	Unpinned bool
	// SourceRef is the resolved revision of a client module - a git commit, not
	// the tag that was asked for, because a tag moves.
	SourceRef string
	// SourceKind is how it was fetched: git, registry, http, s3, gcs, local.
	SourceKind string
}

type EngineProof added in v1.2.0

type EngineProof struct {
	Source            string `json:"source"`
	Version           string `json:"version,omitempty"`
	SHA256            string `json:"sha256"`
	Path              string `json:"-"`
	SignatureVerified bool   `json:"signatureVerified"`
	VerifiedBy        string `json:"verifiedBy,omitempty"`
	TlogIndex         string `json:"tlogIndex,omitempty"`
}

EngineProof is what was established about the process that will execute a unit.

It carries the same evidence a module does, from the same trust store, for the same reason: an engine plugin replaces the thing that RUNS the proved code, so a check that proves the module and not the executor has proved something about a file nobody ran.

func ResolveEngine added in v1.2.0

func ResolveEngine(b *config.EngineBlock, store *TrustStore, cacheDir string) (EngineProof, error)

ResolveEngine locates an engine plugin and proves it, or refuses.

Two rules, and both are the opposite of what Terragrunt does here:

  • The digest is checked against the FILE THAT IS ABOUT TO RUN, on every run. Terragrunt's downloader returns early when the cached binary exists and only re-verifies if the checksum files happen to still sit beside it, which makes the cache the trust anchor between runs.
  • Signatures are optional but verified against the SAME TrustStore modules use, so a team can publish its own engine under its own key. Terragrunt has one hard-coded vendor key, which is why their documentation tells third-party engine users to switch verification off.

There is deliberately no flag that disables either check.

type Entitlement added in v1.1.0

type Entitlement struct {
	Slug    string `json:"slug"`
	Free    bool   `json:"free"`
	Allowed bool   `json:"allowed"`
	Reason  string `json:"reason"`
	Version string `json:"version"`
	URL     string `json:"url"`
}

Entitlement is what one module would do if this token asked for it now.

type EntitlementError added in v1.1.0

type EntitlementError struct {
	Slug    string
	Version string
	Status  int
	Detail  string
	HasAuth bool
}

EntitlementError is the server refusing a paid module.

A distinct type so the CLI can say what to do about it rather than surfacing "HTTP 401", which tells a paying customer nothing.

func (*EntitlementError) Error added in v1.1.0

func (e *EntitlementError) Error() string

type Fetcher

type Fetcher interface {
	Fetch(slug, version string) (*ModuleProof, error)
}

Fetcher is the catalog capability verification needs (satisfied by *Client).

type Identity added in v1.1.0

type Identity struct {
	Account      string        `json:"account"`
	Plan         string        `json:"plan"`
	Purchases    int           `json:"purchases"`
	Subscription *Subscription `json:"subscription"`
	Entitlements []Entitlement `json:"entitlements"`
	Tokens       string        `json:"tokens"`
	// Unsupported marks a catalog with no identity endpoint. The token may be
	// perfectly good, so this is reported as "cannot say" rather than a failure.
	Unsupported bool `json:"-"`
}

Identity is the catalog's answer to "who is this token, and what may it download". It grants nothing: the download route decides, and this only reports what that decision would be, so a run can be stopped before it starts rather than halfway through an apply.

func (*Identity) Blocked added in v1.1.0

func (i *Identity) Blocked() []Entitlement

Blocked lists the modules this token may not download.

func (*Identity) PlanLabel added in v1.1.0

func (i *Identity) PlanLabel() string

PlanLabel renders the plan for display.

type Lock

type Lock struct {
	Modules map[string]LockEntry `json:"modules"`
}

func LoadLock

func LoadLock(dir string) (*Lock, error)

func (*Lock) Get

func (l *Lock) Get(slug, ver string) (*ModuleProof, bool)

func (*Lock) Pin

func (l *Lock) Pin(p *ModuleProof)

Pin records what the catalog said about a module.

A bundle-less proof does NOT erase a bundle already held, provided the digest is unchanged. catalog.Fetch deliberately treats a failed signature-endpoint fetch as non-fatal and returns a proof without one, so a single flaky request used to overwrite a good entry with a bundle-less one: status and signed were kept, and the only actual EVIDENCE was dropped. That leaves the lock in exactly the shape LockFetcher refuses as "an assertion, not evidence", so a later genuinely-offline run that used to pass would block. Destructive, from commands that only report, and not recoverable without the catalog being fully reachable again.

The digest check is what makes keeping it safe. A cosign bundle signs a DIGEST, so while SHA256 is unchanged the old bundle is still evidence about the same bytes. If the digest moved, that bundle describes different bytes and carrying it forward would manufacture exactly the false pairing that making the catalog authoritative was meant to stop.

func (*Lock) Save

func (l *Lock) Save(dir string) error

type LockEntry

type LockEntry struct {
	Status string `json:"status"`
	Signed bool   `json:"signed"`
	SHA256 string `json:"sha256"`
	// Bundle is the signature bundle, stored base64 so the lock stays one
	// readable JSON document. It is what makes the lock a cache of EVIDENCE
	// rather than of verdicts: every load re-verifies it against the pinned
	// key, so editing `status` or `signed` by hand changes nothing.
	Bundle string `json:"bundle,omitempty"`
}

type LockFetcher

type LockFetcher struct {
	Inner Fetcher
	Lock  *Lock
	// AllowOffline permits falling back to the lock when the catalog is
	// unreachable. Off by default: a network failure silently downgrading the
	// check to "whatever this file says" is how an unreachable catalog becomes an
	// unverified apply.
	AllowOffline bool
}

LockFetcher pins proofs in vizier.lock and can answer from it when the catalog is unreachable.

It asks the CATALOG FIRST. The lock used to answer first, which made it authoritative - and it cannot be, because nothing in it is authenticated against the module it claims to describe.

A cosign bundle signs a DIGEST. It says "someone holding the key signed the artifact whose sha256 is D". It says nothing about which slug, which version, or what verification status that artifact reached. All three of those came from the lock file itself, so:

  • a hand-written entry with no bundle at all was accepted whole, and with an unreachable catalog `ghost-module@9.9.9` was reported "proven (live_tested)" at exit 0;
  • a genuine bundle lifted from a real module and re-keyed under another slug and version verified perfectly, because the sha256 it was checked against was supplied by the same file.

The lock's own comment claimed the opposite: "a cache of EVIDENCE rather than of verdicts: every load re-verifies it against the pinned key, so editing status or signed by hand changes nothing." Re-verifying a signature over a digest the attacker also chose changes nothing either.

So the catalog is the source of the binding, and the lock is a cache. When the catalog cannot be reached the run fails closed unless the caller has explicitly asked for offline operation, and a proof that came from the lock is marked so the receipt can say so rather than implying a fresh check.

func (*LockFetcher) Fetch

func (f *LockFetcher) Fetch(slug, ver string) (*ModuleProof, error)

type Module added in v1.2.0

type Module struct {
	Slug       string   `json:"slug"`
	Title      string   `json:"title"`
	Summary    string   `json:"summary"`
	Tool       string   `json:"tool"`
	Provider   string   `json:"provider"`
	Clouds     []string `json:"clouds"`
	Category   string   `json:"category"`
	Tier       string   `json:"tier"`
	PriceCents int      `json:"priceCents"`
	URL        string   `json:"url"`

	Verification struct {
		Level  string `json:"level"`
		Signed bool   `json:"signed"`
	} `json:"verification"`
}

Module is one row of the catalog index (GET /api/v1/modules).

The index is the browsing view and is deliberately thinner than the per-slug endpoint: it carries no version and no digest. Anything that needs either - a source line someone will paste, a proof - must Fetch the module itself. Printing an empty column instead is how a list ends up promising "iacbazaar://<slug>@<version>" while showing no version at all.

type ModuleProof

type ModuleProof struct {
	// CatalogVersion is the version the CATALOG reported, which is not
	// necessarily the one that was requested - the v1 endpoint is addressed by
	// slug only and answers about the current version.
	CatalogVersion string
	// Bundle is the raw Sigstore bundle. Evidence, not a verdict: the check
	// verifies it against the pinned key.
	Bundle  []byte
	Slug    string
	Version string
	Status  string // catalog verification.level (see statusRank); "unverified"/"" = unknown
	Signed  bool
	SHA256  string
	// Tool and System identify the module on the registry download path.
	Tool   string
	System string
	// FromLock marks a proof answered from vizier.lock because the catalog was
	// unreachable. The signature still verifies, but the slug, version and
	// status came from a local file, so a receipt must not present it as a fresh
	// confirmation.
	FromLock bool
}

type StatusError added in v1.2.0

type StatusError struct {
	Slug   string
	Status int
}

StatusError says the catalog was REACHED and returned a non-200.

It exists for exactly one decision: --offline falls back to vizier.lock when the catalog CANNOT BE REACHED. A catalog that answered 401, 403 or 404 was reached, and it said no. Letting a local cache override an authoritative no is how a revoked token, a lost entitlement or a withdrawn module becomes a silent pass, and a module is most often withdrawn because something was wrong with it.

Previously this was a plain formatted string, so the fallback branched on `err != nil` and could not tell a refusal from a dead host.

func (*StatusError) Answered added in v1.2.0

func (e *StatusError) Answered() bool

Answered reports whether the catalog gave a verdict about this module, as opposed to failing to give one.

4xx is an answer: the module is not there, or this caller may not have it. 5xx is not, it is the catalog being broken, and 429 is explicitly "ask me later" rather than a statement about the module. Those two are as uninformed as an unreachable host, so they keep the offline fallback that exists for unreachable hosts.

func (*StatusError) Error added in v1.2.0

func (e *StatusError) Error() string

type Subscription added in v1.1.0

type Subscription struct {
	Tier             string `json:"tier"`
	Status           string `json:"status"`
	CurrentPeriodEnd string `json:"currentPeriodEnd"`
}

type TrustStore added in v1.1.0

type TrustStore struct {
	Keys []TrustedKey
}

TrustStore is an ordered set of keys. The built-in is always first, so the common case costs one verification and the ordering of registered keys never changes which key is reported for a Bazaar module.

func LoadTrustStore added in v1.1.0

func LoadTrustStore(flagPaths []string) (*TrustStore, error)

LoadTrustStore assembles the store from flags, the environment and the default directory.

The default directory is optional: not having one is the normal state for someone who only consumes Bazaar modules, so its absence is silence. An explicitly named path that is missing is still an error, because that one was asked for.

func NewTrustStore added in v1.1.0

func NewTrustStore(paths ...string) (*TrustStore, error)

NewTrustStore builds a store from the built-in key plus every key file found in the given paths. A path may be a single file or a directory; a directory is read one level deep for *.pub and *.pem.

Order of arguments does not matter for correctness, only for which key is reported first when two keys are byte-identical, which is deduplicated anyway.

func (*TrustStore) Find added in v1.1.0

func (s *TrustStore) Find(idOrName string) (TrustedKey, bool)

Find returns the key with this id or name.

func (*TrustStore) Names added in v1.1.0

func (s *TrustStore) Names() []string

Names lists the store's keys for display, built-in first.

type TrustedKey added in v1.1.0

type TrustedKey struct {
	// ID is the first 16 hex characters of the SHA-256 of the DER SPKI. Stable,
	// short enough to put in a policy file, and derived from the key itself so
	// it cannot be relabelled.
	ID string
	// Name is how a person refers to it: "iac-bazaar" for the built-in, or the
	// file's base name for a registered one.
	Name string
	// Source is "builtin" or the absolute path it was read from, so a receipt
	// can say where the trust came from rather than only that it existed.
	Source string
	// contains filtered or unexported fields
}

TrustedKey is one public key a signature may verify against.

func BuiltinKey added in v1.1.0

func BuiltinKey() (TrustedKey, error)

BuiltinKey is the compiled-in IaC Bazaar key as a TrustedKey.

func VerifyBundleWith added in v1.1.0

func VerifyBundleWith(raw []byte, expectedSHA256 string, store *TrustStore) (TrustedKey, error)

VerifyBundleWith checks a bundle against every key in the store and reports which one accepted it.

Both halves of the original check still apply and in the same order: the signature must be valid, AND the digest it covers must be the checksum that was advertised for this artifact. Widening the key set does not widen what a signature is allowed to mean.

func (TrustedKey) String added in v1.1.0

func (k TrustedKey) String() string

type Verifier added in v1.2.0

type Verifier struct {
	Fetcher Fetcher
	Mode    string // enforce | warn | off
	// Trust is the set of keys a signature may verify against. Nil means the
	// compiled-in IaC Bazaar key alone, which is the behaviour every existing
	// caller had before customer keys existed.
	Trust *TrustStore
}

func (*Verifier) Check added in v1.2.0

func (g *Verifier) Check(u *config.Unit) Decision

Check applies the unit's verify policy. In "off" mode everything is allowed. In "enforce"/"warn" a violation is reported; the CALLER decides whether a warn-mode violation proceeds (Decision.Allowed reflects the policy verdict).

func (*Verifier) EffectiveTrust added in v1.2.0

func (g *Verifier) EffectiveTrust() *TrustStore

EffectiveTrust is the key set this check will actually use.

Exported for the console, which must show the SAME list verification consults rather than the raw Trust field: that field is nil in the common case, and a screen reading it would report "no trusted keys" for a binary that trusts the compiled-in one and is working correctly.

Jump to

Keyboard shortcuts

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