package backup import ( "errors" "fmt" "time" ) // FormatVersion is the archive format this build reads and writes. Restore // refuses anything else rather than guessing at a layout it does not know. const FormatVersion = 1 // ManifestName is the archive member holding the manifest. const ManifestName = "manifest.json" // ErrUnknownFormat is returned for an archive this build cannot read. var ErrUnknownFormat = errors.New("unsupported archive format version") // CollectionEntry describes one collection in the archive. Bytes and SHA256 // cover the uncompressed .bson member, which is what restore verifies before // writing anything. type CollectionEntry struct { Name string `json:"name"` Documents int64 `json:"documents"` Bytes int64 `json:"bytes"` SHA256 string `json:"sha256"` } // Manifest is the archive's index and its provenance. // // KeyFingerprint is a pointer so "this archive recorded no key" is a distinct // state from "this archive recorded the empty string". A null here is a real // condition an operator must be told about, not a default. type Manifest struct { FormatVersion int `json:"format_version"` CreatedAt time.Time `json:"created_at"` VantageVersion string `json:"vantage_version"` Hostname string `json:"hostname"` MongoDB string `json:"mongo_db"` MongoServerVersion string `json:"mongo_server_version"` KeyFingerprint *string `json:"key_fingerprint"` Collections []CollectionEntry `json:"collections"` Excluded []string `json:"excluded"` } // Check validates what can be validated without reading the rest of the archive. func (m Manifest) Check() error { if m.FormatVersion != FormatVersion { return fmt.Errorf("%w: archive is version %d, this build reads version %d", ErrUnknownFormat, m.FormatVersion, FormatVersion) } return nil } // Collection looks up one entry by name. func (m Manifest) Collection(name string) (CollectionEntry, bool) { for _, c := range m.Collections { if c.Name == name { return c, true } } return CollectionEntry{}, false } // CiphertextCollections names the collections holding AES-GCM ciphertext. // // It exists to be printed. When a restore proceeds under a key that does not // match the archive, this is the list of what will be unreadable afterwards, // and an operator deserves to see it before the write rather than discover it // a week later. func CiphertextCollections() []string { return []string{"keys", "secrets", "auth_providers", "console_sessions", "settings"} }