github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/libkb/secret_store_upgradeable_test.go (about) 1 // Copyright 2019 Keybase, Inc. All rights reserved. Use of 2 // this source code is governed by the included BSD license. 3 4 package libkb 5 6 import ( 7 "testing" 8 9 "github.com/stretchr/testify/require" 10 ) 11 12 func makeRandomSecretForTest(t *testing.T) LKSecFullSecret { 13 randBytes, err := RandBytes(LKSecLen) 14 require.NoError(t, err) 15 secret, err := newLKSecFullSecretFromBytes(randBytes) 16 require.NoError(t, err) 17 return secret 18 } 19 20 type secretStoreUpgForTest struct { 21 store *SecretStoreUpgradeable 22 memA *SecretStoreMem 23 memB *SecretStoreMem 24 25 shouldFallback SecretStoreFallbackBehavior 26 shouldUpgrade bool 27 } 28 29 func newSecretStoreUpgForTest() *secretStoreUpgForTest { 30 store := secretStoreUpgForTest{ 31 memA: NewSecretStoreMem(), 32 memB: NewSecretStoreMem(), 33 } 34 35 shouldUpgradeOpportunistically := func() bool { 36 return store.shouldUpgrade 37 } 38 shouldStoreInFallback := func(options *SecretStoreOptions) SecretStoreFallbackBehavior { 39 return store.shouldFallback 40 } 41 42 store.store = NewSecretStoreUpgradeable(store.memA, store.memB, "memory primary", "memory secondary", 43 shouldUpgradeOpportunistically, shouldStoreInFallback) 44 return &store 45 } 46 47 func TestUSSUpgradeOnStore(t *testing.T) { 48 tc := SetupTest(t, "secret store ops", 1) 49 defer tc.Cleanup() 50 51 testStore := newSecretStoreUpgForTest() 52 testStore.shouldFallback = SecretStoreFallbackBehaviorAlways 53 testStore.shouldUpgrade = false 54 ss := testStore.store 55 56 m := NewMetaContextForTest(tc) 57 nu := NewNormalizedUsername("tusername") 58 secret := makeRandomSecretForTest(t) 59 60 for i := 0; i < 2; i++ { 61 t.Logf("Doing Store/Retrieve, attempt %d", i) 62 err := ss.StoreSecret(m, nu, secret) 63 require.NoError(t, err) 64 65 // Secret should go to secret store B, and not secret store A 66 // because of fallback behavior. 67 require.Len(t, testStore.memA.secrets, 0) 68 require.Len(t, testStore.memB.secrets, 1) 69 70 rSecret, err := ss.RetrieveSecret(m, nu) 71 require.NoError(t, err) 72 require.True(t, rSecret.Equal(secret)) 73 74 // Retrieve does not upgrade because shouldUpgrade=false 75 require.Len(t, testStore.memA.secrets, 0) 76 require.Len(t, testStore.memB.secrets, 1) 77 78 // Try the whole thing twice to ensure consistent behaviour. 79 } 80 81 // Change fallback behavior, primary secret store can be used again. 82 testStore.shouldFallback = SecretStoreFallbackBehaviorOnError 83 for i := 0; i < 2; i++ { 84 // Not doing fallback anymore, store B should be cleared for NU and 85 // secret should be exclusively in store A. 86 t.Logf("shouldFallback = false, trying again, attempt %d", i) 87 err := ss.StoreSecret(m, nu, secret) 88 require.NoError(t, err) 89 90 require.Len(t, testStore.memA.secrets, 1) 91 require.Len(t, testStore.memB.secrets, 0) 92 93 rSecret, err := ss.RetrieveSecret(m, nu) 94 require.NoError(t, err) 95 require.True(t, rSecret.Equal(secret)) 96 } 97 } 98 99 func TestUSSRetrieveWhenFallback(t *testing.T) { 100 tc := SetupTest(t, "secret store ops", 1) 101 defer tc.Cleanup() 102 103 testStore := newSecretStoreUpgForTest() 104 testStore.shouldFallback = SecretStoreFallbackBehaviorOnError 105 testStore.shouldUpgrade = true 106 ss := testStore.store 107 108 m := NewMetaContextForTest(tc) 109 nu := NewNormalizedUsername("tusername") 110 secret := makeRandomSecretForTest(t) 111 112 err := ss.StoreSecret(m, nu, secret) 113 require.NoError(t, err) 114 115 // Should store in primary secret store. 116 require.Len(t, testStore.memA.secrets, 1) 117 require.Len(t, testStore.memB.secrets, 0) 118 119 rSecret, err := ss.RetrieveSecret(m, nu) 120 require.NoError(t, err) 121 require.True(t, rSecret.Equal(secret)) 122 123 // Enable fallback - assume user changed system settings / configuration. 124 testStore.shouldFallback = SecretStoreFallbackBehaviorAlways 125 126 // Retrieve should still find the secret in primary store. 127 rSecret, err = ss.RetrieveSecret(m, nu) 128 require.NoError(t, err) 129 require.True(t, rSecret.Equal(secret)) 130 131 require.Len(t, testStore.memA.secrets, 1) 132 require.Len(t, testStore.memB.secrets, 0) 133 134 // StoreSecret will skip primary store and store the secret in secondary 135 // store. So it will be stored in both. 136 secret2 := makeRandomSecretForTest(t) 137 err = ss.StoreSecret(m, nu, secret2) 138 require.NoError(t, err) 139 140 require.Len(t, testStore.memA.secrets, 1) 141 require.Len(t, testStore.memB.secrets, 1) 142 143 // Retrieve still works. 144 // TODO: Bug - retrieve still retrieves from primary store. 145 rSecret, err = ss.RetrieveSecret(m, nu) 146 require.NoError(t, err) 147 require.True(t, rSecret.Equal(secret)) 148 } 149 150 func TestUSSOpportunisticUpgrade(t *testing.T) { 151 tc := SetupTest(t, "secret store ops", 1) 152 defer tc.Cleanup() 153 154 testStore := newSecretStoreUpgForTest() 155 testStore.shouldFallback = SecretStoreFallbackBehaviorAlways 156 testStore.shouldUpgrade = true 157 ss := testStore.store 158 159 m := NewMetaContextForTest(tc) 160 nu := NewNormalizedUsername("tusername") 161 secret := makeRandomSecretForTest(t) 162 163 t.Logf("Storing secret with fallback=Always") 164 err := ss.StoreSecret(m, nu, secret) 165 require.NoError(t, err) 166 167 // Secret should go to secret store B, and not secret store A 168 // because we shouldStoreInFallback returns true. 169 require.Len(t, testStore.memA.secrets, 0) 170 require.Len(t, testStore.memB.secrets, 1) 171 172 t.Logf("Retrieving secret with fallback=Always") 173 rSecret, err := ss.RetrieveSecret(m, nu) 174 require.NoError(t, err) 175 require.True(t, rSecret.Equal(secret)) 176 177 // We are still in fallback mode, so upgrade should not happen after last 178 // retrieval. 179 require.Len(t, testStore.memA.secrets, 0) 180 require.Len(t, testStore.memB.secrets, 1) 181 182 // Change shouldFallback to OnError (user upgraded their machine / settings 183 // for example). 184 testStore.shouldFallback = SecretStoreFallbackBehaviorOnError 185 186 t.Logf("Changed shouldFallback to OnError, trying to retrieve") 187 rSecret, err = ss.RetrieveSecret(m, nu) 188 require.NoError(t, err) 189 require.True(t, rSecret.Equal(secret)) 190 191 // Retrieving secret should have upgraded us to store A. 192 require.Len(t, testStore.memA.secrets, 1) 193 require.Len(t, testStore.memB.secrets, 0) 194 195 // Try to retrieve again, should retrieve exclusively from primary secret 196 // store. 197 t.Logf("Retrieving again") 198 rSecret, err = ss.RetrieveSecret(m, nu) 199 require.NoError(t, err) 200 require.True(t, rSecret.Equal(secret)) 201 } 202 203 func TestUSSFallback(t *testing.T) { 204 tc := SetupTest(t, "secret store ops", 1) 205 defer tc.Cleanup() 206 207 failA := NewSecretStoreFail() 208 memB := NewSecretStoreMem() 209 210 behavior := SecretStoreFallbackBehaviorOnError 211 shouldUpgradeOpportunistically := func() bool { 212 return true 213 } 214 shouldStoreInFallback := func(options *SecretStoreOptions) SecretStoreFallbackBehavior { 215 return behavior 216 } 217 218 store := NewSecretStoreUpgradeable(failA, memB, "failure primary", "memory secondary", 219 shouldUpgradeOpportunistically, shouldStoreInFallback) 220 221 m := NewMetaContextForTest(tc) 222 nu := NewNormalizedUsername("tusername") 223 secret := makeRandomSecretForTest(t) 224 225 err := store.StoreSecret(m, nu, secret) 226 require.NoError(t, err) 227 require.Len(t, memB.secrets, 1) 228 229 rSecret, err := store.RetrieveSecret(m, nu) 230 require.NoError(t, err) 231 require.True(t, rSecret.Equal(secret)) 232 233 t.Logf("Changing behavior to SecretStoreFallbackBehaviorNever") 234 behavior = SecretStoreFallbackBehaviorNever 235 236 for i := 0; i < 2; i++ { 237 t.Logf("Attempt %d", i) 238 239 // We should still be able to retrieve our secret. 240 rSecret, err := store.RetrieveSecret(m, nu) 241 require.NoError(t, err) 242 require.True(t, rSecret.Equal(secret)) 243 244 // But we can't store a new one. 245 err = store.StoreSecret(m, nu, secret) 246 require.Error(t, err) 247 248 // Still has old secret. 249 require.Len(t, memB.secrets, 1) 250 251 // Try this twice, to be sure that: 252 // - first retrieval does not affect subsequent ones. 253 // - failed StoreSecret to primary secret store does not affect subsequent 254 // retrievals from fallback. 255 } 256 257 // Clear should clear fallback store as well, despite behavior setting. 258 t.Logf("Trying to ClearSecret") 259 err = store.ClearSecret(m, nu) 260 require.NoError(t, err) 261 require.Len(t, memB.secrets, 0) 262 } 263 264 func TestUSSBothFail(t *testing.T) { 265 tc := SetupTest(t, "secret store ops", 1) 266 defer tc.Cleanup() 267 268 failA := NewSecretStoreFail() 269 failB := NewSecretStoreFail() 270 271 shouldUpgradeOpportunistically := func() bool { 272 return true 273 } 274 shouldStoreInFallback := func(options *SecretStoreOptions) SecretStoreFallbackBehavior { 275 return SecretStoreFallbackBehaviorOnError 276 } 277 278 store := NewSecretStoreUpgradeable(failA, failB, "fail primary", "fail secondary", 279 shouldUpgradeOpportunistically, shouldStoreInFallback) 280 281 m := NewMetaContextForTest(tc) 282 nu := NewNormalizedUsername("tusername") 283 secret := makeRandomSecretForTest(t) 284 285 err := store.StoreSecret(m, nu, secret) 286 require.Error(t, err) 287 288 _, err = store.RetrieveSecret(m, nu) 289 require.Error(t, err) 290 291 // Clear returns an error when both stores fail to clear. 292 err = store.ClearSecret(m, nu) 293 require.Error(t, err) 294 }