pushsecret_controller_v2.go 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  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 pushsecret
  13. import (
  14. "context"
  15. "fmt"
  16. "strings"
  17. corev1 "k8s.io/api/core/v1"
  18. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  19. "k8s.io/apimachinery/pkg/types"
  20. "sigs.k8s.io/controller-runtime/pkg/client"
  21. esapi "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  22. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  23. esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
  24. "github.com/external-secrets/external-secrets/runtime/clientmanager"
  25. )
  26. // isV2SecretStore checks if the referenced SecretStore is a v2 API version.
  27. func (r *Reconciler) isV2SecretStore(ctx context.Context, storeRef esv1alpha1.PushSecretStoreRef, namespace string) bool {
  28. _, ok, err := r.resolveV2Store(ctx, storeRef, namespace)
  29. return err == nil && ok
  30. }
  31. // GetSecretStoresV2 retrieves both v1 and v2 Providers.
  32. func (r *Reconciler) GetSecretStoresV2(ctx context.Context, ps esv1alpha1.PushSecret) (map[esv1alpha1.PushSecretStoreRef]interface{}, error) {
  33. stores := make(map[esv1alpha1.PushSecretStoreRef]interface{})
  34. for _, refStore := range ps.Spec.SecretStoreRefs {
  35. if refStore.LabelSelector != nil {
  36. resolvedStores, err := r.getSecretStoresFromSelectorV2(ctx, refStore, ps.Namespace)
  37. if err != nil {
  38. return nil, err
  39. }
  40. for resolvedRef, store := range resolvedStores {
  41. stores[resolvedRef] = store
  42. }
  43. continue
  44. }
  45. if store, ok, err := r.resolveV2Store(ctx, refStore, ps.Namespace); err != nil {
  46. return nil, err
  47. } else if ok {
  48. stores[refStore] = store
  49. continue
  50. } else {
  51. // Get v1 SecretStore (existing implementation)
  52. store, err := r.getSecretStoreFromName(ctx, refStore, ps.Namespace)
  53. if err != nil {
  54. return nil, err
  55. }
  56. stores[refStore] = store
  57. }
  58. }
  59. return stores, nil
  60. }
  61. func (r *Reconciler) getSecretStoresFromSelectorV2(ctx context.Context, storeRef esv1alpha1.PushSecretStoreRef, namespace string) (map[esv1alpha1.PushSecretStoreRef]interface{}, error) {
  62. selector, err := metav1.LabelSelectorAsSelector(storeRef.LabelSelector)
  63. if err != nil {
  64. return nil, fmt.Errorf("could not convert labels: %w", err)
  65. }
  66. listOptions := &client.ListOptions{LabelSelector: selector}
  67. stores := make(map[esv1alpha1.PushSecretStoreRef]interface{})
  68. switch storeRef.Kind {
  69. case esapi.ProviderKindStr:
  70. listOptions.Namespace = namespace
  71. var providerList esv1.ProviderList
  72. if err := r.List(ctx, &providerList, listOptions); err != nil {
  73. return nil, fmt.Errorf("could not list Providers: %w", err)
  74. }
  75. for i := range providerList.Items {
  76. store := &providerList.Items[i]
  77. stores[esv1alpha1.PushSecretStoreRef{Name: store.Name, Kind: esapi.ProviderKindStr}] = store
  78. }
  79. case esapi.ClusterProviderKindStr:
  80. var providerList esv1.ClusterProviderList
  81. if err := r.List(ctx, &providerList, listOptions); err != nil {
  82. return nil, fmt.Errorf("could not list ClusterProviders: %w", err)
  83. }
  84. for i := range providerList.Items {
  85. store := &providerList.Items[i]
  86. stores[esv1alpha1.PushSecretStoreRef{Name: store.Name, Kind: esapi.ClusterProviderKindStr}] = store
  87. }
  88. case esv1.ClusterSecretStoreKind:
  89. var storeList esv1.ClusterSecretStoreList
  90. if err := r.List(ctx, &storeList, listOptions); err != nil {
  91. return nil, fmt.Errorf("could not list cluster Secret Stores: %w", err)
  92. }
  93. for i := range storeList.Items {
  94. store := &storeList.Items[i]
  95. stores[esv1alpha1.PushSecretStoreRef{Name: store.Name, Kind: esv1.ClusterSecretStoreKind}] = store
  96. }
  97. default:
  98. listOptions.Namespace = namespace
  99. var storeList esv1.SecretStoreList
  100. if err := r.List(ctx, &storeList, listOptions); err != nil {
  101. return nil, fmt.Errorf("could not list Secret Stores: %w", err)
  102. }
  103. for i := range storeList.Items {
  104. store := &storeList.Items[i]
  105. stores[esv1alpha1.PushSecretStoreRef{Name: store.Name, Kind: esv1.SecretStoreKind}] = store
  106. }
  107. }
  108. return stores, nil
  109. }
  110. func (r *Reconciler) resolveV2Store(ctx context.Context, storeRef esv1alpha1.PushSecretStoreRef, namespace string) (interface{}, bool, error) {
  111. if storeRef.APIVersion != "" && storeRef.APIVersion != esapi.SchemeGroupVersion.String() {
  112. return nil, false, nil
  113. }
  114. if storeRef.Name == "" {
  115. return nil, false, nil
  116. }
  117. switch storeRef.Kind {
  118. case esapi.ClusterProviderKindStr:
  119. var store esapi.ClusterProvider
  120. storeKey := types.NamespacedName{Name: storeRef.Name}
  121. if err := r.Client.Get(ctx, storeKey, &store); err != nil {
  122. return nil, true, fmt.Errorf("failed to get v2 ClusterProvider %s: %w", storeRef.Name, err)
  123. }
  124. return &store, true, nil
  125. case esapi.ProviderKindStr:
  126. var store esapi.Provider
  127. storeKey := types.NamespacedName{Name: storeRef.Name, Namespace: namespace}
  128. if err := r.Client.Get(ctx, storeKey, &store); err != nil {
  129. return nil, true, fmt.Errorf("failed to get v2 Provider %s: %w", storeRef.Name, err)
  130. }
  131. return &store, true, nil
  132. case "":
  133. var provider esapi.Provider
  134. providerKey := types.NamespacedName{Name: storeRef.Name, Namespace: namespace}
  135. if err := r.Client.Get(ctx, providerKey, &provider); err == nil {
  136. return &provider, true, nil
  137. }
  138. var clusterProvider esapi.ClusterProvider
  139. clusterProviderKey := types.NamespacedName{Name: storeRef.Name}
  140. if err := r.Client.Get(ctx, clusterProviderKey, &clusterProvider); err == nil {
  141. return &clusterProvider, true, nil
  142. }
  143. }
  144. return nil, false, nil
  145. }
  146. // PushSecretToProvidersV2 pushes secret data to both v1 stores and v2 providers.
  147. func (r *Reconciler) PushSecretToProvidersV2(
  148. ctx context.Context,
  149. stores map[esv1alpha1.PushSecretStoreRef]interface{},
  150. ps esv1alpha1.PushSecret,
  151. secret *corev1.Secret,
  152. mgr *clientmanager.Manager,
  153. ) (esv1alpha1.SyncedPushSecretsMap, error) {
  154. out := make(esv1alpha1.SyncedPushSecretsMap)
  155. for ref, store := range stores {
  156. si, ok := resolvedStoreInfo(ref, store)
  157. if !ok {
  158. continue
  159. }
  160. var err error
  161. out, err = r.handlePushSecretDataForStore(ctx, ps, secret, out, mgr, si)
  162. if err != nil {
  163. return out, err
  164. }
  165. }
  166. return out, nil
  167. }
  168. // DeleteSecretFromProvidersV2 removes secrets from v2 providers when they're no longer needed.
  169. func (r *Reconciler) DeleteSecretFromProvidersV2(ctx context.Context, ps *esv1alpha1.PushSecret, newMap esv1alpha1.SyncedPushSecretsMap, stores map[esv1alpha1.PushSecretStoreRef]interface{}) (esv1alpha1.SyncedPushSecretsMap, error) {
  170. out := mergeSecretState(newMap, ps.Status.SyncedPushSecrets)
  171. mgr := clientmanager.NewManager(r.Client, r.ControllerClass, false)
  172. defer func() {
  173. _ = mgr.Close(ctx)
  174. }()
  175. for storeName, oldData := range ps.Status.SyncedPushSecrets {
  176. // Parse store name format "Kind/Name"
  177. parts := strings.Split(storeName, "/")
  178. if len(parts) != 2 {
  179. continue
  180. }
  181. storeKind := parts[0]
  182. storeNameOnly := parts[1]
  183. secretClient, err := mgr.Get(ctx, esapi.SecretStoreRef{
  184. Name: storeNameOnly,
  185. Kind: storeKind,
  186. }, ps.Namespace, nil)
  187. if err != nil {
  188. return out, fmt.Errorf("could not get secrets client for store %v: %w", storeName, err)
  189. }
  190. newData, ok := newMap[storeName]
  191. if !ok {
  192. err = r.DeleteAllSecretsFromStore(ctx, secretClient, oldData)
  193. if err != nil {
  194. return out, err
  195. }
  196. delete(out, storeName)
  197. continue
  198. }
  199. for oldEntry, oldRef := range oldData {
  200. if _, stillExists := newData[oldEntry]; !stillExists {
  201. err = r.DeleteSecretFromStore(ctx, secretClient, oldRef)
  202. if err != nil {
  203. return out, err
  204. }
  205. delete(out[storeName], oldEntry)
  206. }
  207. }
  208. }
  209. return out, nil
  210. }