pushsecret_controller_v2.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. /*
  2. Copyright © The ESO Authors
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. https://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package pushsecret
  14. import (
  15. "context"
  16. "fmt"
  17. "maps"
  18. "strings"
  19. corev1 "k8s.io/api/core/v1"
  20. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  21. "k8s.io/apimachinery/pkg/types"
  22. "sigs.k8s.io/controller-runtime/pkg/client"
  23. esapi "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  24. esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
  25. esv2alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v2alpha1"
  26. "github.com/external-secrets/external-secrets/runtime/clientmanager"
  27. )
  28. // GetSecretStoresV2 retrieves both v1 stores and clean v2 ProviderStores.
  29. func (r *Reconciler) GetSecretStoresV2(ctx context.Context, ps esv1alpha1.PushSecret) (map[esv1alpha1.PushSecretStoreRef]any, error) {
  30. stores := make(map[esv1alpha1.PushSecretStoreRef]any)
  31. for _, refStore := range ps.Spec.SecretStoreRefs {
  32. if refStore.LabelSelector != nil {
  33. resolvedStores, err := r.getSecretStoresFromSelectorV2(ctx, refStore, ps.Namespace)
  34. if err != nil {
  35. return nil, err
  36. }
  37. maps.Copy(stores, resolvedStores)
  38. continue
  39. }
  40. store, ok, err := r.resolveV2Store(ctx, refStore, ps.Namespace)
  41. if err != nil {
  42. return nil, err
  43. }
  44. if ok {
  45. stores[refStore] = store
  46. continue
  47. }
  48. // Get v1 SecretStore (existing implementation)
  49. store, err = r.getSecretStoreFromName(ctx, refStore, ps.Namespace)
  50. if err != nil {
  51. return nil, err
  52. }
  53. stores[refStore] = store
  54. }
  55. return stores, nil
  56. }
  57. func isCleanStoreRef(ref esv1alpha1.PushSecretStoreRef) bool {
  58. switch ref.Kind {
  59. case esapi.ProviderStoreKindStr, esapi.ClusterProviderStoreKindStr:
  60. return true
  61. }
  62. return ref.APIVersion == esv2alpha1.SchemeGroupVersion.String()
  63. }
  64. func (r *Reconciler) getSecretStoresFromSelectorV2(ctx context.Context, storeRef esv1alpha1.PushSecretStoreRef, namespace string) (map[esv1alpha1.PushSecretStoreRef]any, error) {
  65. selector, err := metav1.LabelSelectorAsSelector(storeRef.LabelSelector)
  66. if err != nil {
  67. return nil, fmt.Errorf("could not convert labels: %w", err)
  68. }
  69. listOptions := &client.ListOptions{LabelSelector: selector}
  70. stores := make(map[esv1alpha1.PushSecretStoreRef]any)
  71. switch storeRef.Kind {
  72. case esapi.ProviderStoreKindStr:
  73. listOptions.Namespace = namespace
  74. var storeList esv2alpha1.ProviderStoreList
  75. if err := r.List(ctx, &storeList, listOptions); err != nil {
  76. return nil, fmt.Errorf("could not list ProviderStores: %w", err)
  77. }
  78. for i := range storeList.Items {
  79. store := &storeList.Items[i]
  80. stores[esv1alpha1.PushSecretStoreRef{Name: store.Name, Kind: esapi.ProviderStoreKindStr}] = store
  81. }
  82. case esapi.ClusterProviderStoreKindStr:
  83. var storeList esv2alpha1.ClusterProviderStoreList
  84. if err := r.List(ctx, &storeList, listOptions); err != nil {
  85. return nil, fmt.Errorf("could not list ClusterProviderStores: %w", err)
  86. }
  87. for i := range storeList.Items {
  88. store := &storeList.Items[i]
  89. stores[esv1alpha1.PushSecretStoreRef{Name: store.Name, Kind: esapi.ClusterProviderStoreKindStr}] = store
  90. }
  91. case esapi.ClusterSecretStoreKind:
  92. var storeList esapi.ClusterSecretStoreList
  93. if err := r.List(ctx, &storeList, listOptions); err != nil {
  94. return nil, fmt.Errorf("could not list cluster Secret Stores: %w", err)
  95. }
  96. for i := range storeList.Items {
  97. store := &storeList.Items[i]
  98. stores[esv1alpha1.PushSecretStoreRef{Name: store.Name, Kind: esapi.ClusterSecretStoreKind}] = store
  99. }
  100. default:
  101. listOptions.Namespace = namespace
  102. var storeList esapi.SecretStoreList
  103. if err := r.List(ctx, &storeList, listOptions); err != nil {
  104. return nil, fmt.Errorf("could not list Secret Stores: %w", err)
  105. }
  106. for i := range storeList.Items {
  107. store := &storeList.Items[i]
  108. stores[esv1alpha1.PushSecretStoreRef{Name: store.Name, Kind: esapi.SecretStoreKind}] = store
  109. }
  110. }
  111. return stores, nil
  112. }
  113. func (r *Reconciler) resolveV2Store(ctx context.Context, storeRef esv1alpha1.PushSecretStoreRef, namespace string) (any, bool, error) {
  114. if storeRef.APIVersion != "" && storeRef.APIVersion != esapi.SchemeGroupVersion.String() && !isCleanStoreRef(storeRef) {
  115. return nil, false, nil
  116. }
  117. if storeRef.Name == "" {
  118. return nil, false, nil
  119. }
  120. switch storeRef.Kind {
  121. case esapi.ProviderStoreKindStr:
  122. var store esv2alpha1.ProviderStore
  123. storeKey := types.NamespacedName{Name: storeRef.Name, Namespace: namespace}
  124. if err := r.Client.Get(ctx, storeKey, &store); err != nil {
  125. return nil, true, fmt.Errorf("failed to get v2 ProviderStore %s: %w", storeRef.Name, err)
  126. }
  127. return &store, true, nil
  128. case esapi.ClusterProviderStoreKindStr:
  129. var store esv2alpha1.ClusterProviderStore
  130. storeKey := types.NamespacedName{Name: storeRef.Name}
  131. if err := r.Client.Get(ctx, storeKey, &store); err != nil {
  132. return nil, true, fmt.Errorf("failed to get v2 ClusterProviderStore %s: %w", storeRef.Name, err)
  133. }
  134. return &store, true, nil
  135. case "":
  136. var providerStore esv2alpha1.ProviderStore
  137. providerStoreKey := types.NamespacedName{Name: storeRef.Name, Namespace: namespace}
  138. if err := r.Client.Get(ctx, providerStoreKey, &providerStore); err == nil {
  139. return &providerStore, true, nil
  140. }
  141. var clusterProviderStore esv2alpha1.ClusterProviderStore
  142. clusterProviderStoreKey := types.NamespacedName{Name: storeRef.Name}
  143. if err := r.Client.Get(ctx, clusterProviderStoreKey, &clusterProviderStore); err == nil {
  144. return &clusterProviderStore, true, nil
  145. }
  146. }
  147. return nil, false, nil
  148. }
  149. // PushSecretToProvidersV2 pushes secret data to both v1 stores and v2 providers.
  150. func (r *Reconciler) PushSecretToProvidersV2(
  151. ctx context.Context,
  152. stores map[esv1alpha1.PushSecretStoreRef]any,
  153. ps esv1alpha1.PushSecret,
  154. secret *corev1.Secret,
  155. mgr *clientmanager.Manager,
  156. ) (esv1alpha1.SyncedPushSecretsMap, error) {
  157. out := make(esv1alpha1.SyncedPushSecretsMap)
  158. for ref, store := range stores {
  159. si, ok := resolvedStoreInfo(ref, store)
  160. if !ok {
  161. continue
  162. }
  163. var err error
  164. out, err = r.handlePushSecretDataForStore(ctx, ps, secret, out, mgr, si)
  165. if err != nil {
  166. return out, err
  167. }
  168. }
  169. return out, nil
  170. }
  171. // DeleteSecretFromProvidersV2 removes secrets from v2 providers when they're no longer needed.
  172. func (r *Reconciler) DeleteSecretFromProvidersV2(
  173. ctx context.Context,
  174. ps *esv1alpha1.PushSecret,
  175. newMap esv1alpha1.SyncedPushSecretsMap,
  176. _ map[esv1alpha1.PushSecretStoreRef]any,
  177. ) (esv1alpha1.SyncedPushSecretsMap, error) {
  178. out := mergeSecretState(newMap, ps.Status.SyncedPushSecrets)
  179. mgr := clientmanager.NewManager(r.Client, r.ControllerClass, false)
  180. defer func() {
  181. _ = mgr.Close(ctx)
  182. }()
  183. for storeName, oldData := range ps.Status.SyncedPushSecrets {
  184. // Parse store name format "Kind/Name"
  185. parts := strings.Split(storeName, "/")
  186. if len(parts) != 2 {
  187. continue
  188. }
  189. storeKind := parts[0]
  190. storeNameOnly := parts[1]
  191. secretClient, err := mgr.Get(ctx, esapi.SecretStoreRef{
  192. Name: storeNameOnly,
  193. Kind: storeKind,
  194. }, ps.Namespace, nil)
  195. if err != nil {
  196. return out, fmt.Errorf("could not get secrets client for store %v: %w", storeName, err)
  197. }
  198. newData, ok := newMap[storeName]
  199. if !ok {
  200. err = r.DeleteAllSecretsFromStore(ctx, secretClient, oldData)
  201. if err != nil {
  202. return out, err
  203. }
  204. delete(out, storeName)
  205. continue
  206. }
  207. for oldEntry, oldRef := range oldData {
  208. if _, stillExists := newData[oldEntry]; !stillExists {
  209. err = r.DeleteSecretFromStore(ctx, secretClient, oldRef)
  210. if err != nil {
  211. return out, err
  212. }
  213. delete(out[storeName], oldEntry)
  214. }
  215. }
  216. }
  217. return out, nil
  218. }