provider.go 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  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. corev1 "k8s.io/api/core/v1"
  24. "k8s.io/apimachinery/pkg/types"
  25. kclient "sigs.k8s.io/controller-runtime/pkg/client"
  26. esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
  27. esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
  28. clock2 "github.com/external-secrets/external-secrets/pkg/provider/yandex/common/clock"
  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 _ esv1beta1.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 esv1beta1.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() esv1beta1.SecretStoreCapabilities {
  91. return esv1beta1.SecretStoreReadOnly
  92. }
  93. // NewClient constructs a Yandex.Cloud Provider.
  94. func (p *YandexCloudProvider) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (esv1beta1.SecretsClient, error) {
  95. input, err := p.adaptInputFunc(store)
  96. if err != nil {
  97. return nil, err
  98. }
  99. objectKey := types.NamespacedName{
  100. Name: input.AuthorizedKey.Name,
  101. Namespace: namespace,
  102. }
  103. // only ClusterStore is allowed to set namespace (and then it's required)
  104. if store.GetObjectKind().GroupVersionKind().Kind == esv1beta1.ClusterSecretStoreKind {
  105. if input.AuthorizedKey.Namespace == nil {
  106. return nil, fmt.Errorf("invalid ClusterSecretStore: missing AuthorizedKey Namespace")
  107. }
  108. objectKey.Namespace = *input.AuthorizedKey.Namespace
  109. }
  110. authorizedKeySecret := &corev1.Secret{}
  111. err = kube.Get(ctx, objectKey, authorizedKeySecret)
  112. if err != nil {
  113. return nil, fmt.Errorf("could not fetch AuthorizedKey secret: %w", err)
  114. }
  115. authorizedKeySecretData := authorizedKeySecret.Data[input.AuthorizedKey.Key]
  116. if (authorizedKeySecretData == nil) || (len(authorizedKeySecretData) == 0) {
  117. return nil, fmt.Errorf("missing AuthorizedKey")
  118. }
  119. var authorizedKey iamkey.Key
  120. err = json.Unmarshal(authorizedKeySecretData, &authorizedKey)
  121. if err != nil {
  122. return nil, fmt.Errorf("unable to unmarshal authorized key: %w", err)
  123. }
  124. var caCertificateData []byte
  125. if input.CACertificate != nil {
  126. certObjectKey := types.NamespacedName{
  127. Name: input.CACertificate.Name,
  128. Namespace: namespace,
  129. }
  130. if store.GetObjectKind().GroupVersionKind().Kind == esv1beta1.ClusterSecretStoreKind {
  131. if input.CACertificate.Namespace == nil {
  132. return nil, fmt.Errorf("invalid ClusterSecretStore: missing CA certificate Namespace")
  133. }
  134. certObjectKey.Namespace = *input.CACertificate.Namespace
  135. }
  136. caCertificateSecret := &corev1.Secret{}
  137. err := kube.Get(ctx, certObjectKey, caCertificateSecret)
  138. if err != nil {
  139. return nil, fmt.Errorf("could not fetch CA certificate secret: %w", err)
  140. }
  141. caCertificateData = caCertificateSecret.Data[input.CACertificate.Key]
  142. if (caCertificateData == nil) || (len(caCertificateData) == 0) {
  143. return nil, fmt.Errorf("missing CA Certificate")
  144. }
  145. }
  146. secretGetter, err := p.getOrCreateSecretGetter(ctx, input.APIEndpoint, &authorizedKey, caCertificateData)
  147. if err != nil {
  148. return nil, fmt.Errorf("failed to create Yandex.Cloud client: %w", err)
  149. }
  150. iamToken, err := p.getOrCreateIamToken(ctx, input.APIEndpoint, &authorizedKey, caCertificateData)
  151. if err != nil {
  152. return nil, fmt.Errorf("failed to create IAM token: %w", err)
  153. }
  154. return &yandexCloudSecretsClient{secretGetter, nil, iamToken.Token}, nil
  155. }
  156. func (p *YandexCloudProvider) getOrCreateSecretGetter(ctx context.Context, apiEndpoint string, authorizedKey *iamkey.Key, caCertificate []byte) (SecretGetter, error) {
  157. p.secretGetterMapMutex.Lock()
  158. defer p.secretGetterMapMutex.Unlock()
  159. if _, ok := p.secretGetteMap[apiEndpoint]; !ok {
  160. p.logger.Info("creating SecretGetter", "apiEndpoint", apiEndpoint)
  161. secretGetter, err := p.newSecretGetterFunc(ctx, apiEndpoint, authorizedKey, caCertificate)
  162. if err != nil {
  163. return nil, err
  164. }
  165. p.secretGetteMap[apiEndpoint] = secretGetter
  166. }
  167. return p.secretGetteMap[apiEndpoint], nil
  168. }
  169. func (p *YandexCloudProvider) getOrCreateIamToken(ctx context.Context, apiEndpoint string, authorizedKey *iamkey.Key, caCertificate []byte) (*IamToken, error) {
  170. p.iamTokenMapMutex.Lock()
  171. defer p.iamTokenMapMutex.Unlock()
  172. iamTokenKey := buildIamTokenKey(authorizedKey)
  173. if iamToken, ok := p.iamTokenMap[iamTokenKey]; !ok || !p.isIamTokenUsable(iamToken) {
  174. p.logger.Info("creating IAM token", "authorizedKeyId", authorizedKey.Id)
  175. iamToken, err := p.newIamTokenFunc(ctx, apiEndpoint, authorizedKey, caCertificate)
  176. if err != nil {
  177. return nil, err
  178. }
  179. p.logger.Info("created IAM token", "authorizedKeyId", authorizedKey.Id, "expiresAt", iamToken.ExpiresAt)
  180. p.iamTokenMap[iamTokenKey] = iamToken
  181. }
  182. return p.iamTokenMap[iamTokenKey], nil
  183. }
  184. func (p *YandexCloudProvider) isIamTokenUsable(iamToken *IamToken) bool {
  185. now := p.clock.CurrentTime()
  186. return now.Add(maxSecretsClientLifetime).Before(iamToken.ExpiresAt)
  187. }
  188. func buildIamTokenKey(authorizedKey *iamkey.Key) iamTokenKey {
  189. privateKeyHash := sha256.Sum256([]byte(authorizedKey.PrivateKey))
  190. return iamTokenKey{
  191. authorizedKey.GetId(),
  192. authorizedKey.GetServiceAccountId(),
  193. hex.EncodeToString(privateKeyHash[:]),
  194. }
  195. }
  196. // Used for testing.
  197. func (p *YandexCloudProvider) IsIamTokenCached(authorizedKey *iamkey.Key) bool {
  198. p.iamTokenMapMutex.Lock()
  199. defer p.iamTokenMapMutex.Unlock()
  200. _, ok := p.iamTokenMap[buildIamTokenKey(authorizedKey)]
  201. return ok
  202. }
  203. func (p *YandexCloudProvider) CleanUpIamTokenMap() {
  204. p.iamTokenMapMutex.Lock()
  205. defer p.iamTokenMapMutex.Unlock()
  206. for key, value := range p.iamTokenMap {
  207. if p.clock.CurrentTime().After(value.ExpiresAt) {
  208. p.logger.Info("deleting IAM token", "authorizedKeyId", key.authorizedKeyID)
  209. delete(p.iamTokenMap, key)
  210. }
  211. }
  212. }
  213. func (p *YandexCloudProvider) ValidateStore(store esv1beta1.GenericStore) error {
  214. _, err := p.adaptInputFunc(store) // adaptInputFunc validates the input store
  215. if err != nil {
  216. return err
  217. }
  218. return nil
  219. }