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
- Variables
- func CanonicalManifestBytes(m Manifest) ([]byte, error)
- func HashFile(path string) (string, error)
- func LoadEd25519PublicKey(path string) (ed25519.PublicKey, error)
- func VerifyDetachedSignature(m Manifest, sigPath, pubKeyPath string) error
- func WriteArchive(out string, format Format, dbPath string, m Manifest) error
- func WriteManifest(path string, m Manifest) error
- type Format
- type Manifest
- type ScanRef
Constants ¶
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 ¶
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.
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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.
func DetectFormat ¶
DetectFormat returns the archive format inferred from the path's extension. Match is case-insensitive. Unknown extensions surface a clear error listing supported shapes.
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 ¶
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 ¶
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.