github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/libkb/secret_store_upgradeable.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 "fmt" 7 8 type SecretStoreFallbackBehavior int 9 10 const ( 11 SecretStoreFallbackBehaviorOnError SecretStoreFallbackBehavior = iota 12 SecretStoreFallbackBehaviorAlways 13 SecretStoreFallbackBehaviorNever 14 ) 15 16 type SecretStoreUpgradeable struct { 17 a SecretStoreAll 18 labelA string 19 b SecretStoreAll 20 labelB string 21 shouldUpgradeOpportunistically func() bool 22 shouldStoreInFallback func(*SecretStoreOptions) SecretStoreFallbackBehavior 23 options *SecretStoreOptions 24 } 25 26 var _ SecretStoreAll = (*SecretStoreUpgradeable)(nil) 27 28 func NewSecretStoreUpgradeable(a, b SecretStoreAll, labelA, labelB string, shouldUpgradeOpportunistically func() bool, shouldStoreInFallback func(*SecretStoreOptions) SecretStoreFallbackBehavior) *SecretStoreUpgradeable { 29 return &SecretStoreUpgradeable{ 30 a: a, 31 labelA: labelA, 32 b: b, 33 labelB: labelB, 34 shouldUpgradeOpportunistically: shouldUpgradeOpportunistically, 35 shouldStoreInFallback: shouldStoreInFallback, 36 } 37 } 38 39 func (s *SecretStoreUpgradeable) RetrieveSecret(mctx MetaContext, username NormalizedUsername) (secret LKSecFullSecret, err error) { 40 defer mctx.Trace(fmt.Sprintf("SecretStoreUpgradeable.RetrieveSecret(%s)", username), 41 &err)() 42 43 mctx.Debug("Trying to retrieve secret from primary store (%s)", s.labelA) 44 secret, err1 := s.a.RetrieveSecret(mctx, username) 45 if err1 == nil { 46 // Found secret in primary store - return, we don't need to do anything 47 // else here. 48 mctx.Debug("Found secret in primary store (%s)", s.labelA) 49 return secret, nil 50 } 51 52 mctx.Debug("Failed to find secret in primary store (%s): %s, falling back to %s.", s.labelA, err1, s.labelB) 53 54 secret, err2 := s.b.RetrieveSecret(mctx, username) 55 if err2 != nil { 56 mctx.Debug("Failed to retrieve secret from secondary store (%s): %v", s.labelB, err2) 57 // Do not return combined errors here. We want to return typed errors, 58 // like: `SecretStoreError`. Secret store API consumers rely on error 59 // types. 60 return LKSecFullSecret{}, err2 61 } 62 63 shouldUpgrade := s.shouldUpgradeOpportunistically() 64 fallbackBehavior := s.shouldStoreInFallback(s.options) 65 mctx.Debug("Fallback settings are: shouldUpgrade: %t, fallbackBehavior: %v", shouldUpgrade, fallbackBehavior) 66 if !shouldUpgrade || fallbackBehavior == SecretStoreFallbackBehaviorAlways { 67 // Do not upgrade opportunistically, or we are still in Fallback Mode 68 // ALWAYS and should exclusively use store B - do not try fall through 69 // to try to store in A. 70 mctx.Debug("Not trying to upgrade after retrieving from secondary store (%s)", s.labelB) 71 return secret, nil 72 } 73 74 mctx.Debug("Secret found in secondary store (%s), trying to upgrade to primary store (%s)", s.labelB, s.labelA) 75 76 storeAErr := s.a.StoreSecret(mctx, username, secret) 77 if storeAErr == nil { 78 mctx.Debug("Upgraded secret for %s to primary store (%s)", username, s.labelA) 79 80 clearBErr := s.b.ClearSecret(mctx, username) 81 mctx.Debug("After secret upgrade: clearSecret from secondary store (%s) returned: %v", s.labelA, clearBErr) 82 } else { 83 mctx.Debug("Failed to upgrade secret for %s to primary store (%s): %s", username, s.labelA, storeAErr) 84 } 85 return secret, nil 86 } 87 88 func (s *SecretStoreUpgradeable) StoreSecret(mctx MetaContext, username NormalizedUsername, secret LKSecFullSecret) (err error) { 89 defer mctx.Trace("SecretStoreUpgradeable.StoreSecret", &err)() 90 91 fallbackBehavior := s.shouldStoreInFallback(s.options) 92 if fallbackBehavior == SecretStoreFallbackBehaviorAlways { 93 mctx.Debug("shouldStoreInFallback returned ALWAYS for options %+v, storing in secondary store (%s)", s.options, s.labelB) 94 return s.b.StoreSecret(mctx, username, secret) 95 } 96 97 err1 := s.a.StoreSecret(mctx, username, secret) 98 if err1 == nil { 99 mctx.Debug("Stored secret for %s in primary store (%s), attempting clear for secondary store (%s)", username, s.labelA, s.labelB) 100 clearBErr := s.b.ClearSecret(mctx, username) 101 if clearBErr == nil { 102 // Store may also return nil error when there was nothing to clear. 103 mctx.Debug("ClearSecret error=<nil> for %s from secondary store (%s)", username, s.labelB) 104 } else { 105 mctx.Debug("Failed to clear secret for %s from secondary store (%s): %s", username, s.labelB, clearBErr) 106 } 107 return nil 108 } 109 110 if fallbackBehavior == SecretStoreFallbackBehaviorNever { 111 mctx.Warning("Failed to reach system keyring (primary store (%s): %s), not falling back to secondary store (%s) because of fallback behavior.", s.labelA, err1, s.labelB) 112 return err1 113 } 114 115 mctx.Warning("Failed to reach system keyring (primary store (%s): %s), falling back to secondary store (%s).", s.labelA, err1, s.labelB) 116 err2 := s.b.StoreSecret(mctx, username, secret) 117 if err2 == nil { 118 return nil 119 } 120 err = CombineErrors(err1, err2) 121 return err 122 } 123 124 func (s *SecretStoreUpgradeable) ClearSecret(mctx MetaContext, username NormalizedUsername) (err error) { 125 defer mctx.Trace("SecretStoreUpgradeable.ClearSecret", &err)() 126 err1 := s.a.ClearSecret(mctx, username) 127 if err1 != nil { 128 mctx.Debug("Failed to clear secret in primary store (%s): %s", s.labelA, err1) 129 } 130 err2 := s.b.ClearSecret(mctx, username) 131 if err2 != nil { 132 mctx.Debug("Failed to clear secret in secondary store (%s): %s", s.labelB, err2) 133 } 134 // Only return an error if both failed 135 if err1 != nil && err2 != nil { 136 err := CombineErrors(err1, err2) 137 return err 138 } 139 return nil 140 } 141 142 func (s *SecretStoreUpgradeable) GetUsersWithStoredSecrets(mctx MetaContext) (usernames []string, err error) { 143 defer mctx.Trace("SecretStoreUpgradeable.GetUsersWithStoredSecrets", &err)() 144 usernameMap := make(map[string]bool) 145 usernamesA, err1 := s.a.GetUsersWithStoredSecrets(mctx) 146 if err1 == nil { 147 for _, u := range usernamesA { 148 usernameMap[u] = true 149 } 150 } else { 151 mctx.Debug("Failed to GetUsersWithStoredSecrets in primary store (%s): %s", s.labelA, err1) 152 } 153 usernamesB, err2 := s.b.GetUsersWithStoredSecrets(mctx) 154 if err2 == nil { 155 for _, u := range usernamesB { 156 usernameMap[u] = true 157 } 158 } else { 159 mctx.Debug("Failed to GetUsersWithStoredSecrets in secondary store (%s): %s", s.labelB, err2) 160 } 161 162 for username := range usernameMap { 163 usernames = append(usernames, username) 164 } 165 166 err = CombineErrors(err1, err2) 167 // Only return an error if both failed 168 if err1 != nil && err2 != nil { 169 return nil, err 170 } 171 return usernames, nil 172 } 173 174 func (s *SecretStoreUpgradeable) GetOptions(MetaContext) *SecretStoreOptions { return s.options } 175 func (s *SecretStoreUpgradeable) SetOptions(_ MetaContext, options *SecretStoreOptions) { 176 s.options = options 177 }