Documentation
¶
Overview ¶
This file implements the offline/air-gapped install path (plan-v2/step5 §7): Bundle prepares a self-contained tarball on a networked machine, running the full real install-equivalent verification before ever writing the tarball; InstallFromBundle installs from that tarball on a machine with NO network access at all, re-verifying everything against the offline binary's own compiled-in trust anchors — it NEVER skips verification just because the network is down.
Soundness ¶
A bundle is a carrier for an already-completed, already-verified installation, replayed later on a different machine — never a way to defer verification to a machine that can't fully perform it. Bundle itself refuses to package an artifact that would not itself pass a normal `install` (VerifyArtifact must return Signed: true); InstallFromBundle re-runs index verification (via the SAME registry.TrustedVerifier code path a live fetch uses — index verification has no network dependency of its own once the bytes are in hand) and artifact signature/provenance verification (via the same sigstore-go `--bundle` offline verification the online path already uses — see trust/sigstore.go's doc comments: it never makes a live Fulcio/Rekor query, bundle or not).
This file backs `conduit connectors audit` (plan-v2/step5 §4): the mechanism that protects ALREADY-installed connectors. install-time refusal only stops a new bad install; a connector installed last month whose version is yanked next week, or whose publisher is revoked, keeps running silently until something re-checks it. audit is that re-check.
Reuses PR-2's verification exactly — no lower-trust shortcut ¶
audit fetches and verifies the index through the SAME IndexVerifier (registry.TrustedVerifier, in production) install.go uses — same trust anchors, same rollback/staleness/integrity checks, same error codes. There is no second, lower-assurance "audit index fetch" path: an index this command can't cryptographically verify is a hard failure for the whole run (see RunAudit), never a degraded partial result. Named connectoraudit to avoid colliding with audit.go, which is the unrelated append-only install/uninstall event log (AppendAuditEvent) — this file has nothing to do with that one.
Package registry is the connector registry's top-level orchestration: resolving a connector name/version against the signed index and installing it (docs/design-documents/20260713-connector-registry-mvp.md, the epic; plan-v2, the consolidated implementation plan).
This PR's scope (PR-0 — shared foundations, Tier 2) ¶
This package ships only the contracts every later PR compiles against — no install pipeline, no verification implementation. Concretely:
- IndexVerifier / ArtifactVerifier (verify.go): the two-interface seam PR-1 (install core) and PR-2 (trust core) both build against, fixing a real compile error in the original step-plans' single-receiver, duplicate-method-name sketch (plan-v2 §2.2).
- FailClosedVerifier (verify.go): the ONLY IndexVerifier/ArtifactVerifier wired into production Install until PR-2 lands. It performs a shape/schema check only (via pkg/registry/index.ParseUnverified) and unconditionally refuses artifact verification — this package introduces NO code path that can accept an artifact as verified.
- Manifest (manifest.go): the name@version-keyed install-manifest format (plan-v2 §3) — the load-bearing fix letting two pipelines pin two different versions of one connector simultaneously.
- NormalizeVersion (semver.go): the one, only version-comparison primitive (plan-v2 §5) — semver equality tolerating a leading "v" — every version comparison anywhere in this codebase must go through this, never a bare string comparison.
- The full canonical error-code table (codes.go, plan-v2 §4), including codes no PR-0 code path triggers yet, registered now so docs/llms.txt generation has one complete, stable source from the first PR.
Fail-closed by construction ¶
Nothing in this package can install, verify, or otherwise accept a connector artifact. FailClosedVerifier.VerifyArtifact always returns ErrVerificationNotConfigured; VerifyIndex only ever produces a VerifiedIndex with Verified: false. The real install orchestrator (resolve.go, download.go, extract.go, lock.go, install.go — PR-1) and the real cryptographic verification bodies (PR-2, Tier 1) are deliberately not part of this PR.
This file backs `conduit connectors list --installed` (plan-v2/step5 §3): enumerating the local install manifest, independent of whether any pipeline uses an entry or whether the engine is even running — a fundamentally different thing from the existing `connectors list`, which queries the running engine's pipeline connector INSTANCES. See cmd/conduit/root/connectors/list.go for why the two render as visibly distinct tables.
Index ¶
- Constants
- Variables
- func AcquireManifestLock(connectorsPath string, timeout time.Duration) (*flock.Flock, error)
- func AcquireTargetLock(connectorsPath, name string, timeout time.Duration) (*flock.Flock, error)
- func AppendAuditEvent(path string, ev AuditEvent) error
- func CacheLookup(connectorsPath, digestHex string) ([]byte, bool, error)
- func CachePopulate(connectorsPath, digestHex string, data []byte, sourceURL string) error
- func CacheSweepTmp(connectorsPath string, maxAge time.Duration)
- func CheckCorruption(got [32]byte, want string) error
- func ExtractBinary(archivePath, destDir string) (string, error)
- func IndexStatePath(connectorsPath string) string
- func ManifestKey(name, version string) (string, error)
- func ManifestLockPath(connectorsPath string) string
- func NormalizeVersion(s string) (*semver.Version, error)
- func SaveManifest(path string, m *Manifest) error
- func SelectArtifact(connName string, v index.ConnectorVersion, goos, goarch string) (*index.Artifact, error)
- func TargetLockPath(connectorsPath, name string) string
- type ArtifactRef
- type ArtifactVerifier
- type AuditEvent
- type AuditFinding
- type AuditOptions
- type AuditReport
- type AuditResultEntry
- type AuditStatus
- type BundleManifest
- type BundleOptions
- type BundleResult
- type CacheMeta
- type DownloadResult
- type FailClosedVerifier
- type InUseRef
- type IndexVerifier
- type InstallBundleOptions
- type InstallOptions
- type InstallResult
- type InstalledConnector
- type InstalledStatus
- type ListInstalledOptions
- type ListInstalledResult
- type Manifest
- type ManifestEntry
- type ResolveOptions
- type ResolvedVersion
- type TrustedVerifier
- type UninstallOptions
- type UninstallResult
- type VerifyResult
Constants ¶
const BundleFormatVersion = 1
BundleFormatVersion is the bundleFormatVersion this build writes and the maximum InstallFromBundle accepts — its own small compat field, same announce/warn/remove discipline as the index's schemaVersion: a too-new bundle format on an old conduit binary says "upgrade conduit," never mis-parses.
const DefaultIndexURL = "https://registry.conduitdata.io/index.json"
DefaultIndexURL is the well-known registry index URL Install fetches from when the caller does not set InstallOptions.IndexFile (plan-v2 §8: the same GitHub Pages deployment that serves the human-facing site). Served by the conduit-connector-registry Pages deploy on the conduitdata.io domain.
const DefaultLockTimeout = 30 * time.Second
DefaultLockTimeout bounds how long Install waits to acquire a lock before refusing with CodeInstallLocked — never an indefinite hang.
const InstallSourceIndex = "index"
InstallSourceIndex is the InstallOptions/ManifestEntry Source value for a normal, online install. InstallSourceOfflineBundle (bundle.go, PR-4) is the other.
const InstallSourceOfflineBundle = "offline-bundle"
InstallSourceOfflineBundle is the ManifestEntry.Source value recorded by InstallFromBundle — see InstallSourceIndex (install.go) for the online counterpart.
const ManifestSchemaVersion = 1
ManifestSchemaVersion is the manifest.json schemaVersion this build writes, and the value LoadManifest expects.
const MaxBundleBytes int64 = 1 * 1024 * 1024
MaxBundleBytes caps a fetched signature/provenance bundle fetch (P0-2 item 1, plan-v2 §2.4): a Sigstore bundle with a cert chain and Rekor inclusion proof is normally a few KB; 1 MiB is generous headroom.
const StaleBundleEnvVar = "CONDUIT_ALLOW_STALE_BUNDLE"
StaleBundleEnvVar is the non-interactive escape hatch for --allow-stale-bundle (gated identically to --allow-unsigned per DeVaris's ratification, plan-v2/step5 §7 step 3, §9 decision item 2).
const StandaloneArtifactKind = "standalone"
StandaloneArtifactKind is the only artifact kind this install pipeline knows how to install. A future kind (e.g. a WASM processor-shaped artifact) is skipped, not treated as an error, by SelectArtifact — an unrecognized kind refuses gracefully via "no matching artifact", never a crash.
Variables ¶
var ( // CodeConnectorNotFound is raised when an exact-match name lookup in // the index fails. CodeConnectorNotFound = conduiterr.Register("registry.connector_not_found", codes.NotFound) // CodeVersionNotFound is raised when the connector exists but the // requested @version does not. CodeVersionNotFound = conduiterr.Register("registry.version_not_found", codes.NotFound) // CodeIncompatibleVersion is raised when an explicit @version pin is // below the running Conduit's min-versions, or (see ManifestKey) when a // version string fails to parse as semver at all. CodeIncompatibleVersion = conduiterr.Register("registry.incompatible_version", codes.FailedPrecondition) // CodeNoPlatformArtifact is raised when no artifact exists for the // host (os, arch). CodeNoPlatformArtifact = conduiterr.Register("registry.no_platform_artifact", codes.NotFound) // CodeCorruptDownload is raised when the received-bytes sha256 does not // match the index's declared sha256 — DataLoss, matching gRPC's literal // "unrecoverable data loss or corruption" semantics for a digest // mismatch; a retry is a fresh download attempt, not a recovery of the // same lost bytes. CodeCorruptDownload = conduiterr.Register("registry.corrupt_download", codes.DataLoss) // CodeInstallLocked is raised when the per-target install lock is not // acquired within --lock-timeout. CodeInstallLocked = conduiterr.Register("registry.install_locked", codes.Unavailable) // CodeArchiveInvalid is raised when a downloaded archive has zero or // more than one candidate executable, a path-traversal entry, or a // symlink entry. CodeArchiveInvalid = conduiterr.Register("registry.archive_invalid", codes.Internal) // longer this package's production wiring as of PR-2 (see // TrustedVerifier), but still the correct, intentional behavior for any // caller that explicitly constructs Install with FailClosedVerifier // (e.g. a build with the trust core deliberately disabled), and for the // structural belt-and-suspenders refusal if a future ArtifactVerifier // implementation ever returns success without actually signing. CodeVerificationUnavailable = conduiterr.Register("registry.verification_unavailable", codes.Unimplemented) // CodeConnectorNotInstalled is raised on an uninstall/manifest lookup // miss. CodeConnectorNotInstalled = conduiterr.Register("registry.connector_not_installed", codes.NotFound) // CodeConnectorInUse is raised when an uninstall is refused because a // pipeline still references the exact name@version. CodeConnectorInUse = conduiterr.Register("registry.connector_in_use", codes.FailedPrecondition) // CodeAmbiguousUninstall is raised when "uninstall <name>" is given // with no version and more than one version of that name is installed // (§3.1 — a new behavior versus every original step-plan, introduced by // the manifest's name@version key change). CodeAmbiguousUninstall = conduiterr.Register("registry.ambiguous_uninstall", codes.InvalidArgument) // CodeBundleStale is raised when an offline --bundle is older than // maxStaleness and --allow-stale-bundle was not set. CodeBundleStale = conduiterr.Register("registry.bundle_stale", codes.FailedPrecondition) // CodeDownloadFailed is raised on a non-2xx artifact download, a // redirect loop, or a connection reset. CodeDownloadFailed = conduiterr.Register("registry.download_failed", codes.Unavailable) // compiled-in registry trust anchors could not be loaded (the embedded // public keys are missing or unparseable) — a build/release defect, not a // normal expired-anchor condition. It is codes.Internal (not the user's // input to fix) and machine-signals "reinstall a release build of // conduit," never "retry". Distinct from index.CodeTrustAnchorExpired, // which means the build HAS anchors but the fetched index was signed by a // key not among them. Verification never proceeds when this is raised — // it can only make installs fail closed, never open. CodeTrustAnchorsUnavailable = conduiterr.Register("registry.trust_anchors_unavailable", codes.Internal) )
Registry error codes — the "registry"-owned rows of the canonical registry error table (plan-v2 §4). Registered in full here, even though most are not triggered by any PR-0 code path yet (the install pipeline is PR-1; trust verification is PR-2) — plan-v2 §2.3 asks for the whole table to exist from the first PR, so docs/llms.txt generation has one complete, stable source of truth from day one, and so PR-1/PR-2 never have to invent a code plan-v2 already named.
See pkg/registry/index's codes.go and pkg/registry/trust's codes.go for the "index"- and "trust"-owned rows respectively — those two packages register their own codes locally (rather than here) specifically to avoid an import cycle, since this package imports both of them.
var ErrVerificationNotConfigured = conduiterr.New(CodeVerificationUnavailable,
"artifact verification is not configured in this build — connector installation is refused "+
"until the trust core (signature + provenance verification) is wired in")
ErrVerificationNotConfigured is returned by FailClosedVerifier.VerifyArtifact. Its presence in an error chain means: no real trust core is wired into this build, so connector installation is refused unconditionally.
Functions ¶
func AcquireManifestLock ¶
AcquireManifestLock acquires the short-held global manifest lock — required IN ADDITION to the per-target lock, because two installs of DIFFERENT connector names never contend on separate TargetLocks at all, but still share one manifest.json. Hold this only around the read-modify-write of manifest.json, never for the whole install pipeline.
func AcquireTargetLock ¶
AcquireTargetLock acquires the per-connector-name install lock, serializing the FULL pipeline (download through manifest write) for two concurrent installs of the SAME connector name. Callers must Unlock the returned lock on every exit path (defer immediately after a successful call).
func AppendAuditEvent ¶
func AppendAuditEvent(path string, ev AuditEvent) error
AppendAuditEvent appends ev as one JSON line to path, creating the file (and its parent directory) if needed.
Invariant: callers must only append an audit event AFTER the install it describes has already durably completed (binary renamed into place, manifest written) — install.go's call site does this last, deliberately, so the audit trail never claims an install happened before it actually did.
A single O_APPEND write of this size is atomic at the OS level — no temp-file-plus-rename dance is needed the way manifest.go's SaveManifest needs one for a whole-file rewrite — but Sync is still called explicitly to force the append to durable storage before Install reports success to its caller.
func CacheLookup ¶
CacheLookup returns the cached artifact bytes for digestHex (already normalized — see normalizeDigestHex), re-verifying the stored bytes' own digest before ever returning them — never trust-on-read. A miss (absent OR corrupt) reports (nil, false, nil); a corrupt entry (recomputed digest does not match the directory it is stored under) is evicted as a side effect, best-effort, so a miss also self-heals the cache for the next caller (step5 §5's corruption-handling requirement).
The cache is purely a download optimization: a hit here still requires the caller to run the FULL verification gate exactly as it would for a fresh download (see install.go's stageArtifact) — this function makes no trust claim about the bytes it returns.
func CachePopulate ¶
CachePopulate atomically stores data under digestHex: written to a uniquely-named temp directory first, then renamed into place, so a crash mid-populate leaves an orphaned ".tmp-populate-*" directory (cleaned up by CacheSweepTmp) rather than a corrupt "<digest>/" entry masquerading as valid.
data's own digest is recomputed and asserted to match digestHex — a defensive check, since every real caller already knows the digest from a just-corruption-checked download, but this function must never silently cache the wrong bytes under the wrong key.
Two concurrent populates of the SAME digestHex (two installs racing to cache the identical content-addressed artifact) are safe: if the final rename loses the race because the destination directory now exists, that destination is — by construction of content-addressing — the identical bytes this call would have written, so losing the race is treated as a successful no-op, not an error.
func CacheSweepTmp ¶
CacheSweepTmp removes every ".tmp-populate-*" directory under the cache root older than cacheTmpMaxAge — best-effort cleanup of an orphaned partial-populate left by a crash (step5 §5). Never returns an error to a caller that treats the cache as a pure optimization: a sweep failure just means a leftover temp directory persists a bit longer, not a correctness problem, so this function swallows its own errors rather than failing an install over cache housekeeping.
func CheckCorruption ¶
CheckCorruption compares the digest computed from the ACTUALLY RECEIVED bytes (got, from Download's incremental hash) against the index's declared sha256 (want, an optionally "sha256:"-prefixed 64-character hex string).
This is integrity, not trust: a byte-for-byte match here detects transport-level corruption (a bit-flip, a truncated transfer, a compromised CDN edge serving different bytes) but says nothing about WHO produced those bytes — proving that is ArtifactVerifier.VerifyArtifact's job (the actual authorization/trust boundary), run separately and always AFTER this check, never in place of it.
func ExtractBinary ¶
ExtractBinary opens the tar.gz archive at archivePath and extracts the single root-level regular file it must contain into destDir, returning its path. It refuses, with CodeArchiveInvalid, rather than guesses, on every one of the following:
- A path-traversal entry: an absolute path, or a relative path that escapes destDir once cleaned (e.g. "../../etc/passwd").
- A symlink or hardlink entry: a malicious archive must not be able to point extraction at an arbitrary target via a link.
- Zero or more than one candidate root-level regular file: this pipeline installs exactly one connector binary per artifact, so an ambiguous archive is refused, not guessed at. Non-root-level regular files (e.g. a LICENSE nested in a subdirectory) are extracted-and- ignored as far as "candidate" status goes, so an archive that legitimately bundles extra files alongside the binary at the root still resolves unambiguously.
- An archive that expands past maxExtractedBytes.
func IndexStatePath ¶
IndexStatePath is indexStatePath, exported for cmd/conduit/root/connectors/install.go — the one production call site that constructs a TrustedVerifier and must agree with Install's own internal path convention without duplicating it.
func ManifestKey ¶
ManifestKey builds the "name@version" manifest key, going through NormalizeVersion so a given install always produces the same key regardless of whether the caller wrote "v0.14.1" or "0.14.1" (§5) — manifest keys themselves are stored WITHOUT a "v" prefix.
func ManifestLockPath ¶
ManifestLockPath returns the short-held global lock file guarding manifest.json's read-modify-write critical section.
func NormalizeVersion ¶
NormalizeVersion parses a version string as semver, tolerating an optional leading "v" (plan-v2 §5): @version matching is semver equality, never raw string equality. The index stores bare semver ("0.14.0"); user input, manifest entries, and CLI flags may carry either form ("v0.14.0" or "0.14.0").
Every version comparison in this codebase — Resolve's exact-@version lookup (PR-1), manifest key construction (ManifestKey), minConduitVersion /minProtocolVersion compatibility checks, audit's installed-version-vs- index lookup (PR-4) — goes through NormalizeVersion and semver.Version.Equal/.Compare, never a bare string comparison. This directly prevents the bug plan-v2-requirements.md calls out: a "@v0.14.0" install request must resolve against an index entry stored as "0.14.0", not spuriously 404.
func SaveManifest ¶
SaveManifest writes m to path atomically (temp file + rename in the same directory, via pkg/foundation/atomicfile), so a crash mid-write can never leave a torn manifest (Invariant 5).
func SelectArtifact ¶
func SelectArtifact(connName string, v index.ConnectorVersion, goos, goarch string) (*index.Artifact, error)
SelectArtifact finds the artifact in v matching the exact (goos, goarch) pair among StandaloneArtifactKind entries. No match refuses with CodeNoPlatformArtifact, listing the (os, arch) pairs that DO exist for this version so the operator/agent knows what is actually available rather than just "not found".
func TargetLockPath ¶
TargetLockPath returns the per-connector-name lock file path.
Types ¶
type ArtifactRef ¶
ArtifactRef bundles what VerifyArtifact needs: the digest computed from actually-received bytes (the corruption check, CodeCorruptDownload, run first and separately from trust verification) and the two fetched Sigstore bundles. The install orchestrator (pkg/registry install pipeline, PR-1) fetches artifact.signature.bundleURL and the applicable slsaProvenance.bundleURL — via pkg/registry/boundedfetch, capped per P0-2 — before calling VerifyArtifact; this package never fetches on its own.
type ArtifactVerifier ¶
type ArtifactVerifier interface {
VerifyArtifact(ctx context.Context, ref ArtifactRef, identity trust.PinnedIdentity) (VerifyResult, error)
}
ArtifactVerifier is the authorization gate: signature + SLSA provenance against the connector name's pinned identity (R-1 §c steps 5b/6). The real implementation (PR-2) is backed by pkg/registry/trust.
type AuditEvent ¶
type AuditEvent struct {
Event string `json:"event"` // "connector_install" or "connector_uninstall" (PR-4)
Connector string `json:"connector"`
Version string `json:"version"`
Digest string `json:"digest"` // "sha256:<hex>"
Operator string `json:"operator,omitempty"`
Timestamp time.Time `json:"timestamp"`
Signed bool `json:"signed"`
VerifiedIdentity string `json:"verifiedIdentity"`
AllowUnsigned bool `json:"allowUnsigned"`
// Forced is set on an "connector_uninstall" event when --force overrode
// an in-use refusal (PR-4, step5 §2 step 8). Always false/omitted for
// "connector_install" events.
Forced bool `json:"forced,omitempty"`
}
AuditEvent is one append-only entry in <connectors.path>/.registry/audit.jsonl.
Signed/VerifiedIdentity/AllowUnsigned mirror ManifestEntry's own fields: present now (PR-0/PR-1) even though every event this build can produce has Signed: false, VerifiedIdentity: "", AllowUnsigned: false (see manifest.go's ManifestEntry doc — the same reasoning applies here).
type AuditFinding ¶
type AuditFinding string
AuditFinding is the specific condition an audited entry triggered, or "" for a clean pass. These are local-integrity/informational states or registry-trust findings — NOT new conduiterr codes in their own right (plan-v2 §4): REVOKED_PUBLISHER/YANKED_VERSION reuse trust.CodeIdentityRevoked/index.CodeVersionYanked verbatim via AuditResultEntry.Code, exactly as an install-time refusal would.
const ( FindingNone AuditFinding = "" FindingDelisted AuditFinding = "DELISTED" FindingUnknownVersion AuditFinding = "UNKNOWN_VERSION" FindingYankedVersion AuditFinding = "YANKED_VERSION" FindingRevokedPublisher AuditFinding = "REVOKED_PUBLISHER" FindingMissingArtifact AuditFinding = "MISSING_ARTIFACT" FindingDrifted AuditFinding = "DRIFTED" )
type AuditOptions ¶
type AuditOptions struct {
ConnectorsPath string
IndexURL string
IndexFile string
// IndexVerifier MUST be the real trust-core verifier (registry.TrustedVerifier)
// in production — the same one install.go wires in. audit reuses it
// exactly; there is no lower-trust alternative for this field (plan-v2
// §7 step5 note, and this PR's own scope guardrail).
IndexVerifier IndexVerifier
}
AuditOptions is RunAudit's configuration.
type AuditReport ¶
type AuditReport struct {
Findings []AuditResultEntry `json:"findings"`
}
AuditReport is RunAudit's full result.
func RunAudit ¶
func RunAudit(ctx context.Context, opts AuditOptions) (*AuditReport, error)
RunAudit re-fetches and re-verifies the current signed index (steps 1), loads the local manifest (step 2), and checks EVERY installed connector against the verified index data (step 3): a connector whose publisher has since been revoked, or whose installed version has since been yanked, or that has vanished from the index entirely, or whose exact version is no longer listed — plus the independent local-integrity checks (missing artifact, drifted digest).
A hard index-verification failure (unreachable, tampered/integrity, stale, rollback, or an unrecognized trust anchor) fails THE WHOLE RUN — never a per-connector finding, and never "audit half the connectors against a trusted index and half against nothing": you cannot partially trust an unverified index. This is returned as an error (a HARD command failure at the CLI layer), never folded into AuditReport.
func (AuditReport) ExitCode ¶
func (r AuditReport) ExitCode() int
ExitCode aggregates every Fail finding to one process exit code via the SAME worst-bucket rule as pkg/conduit/check.Report.ExitCode: each Fail finding's Code is classified independently via conduiterr.LookupCode + exitcode.ExitCode, and the largest bucket wins — never "first finding wins." Zero Fail findings returns exitcode.OK.
type AuditResultEntry ¶
type AuditResultEntry struct {
Name string `json:"name"`
InstalledVersion string `json:"installedVersion"`
Digest string `json:"digest,omitempty"`
Finding AuditFinding `json:"finding"`
Status AuditStatus `json:"status"`
Reason string `json:"reason,omitempty"`
Suggestion string `json:"suggestion,omitempty"`
// Code is the registered conduiterr reason string backing a Fail
// finding (REVOKED_PUBLISHER -> trust.CodeIdentityRevoked,
// YANKED_VERSION -> index.CodeVersionYanked) — AuditReport.ExitCode
// resolves this exactly the way pkg/conduit/check.Report.ExitCode
// resolves CheckResult.Code.
Code string `json:"code,omitempty"`
// MissingArtifact/Drifted are the independent local-integrity signals
// (step5 §6) — always populated regardless of which Finding "won" the
// slot above, so a caller/test can assert on them directly even when a
// registry-trust finding (yanked/revoked/delisted/unknown-version) also
// fired for the same entry.
MissingArtifact bool `json:"missingArtifact,omitempty"`
Drifted bool `json:"drifted,omitempty"`
// UnsignedInstall reports whether this exact name@version was ever
// recorded in the append-only unsigned-installs.log — NOT derived from
// manifest.json's own Signed/AllowUnsigned fields, which are locally
// rewritable by anyone with filesystem access (plan-v2 §13 P2 nit).
UnsignedInstall bool `json:"unsignedInstall,omitempty"`
}
AuditResultEntry is one installed connector's audit outcome.
type AuditStatus ¶
type AuditStatus string
AuditStatus mirrors pkg/conduit/check.Status (pass/warn/fail) — audit deliberately reuses that package's aggregation pattern (see AuditReport.ExitCode) rather than inventing a parallel one.
const ( AuditStatusPass AuditStatus = "pass" AuditStatusWarn AuditStatus = "warn" AuditStatusFail AuditStatus = "fail" )
type BundleManifest ¶
type BundleManifest struct {
BundleFormatVersion int `json:"bundleFormatVersion"`
Name string `json:"name"`
Version string `json:"version"`
OS string `json:"os"`
Arch string `json:"arch"`
SHA256 string `json:"sha256"`
Size int64 `json:"size"`
CreatedAt time.Time `json:"createdAt"`
}
BundleManifest is the small manifest.json entry inside a bundle tarball — distinct from (and much smaller than) registry.ManifestEntry, the install manifest's own per-connector record.
type BundleOptions ¶
type BundleOptions struct {
Name string
Version string
OutputPath string
IndexURL string
IndexFile string
GOOS, GOArch string
// IndexVerifier/ArtifactVerifier are REQUIRED (mirroring
// InstallOptions) — bundling is a normal, fully-verified install whose
// output is redirected into a tarball instead of --connectors.path; it
// is never a trust shortcut, so there is no FailClosedVerifier-style
// default here either.
IndexVerifier IndexVerifier
ArtifactVerifier ArtifactVerifier
RunningConduitVersion string
RunningProtocolVersion string
HTTPClient *http.Client
}
BundleOptions is Bundle's configuration.
type BundleResult ¶
type BundleResult struct {
Name string `json:"name"`
Version string `json:"version"`
OS string `json:"os"`
Arch string `json:"arch"`
OutputPath string `json:"outputPath"`
Digest string `json:"digest"`
Size int64 `json:"size"`
}
BundleResult is Bundle's success-path result.
func Bundle ¶
func Bundle(ctx context.Context, opts BundleOptions) (*BundleResult, error)
Bundle prepares an offline install bundle: fetch+verify the index (identical to install.go's own pipeline), resolve name/version, select the host-requested platform artifact, download and corruption-check it, fetch its signature/provenance bundles, and run FULL identity-pinned verification — all while online — before packaging everything (including the complete signed index snapshot, so the offline machine can itself re-check yank/revocation status) into a single tarball.
type CacheMeta ¶
type CacheMeta struct {
SHA256 string `json:"sha256"`
Size int64 `json:"size"`
CachedAt time.Time `json:"cachedAt"`
SourceURL string `json:"sourceUrl,omitempty"`
}
CacheMeta is the small companion file written alongside a cached artifact's bytes (step5 §5).
type DownloadResult ¶
DownloadResult is what Download reports: the digest computed from the bytes actually received (input to CheckCorruption, and later ArtifactRef.Digest — never re-read from disk after the fact) and the exact byte count written.
func Download ¶
func Download(ctx context.Context, client *http.Client, url, destPath string, maxBytes int64) (DownloadResult, error)
Download streams url's response body into destPath (created exclusively, 0600 — refuses if destPath already exists, since every staging path is meant to be freshly created by the caller), computing a sha256 digest incrementally over the same bytes as they are written — never buffering the whole artifact in memory. The write is capped at maxBytes+1: reading exactly maxBytes+1 bytes back (rather than hitting EOF at or before maxBytes) proves the real payload exceeds the index's declared size rather than happening to land exactly on it (the same technique pkg/registry/boundedfetch uses for the index and bundle fetches — applied here directly, rather than via that package, because an artifact is streamed to disk rather than buffered into a []byte).
Download does not compare the digest to any expected value — that is CheckCorruption's job, deliberately kept separate so the "this is integrity, not trust" distinction (Decision §3 step 5a) stays visible at the call site instead of being buried inside this function.
type FailClosedVerifier ¶
type FailClosedVerifier struct{}
FailClosedVerifier is the ONLY IndexVerifier/ArtifactVerifier this package wires into production Install by default until PR-2 lands. It cannot regress into "verification silently skipped": VerifyIndex only ever performs a shape/schema check (index.ParseUnverified) and marks the result Verified: false; VerifyArtifact unconditionally refuses. The install pipeline (PR-1) takes both verifiers as required constructor arguments with no default — the one production call site passes FailClosedVerifier{} explicitly and visibly until PR-2 swaps it for the real trust-backed implementation.
func (FailClosedVerifier) VerifyArtifact ¶
func (FailClosedVerifier) VerifyArtifact(_ context.Context, _ ArtifactRef, _ trust.PinnedIdentity) (VerifyResult, error)
VerifyArtifact always refuses: no real trust core exists in this build.
func (FailClosedVerifier) VerifyIndex ¶
func (FailClosedVerifier) VerifyIndex(_ context.Context, raw []byte) (*index.VerifiedIndex, error)
VerifyIndex performs a shape/schema check ONLY (index.ParseUnverified) — never a trust decision — and reports the result as Verified: false, so a caller cannot mistake it for a cryptographically-checked index by accident.
type InUseRef ¶
type InUseRef struct {
PipelineID string `json:"pipelineId"`
ConnectorID string `json:"connectorId"`
}
InUseRef identifies one connector instance (in a pipeline, running or merely provisioned) whose plugin reference exactly matches the name@version an uninstall is about to remove.
Deliberately plain data, not an interface Uninstall calls out to: the CALLER (cmd/conduit/root/connectors/uninstall.go) is responsible for collecting this list — via the running engine's ConnectorServiceClient if reachable, falling back to a direct scan of provisioned pipeline configs via pkg/provisioning/config if not (step5 §2 step 2) — exactly the same "collect primitive signals at the CLI layer" pattern install.go already uses for TTY/CIEnv/EnvVarSet. This keeps pkg/registry free of a gRPC client or pkg/provisioning dependency, and keeps Uninstall itself a pure, easily-unit-tested function of already-known facts.
type IndexVerifier ¶
type IndexVerifier interface {
VerifyIndex(ctx context.Context, raw []byte) (*index.VerifiedIndex, error)
}
IndexVerifier authenticates a fetched index's raw bytes before any field is trusted for a security decision (R-1 §a). The real implementation (PR-2) is backed by pkg/registry/index.Verify; FailClosedVerifier is the only production wiring until then.
type InstallBundleOptions ¶
type InstallBundleOptions struct {
BundlePath string
ConnectorsPath string
// Verifier MUST be a real *TrustedVerifier — offline install never uses
// FailClosedVerifier-style verification-disabled wiring; there is no
// "trust it because we can't check" fallback for the offline path (see
// this file's doc comment). Concrete, not the IndexVerifier/
// ArtifactVerifier interfaces, because the stale-bundle carve-out below
// needs to construct a staleness-relaxed COPY of the same verifier for
// exactly one retry — an interface value can't be adjusted that way.
Verifier *TrustedVerifier
InstalledBy string
LockTimeout time.Duration
// RunningConduitVersion/RunningProtocolVersion are this build's own
// versions, compared against the bundled version's minConduitVersion/
// minProtocolVersion exactly as an online install would (resolve.go) —
// an offline install must refuse an incompatible pinned version just as
// readily as an online one; being offline is not a compatibility
// exemption.
RunningConduitVersion string
RunningProtocolVersion string
// AllowStaleBundle requests tolerating a bundled index snapshot older
// than the verifier's MaxStaleness — gated identically to
// --allow-unsigned (plan-v2/step5 §7 step 3, §9 decision item 2): TTY/
// operator-policy gated, forbiddable, never silently flippable. Only
// ever consulted after a first verification attempt fails SPECIFICALLY
// with index.CodeIndexStale — every other index verification failure
// (tampering, rollback, an unrecognized trust anchor) is refused
// unconditionally, with no override of any kind.
AllowStaleBundle bool
// TTY, CIEnv, IsMCP, EnvVarSet, TypedConfirmation, OperatorAllowStaleBundle
// are the primitive signals policy.StaleBundleContext needs, collected
// by the CLI layer (which never imports pkg/registry/policy directly —
// see the PolicyBypass depguard rule) and passed through here. Only
// consulted when AllowStaleBundle is true AND the bundle actually turns
// out to be stale.
TTY bool
CIEnv bool
IsMCP bool
EnvVarSet bool
TypedConfirmation bool
OperatorAllowStaleBundle bool
}
InstallBundleOptions is InstallFromBundle's configuration.
type InstallOptions ¶
type InstallOptions struct {
// Name is the connector name to install (exact match only).
Name string
// Version is an optional version constraint ("0.14.1" or "v0.14.1").
// Empty selects the newest version compatible with
// RunningConduitVersion/RunningProtocolVersion.
Version string
// ConnectorsPath is the standalone connectors directory
// (--connectors.path) the binary is installed into, and under which
// .registry/ bookkeeping lives.
ConnectorsPath string
// IndexURL is the index to fetch over HTTP(S). Ignored if IndexFile is
// set.
IndexURL string
// IndexFile reads the index from a local path instead (offline mode);
// takes priority over IndexURL when both are set.
IndexFile string
IndexVerifier IndexVerifier
ArtifactVerifier ArtifactVerifier
// RunningConduitVersion/RunningProtocolVersion are this build's own
// versions, compared against each candidate version's
// minConduitVersion/minProtocolVersion (see resolve.go).
RunningConduitVersion string
RunningProtocolVersion string
// InstalledBy identifies the operator for the manifest entry and audit
// event (best-effort; e.g. the OS user running the command).
InstalledBy string
// LockTimeout bounds how long Install waits to acquire the per-target
// and manifest locks. Zero uses DefaultLockTimeout.
LockTimeout time.Duration
// DryRun performs resolution and platform selection only — no
// download, no filesystem write under ConnectorsPath.
DryRun bool
// HTTPClient overrides the download/bundle-fetch client. nil uses a
// redirect-bounded default (newDownloadClient). Primarily a test seam.
HTTPClient *http.Client
// GOOS/GOArch override runtime.GOOS/runtime.GOARCH for platform
// selection — a test seam only, never a user-facing flag (installing
// for a different host than the one running `install` is out of
// scope).
GOOS, GOArch string
// AllowUnsigned requests skipping SIGNATURE/PROVENANCE verification
// only — the sha256 corruption check (CheckCorruption) ALWAYS still
// runs regardless of this flag; see downloadVerifyAndInstall. Subject
// to policy.Decide's full gate (plan-v2 §6): setting this true does
// NOT by itself skip verification — Install is the only call site of
// policy.Decide in this codebase (enforced by the PolicyBypass
// depguard rule in .golangci.yml), and Decide can still refuse
// (CodeUnsignedInstallNonInteractive/CodeUnsignedInstallDisabledByPolicy)
// depending on TTY/CIEnv/IsMCP/OperatorAllowUnsigned/EnvVarSet/
// TypedConfirmation below.
AllowUnsigned bool
// TTY, CIEnv, IsMCP, EnvVarSet, and TypedConfirmation are the primitive
// signals policy.Context needs, collected by the CLI/MCP layer (which
// never imports pkg/registry/policy directly — see the PolicyBypass
// depguard rule) and passed through here. Only consulted when
// AllowUnsigned is true.
TTY bool
CIEnv bool
IsMCP bool
EnvVarSet bool
TypedConfirmation bool
// OperatorAllowUnsigned is the operator's install.allow-unsigned config
// value (read once at CLI startup, never re-read per request) — false
// hard-disables the AllowUnsigned path regardless of every other field
// above. Named distinctly from AllowUnsigned (the per-invocation
// request) so the two are never confused: AllowUnsigned is "the caller
// is asking for this," OperatorAllowUnsigned is "the operator permits
// asking at all."
OperatorAllowUnsigned bool
// UnsignedInstallsLogPath overrides the default
// <ConnectorsPath>/.registry/unsigned-installs.log destination for the
// append-only unsigned-install audit trail — a test seam.
UnsignedInstallsLogPath string
}
InstallOptions is Install's full configuration.
IndexVerifier and ArtifactVerifier are REQUIRED, no-default constructor- style arguments (plan-v2 §2.2): the one production call site (cmd/conduit/root/connectors/install.go) passes FailClosedVerifier{} for both, explicitly and visibly, until PR-2 lands. Nothing in this package supplies a default — a nil verifier is a caller bug, not a "verification skipped" default, and Install.validate refuses it outright.
type InstallResult ¶
type InstallResult struct {
Name string `json:"name"`
Version string `json:"version"`
OS string `json:"os"`
Arch string `json:"arch"`
// Deprecated mirrors the resolved version's index.ConnectorVersion.Deprecated
// flag — informational only; it never causes a refusal (see resolve.go).
Deprecated bool `json:"deprecated,omitempty"`
DryRun bool `json:"dryRun,omitempty"`
ArtifactURL string `json:"artifactUrl,omitempty"`
Size int64 `json:"size,omitempty"`
AlreadyInstalled bool `json:"alreadyInstalled,omitempty"`
ArtifactFile string `json:"artifactFile,omitempty"`
Digest string `json:"digest,omitempty"`
SourceIndexVersion int64 `json:"sourceIndexVersion,omitempty"`
}
InstallResult is Install's success-path result, also used (with DryRun or AlreadyInstalled set) for the two non-error early-exit paths.
func Install ¶
func Install(ctx context.Context, opts InstallOptions) (*InstallResult, error)
Install runs the full non-crypto install pipeline: fetch+shape-check the index, resolve name/version, select a host platform artifact, download it into a private per-install staging directory, check byte-for-byte integrity, run the verification gate, then — only past every one of those — atomically install the binary and record the manifest/audit entries.
Fail-closed by construction ¶
Step 6 below (ArtifactVerifier.VerifyArtifact) is the ONLY authorization gate between an integrity-checked download and an installed, runnable binary. With the production FailClosedVerifier (verify.go), this call ALWAYS returns ErrVerificationNotConfigured — so no code path in this function can reach extraction, the final rename, or a manifest write in a normal build. This is verified directly by install_test.go's TestInstall_FailClosedByConstruction (using FailClosedVerifier, the exact production wiring cmd/conduit/root/connectors/install.go uses) alongside TestInstall_FullPipeline (using a test-only pass-through verifier injected via this same InstallOptions field — never a separate code path — to exercise resolve/download/stage/sha256/manifest end to end).
func InstallFromBundle ¶
func InstallFromBundle(ctx context.Context, opts InstallBundleOptions) (*InstallResult, error)
InstallFromBundle installs a connector fully offline: NO network call of any kind is made by this function or anything it calls (index verification and Sigstore bundle verification both operate purely over bytes already in hand — see this file's doc comment) — asserted, not merely assumed: if index-snapshot.json is malformed, this function fails closed rather than ever falling back to a live index fetch, which would silently reintroduce the network dependency this path exists to avoid.
type InstalledConnector ¶
type InstalledConnector struct {
Name string `json:"name"`
Version string `json:"version"`
Signed bool `json:"signed"`
Source string `json:"source"`
InstalledAt time.Time `json:"installedAt"`
Digest string `json:"digest,omitempty"`
LatestAvailable string `json:"latestAvailable,omitempty"`
Status InstalledStatus `json:"status"`
}
InstalledConnector is one row of `list --installed` — a plugin artifact recorded in the local manifest, not a pipeline connector instance.
type InstalledStatus ¶
type InstalledStatus string
InstalledStatus is list --installed's per-row status (step5 §3).
const ( InstalledStatusOK InstalledStatus = "ok" InstalledStatusUpdateAvailable InstalledStatus = "update-available" InstalledStatusMissingArtifact InstalledStatus = "missing-artifact" InstalledStatusDrifted InstalledStatus = "drifted" InstalledStatusIndexUnreachable InstalledStatus = "index-unreachable" InstalledStatusNotInIndex InstalledStatus = "not-in-index" )
type ListInstalledOptions ¶
ListInstalledOptions is ListInstalled's configuration.
type ListInstalledResult ¶
type ListInstalledResult struct {
Installed []InstalledConnector `json:"installed"`
// IndexUnreachable is true when the registry index could not be
// fetched/parsed AT ALL for this run — every row still renders (with a
// blank LatestAvailable) rather than failing the whole command, per
// step5 §3: this command degrades gracefully because checking for
// updates is informational, not a security check (contrast with
// `audit`, which hard-fails on an unreachable/untrusted index).
IndexUnreachable bool `json:"indexUnreachable"`
}
ListInstalledResult is ListInstalled's return shape.
func ListInstalled ¶
func ListInstalled(ctx context.Context, opts ListInstalledOptions) (*ListInstalledResult, error)
ListInstalled enumerates every entry in the local install manifest.
The index is consulted best-effort, and ONLY for the informational LatestAvailable/update-available signal — never for a schema-version or signature check, and the index bytes are never cryptographically verified here (index.ParseUnverified, a shape/schema check only): an operator who wants a verified re-check of installed connectors against yank/revocation runs `conduit connectors audit`, which does real verification and hard-fails when it can't.
type Manifest ¶
type Manifest struct {
SchemaVersion int `json:"schemaVersion"`
Installs map[string]ManifestEntry `json:"installs"`
}
Manifest is the persisted <connectors.path>/.registry/manifest.json format (plan-v2 §3): every installed connector, keyed by "name@version" so two pipelines can pin two different versions of the same connector simultaneously. This is the load-bearing fix over every original step-plan's single-version-per-name shape (§3.1) — resolved by DeVaris decision, collapsing step2's and step5's two divergent manifest formats into one.
func LoadManifest ¶
LoadManifest reads and parses a manifest file. A missing file is not an error: it returns an empty, schema-current Manifest, matching "no connectors installed yet".
func (*Manifest) InstalledVersions ¶
InstalledVersions returns every version of name currently installed, sorted ascending by semver. Used by the eventual uninstall command's CodeAmbiguousUninstall path (§3.1, PR-4: "conduit connectors uninstall <name>" without a version refuses when more than one version is installed) and by "list --installed"'s per-name grouping — both PR-4 concerns; this is the pure data-query primitive they build on.
type ManifestEntry ¶
type ManifestEntry struct {
Name string `json:"name"`
Version string `json:"version"`
Kind string `json:"kind"`
OS string `json:"os"`
Arch string `json:"arch"`
ArtifactFile string `json:"artifactFile"`
Digest string `json:"digest"`
Size int64 `json:"size"`
InstalledAt time.Time `json:"installedAt"`
InstalledBy string `json:"installedBy,omitempty"`
// SourceIndexVersion is the payload.index.version this install was
// resolved against — needed by audit (PR-4) to know whether an
// install's rollback high-water mark came from a live or bundled index
// snapshot.
SourceIndexVersion int64 `json:"sourceIndexVersion"`
// Source is "index" or "offline-bundle".
Source string `json:"source"`
// BundleIndexVersion is set only when Source == "offline-bundle".
BundleIndexVersion int64 `json:"bundleIndexVersion,omitempty"`
Signed bool `json:"signed"`
VerifiedIdentity string `json:"verifiedIdentity"`
AllowUnsigned bool `json:"allowUnsigned"`
}
ManifestEntry is one installed connector version.
Signed/VerifiedIdentity/AllowUnsigned were present from PR-0/PR-1 even though only FailClosedVerifier existed then (every entry a PR-1 build could produce had Signed: false, VerifiedIdentity: "", AllowUnsigned: false, and PR-1 could not, by construction, produce ANY entry at all in a normal build). As of PR-2 (the real trust core), these fields carry their intended meaning: Signed/VerifiedIdentity reflect a real signature/provenance verification outcome, and AllowUnsigned is true only for an install that went through the full policy.Decide gate (plan-v2 §6) — see pkg/registry/policy and install.go's unsignedInstallGate. No schema change, no migration: the field shapes are unchanged from PR-0.
type ResolveOptions ¶
type ResolveOptions struct {
// Name is looked up by EXACT match only — plan-v2/the design doc's
// explicit anti-typosquat stance: a near-miss name (e.g. "postgress")
// is CodeConnectorNotFound, never a fuzzy suggestion toward "postgres".
Name string
// Version is a version constraint string ("0.14.1" or "v0.14.1",
// compared via NormalizeVersion — never raw string equality). Empty
// means "newest version compatible with RunningConduitVersion/
// RunningProtocolVersion".
Version string
// RunningConduitVersion/RunningProtocolVersion are compared against
// each candidate ConnectorVersion's MinConduitVersion/
// MinProtocolVersion. A value that does not parse as semver (e.g. Go's
// own "development" build-info fallback for a locally built binary) is
// treated as satisfying every compatibility check — a local dev build
// must not hard-refuse every install just because it has no embedded
// version.
RunningConduitVersion string
RunningProtocolVersion string
}
ResolveOptions is Resolve's input: which connector, which version (or none, for newest-compatible), and the running build's own versions to check compatibility against.
type ResolvedVersion ¶
type ResolvedVersion struct {
Connector index.Connector
Version index.ConnectorVersion
}
ResolvedVersion bundles what platform selection and the rest of the install pipeline need together: the connector (for its Publisher identity pin and Name) and the one ConnectorVersion that was selected.
func Resolve ¶
func Resolve(payload index.Payload, opts ResolveOptions) (*ResolvedVersion, error)
Resolve finds the connector named opts.Name in payload and selects one of its versions per opts.Version (exact match) or, if empty, the newest version compatible with the running build. It refuses (with a specific, coded reason) a connector whose publisher identity has been revoked, a pinned version that has been yanked, and an explicit version pin that is incompatible with the running Conduit/protocol version — but does NOT refuse a deprecated version (deprecated is a soft, informational flag, not a trust or safety state; see ResolvedVersion.Version.Deprecated, surfaced by the caller in --json/output). This mirrors the frozen index schema's own modeling: schemaVersion the parser is strict; deprecated is documented as orthogonal to trust, and no error code exists in the canonical table (plan-v2 §4) for refusing on it.
Resolve never mutates payload and performs no I/O — it is a pure function over an already-fetched (and, in a real build, already-verified) index.
type TrustedVerifier ¶
type TrustedVerifier struct {
// Anchors are this build's compiled-in Conduit registry root/freshness
// public keys (index.TrustAnchors). A released build embeds the real
// ceremony anchors (cmd/conduit/root/connectors/anchors.go go:embeds the
// public PEMs) and verifies the served signed index against them. An empty
// Anchors value (a custom/stripped build with no embedded anchors) means
// every real index fails closed with CodeTrustAnchorExpired — the correct
// fail-closed behavior, NOT a silent bypass.
Anchors index.TrustAnchors
// StatePath is where the persisted index rollback high-water mark lives
// (index.LoadState/SaveState) — see indexStatePath.
StatePath string
// MaxStaleness overrides index.DefaultMaxStaleness when non-zero.
MaxStaleness time.Duration
// RequireProvenance, if true, refuses an artifact whose index entry has
// no slsaProvenance reference at all (ArtifactRef.ProvenanceBundle is
// empty) rather than treating "no provenance to check" as vacuously
// satisfied.
//
// Resolved to true in this build's one production call site
// (cmd/conduit/root/connectors/install.go) per DeVaris's Tier-1 posture
// decision: a "verified" artifact always includes the L3 SLSA build
// attestation, so signature-only (no-provenance) artifacts are refused
// with trust.CodeProvenanceInvalid. This resolves the scope ambiguity
// this field was originally added to flag (the frozen index schema
// marks slsaProvenance optional/omitempty at the version level, while
// P1-2 requires builder-ID binding "from day one, no soft period" for
// provenance that IS present) — the ambiguity is now closed: absence is
// no longer treated as vacuously satisfied anywhere provenance is
// required.
//
// The zero value remains false (provenance verified-and-bound whenever
// present, but absence alone does not refuse) so existing unit/e2e
// tests that construct a bare TrustedVerifier{} to exercise unrelated
// checks (index integrity, identity pinning, rollback, staleness) are
// unaffected — every test exercising the artifact-verification success
// path now sets this explicitly to match production. A hard requirement
// downstream of this default: the seed-connector bootstrap (plan-v2 §9)
// must produce L3 provenance for all 6 seed connectors, or none of them
// will install once production trust anchors land.
RequireProvenance bool
// LockTimeout bounds how long VerifyIndex waits to acquire the
// index-state lock (see acquireIndexStateLock). Zero uses
// DefaultLockTimeout.
LockTimeout time.Duration
}
TrustedVerifier is the real IndexVerifier/ArtifactVerifier implementation (PR-2): index-signature + freeze/rollback verification via pkg/registry/index, and identity-pinned artifact-signature + SLSA provenance verification via pkg/registry/trust. This is what cmd/conduit/root/connectors/install.go wires in place of FailClosedVerifier (verify.go) as of this PR — every real `conduit connectors install` invocation now actually installs verified artifacts.
func (*TrustedVerifier) VerifyArtifact ¶
func (v *TrustedVerifier) VerifyArtifact(ctx context.Context, ref ArtifactRef, identity trust.PinnedIdentity) (VerifyResult, error)
VerifyArtifact is the authorization gate (R-1 §c steps 5b/6): identity- pinned signature verification, then — if a provenance bundle is present (or unconditionally, if RequireProvenance) — SLSA provenance verification and subject-digest/builder-ID binding. Any failure refuses with the specific code from pkg/registry/trust; success in every step returns VerifyResult{Signed: true, VerifiedIdentity: <the actual signing SAN>}.
Invariant: the pinned identity.IdentityPattern is defensively re-validated for tightness (trust.ValidateIdentityPattern) before it is ever used to accept a signature or attestation. identity.IdentityPattern comes from Publisher.ExpectedIdentityPattern in an index that has already passed index-signature verification (VerifyIndex) — but a valid signature over the index only proves the index wasn't tampered with in transit, not that its pinned pattern is tight. Tightness is otherwise enforced solely by a manual reviewer checklist in index-CI (which does not exist yet as of this PR) — a single point of failure a compromised or careless publisher registration could slip past. Without this check, a signature-verified index entry with e.g. ExpectedIdentityPattern: "^.*$" would let ANY identity's valid signature satisfy the pin, silently collapsing identity pinning to "any signature at all". Refusing here, before VerifyArtifactSignature/VerifyAttestationEnvelope ever consult the pattern, is defense-in-depth: it holds even when index-CI's checklist is bypassed, missing, or wrong.
func (*TrustedVerifier) VerifyIndex ¶
func (v *TrustedVerifier) VerifyIndex(_ context.Context, raw []byte) (*index.VerifiedIndex, error)
VerifyIndex implements R-1 §a-§b in full via pkg/registry/index: signature verification (index.Verify) against Anchors, then — only after that succeeds — the two independently-triggerable freeze/rollback checks (index.CheckRollback, index.CheckStaleness), and only once ALL of those pass, persists the new high-water mark (index.SaveState) — never on a rejected fetch, per index.CheckRollback's doc comment.
Concurrency ¶
The entire LoadState -> verify -> CheckRollback/CheckStaleness -> SaveState sequence runs under acquireIndexStateLock, held for the whole critical section (not just around SaveState's own write). This is required — not a nicety — because VerifyIndex runs BEFORE Install's per-target lock is ever acquired (index resolution is step 1-3 of the pipeline, ahead of AcquireTargetLock at step 4), so two concurrent installs of DIFFERENT connector names, which never contend on separate TargetLocks, would otherwise both read the same high-water mark, both pass their own rollback check against it, and race to write — the LATER write silently winning even if it verified an OLDER (though still valid and fresh) index, non-monotonically regressing the persisted anti-replay floor. Found during this PR's adversarial self-review; fixed here rather than shipped as a known gap.
type UninstallOptions ¶
type UninstallOptions struct {
// Name is the connector name to uninstall.
Name string
// Version is an optional exact version ("0.14.1" or "v0.14.1"). Empty
// auto-resolves ONLY if exactly one version of Name is currently
// installed; if more than one is installed, an empty Version refuses
// with CodeAmbiguousUninstall (plan-v2 §3.1) rather than guessing.
Version string
// ConnectorsPath is the standalone connectors directory
// (--connectors.path) this uninstall operates against.
ConnectorsPath string
// Force proceeds even when InUseRefs is non-empty. The result still
// reports every affected pipeline via Warnings — this is a loudly
// surfaced warning, not a silent flag flip (step5 §2 step 4).
Force bool
// InUseRefs is every connector instance (across every pipeline) whose
// plugin reference is exactly standalone:<name>@<version> — see
// InUseRef's doc comment for why the caller, not this function,
// collects it.
InUseRefs []InUseRef
// InstalledBy identifies the operator for the audit event (best-effort).
InstalledBy string
// LockTimeout bounds how long Uninstall waits to acquire the
// per-connector-name lock and the manifest lock. Zero uses
// DefaultLockTimeout.
LockTimeout time.Duration
}
UninstallOptions is Uninstall's full configuration.
type UninstallResult ¶
type UninstallResult struct {
Name string `json:"name"`
Version string `json:"version"`
Digest string `json:"digest,omitempty"`
Forced bool `json:"forced"`
Warnings []string `json:"warnings"`
// DriftDetected is true when the on-disk artifact's sha256 no longer
// matched the manifest's recorded digest at uninstall time (step5 §6) —
// informational: the artifact is removed either way.
DriftDetected bool `json:"driftDetected,omitempty"`
// ArtifactAlreadyMissing is true when the artifact file was already gone
// before Uninstall ran (e.g. removed manually with rm) — not a failure;
// Uninstall still cleans up the manifest entry.
ArtifactAlreadyMissing bool `json:"artifactAlreadyMissing,omitempty"`
}
UninstallResult is Uninstall's success-path result.
func Uninstall ¶
func Uninstall(opts UninstallOptions) (*UninstallResult, error)
Uninstall removes an installed connector's artifact and manifest entry.
Invariant 5 (atomic state/checkpoint writes): the manifest is only ever rewritten via SaveManifest's atomic temp+rename — a crash mid-uninstall leaves either the pre-uninstall manifest (artifact possibly already deleted, in which case a retry finishes the job — os.Remove tolerates "already gone") or the fully-updated post-uninstall manifest, never a torn one.
type VerifyResult ¶
type VerifyResult struct {
// Signed is false only when the pkg/registry/policy --allow-unsigned
// path explicitly bypassed verification.
Signed bool
// VerifiedIdentity is the SAN that signed the artifact, once PR-2
// lands.
VerifiedIdentity string
}
VerifyResult is what a successful (or explicitly policy-bypassed) artifact verification reports back to the installer, for the manifest's Signed/VerifiedIdentity fields (§3).
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package boundedfetch is the shared size-capped fetch primitive behind P0-2 (plan-v2 §2.4 item 1): attacker-controlled bytes (an index, a connector artifact, a signature bundle, a provenance bundle) are always read through an io.LimitReader capped one byte past the caller's limit, so a response that would exceed the cap is distinguished (ErrTooLarge) from one that happens to land exactly on it, and from any other fetch failure.
|
Package boundedfetch is the shared size-capped fetch primitive behind P0-2 (plan-v2 §2.4 item 1): attacker-controlled bytes (an index, a connector artifact, a signature bundle, a provenance bundle) are always read through an io.LimitReader capped one byte past the caller's limit, so a response that would exceed the cap is distinguished (ErrTooLarge) from one that happens to land exactly on it, and from any other fetch failure. |
|
Package index parses, freezes, and (eventually) verifies the signed connector registry index — the security-critical foundation `install`, `audit`, and the publish Action all trust.
|
Package index parses, freezes, and (eventually) verifies the signed connector registry index — the security-critical foundation `install`, `audit`, and the publish Action all trust. |
|
Package policy is the --allow-unsigned gate (plan-v2 §6, P1-3).
|
Package policy is the --allow-unsigned gate (plan-v2 §6, P1-3). |
|
adversarial
Package adversarial is the shared fixture corpus (plan-v2 §11, P1-4): a small set of hand-built, LOCALLY-generated (never touching production Sigstore or Conduit registry infrastructure) signed/attested test entities, each paired with the pkg/registry/trust error this build's verification pipeline must produce for it.
|
Package adversarial is the shared fixture corpus (plan-v2 §11, P1-4): a small set of hand-built, LOCALLY-generated (never touching production Sigstore or Conduit registry infrastructure) signed/attested test entities, each paired with the pkg/registry/trust error this build's verification pipeline must produce for it. |
|
trustroot
Package trustroot embeds a pinned, offline snapshot of Sigstore's own public-good trust root (layer 1: Fulcio CA, Rekor transparency log, CTFE public keys — see pkg/registry/trust's package doc for the two-layer distinction).
|
Package trustroot embeds a pinned, offline snapshot of Sigstore's own public-good trust root (layer 1: Fulcio CA, Rekor transparency log, CTFE public keys — see pkg/registry/trust's package doc for the two-layer distinction). |