provider.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  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. "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
  25. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  26. esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
  27. clock2 "github.com/external-secrets/external-secrets/pkg/provider/yandex/common/clock"
  28. "github.com/external-secrets/external-secrets/pkg/utils/resolvers"
  29. )
  30. const maxSecretsClientLifetime = 5 * time.Minute // supposed SecretsClient lifetime is quite short
  31. // https://github.com/external-secrets/external-secrets/issues/644
  32. var _ esv1.Provider = &YandexCloudProvider{}
  33. // Implementation of v1beta1.Provider.
  34. type YandexCloudProvider struct {
  35. logger logr.Logger
  36. clock clock2.Clock
  37. adaptInputFunc AdaptInputFunc
  38. newSecretGetterFunc NewSecretGetterFunc
  39. newIamTokenFunc NewIamTokenFunc
  40. secretGetteMap map[string]SecretGetter // apiEndpoint -> SecretGetter
  41. secretGetterMapMutex sync.Mutex
  42. iamTokenMap map[iamTokenKey]*IamToken
  43. iamTokenMapMutex sync.Mutex
  44. }
  45. type iamTokenKey struct {
  46. authorizedKeyID string
  47. serviceAccountID string
  48. privateKeyHash string
  49. }
  50. func InitYandexCloudProvider(
  51. logger logr.Logger,
  52. clock clock2.Clock,
  53. adaptInputFunc AdaptInputFunc,
  54. newSecretGetterFunc NewSecretGetterFunc,
  55. newIamTokenFunc NewIamTokenFunc,
  56. iamTokenCleanupDelay time.Duration,
  57. ) *YandexCloudProvider {
  58. provider := &YandexCloudProvider{
  59. logger: logger,
  60. clock: clock,
  61. adaptInputFunc: adaptInputFunc,
  62. newSecretGetterFunc: newSecretGetterFunc,
  63. newIamTokenFunc: newIamTokenFunc,
  64. secretGetteMap: make(map[string]SecretGetter),
  65. iamTokenMap: make(map[iamTokenKey]*IamToken),
  66. }
  67. if iamTokenCleanupDelay > 0 {
  68. go func() {
  69. for {
  70. time.Sleep(iamTokenCleanupDelay)
  71. provider.CleanUpIamTokenMap()
  72. }
  73. }()
  74. }
  75. return provider
  76. }
  77. type NewSecretSetterFunc func()
  78. type AdaptInputFunc func(store esv1.GenericStore) (*SecretsClientInput, error)
  79. type NewSecretGetterFunc func(ctx context.Context, apiEndpoint string, authorizedKey *iamkey.Key, caCertificate []byte) (SecretGetter, error)
  80. type NewIamTokenFunc func(ctx context.Context, apiEndpoint string, authorizedKey *iamkey.Key, caCertificate []byte) (*IamToken, error)
  81. type IamToken struct {
  82. Token string
  83. ExpiresAt time.Time
  84. }
  85. type SecretsClientInput struct {
  86. APIEndpoint string
  87. AuthorizedKey esmeta.SecretKeySelector
  88. CACertificate *esmeta.SecretKeySelector
  89. }
  90. func (p *YandexCloudProvider) Capabilities() esv1.SecretStoreCapabilities {
  91. return esv1.SecretStoreReadOnly
  92. }
  93. // NewClient constructs a Yandex.Cloud Provider.
  94. func (p *YandexCloudProvider) NewClient(ctx context.Context, store esv1.GenericStore, kube kclient.Client, namespace string) (esv1.SecretsClient, error) {
  95. input, err := p.adaptInputFunc(store)
  96. if err != nil {
  97. return nil, err
  98. }
  99. key, err := resolvers.SecretKeyRef(
  100. ctx,
  101. kube,
  102. store.GetKind(),
  103. namespace,
  104. &input.AuthorizedKey,
  105. )
  106. if err != nil {
  107. return nil, err
  108. }
  109. var authorizedKey iamkey.Key
  110. err = json.Unmarshal([]byte(key), &authorizedKey)
  111. if err != nil {
  112. return nil, fmt.Errorf("unable to unmarshal authorized key: %w", err)
  113. }
  114. var caCertificateData []byte
  115. if input.CACertificate != nil {
  116. caCert, err := resolvers.SecretKeyRef(
  117. ctx,
  118. kube,
  119. store.GetKind(),
  120. namespace,
  121. input.CACertificate,
  122. )
  123. if err != nil {
  124. return nil, err
  125. }
  126. caCertificateData = []byte(caCert)
  127. }
  128. secretGetter, err := p.getOrCreateSecretGetter(ctx, input.APIEndpoint, &authorizedKey, caCertificateData)
  129. if err != nil {
  130. return nil, fmt.Errorf("failed to create Yandex.Cloud client: %w", err)
  131. }
  132. iamToken, err := p.getOrCreateIamToken(ctx, input.APIEndpoint, &authorizedKey, caCertificateData)
  133. if err != nil {
  134. return nil, fmt.Errorf("failed to create IAM token: %w", err)
  135. }
  136. return &yandexCloudSecretsClient{secretGetter, nil, iamToken.Token}, nil
  137. }
  138. func (p *YandexCloudProvider) getOrCreateSecretGetter(ctx context.Context, apiEndpoint string, authorizedKey *iamkey.Key, caCertificate []byte) (SecretGetter, error) {
  139. p.secretGetterMapMutex.Lock()
  140. defer p.secretGetterMapMutex.Unlock()
  141. if _, ok := p.secretGetteMap[apiEndpoint]; !ok {
  142. p.logger.Info("creating SecretGetter", "apiEndpoint", apiEndpoint)
  143. secretGetter, err := p.newSecretGetterFunc(ctx, apiEndpoint, authorizedKey, caCertificate)
  144. if err != nil {
  145. return nil, err
  146. }
  147. p.secretGetteMap[apiEndpoint] = secretGetter
  148. }
  149. return p.secretGetteMap[apiEndpoint], nil
  150. }
  151. func (p *YandexCloudProvider) getOrCreateIamToken(ctx context.Context, apiEndpoint string, authorizedKey *iamkey.Key, caCertificate []byte) (*IamToken, error) {
  152. p.iamTokenMapMutex.Lock()
  153. defer p.iamTokenMapMutex.Unlock()
  154. iamTokenKey := buildIamTokenKey(authorizedKey)
  155. if iamToken, ok := p.iamTokenMap[iamTokenKey]; !ok || !p.isIamTokenUsable(iamToken) {
  156. p.logger.Info("creating IAM token", "authorizedKeyId", authorizedKey.Id)
  157. iamToken, err := p.newIamTokenFunc(ctx, apiEndpoint, authorizedKey, caCertificate)
  158. if err != nil {
  159. return nil, err
  160. }
  161. p.logger.Info("created IAM token", "authorizedKeyId", authorizedKey.Id, "expiresAt", iamToken.ExpiresAt)
  162. p.iamTokenMap[iamTokenKey] = iamToken
  163. }
  164. return p.iamTokenMap[iamTokenKey], nil
  165. }
  166. func (p *YandexCloudProvider) isIamTokenUsable(iamToken *IamToken) bool {
  167. now := p.clock.CurrentTime()
  168. return now.Add(maxSecretsClientLifetime).Before(iamToken.ExpiresAt)
  169. }
  170. func buildIamTokenKey(authorizedKey *iamkey.Key) iamTokenKey {
  171. privateKeyHash := sha256.Sum256([]byte(authorizedKey.PrivateKey))
  172. return iamTokenKey{
  173. authorizedKey.GetId(),
  174. authorizedKey.GetServiceAccountId(),
  175. hex.EncodeToString(privateKeyHash[:]),
  176. }
  177. }
  178. // Used for testing.
  179. func (p *YandexCloudProvider) IsIamTokenCached(authorizedKey *iamkey.Key) bool {
  180. p.iamTokenMapMutex.Lock()
  181. defer p.iamTokenMapMutex.Unlock()
  182. _, ok := p.iamTokenMap[buildIamTokenKey(authorizedKey)]
  183. return ok
  184. }
  185. func (p *YandexCloudProvider) CleanUpIamTokenMap() {
  186. p.iamTokenMapMutex.Lock()
  187. defer p.iamTokenMapMutex.Unlock()
  188. for key, value := range p.iamTokenMap {
  189. if p.clock.CurrentTime().After(value.ExpiresAt) {
  190. p.logger.Info("deleting IAM token", "authorizedKeyId", key.authorizedKeyID)
  191. delete(p.iamTokenMap, key)
  192. }
  193. }
  194. }
  195. func (p *YandexCloudProvider) ValidateStore(store esv1.GenericStore) (admission.Warnings, error) {
  196. _, err := p.adaptInputFunc(store) // adaptInputFunc validates the input store
  197. if err != nil {
  198. return nil, err
  199. }
  200. return nil, nil
  201. }