snapshot

package
v0.24.0 Latest Latest
Warning

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

Go to latest
Published: Jul 28, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Overview

Package snapshot defines the disco-snapshot envelope: a manifest carrying tool version, scan IDs, generated-at timestamp, and a SHA-256 of the frozen DB. Used by `disco snapshot` to write the envelope and by `disco verify` to recompute the hash and compare. Pure stdlib so the receiver-side `disco verify` works regardless of the producer's build.

Index

Constants

View Source
const FormatV1 = "disco-snapshot/v1"

FormatV1 is the only manifest format this package writes or accepts. Future revisions (e.g. signed manifests) bump this and add backward- compatible reader paths.

Variables

View Source
var (
	// ErrArchiveCorrupt covers gzip/xz/tar/zip framing or CRC failures —
	// the bytes are not a well-formed archive.
	ErrArchiveCorrupt = errors.New("archive corrupt or truncated")
	// ErrManifestMissing means the archive opened cleanly but the
	// disco.db or manifest.json entry is absent.
	ErrManifestMissing = errors.New("manifest entry missing")
	// ErrManifestFormat means manifest.json was present but did not parse
	// as JSON / failed schema decode.
	ErrManifestFormat = errors.New("manifest format invalid")
	// ErrTrailingBytes flags a tail-tampered archive (`echo x >> snap.tgz`).
	ErrTrailingBytes = errors.New("trailing bytes after archive end")
)

Sentinel errors so cmd/verify.go can render auditor-grade specific stderr strings instead of one collapsed "archive corrupt or truncated". Same exit code (1) for all; the difference is the diagnostic an auditor quotes in their report.

View Source
var SupportedFormats = []string{".zip", ".tar.gz", ".tgz", ".tar.xz", ".txz"}

SupportedFormats lists the file-extension shapes accepted by DetectFormat. Order is stable for help-text rendering.

Functions

func CanonicalManifestBytes

func CanonicalManifestBytes(m Manifest) ([]byte, error)

CanonicalManifestBytes returns the deterministic byte form of m used as the signing payload for a disco-snapshot. The shape is the JCS-style (RFC 8785) canonical JSON: compact (no whitespace), struct fields in declaration order. encoding/json preserves struct field order, and the Manifest struct fields are all simple types (strings, []string), so a plain Marshal call is already canonical.

The byte sequence is what `disco snapshot --signing-payload` writes and what `disco verify --signature ...` re-derives from the embedded manifest.json before checking the ed25519 signature.

func HashFile

func HashFile(path string) (string, error)

HashFile returns the lowercase-hex SHA-256 of the file at path. Streams via io.Copy so large DBs don't load entirely into memory.

func LoadEd25519PublicKey

func LoadEd25519PublicKey(path string) (ed25519.PublicKey, error)

LoadEd25519PublicKey reads an ed25519 public key from path. Accepts:

  • PEM-wrapped X.509/PKIX SubjectPublicKeyInfo (`openssl pkey -pubout`).
  • OpenSSH authorized-keys / `.pub` line (`ssh-ed25519 AAAAC3... [comment]`).
  • Raw 32-byte key (binary).

OpenSSH parsing uses x/crypto/ssh, already a transitive dep — no new module required. SSHSIG-armored signatures (`ssh-keygen -Y sign` output) are NOT accepted on the --signature path; pair an OpenSSH pubkey with a raw 64-byte ed25519 signature produced by openssl/cosign/minisign instead.

func VerifyDetachedSignature

func VerifyDetachedSignature(m Manifest, sigPath, pubKeyPath string) error

VerifyDetachedSignature loads the ed25519 public key at pubKeyPath, the signature at sigPath, and validates that the canonical bytes of m carry the signature. Returns nil on a verified match.

func WriteArchive

func WriteArchive(out string, format Format, dbPath string, m Manifest) error

WriteArchive packages dbPath + manifest into a single archive at out. Writes via "<out>.tmp" + os.Rename so a failed write leaves no partial archive at the final path. dbPath is streamed (not loaded into memory).

func WriteManifest

func WriteManifest(path string, m Manifest) error

WriteManifest writes m to path with two-space indent and a trailing newline. encoding/json marshals struct fields in declaration order; the Manifest struct is laid out so the on-disk shape is deterministic. File mode 0o600 to mirror the store's DB permissions.

Types

type Format

type Format int

Format identifies the on-disk archive container for a disco snapshot. FormatUnknown is the zero value; callers must reject it.

const (
	FormatUnknown Format = iota
	FormatZip
	FormatTarGz
	FormatTarXz
)

func DetectFormat

func DetectFormat(path string) (Format, error)

DetectFormat returns the archive format inferred from the path's extension. Match is case-insensitive. Unknown extensions surface a clear error listing supported shapes.

func (Format) String

func (f Format) String() string

String returns the canonical extension prefix for the format.

type Manifest

type Manifest struct {
	Format      string    `json:"format"`
	ToolVersion string    `json:"toolVersion"`
	GeneratedAt string    `json:"generatedAt"`
	DBSHA256    string    `json:"dbSha256"`
	Scans       []ScanRef `json:"scans"`
}

Manifest is the disco-snapshot envelope. JSON keys are stable; receiver-side checksums of manifest.json are reproducible across machines as long as the producer used WriteManifest (sorted-key indent).

func ArchiveContents

func ArchiveContents(path string) (Manifest, string, error)

ArchiveContents returns the manifest plus a hex SHA-256 of the inner disco.db, computed by streaming the archive once. No temp files; no full DB load. Manifest is decoded from the manifest.json entry. Order of entries inside the archive is irrelevant — the function reads to completion before returning.

func ReadManifest

func ReadManifest(path string) (Manifest, error)

ReadManifest reads and decodes a manifest.json file written by WriteManifest. Malformed JSON or absent file returns a wrapped error.

type ScanRef

type ScanRef struct {
	ID         string         `json:"id"`
	StartedAt  string         `json:"startedAt,omitempty"`
	FinishedAt string         `json:"finishedAt,omitempty"`
	Scope      map[string]any `json:"scope,omitempty"`
}

ScanRef enumerates one scan included in the snapshot. Scope mirrors the scans.scope JSON column from the source DB (regions, profile, services, skip_globals when the operator's invocation produced them) so an audit trail travels with the evidence — auditors can answer "what was actually scanned?" from the signed envelope alone, no live DB round-trip needed.

Jump to

Keyboard shortcuts

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