-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathmain_encryption_confchange_test.go
More file actions
132 lines (119 loc) · 5.67 KB
/
Copy pathmain_encryption_confchange_test.go
File metadata and controls
132 lines (119 loc) · 5.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
package main
import (
"context"
"testing"
"github.com/bootjp/elastickv/internal/encryption"
"github.com/bootjp/elastickv/kv"
"github.com/bootjp/elastickv/store"
"github.com/cockroachdb/errors"
)
// stubDeriveNodeID returns a sentinel uint64 for any input — lets the
// tests assert that whatever raftID lands at the adapter is hashed
// through the injected function value, not a hardcoded global.
const stubDerivedNodeID uint64 = 0xCAFEF00DDEADBEEF
func stubDeriveNodeID(string) uint64 { return stubDerivedNodeID }
// TestEncryptionPreRegister_PreBootstrapSkips pins design §5.2: when
// the StateCache reports (0, false) — no active storage DEK, either
// pre-bootstrap or encryption-disabled — PreAddMember returns nil
// without proposing or reading the registry.
func TestEncryptionPreRegister_PreBootstrapSkips(t *testing.T) {
t.Parallel()
cache := encryption.NewStateCache() // zero-value: ActiveStorageKeyID()=(0,false)
st := newRegistrationTestStore(t)
defaultGroup := &kv.ShardGroup{Store: st}
pre := newEncryptionPreRegister(&kv.ShardedCoordinator{}, defaultGroup, cache, stubDeriveNodeID)
if pre == nil {
t.Fatal("newEncryptionPreRegister returned nil despite non-nil cache+group")
}
if err := pre.PreAddMember(context.Background(), "n1"); err != nil {
t.Errorf("PreAddMember should skip pre-bootstrap: got %v", err)
}
}
// TestEncryptionPreRegister_NilCacheReturnsNilInterceptor pins the
// adapter constructor's defensive nil-check: missing wiring inputs
// produce a nil interceptor (no pre-step) rather than a panicking
// runtime hook.
func TestEncryptionPreRegister_NilCacheReturnsNilInterceptor(t *testing.T) {
t.Parallel()
if got := newEncryptionPreRegister(nil, nil, nil, nil); got != nil {
t.Errorf("all-nil inputs: want nil interceptor, got %T", got)
}
if got := newEncryptionPreRegister(&kv.ShardedCoordinator{}, &kv.ShardGroup{}, nil, stubDeriveNodeID); got != nil {
t.Error("nil cache: want nil interceptor")
}
if got := newEncryptionPreRegister(&kv.ShardedCoordinator{}, &kv.ShardGroup{}, encryption.NewStateCache(), nil); got != nil {
t.Error("nil deriveNodeID: want nil interceptor")
}
}
// TestEncryptionPreRegister_IdempotentWhenRowExists pins design §5.2
// + the gemini-CRITICAL/claude-round-2 fix: when a registry row
// already exists for (activeDEK, NodeID16(newNodeFullID)) with the
// SAME FullNodeID, PreAddMember returns nil without proposing
// (idempotent retry, e.g. after a leader flip). This pins the
// §3.1 read-before-propose guard against the §4.1 case-3
// ErrLocalEpochRollback regression that would otherwise fire on
// re-proposing epoch=0.
func TestEncryptionPreRegister_IdempotentWhenRowExists(t *testing.T) {
t.Parallel()
st := newRegistrationTestStore(t)
// Pre-populate (testRegDEKID, stubDerivedNodeID, 0). Use the
// registry handle directly (writeRegistryRow uses a different
// FullNodeID derivation).
reg, err := store.WriterRegistryFor(st)
if err != nil {
t.Fatalf("WriterRegistryFor: %v", err)
}
val := encryption.EncodeRegistryValue(encryption.RegistryValue{
FullNodeID: stubDerivedNodeID, FirstSeenLocalEpoch: 0, LastSeenLocalEpoch: 0,
})
if err := reg.SetRegistryRow(encryption.RegistryKey(testRegDEKID, encryption.NodeID16(stubDerivedNodeID)), val); err != nil {
t.Fatalf("SetRegistryRow: %v", err)
}
cache := encryption.NewStateCache()
sc := &encryption.Sidecar{Version: encryption.SidecarVersion, StorageEnvelopeActive: true}
sc.Active.Storage = testRegDEKID
cache.RefreshFromSidecar(sc)
pre := newEncryptionPreRegister(&kv.ShardedCoordinator{}, &kv.ShardGroup{Store: st}, cache, stubDeriveNodeID)
if err := pre.PreAddMember(context.Background(), "raftN"); err != nil {
t.Errorf("PreAddMember should skip when matching row exists: got %v", err)
}
}
// TestEncryptionPreRegister_Uint16CollisionReturnsTypedError pins
// design §5.2 + §3.1's §6.1-collision branch: when a row exists at
// the same uint16 truncation with a DIFFERENT FullNodeID, the guard
// returns ErrWriterUint16Collision WITHOUT proposing. No §4.1
// case-4 halt-apply is triggered; the admin RPC layer surfaces a
// retryable client-facing error.
func TestEncryptionPreRegister_Uint16CollisionReturnsTypedError(t *testing.T) {
t.Parallel()
st := newRegistrationTestStore(t)
reg, err := store.WriterRegistryFor(st)
if err != nil {
t.Fatalf("WriterRegistryFor: %v", err)
}
// Stored row's FullNodeID == stubDerivedNodeID; a colliding
// FullNodeID is one that hits the same uint16 truncation but
// differs in the upper 48 bits.
val := encryption.EncodeRegistryValue(encryption.RegistryValue{
FullNodeID: stubDerivedNodeID, FirstSeenLocalEpoch: 0, LastSeenLocalEpoch: 0,
})
if err := reg.SetRegistryRow(encryption.RegistryKey(testRegDEKID, encryption.NodeID16(stubDerivedNodeID)), val); err != nil {
t.Fatalf("SetRegistryRow: %v", err)
}
cache := encryption.NewStateCache()
sc := &encryption.Sidecar{Version: encryption.SidecarVersion, StorageEnvelopeActive: true}
sc.Active.Storage = testRegDEKID
cache.RefreshFromSidecar(sc)
// Adapter that derives a DIFFERENT FullNodeID with the same uint16
// truncation as stubDerivedNodeID.
collidingFullNodeID := (uint64(0x1111_2222_3333) << 16) | uint64(encryption.NodeID16(stubDerivedNodeID))
if collidingFullNodeID == stubDerivedNodeID {
t.Fatalf("test bug: colliding == stored; choose a different upper-16 mask")
}
collidingDerive := func(string) uint64 { return collidingFullNodeID }
pre := newEncryptionPreRegister(&kv.ShardedCoordinator{}, &kv.ShardGroup{Store: st}, cache, collidingDerive)
err = pre.PreAddMember(context.Background(), "raftN")
if !errors.Is(err, encryption.ErrWriterUint16Collision) {
t.Errorf("PreAddMember on §6.1 collision: want ErrWriterUint16Collision, got %v", err)
}
}