Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1954080faf |
No files matched your search
@@ -40,6 +40,9 @@ var (
|
|||||||
// which is secret, so the message is safe to print and log.
|
// which is secret, so the message is safe to print and log.
|
||||||
errInvalidAgeSecretKey = errors.New(
|
errInvalidAgeSecretKey = errors.New(
|
||||||
"configured age secret key holds no usable age identity")
|
"configured age secret key holds no usable age identity")
|
||||||
|
// errWrongPrivateKey is returned when the configured key parses but
|
||||||
|
// is not one of the keys the snapshot was encrypted to.
|
||||||
|
errWrongPrivateKey = errors.New("cannot decrypt this snapshot")
|
||||||
errBlobMissingFromIndex = errors.New("blob hash missing from blob index")
|
errBlobMissingFromIndex = errors.New("blob hash missing from blob index")
|
||||||
errChunkNotInAnyBlob = errors.New("chunk not found in any blob")
|
errChunkNotInAnyBlob = errors.New("chunk not found in any blob")
|
||||||
errBlobIDNotInHashIndex = errors.New("blob id missing from hash index")
|
errBlobIDNotInHashIndex = errors.New("blob id missing from hash index")
|
||||||
@@ -132,6 +135,10 @@ func (v *Vaultik) Restore(opts *RestoreOptions) error {
|
|||||||
log.Info("Downloading snapshot metadata...")
|
log.Info("Downloading snapshot metadata...")
|
||||||
|
|
||||||
tempDB, tempDir, err := v.downloadSnapshotDB(opts.SnapshotID, identities)
|
tempDB, tempDir, err := v.downloadSnapshotDB(opts.SnapshotID, identities)
|
||||||
|
if errors.Is(err, errWrongPrivateKey) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("downloading snapshot database: %w", err)
|
return fmt.Errorf("downloading snapshot database: %w", err)
|
||||||
}
|
}
|
||||||
@@ -323,6 +330,28 @@ func (v *Vaultik) restoreIdentities() ([]age.Identity, error) {
|
|||||||
return identities, nil
|
return identities, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newSnapshotDBReader opens the decrypting reader for a snapshot
|
||||||
|
// database. When none of the identities can decrypt it, the error says
|
||||||
|
// in plain words that the configured private key is the wrong one and
|
||||||
|
// names where it was configured.
|
||||||
|
func (v *Vaultik) newSnapshotDBReader(
|
||||||
|
r io.Reader, identities []age.Identity,
|
||||||
|
) (*blobgen.Reader, error) {
|
||||||
|
blobReader, err := blobgen.NewReader(r, identities...)
|
||||||
|
|
||||||
|
if _, ok := errors.AsType[*age.NoIdentityMatchError](err); ok {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"%w: the private key in %s is not one of the keys the backup was encrypted to",
|
||||||
|
errWrongPrivateKey, v.Config.AgeSecretKeySourceName())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return blobReader, nil
|
||||||
|
}
|
||||||
|
|
||||||
// restoreAllFiles processes files in blob-locality order: drain every
|
// restoreAllFiles processes files in blob-locality order: drain every
|
||||||
// file whose blob set is on disk, download the missing blobs for the
|
// file whose blob set is on disk, download the missing blobs for the
|
||||||
// pending file with the smallest uncached count, repeat. This keeps
|
// pending file with the smallest uncached count, repeat. This keeps
|
||||||
@@ -783,9 +812,9 @@ func (v *Vaultik) downloadSnapshotDB(
|
|||||||
// the plaintext to a temp file. Neither the encrypted bytes nor the
|
// the plaintext to a temp file. Neither the encrypted bytes nor the
|
||||||
// decrypted database is ever held whole in memory; a snapshot database
|
// decrypted database is ever held whole in memory; a snapshot database
|
||||||
// can be large.
|
// can be large.
|
||||||
blobReader, err := blobgen.NewReader(reader, identities...)
|
blobReader, err := v.newSnapshotDBReader(reader, identities)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", fmt.Errorf("creating decryption reader: %w", err)
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() { _ = blobReader.Close() }()
|
defer func() { _ = blobReader.Close() }()
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package vaultik_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"filippo.io/age"
|
||||||
|
"github.com/spf13/afero"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"sneak.berlin/go/vaultik/internal/blobgen"
|
||||||
|
"sneak.berlin/go/vaultik/internal/config"
|
||||||
|
"sneak.berlin/go/vaultik/internal/snapshot"
|
||||||
|
"sneak.berlin/go/vaultik/internal/ui"
|
||||||
|
"sneak.berlin/go/vaultik/internal/vaultik"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestWrongPrivateKeyError checks that restore and deep verify, given a
|
||||||
|
// valid private key the backup was not encrypted to, fail with one plain
|
||||||
|
// sentence that names where the key was configured.
|
||||||
|
func TestWrongPrivateKeyError(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
store := NewMockStorer()
|
||||||
|
|
||||||
|
const snapshotID = "test-host_2026-01-01T00:00:00Z"
|
||||||
|
|
||||||
|
remoteKey := snapshot.RemoteSnapshotKey(snapshotID)
|
||||||
|
|
||||||
|
manifest, err := snapshot.EncodeManifest(
|
||||||
|
&snapshot.Manifest{SnapshotID: remoteKey}, 3)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, store.Put(ctx,
|
||||||
|
"metadata/"+remoteKey+"/manifest.json.zst", bytes.NewReader(manifest)))
|
||||||
|
|
||||||
|
// The database is encrypted to the test recipient only.
|
||||||
|
var db bytes.Buffer
|
||||||
|
|
||||||
|
writer, err := blobgen.NewWriter(&db, 1, []string{testAgePublicKey})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = writer.Write([]byte("snapshot database"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, writer.Close())
|
||||||
|
require.NoError(t, store.Put(ctx,
|
||||||
|
"metadata/"+remoteKey+"/db.zst.age", &db))
|
||||||
|
|
||||||
|
otherKey, err := age.GenerateX25519Identity()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
v := &vaultik.Vaultik{
|
||||||
|
Config: &config.Config{AgeSecretKey: otherKey.String()},
|
||||||
|
Storage: store,
|
||||||
|
Fs: afero.NewMemMapFs(),
|
||||||
|
Stdout: io.Discard,
|
||||||
|
Stderr: io.Discard,
|
||||||
|
UI: ui.NewWithColor(io.Discard, false),
|
||||||
|
}
|
||||||
|
v.SetContext(ctx)
|
||||||
|
|
||||||
|
// A Config built directly has no recorded source, so the error names
|
||||||
|
// the config-file field.
|
||||||
|
const want = "cannot decrypt this snapshot: the private key in " +
|
||||||
|
"age_secret_key is not one of the keys the backup was encrypted to"
|
||||||
|
|
||||||
|
err = v.Restore(&vaultik.RestoreOptions{
|
||||||
|
SnapshotID: snapshotID,
|
||||||
|
TargetDir: t.TempDir(),
|
||||||
|
})
|
||||||
|
require.EqualError(t, err, want)
|
||||||
|
|
||||||
|
err = v.RunDeepVerify(snapshotID, &vaultik.VerifyOptions{Deep: true})
|
||||||
|
require.EqualError(t, err, want)
|
||||||
|
}
|
||||||
@@ -267,6 +267,10 @@ func (v *Vaultik) downloadVerifiedSnapshotDB(
|
|||||||
defer func() { _ = dbReader.Close() }()
|
defer func() { _ = dbReader.Close() }()
|
||||||
|
|
||||||
tdb, err := v.decryptAndLoadDatabase(dbReader, identities)
|
tdb, err := v.decryptAndLoadDatabase(dbReader, identities)
|
||||||
|
if errors.Is(err, errWrongPrivateKey) {
|
||||||
|
return nil, v.deepVerifyFailure(result, opts, err.Error(), err)
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, v.deepVerifyFailure(result, opts,
|
return nil, v.deepVerifyFailure(result, opts,
|
||||||
fmt.Sprintf("failed to decrypt database: %v", err),
|
fmt.Sprintf("failed to decrypt database: %v", err),
|
||||||
@@ -351,9 +355,9 @@ func (v *Vaultik) decryptAndLoadDatabase(
|
|||||||
reader io.ReadCloser, identities []age.Identity,
|
reader io.ReadCloser, identities []age.Identity,
|
||||||
) (*tempDB, error) {
|
) (*tempDB, error) {
|
||||||
// Decrypt and decompress through the shared blobgen reader.
|
// Decrypt and decompress through the shared blobgen reader.
|
||||||
blobReader, err := blobgen.NewReader(reader, identities...)
|
blobReader, err := v.newSnapshotDBReader(reader, identities)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create decryption reader: %w", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() { _ = blobReader.Close() }()
|
defer func() { _ = blobReader.Close() }()
|
||||||
|
|||||||
Reference in new issue
Block a user