Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FIXED] Restore of encrypted filestore with no main key could cause dataloss #4301

Merged
merged 1 commit into from Jul 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 9 additions & 1 deletion server/filestore.go
Expand Up @@ -349,6 +349,14 @@ func newFileStoreWithCreated(fcfg FileStoreConfig, cfg StreamConfig, created tim
return nil, fmt.Errorf("could not create hash: %v", err)
}

keyFile := filepath.Join(fs.fcfg.StoreDir, JetStreamMetaFileKey)
// Make sure we do not have an encrypted store underneath of us but no main key.
if fs.prf == nil {
if _, err := os.Stat(keyFile); err == nil {
return nil, errNoMainKey
}
}

// Recover our message state.
if err := fs.recoverMsgs(); err != nil {
return nil, err
Expand All @@ -366,7 +374,6 @@ func newFileStoreWithCreated(fcfg FileStoreConfig, cfg StreamConfig, created tim
// If we expect to be encrypted check that what we are restoring is not plaintext.
// This can happen on snapshot restores or conversions.
if fs.prf != nil {
keyFile := filepath.Join(fs.fcfg.StoreDir, JetStreamMetaFileKey)
if _, err := os.Stat(keyFile); err != nil && os.IsNotExist(err) {
if err := fs.writeStreamMeta(); err != nil {
return nil, err
Expand Down Expand Up @@ -4390,6 +4397,7 @@ var (
errMsgBlkTooBig = errors.New("message block size exceeded int capacity")
errUnknownCipher = errors.New("unknown cipher")
errDIOStalled = errors.New("IO is stalled")
errNoMainKey = errors.New("encrypted store encountered with no main key")
)

// Used for marking messages that have had their checksums checked.
Expand Down
38 changes: 38 additions & 0 deletions server/filestore_test.go
Expand Up @@ -5499,3 +5499,41 @@ func TestFileStoreNumPendingLargeNumBlks(t *testing.T) {
require_True(t, time.Since(start) < 50*time.Millisecond)
require_True(t, total == 4000)
}

func TestFileStoreRestoreEncryptedWithNoKeyFuncFails(t *testing.T) {
// No need for all permutations here.
fcfg := FileStoreConfig{StoreDir: t.TempDir(), Cipher: AES}
scfg := StreamConfig{Name: "zzz", Subjects: []string{"zzz"}, Storage: FileStorage}

// Create at first with encryption (prf)
prf := func(context []byte) ([]byte, error) {
h := hmac.New(sha256.New, []byte("dlc22"))
if _, err := h.Write(context); err != nil {
return nil, err
}
return h.Sum(nil), nil
}

fs, err := newFileStoreWithCreated(
fcfg, scfg,
time.Now(),
prf,
)
require_NoError(t, err)

subj, msg := "zzz", bytes.Repeat([]byte("X"), 100)
numMsgs := 100
for i := 0; i < numMsgs; i++ {
fs.StoreMsg(subj, nil, msg)
}

fs.Stop()

// Make sure if we try to restore with no prf (key) that it fails.
_, err = newFileStoreWithCreated(
fcfg, scfg,
time.Now(),
nil,
)
require_Error(t, err, errNoMainKey)
}
1 change: 1 addition & 0 deletions server/jetstream_cluster.go
Expand Up @@ -759,6 +759,7 @@ func (js *jetStream) setupMetaGroup() error {
s.Errorf("Error creating filestore: %v", err)
return err
}

// Register our server.
fs.registerServer(s)

Expand Down
47 changes: 47 additions & 0 deletions server/jetstream_cluster_3_test.go
Expand Up @@ -4519,3 +4519,50 @@ func TestJetStreamClusterSnapshotAndRestoreWithHealthz(t *testing.T) {
require_NoError(t, err)
require_True(t, si.State.Msgs == uint64(toSend))
}

func TestJetStreamClusterBadEncryptKey(t *testing.T) {
c := createJetStreamClusterWithTemplate(t, jsClusterEncryptedTempl, "JSC", 3)
defer c.shutdown()

nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()

// Create 10 streams.
for i := 0; i < 10; i++ {
_, err := js.AddStream(&nats.StreamConfig{
Name: fmt.Sprintf("TEST-%d", i),
Replicas: 3,
})
require_NoError(t, err)
}

// Grab random server.
s := c.randomServer()
s.Shutdown()
s.WaitForShutdown()

var opts *Options
for i := 0; i < len(c.servers); i++ {
if c.servers[i] == s {
opts = c.opts[i]
break
}
}
require_NotNil(t, opts)

// Replace key with an empty key.
buf, err := os.ReadFile(opts.ConfigFile)
require_NoError(t, err)
nbuf := bytes.Replace(buf, []byte("key: \"s3cr3t!\""), []byte("key: \"\""), 1)
err = os.WriteFile(opts.ConfigFile, nbuf, 0640)
require_NoError(t, err)

// Make sure trying to start the server now fails.
s, err = NewServer(LoadConfig(opts.ConfigFile))
require_NoError(t, err)
require_NotNil(t, s)
s.Start()
if err := s.readyForConnections(1 * time.Second); err == nil {
t.Fatalf("Expected server not to start")
}
}