provider.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. /*
  2. Licensed under the Apache License, Version 2.0 (the "License");
  3. you may not use this file except in compliance with the License.
  4. You may obtain a copy of the License at
  5. http://www.apache.org/licenses/LICENSE-2.0
  6. Unless required by applicable law or agreed to in writing, software
  7. distributed under the License is distributed on an "AS IS" BASIS,
  8. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9. See the License for the specific language governing permissions and
  10. limitations under the License.
  11. */
  12. package common
  13. import (
  14. "context"
  15. "crypto/sha256"
  16. "encoding/hex"
  17. "encoding/json"
  18. "fmt"
  19. "sync"
  20. "time"
  21. "github.com/go-logr/logr"
  22. "github.com/yandex-cloud/go-sdk/iamkey"
  23. kclient "sigs.k8s.io/controller-runtime/pkg/client"
  24. esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
  25. esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
  26. clock2 "github.com/external-secrets/external-secrets/pkg/provider/yandex/common/clock"
  27. "github.com/external-secrets/external-secrets/pkg/utils/resolvers"
  28. )
  29. const maxSecretsClientLifetime = 5 * time.Minute // supposed SecretsClient lifetime is quite short
  30. // https://github.com/external-secrets/external-secrets/issues/644
  31. var _ esv1beta1.Provider = &YandexCloudProvider{}
  32. // Implementation of v1beta1.Provider.
  33. type YandexCloudProvider struct {
  34. logger logr.Logger
  35. clock clock2.Clock
  36. adaptInputFunc AdaptInputFunc
  37. newSecretGetterFunc NewSecretGetterFunc
  38. newIamTokenFunc NewIamTokenFunc
  39. secretGetteMap map[string]SecretGetter // apiEndpoint -> SecretGetter
  40. secretGetterMapMutex sync.Mutex
  41. iamTokenMap map[iamTokenKey]*IamToken
  42. iamTokenMapMutex sync.Mutex
  43. }
  44. type iamTokenKey struct {
  45. authorizedKeyID string
  46. serviceAccountID string
  47. privateKeyHash string
  48. }
  49. func InitYandexCloudProvider(
  50. logger logr.Logger,
  51. clock clock2.Clock,
  52. adaptInputFunc AdaptInputFunc,
  53. newSecretGetterFunc NewSecretGetterFunc,
  54. newIamTokenFunc NewIamTokenFunc,
  55. iamTokenCleanupDelay time.Duration,
  56. ) *YandexCloudProvider {
  57. provider := &YandexCloudProvider{
  58. logger: logger,
  59. clock: clock,
  60. adaptInputFunc: adaptInputFunc,
  61. newSecretGetterFunc: newSecretGetterFunc,
  62. newIamTokenFunc: newIamTokenFunc,
  63. secretGetteMap: make(map[string]SecretGetter),
  64. iamTokenMap: make(map[iamTokenKey]*IamToken),
  65. }
  66. if iamTokenCleanupDelay > 0 {
  67. go func() {
  68. for {
  69. time.Sleep(iamTokenCleanupDelay)
  70. provider.CleanUpIamTokenMap()
  71. }
  72. }()
  73. }
  74. return provider
  75. }
  76. type NewSecretSetterFunc func()
  77. type AdaptInputFunc func(store esv1beta1.GenericStore) (*SecretsClientInput, error)
  78. type NewSecretGetterFunc func(ctx context.Context, apiEndpoint string, authorizedKey *iamkey.Key, caCertificate []byte) (SecretGetter, error)
  79. type NewIamTokenFunc func(ctx context.Context, apiEndpoint string, authorizedKey *iamkey.Key, caCertificate []byte) (*IamToken, error)
  80. type IamToken struct {
  81. Token string
  82. ExpiresAt time.Time
  83. }
  84. type SecretsClientInput struct {
  85. APIEndpoint string
  86. AuthorizedKey esmeta.SecretKeySelector
  87. CACertificate *esmeta.SecretKeySelector
  88. }
  89. func (p *YandexCloudProvider) Capabilities() esv1beta1.SecretStoreCapabilities {
  90. return esv1beta1.SecretStoreReadOnly
  91. }
  92. // NewClient constructs a Yandex.Cloud Provider.
  93. func (p *YandexCloudProvider) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (esv1beta1.SecretsClient, error) {
  94. input, err := p.adaptInputFunc(store)
  95. if err != nil {
  96. return nil, err
  97. }
  98. key, err := resolvers.SecretKeyRef(
  99. ctx,
  100. kube,
  101. store.GetKind(),
  102. namespace,
  103. &input.AuthorizedKey,
  104. )
  105. if err != nil {
  106. return nil, err
  107. }
  108. var authorizedKey iamkey.Key
  109. err = json.Unmarshal([]byte(key), &authorizedKey)
  110. if err != nil {
  111. return nil, fmt.Errorf("unable to unmarshal authorized key: %w", err)
  112. }
  113. var caCertificateData []byte
  114. if input.CACertificate != nil {
  115. caCert, err := resolvers.SecretKeyRef(
  116. ctx,
  117. kube,
  118. store.GetKind(),
  119. namespace,
  120. input.CACertificate,
  121. )
  122. if err != nil {
  123. return nil, err
  124. }
  125. caCertificateData = []byte(caCert)
  126. }
  127. secretGetter, err := p.getOrCreateSecretGetter(ctx, input.APIEndpoint, &authorizedKey, caCertificateData)
  128. if err != nil {
  129. return nil, fmt.Errorf("failed to create Yandex.Cloud client: %w", err)
  130. }
  131. iamToken, err := p.getOrCreateIamToken(ctx, input.APIEndpoint, &authorizedKey, caCertificateData)
  132. if err != nil {
  133. return nil, fmt.Errorf("failed to create IAM token: %w", err)
  134. }
  135. return &yandexCloudSecretsClient{secretGetter, nil, iamToken.Token}, nil
  136. }
  137. func (p *YandexCloudProvider) getOrCreateSecretGetter(ctx context.Context, apiEndpoint string, authorizedKey *iamkey.Key, caCertificate []byte) (SecretGetter, error) {
  138. p.secretGetterMapMutex.Lock()
  139. defer p.secretGetterMapMutex.Unlock()
  140. if _, ok := p.secretGetteMap[apiEndpoint]; !ok {
  141. p.logger.Info("creating SecretGetter", "apiEndpoint", apiEndpoint)
  142. secretGetter, err := p.newSecretGetterFunc(ctx, apiEndpoint, authorizedKey, caCertificate)
  143. if err != nil {
  144. return nil, err
  145. }
  146. p.secretGetteMap[apiEndpoint] = secretGetter
  147. }
  148. return p.secretGetteMap[apiEndpoint], nil
  149. }
  150. func (p *YandexCloudProvider) getOrCreateIamToken(ctx context.Context, apiEndpoint string, authorizedKey *iamkey.Key, caCertificate []byte) (*IamToken, error) {
  151. p.iamTokenMapMutex.Lock()
  152. defer p.iamTokenMapMutex.Unlock()
  153. iamTokenKey := buildIamTokenKey(authorizedKey)
  154. if iamToken, ok := p.iamTokenMap[iamTokenKey]; !ok || !p.isIamTokenUsable(iamToken) {
  155. p.logger.Info("creating IAM token", "authorizedKeyId", authorizedKey.Id)
  156. iamToken, err := p.newIamTokenFunc(ctx, apiEndpoint, authorizedKey, caCertificate)
  157. if err != nil {
  158. return nil, err
  159. }
  160. p.logger.Info("created IAM token", "authorizedKeyId", authorizedKey.Id, "expiresAt", iamToken.ExpiresAt)
  161. p.iamTokenMap[iamTokenKey] = iamToken
  162. }
  163. return p.iamTokenMap[iamTokenKey], nil
  164. }
  165. func (p *YandexCloudProvider) isIamTokenUsable(iamToken *IamToken) bool {
  166. now := p.clock.CurrentTime()
  167. return now.Add(maxSecretsClientLifetime).Before(iamToken.ExpiresAt)
  168. }
  169. func buildIamTokenKey(authorizedKey *iamkey.Key) iamTokenKey {
  170. privateKeyHash := sha256.Sum256([]byte(authorizedKey.PrivateKey))
  171. return iamTokenKey{
  172. authorizedKey.GetId(),
  173. authorizedKey.GetServiceAccountId(),
  174. hex.EncodeToString(privateKeyHash[:]),
  175. }
  176. }
  177. // Used for testing.
  178. func (p *YandexCloudProvider) IsIamTokenCached(authorizedKey *iamkey.Key) bool {
  179. p.iamTokenMapMutex.Lock()
  180. defer p.iamTokenMapMutex.Unlock()
  181. _, ok := p.iamTokenMap[buildIamTokenKey(authorizedKey)]
  182. return ok
  183. }
  184. func (p *YandexCloudProvider) CleanUpIamTokenMap() {
  185. p.iamTokenMapMutex.Lock()
  186. defer p.iamTokenMapMutex.Unlock()
  187. for key, value := range p.iamTokenMap {
  188. if p.clock.CurrentTime().After(value.ExpiresAt) {
  189. p.logger.Info("deleting IAM token", "authorizedKeyId", key.authorizedKeyID)
  190. delete(p.iamTokenMap, key)
  191. }
  192. }
  193. }
  194. func (p *YandexCloudProvider) ValidateStore(store esv1beta1.GenericStore) error {
  195. _, err := p.adaptInputFunc(store) // adaptInputFunc validates the input store
  196. if err != nil {
  197. return err
  198. }
  199. return nil
  200. }