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  }