pushsecret_controller.go 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700
  1. /*
  2. Copyright © 2025 ESO Maintainer Team
  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 implements the controller for managing PushSecret resources.
  14. package pushsecret
  15. import (
  16. "context"
  17. "errors"
  18. "fmt"
  19. "maps"
  20. "strings"
  21. "time"
  22. "github.com/go-logr/logr"
  23. v1 "k8s.io/api/core/v1"
  24. apierrors "k8s.io/apimachinery/pkg/api/errors"
  25. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  26. "k8s.io/apimachinery/pkg/runtime"
  27. "k8s.io/apimachinery/pkg/types"
  28. "k8s.io/client-go/rest"
  29. "k8s.io/client-go/tools/record"
  30. ctrl "sigs.k8s.io/controller-runtime"
  31. "sigs.k8s.io/controller-runtime/pkg/client"
  32. "sigs.k8s.io/controller-runtime/pkg/controller"
  33. "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
  34. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  35. esapi "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
  36. genv1alpha1 "github.com/external-secrets/external-secrets/apis/generators/v1alpha1"
  37. ctrlmetrics "github.com/external-secrets/external-secrets/pkg/controllers/metrics"
  38. "github.com/external-secrets/external-secrets/pkg/controllers/pushsecret/psmetrics"
  39. "github.com/external-secrets/external-secrets/pkg/controllers/secretstore"
  40. "github.com/external-secrets/external-secrets/pkg/controllers/util"
  41. "github.com/external-secrets/external-secrets/pkg/esutils"
  42. "github.com/external-secrets/external-secrets/pkg/esutils/resolvers"
  43. "github.com/external-secrets/external-secrets/pkg/generator/statemanager"
  44. "github.com/external-secrets/external-secrets/pkg/provider/util/locks"
  45. // Load registered generators.
  46. _ "github.com/external-secrets/external-secrets/pkg/generator/register"
  47. )
  48. const (
  49. errFailedGetSecret = "could not get source secret"
  50. errPatchStatus = "error merging"
  51. errGetSecretStore = "could not get SecretStore %q, %w"
  52. errGetClusterSecretStore = "could not get ClusterSecretStore %q, %w"
  53. errSetSecretFailed = "could not write remote ref %v to target secretstore %v: %v"
  54. errFailedSetSecret = "set secret failed: %v"
  55. errConvert = "could not apply conversion strategy to keys: %v"
  56. pushSecretFinalizer = "pushsecret.externalsecrets.io/finalizer"
  57. errCloudNotUpdateFinalizer = "could not update finalizers: %w"
  58. )
  59. // Reconciler is the controller for PushSecret resources.
  60. // It manages the lifecycle of PushSecrets, ensuring that secrets are pushed to
  61. // specified secret stores according to the defined policies and templates.
  62. type Reconciler struct {
  63. client.Client
  64. Log logr.Logger
  65. Scheme *runtime.Scheme
  66. recorder record.EventRecorder
  67. RestConfig *rest.Config
  68. RequeueInterval time.Duration
  69. ControllerClass string
  70. }
  71. // SetupWithManager sets up the controller with the Manager.
  72. // It configures the controller to watch PushSecret resources and
  73. // manages indexing for efficient lookups based on secret stores and deletion policies.
  74. func (r *Reconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, opts controller.Options) error {
  75. r.recorder = mgr.GetEventRecorderFor("pushsecret")
  76. // Index PushSecrets by the stores they have pushed to (for finalizer management on store deletion)
  77. // Refer to common.go for more details on the index function
  78. if err := mgr.GetFieldIndexer().IndexField(ctx, &esapi.PushSecret{}, "status.syncedPushSecrets", func(obj client.Object) []string {
  79. ps := obj.(*esapi.PushSecret)
  80. // Only index PushSecrets with DeletionPolicy=Delete for efficiency
  81. if ps.Spec.DeletionPolicy != esapi.PushSecretDeletionPolicyDelete {
  82. return nil
  83. }
  84. // Format is typically "Kind/Name" (e.g., "SecretStore/store1", "ClusterSecretStore/clusterstore1")
  85. storeKeys := make([]string, 0, len(ps.Status.SyncedPushSecrets))
  86. for storeKey := range ps.Status.SyncedPushSecrets {
  87. storeKeys = append(storeKeys, storeKey)
  88. }
  89. return storeKeys
  90. }); err != nil {
  91. return err
  92. }
  93. // Index PushSecrets by deletionPolicy for quick filtering
  94. if err := mgr.GetFieldIndexer().IndexField(ctx, &esapi.PushSecret{}, "spec.deletionPolicy", func(obj client.Object) []string {
  95. ps := obj.(*esapi.PushSecret)
  96. return []string{string(ps.Spec.DeletionPolicy)}
  97. }); err != nil {
  98. return err
  99. }
  100. return ctrl.NewControllerManagedBy(mgr).
  101. WithOptions(opts).
  102. For(&esapi.PushSecret{}).
  103. Complete(r)
  104. }
  105. // Reconcile is part of the main kubernetes reconciliation loop which aims to
  106. // move the current state of the cluster closer to the desired state.
  107. // For more details, check Reconcile and its Result here:
  108. // - https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/reconcile
  109. func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
  110. log := r.Log.WithValues("pushsecret", req.NamespacedName)
  111. resourceLabels := ctrlmetrics.RefineNonConditionMetricLabels(map[string]string{"name": req.Name, "namespace": req.Namespace})
  112. start := time.Now()
  113. pushSecretReconcileDuration := psmetrics.GetGaugeVec(psmetrics.PushSecretReconcileDurationKey)
  114. defer func() { pushSecretReconcileDuration.With(resourceLabels).Set(float64(time.Since(start))) }()
  115. var ps esapi.PushSecret
  116. mgr := secretstore.NewManager(r.Client, r.ControllerClass, false)
  117. defer func() {
  118. _ = mgr.Close(ctx)
  119. }()
  120. if err := r.Get(ctx, req.NamespacedName, &ps); err != nil {
  121. if apierrors.IsNotFound(err) {
  122. return ctrl.Result{}, nil
  123. }
  124. msg := "unable to get PushSecret"
  125. r.recorder.Event(&ps, v1.EventTypeWarning, esapi.ReasonErrored, msg)
  126. log.Error(err, msg)
  127. return ctrl.Result{}, fmt.Errorf("get resource: %w", err)
  128. }
  129. refreshInt := r.RequeueInterval
  130. if ps.Spec.RefreshInterval != nil {
  131. refreshInt = ps.Spec.RefreshInterval.Duration
  132. }
  133. p := client.MergeFrom(ps.DeepCopy())
  134. defer func() {
  135. err := r.Client.Status().Patch(ctx, &ps, p)
  136. if err != nil && !apierrors.IsNotFound(err) {
  137. log.Error(err, errPatchStatus)
  138. }
  139. }()
  140. switch ps.Spec.DeletionPolicy {
  141. case esapi.PushSecretDeletionPolicyDelete:
  142. // finalizer logic. Only added if we should delete the secrets
  143. if ps.ObjectMeta.DeletionTimestamp.IsZero() {
  144. if added := controllerutil.AddFinalizer(&ps, pushSecretFinalizer); added {
  145. if err := r.Client.Update(ctx, &ps, &client.UpdateOptions{}); err != nil {
  146. return ctrl.Result{}, fmt.Errorf(errCloudNotUpdateFinalizer, err)
  147. }
  148. return ctrl.Result{Requeue: true}, nil
  149. }
  150. } else if controllerutil.ContainsFinalizer(&ps, pushSecretFinalizer) {
  151. // trigger a cleanup with no Synced Map
  152. badState, err := r.DeleteSecretFromProviders(ctx, &ps, esapi.SyncedPushSecretsMap{}, mgr)
  153. if err != nil {
  154. msg := fmt.Sprintf("Failed to Delete Secrets from Provider: %v", err)
  155. r.markAsFailed(msg, &ps, badState)
  156. return ctrl.Result{}, err
  157. }
  158. controllerutil.RemoveFinalizer(&ps, pushSecretFinalizer)
  159. if err := r.Client.Update(ctx, &ps, &client.UpdateOptions{}); err != nil {
  160. return ctrl.Result{}, fmt.Errorf("could not update finalizers: %w", err)
  161. }
  162. return ctrl.Result{}, nil
  163. }
  164. case esapi.PushSecretDeletionPolicyNone:
  165. if controllerutil.ContainsFinalizer(&ps, pushSecretFinalizer) {
  166. controllerutil.RemoveFinalizer(&ps, pushSecretFinalizer)
  167. if err := r.Client.Update(ctx, &ps, &client.UpdateOptions{}); err != nil {
  168. return ctrl.Result{}, fmt.Errorf(errCloudNotUpdateFinalizer, err)
  169. }
  170. }
  171. default:
  172. }
  173. timeSinceLastRefresh := 0 * time.Second
  174. if !ps.Status.RefreshTime.IsZero() {
  175. timeSinceLastRefresh = time.Since(ps.Status.RefreshTime.Time)
  176. }
  177. if !shouldRefresh(ps) {
  178. refreshInt = (ps.Spec.RefreshInterval.Duration - timeSinceLastRefresh) + 5*time.Second
  179. log.V(1).Info("skipping refresh", "rv", ctrlutil.GetResourceVersion(ps.ObjectMeta), "nr", refreshInt.Seconds())
  180. return ctrl.Result{RequeueAfter: refreshInt}, nil
  181. }
  182. secrets, err := r.resolveSecrets(ctx, &ps)
  183. if err != nil {
  184. r.markAsFailed(errFailedGetSecret, &ps, nil)
  185. return ctrl.Result{}, err
  186. }
  187. secretStores, err := r.GetSecretStores(ctx, ps)
  188. if err != nil {
  189. r.markAsFailed(err.Error(), &ps, nil)
  190. return ctrl.Result{}, err
  191. }
  192. // Filter out SecretStores that are being deleted to avoid finalizer conflicts
  193. activeSecretStores := make(map[esapi.PushSecretStoreRef]esv1.GenericStore, len(secretStores))
  194. for ref, store := range secretStores {
  195. // Skip stores that are being deleted
  196. if !store.GetDeletionTimestamp().IsZero() {
  197. log.Info("skipping SecretStore that is being deleted", "storeName", store.GetName(), "storeKind", store.GetKind())
  198. continue
  199. }
  200. activeSecretStores[ref] = store
  201. }
  202. secretStores, err = removeUnmanagedStores(ctx, req.Namespace, r, activeSecretStores)
  203. if err != nil {
  204. r.markAsFailed(err.Error(), &ps, nil)
  205. return ctrl.Result{}, err
  206. }
  207. // if no stores are managed by this controller
  208. if len(secretStores) == 0 {
  209. return ctrl.Result{}, nil
  210. }
  211. allSyncedSecrets := make(esapi.SyncedPushSecretsMap)
  212. for _, secret := range secrets {
  213. if err := r.applyTemplate(ctx, &ps, &secret); err != nil {
  214. return ctrl.Result{}, err
  215. }
  216. syncedSecrets, err := r.PushSecretToProviders(ctx, secretStores, ps, &secret, mgr)
  217. if err != nil {
  218. if errors.Is(err, locks.ErrConflict) {
  219. log.Info("retry to acquire lock to update the secret later", "error", err)
  220. return ctrl.Result{Requeue: true}, nil
  221. }
  222. totalSecrets := mergeSecretState(syncedSecrets, ps.Status.SyncedPushSecrets)
  223. msg := fmt.Sprintf(errFailedSetSecret, err)
  224. r.markAsFailed(msg, &ps, totalSecrets)
  225. return ctrl.Result{}, err
  226. }
  227. switch ps.Spec.DeletionPolicy {
  228. case esapi.PushSecretDeletionPolicyDelete:
  229. badSyncState, err := r.DeleteSecretFromProviders(ctx, &ps, syncedSecrets, mgr)
  230. if err != nil {
  231. msg := fmt.Sprintf("Failed to Delete Secrets from Provider: %v", err)
  232. r.markAsFailed(msg, &ps, badSyncState)
  233. return ctrl.Result{}, err
  234. }
  235. case esapi.PushSecretDeletionPolicyNone:
  236. default:
  237. }
  238. allSyncedSecrets = mergeSecretState(allSyncedSecrets, syncedSecrets)
  239. }
  240. r.markAsDone(&ps, allSyncedSecrets, start)
  241. return ctrl.Result{RequeueAfter: refreshInt}, nil
  242. }
  243. func shouldRefresh(ps esapi.PushSecret) bool {
  244. if ps.Status.SyncedResourceVersion != ctrlutil.GetResourceVersion(ps.ObjectMeta) {
  245. return true
  246. }
  247. if ps.Spec.RefreshInterval.Duration == 0 && ps.Status.SyncedResourceVersion != "" {
  248. return false
  249. }
  250. if ps.Status.RefreshTime.IsZero() {
  251. return true
  252. }
  253. return ps.Status.RefreshTime.Add(ps.Spec.RefreshInterval.Duration).Before(time.Now())
  254. }
  255. func (r *Reconciler) markAsFailed(msg string, ps *esapi.PushSecret, syncState esapi.SyncedPushSecretsMap) {
  256. cond := NewPushSecretCondition(esapi.PushSecretReady, v1.ConditionFalse, esapi.ReasonErrored, msg)
  257. SetPushSecretCondition(ps, *cond)
  258. if syncState != nil {
  259. r.setSecrets(ps, syncState)
  260. }
  261. r.recorder.Event(ps, v1.EventTypeWarning, esapi.ReasonErrored, msg)
  262. }
  263. func (r *Reconciler) markAsDone(ps *esapi.PushSecret, secrets esapi.SyncedPushSecretsMap, start time.Time) {
  264. msg := "PushSecret synced successfully"
  265. if ps.Spec.UpdatePolicy == esapi.PushSecretUpdatePolicyIfNotExists {
  266. msg += ". Existing secrets in providers unchanged."
  267. }
  268. cond := NewPushSecretCondition(esapi.PushSecretReady, v1.ConditionTrue, esapi.ReasonSynced, msg)
  269. SetPushSecretCondition(ps, *cond)
  270. r.setSecrets(ps, secrets)
  271. ps.Status.RefreshTime = metav1.NewTime(start)
  272. ps.Status.SyncedResourceVersion = ctrlutil.GetResourceVersion(ps.ObjectMeta)
  273. r.recorder.Event(ps, v1.EventTypeNormal, esapi.ReasonSynced, msg)
  274. }
  275. func (r *Reconciler) setSecrets(ps *esapi.PushSecret, status esapi.SyncedPushSecretsMap) {
  276. ps.Status.SyncedPushSecrets = status
  277. }
  278. func mergeSecretState(newMap, old esapi.SyncedPushSecretsMap) esapi.SyncedPushSecretsMap {
  279. if newMap == nil {
  280. return old
  281. }
  282. out := newMap.DeepCopy()
  283. for k, v := range old {
  284. _, ok := out[k]
  285. if !ok {
  286. out[k] = make(map[string]esapi.PushSecretData)
  287. }
  288. maps.Insert(out[k], maps.All(v))
  289. }
  290. return out
  291. }
  292. // DeleteSecretFromProviders removes secrets from providers that are no longer needed.
  293. // It compares the existing synced secrets in the PushSecret status with the new desired state,
  294. // and deletes any secrets that are no longer present in the new state.
  295. func (r *Reconciler) DeleteSecretFromProviders(ctx context.Context, ps *esapi.PushSecret, newMap esapi.SyncedPushSecretsMap, mgr *secretstore.Manager) (esapi.SyncedPushSecretsMap, error) {
  296. out := mergeSecretState(newMap, ps.Status.SyncedPushSecrets)
  297. for storeName, oldData := range ps.Status.SyncedPushSecrets {
  298. storeRef := esv1.SecretStoreRef{
  299. Name: strings.Split(storeName, "/")[1],
  300. Kind: strings.Split(storeName, "/")[0],
  301. }
  302. client, err := mgr.Get(ctx, storeRef, ps.Namespace, nil)
  303. if err != nil {
  304. return out, fmt.Errorf("could not get secrets client for store %v: %w", storeName, err)
  305. }
  306. newData, ok := newMap[storeName]
  307. if !ok {
  308. err = r.DeleteAllSecretsFromStore(ctx, client, oldData)
  309. if err != nil {
  310. return out, err
  311. }
  312. delete(out, storeName)
  313. continue
  314. }
  315. for oldEntry, oldRef := range oldData {
  316. _, ok := newData[oldEntry]
  317. if !ok {
  318. err = r.DeleteSecretFromStore(ctx, client, oldRef)
  319. if err != nil {
  320. return out, err
  321. }
  322. delete(out[storeName], oldRef.Match.RemoteRef.RemoteKey)
  323. }
  324. }
  325. }
  326. return out, nil
  327. }
  328. // DeleteAllSecretsFromStore removes all secrets from a given secret store.
  329. func (r *Reconciler) DeleteAllSecretsFromStore(ctx context.Context, client esv1.SecretsClient, data map[string]esapi.PushSecretData) error {
  330. for _, v := range data {
  331. err := r.DeleteSecretFromStore(ctx, client, v)
  332. if err != nil {
  333. return err
  334. }
  335. }
  336. return nil
  337. }
  338. // DeleteSecretFromStore removes a specific secret from a given secret store.
  339. func (r *Reconciler) DeleteSecretFromStore(ctx context.Context, client esv1.SecretsClient, data esapi.PushSecretData) error {
  340. return client.DeleteSecret(ctx, data.Match.RemoteRef)
  341. }
  342. // PushSecretToProviders pushes the secret data to the specified secret stores.
  343. // It iterates over each store and handles the push operation according to the
  344. // defined update policies and conversion strategies.
  345. func (r *Reconciler) PushSecretToProviders(ctx context.Context, stores map[esapi.PushSecretStoreRef]esv1.GenericStore, ps esapi.PushSecret, secret *v1.Secret, mgr *secretstore.Manager) (esapi.SyncedPushSecretsMap, error) {
  346. out := make(esapi.SyncedPushSecretsMap)
  347. for ref, store := range stores {
  348. out, err := r.handlePushSecretDataForStore(ctx, ps, secret, out, mgr, store.GetName(), ref.Kind)
  349. if err != nil {
  350. return out, err
  351. }
  352. }
  353. return out, nil
  354. }
  355. func (r *Reconciler) handlePushSecretDataForStore(ctx context.Context, ps esapi.PushSecret, secret *v1.Secret, out esapi.SyncedPushSecretsMap, mgr *secretstore.Manager, storeName, refKind string) (esapi.SyncedPushSecretsMap, error) {
  356. storeKey := fmt.Sprintf("%v/%v", refKind, storeName)
  357. out[storeKey] = make(map[string]esapi.PushSecretData)
  358. storeRef := esv1.SecretStoreRef{
  359. Name: storeName,
  360. Kind: refKind,
  361. }
  362. originalSecretData := secret.Data
  363. secretClient, err := mgr.Get(ctx, storeRef, ps.GetNamespace(), nil)
  364. if err != nil {
  365. return out, fmt.Errorf("could not get secrets client for store %v: %w", storeName, err)
  366. }
  367. for _, data := range ps.Spec.Data {
  368. secretData, err := esutils.ReverseKeys(data.ConversionStrategy, originalSecretData)
  369. if err != nil {
  370. return nil, fmt.Errorf(errConvert, err)
  371. }
  372. secret.Data = secretData
  373. key := data.GetSecretKey()
  374. if !secretKeyExists(key, secret) {
  375. return out, fmt.Errorf("secret key %v does not exist", key)
  376. }
  377. switch ps.Spec.UpdatePolicy {
  378. case esapi.PushSecretUpdatePolicyIfNotExists:
  379. exists, err := secretClient.SecretExists(ctx, data.Match.RemoteRef)
  380. if err != nil {
  381. return out, fmt.Errorf("could not verify if secret exists in store: %w", err)
  382. } else if exists {
  383. out[storeKey][statusRef(data)] = data
  384. continue
  385. }
  386. case esapi.PushSecretUpdatePolicyReplace:
  387. default:
  388. }
  389. if err := secretClient.PushSecret(ctx, secret, data); err != nil {
  390. return out, fmt.Errorf(errSetSecretFailed, key, storeName, err)
  391. }
  392. out[storeKey][statusRef(data)] = data
  393. }
  394. return out, nil
  395. }
  396. func secretKeyExists(key string, secret *v1.Secret) bool {
  397. _, ok := secret.Data[key]
  398. return key == "" || ok
  399. }
  400. const defaultGeneratorStateKey = "__pushsecret"
  401. func (r *Reconciler) resolveSecrets(ctx context.Context, ps *esapi.PushSecret) ([]v1.Secret, error) {
  402. var err error
  403. generatorState := statemanager.New(ctx, r.Client, r.Scheme, ps.Namespace, ps)
  404. defer func() {
  405. if err != nil {
  406. if err := generatorState.Rollback(); err != nil {
  407. r.Log.Error(err, "error rolling back generator state")
  408. }
  409. return
  410. }
  411. if err := generatorState.Commit(); err != nil {
  412. r.Log.Error(err, "error committing generator state")
  413. }
  414. }()
  415. switch {
  416. case ps.Spec.Selector.Secret != nil && ps.Spec.Selector.Secret.Name != "":
  417. secretName := types.NamespacedName{Name: ps.Spec.Selector.Secret.Name, Namespace: ps.Namespace}
  418. secret := &v1.Secret{}
  419. if err := r.Client.Get(ctx, secretName, secret); err != nil {
  420. return nil, err
  421. }
  422. generatorState.EnqueueFlagLatestStateForGC(defaultGeneratorStateKey)
  423. return []v1.Secret{*secret}, nil
  424. case ps.Spec.Selector.GeneratorRef != nil:
  425. secret, err := r.resolveSecretFromGenerator(ctx, ps.Namespace, ps.Spec.Selector.GeneratorRef, generatorState)
  426. if err != nil {
  427. return nil, fmt.Errorf("could not resolve secret from generator ref %v: %w", ps.Spec.Selector.GeneratorRef, err)
  428. }
  429. return []v1.Secret{*secret}, nil
  430. case ps.Spec.Selector.Secret != nil && ps.Spec.Selector.Secret.Selector != nil:
  431. labelSelector, err := metav1.LabelSelectorAsSelector(ps.Spec.Selector.Secret.Selector)
  432. if err != nil {
  433. return nil, err
  434. }
  435. var secretList v1.SecretList
  436. err = r.List(ctx, &secretList, &client.ListOptions{LabelSelector: labelSelector, Namespace: ps.Namespace})
  437. if err != nil {
  438. return nil, err
  439. }
  440. return secretList.Items, err
  441. }
  442. return nil, errors.New("no secret selector provided")
  443. }
  444. func (r *Reconciler) resolveSecretFromGenerator(ctx context.Context, namespace string, generatorRef *esv1.GeneratorRef, generatorState *statemanager.Manager) (*v1.Secret, error) {
  445. gen, genResource, err := resolvers.GeneratorRef(ctx, r.Client, r.Scheme, namespace, generatorRef)
  446. if err != nil {
  447. return nil, fmt.Errorf("unable to resolve generator: %w", err)
  448. }
  449. var prevState *genv1alpha1.GeneratorState
  450. if generatorState != nil {
  451. prevState, err = generatorState.GetLatestState(defaultGeneratorStateKey)
  452. if err != nil {
  453. return nil, fmt.Errorf("unable to get latest state: %w", err)
  454. }
  455. }
  456. secretMap, newState, err := gen.Generate(ctx, genResource, r.Client, namespace)
  457. if err != nil {
  458. return nil, fmt.Errorf("unable to generate: %w", err)
  459. }
  460. if prevState != nil && generatorState != nil {
  461. generatorState.EnqueueMoveStateToGC(defaultGeneratorStateKey)
  462. }
  463. if generatorState != nil {
  464. generatorState.EnqueueSetLatest(ctx, defaultGeneratorStateKey, namespace, genResource, gen, newState)
  465. }
  466. return &v1.Secret{
  467. ObjectMeta: metav1.ObjectMeta{
  468. Name: "___generated-secret",
  469. Namespace: namespace,
  470. },
  471. Data: secretMap,
  472. }, err
  473. }
  474. // GetSecretStores retrieves the SecretStore and ClusterSecretStore resources
  475. // referenced in the PushSecret. It supports both direct references by name
  476. // and label selectors to find multiple stores.
  477. func (r *Reconciler) GetSecretStores(ctx context.Context, ps esapi.PushSecret) (map[esapi.PushSecretStoreRef]esv1.GenericStore, error) {
  478. stores := make(map[esapi.PushSecretStoreRef]esv1.GenericStore)
  479. for _, refStore := range ps.Spec.SecretStoreRefs {
  480. if refStore.LabelSelector != nil {
  481. labelSelector, err := metav1.LabelSelectorAsSelector(refStore.LabelSelector)
  482. if err != nil {
  483. return nil, fmt.Errorf("could not convert labels: %w", err)
  484. }
  485. if refStore.Kind == esv1.ClusterSecretStoreKind {
  486. clusterSecretStoreList := esv1.ClusterSecretStoreList{}
  487. err = r.List(ctx, &clusterSecretStoreList, &client.ListOptions{LabelSelector: labelSelector})
  488. if err != nil {
  489. return nil, fmt.Errorf("could not list cluster Secret Stores: %w", err)
  490. }
  491. for k, v := range clusterSecretStoreList.Items {
  492. key := esapi.PushSecretStoreRef{
  493. Name: v.Name,
  494. Kind: esv1.ClusterSecretStoreKind,
  495. }
  496. stores[key] = &clusterSecretStoreList.Items[k]
  497. }
  498. } else {
  499. secretStoreList := esv1.SecretStoreList{}
  500. err = r.List(ctx, &secretStoreList, &client.ListOptions{LabelSelector: labelSelector, Namespace: ps.Namespace})
  501. if err != nil {
  502. return nil, fmt.Errorf("could not list Secret Stores: %w", err)
  503. }
  504. for k, v := range secretStoreList.Items {
  505. key := esapi.PushSecretStoreRef{
  506. Name: v.Name,
  507. Kind: esv1.SecretStoreKind,
  508. }
  509. stores[key] = &secretStoreList.Items[k]
  510. }
  511. }
  512. } else {
  513. store, err := r.getSecretStoreFromName(ctx, refStore, ps.Namespace)
  514. if err != nil {
  515. return nil, err
  516. }
  517. stores[refStore] = store
  518. }
  519. }
  520. return stores, nil
  521. }
  522. func (r *Reconciler) getSecretStoreFromName(ctx context.Context, refStore esapi.PushSecretStoreRef, ns string) (esv1.GenericStore, error) {
  523. if refStore.Name == "" {
  524. return nil, errors.New("refStore Name must be provided")
  525. }
  526. ref := types.NamespacedName{
  527. Name: refStore.Name,
  528. }
  529. if refStore.Kind == esv1.ClusterSecretStoreKind {
  530. var store esv1.ClusterSecretStore
  531. err := r.Get(ctx, ref, &store)
  532. if err != nil {
  533. return nil, fmt.Errorf(errGetClusterSecretStore, ref.Name, err)
  534. }
  535. return &store, nil
  536. }
  537. ref.Namespace = ns
  538. var store esv1.SecretStore
  539. err := r.Get(ctx, ref, &store)
  540. if err != nil {
  541. return nil, fmt.Errorf(errGetSecretStore, ref.Name, err)
  542. }
  543. return &store, nil
  544. }
  545. // NewPushSecretCondition creates a new PushSecret condition.
  546. func NewPushSecretCondition(condType esapi.PushSecretConditionType, status v1.ConditionStatus, reason, message string) *esapi.PushSecretStatusCondition {
  547. return &esapi.PushSecretStatusCondition{
  548. Type: condType,
  549. Status: status,
  550. LastTransitionTime: metav1.Now(),
  551. Reason: reason,
  552. Message: message,
  553. }
  554. }
  555. // SetPushSecretCondition updates the PushSecret to include the provided condition.
  556. func SetPushSecretCondition(ps *esapi.PushSecret, condition esapi.PushSecretStatusCondition) {
  557. currentCond := GetPushSecretCondition(ps.Status.Conditions, condition.Type)
  558. if currentCond != nil && currentCond.Status == condition.Status &&
  559. currentCond.Reason == condition.Reason && currentCond.Message == condition.Message {
  560. psmetrics.UpdatePushSecretCondition(ps, &condition, 1.0)
  561. return
  562. }
  563. // Do not update lastTransitionTime if the status of the condition doesn't change.
  564. if currentCond != nil && currentCond.Status == condition.Status {
  565. condition.LastTransitionTime = currentCond.LastTransitionTime
  566. }
  567. ps.Status.Conditions = append(FilterOutCondition(ps.Status.Conditions, condition.Type), condition)
  568. if currentCond != nil {
  569. psmetrics.UpdatePushSecretCondition(ps, currentCond, 0.0)
  570. }
  571. psmetrics.UpdatePushSecretCondition(ps, &condition, 1.0)
  572. }
  573. // FilterOutCondition returns an empty set of conditions with the provided type.
  574. func FilterOutCondition(conditions []esapi.PushSecretStatusCondition, condType esapi.PushSecretConditionType) []esapi.PushSecretStatusCondition {
  575. newConditions := make([]esapi.PushSecretStatusCondition, 0, len(conditions))
  576. for _, c := range conditions {
  577. if c.Type == condType {
  578. continue
  579. }
  580. newConditions = append(newConditions, c)
  581. }
  582. return newConditions
  583. }
  584. // GetPushSecretCondition returns the condition with the provided type.
  585. func GetPushSecretCondition(conditions []esapi.PushSecretStatusCondition, condType esapi.PushSecretConditionType) *esapi.PushSecretStatusCondition {
  586. for i := range conditions {
  587. c := conditions[i]
  588. if c.Type == condType {
  589. return &c
  590. }
  591. }
  592. return nil
  593. }
  594. func statusRef(ref esv1.PushSecretData) string {
  595. if ref.GetProperty() != "" {
  596. return ref.GetRemoteKey() + "/" + ref.GetProperty()
  597. }
  598. return ref.GetRemoteKey()
  599. }
  600. // removeUnmanagedStores iterates over all SecretStore references and evaluates the controllerClass property.
  601. // Returns a map containing only managed stores.
  602. func removeUnmanagedStores(ctx context.Context, namespace string, r *Reconciler, ss map[esapi.PushSecretStoreRef]esv1.GenericStore) (map[esapi.PushSecretStoreRef]esv1.GenericStore, error) {
  603. for ref := range ss {
  604. var store esv1.GenericStore
  605. switch ref.Kind {
  606. case esv1.SecretStoreKind:
  607. store = &esv1.SecretStore{}
  608. case esv1.ClusterSecretStoreKind:
  609. store = &esv1.ClusterSecretStore{}
  610. namespace = ""
  611. }
  612. err := r.Client.Get(ctx, types.NamespacedName{
  613. Name: ref.Name,
  614. Namespace: namespace,
  615. }, store)
  616. if err != nil {
  617. return ss, err
  618. }
  619. class := store.GetSpec().Controller
  620. if class != "" && class != r.ControllerClass {
  621. delete(ss, ref)
  622. }
  623. }
  624. return ss, nil
  625. }