pushsecret_controller.go 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733
  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. ctrlutil "github.com/external-secrets/external-secrets/pkg/controllers/util"
  40. "github.com/external-secrets/external-secrets/runtime/clientmanager"
  41. "github.com/external-secrets/external-secrets/runtime/esutils"
  42. "github.com/external-secrets/external-secrets/runtime/esutils/resolvers"
  43. "github.com/external-secrets/external-secrets/runtime/statemanager"
  44. "github.com/external-secrets/external-secrets/runtime/util/locks"
  45. // Load registered generators.
  46. _ "github.com/external-secrets/external-secrets/pkg/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 := clientmanager.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. // Get secret stores early so they can be used for finalizer deletion
  141. secretStoresV2, err := r.GetSecretStoresV2(ctx, ps)
  142. if err != nil {
  143. r.markAsFailed(err.Error(), &ps, nil)
  144. return ctrl.Result{}, err
  145. }
  146. // Filter and prepare stores (this logic was moved from later)
  147. activeSecretStores := make(map[esapi.PushSecretStoreRef]interface{}, len(secretStoresV2))
  148. for ref, store := range secretStoresV2 {
  149. if v1Store, ok := store.(esv1.GenericStore); ok {
  150. if !v1Store.GetDeletionTimestamp().IsZero() {
  151. log.Info("skipping SecretStore that is being deleted", "storeName", v1Store.GetName(), "storeKind", v1Store.GetKind())
  152. continue
  153. }
  154. }
  155. activeSecretStores[ref] = store
  156. }
  157. activeSecretStoresV1 := make(map[esapi.PushSecretStoreRef]esv1.GenericStore)
  158. for ref, store := range activeSecretStores {
  159. if v1Store, ok := store.(esv1.GenericStore); ok {
  160. activeSecretStoresV1[ref] = v1Store
  161. }
  162. }
  163. filteredV1Stores, err := removeUnmanagedStores(ctx, req.Namespace, r, activeSecretStoresV1)
  164. if err != nil {
  165. r.markAsFailed(err.Error(), &ps, nil)
  166. return ctrl.Result{}, err
  167. }
  168. finalStores := make(map[esapi.PushSecretStoreRef]interface{})
  169. for ref, store := range filteredV1Stores {
  170. finalStores[ref] = store
  171. }
  172. for ref, store := range activeSecretStores {
  173. if _, ok := store.(esv1.GenericStore); !ok {
  174. finalStores[ref] = store
  175. }
  176. }
  177. switch ps.Spec.DeletionPolicy {
  178. case esapi.PushSecretDeletionPolicyDelete:
  179. // finalizer logic. Only added if we should delete the secrets
  180. if ps.ObjectMeta.DeletionTimestamp.IsZero() {
  181. if added := controllerutil.AddFinalizer(&ps, pushSecretFinalizer); added {
  182. if err := r.Client.Update(ctx, &ps, &client.UpdateOptions{}); err != nil {
  183. return ctrl.Result{}, fmt.Errorf(errCloudNotUpdateFinalizer, err)
  184. }
  185. return ctrl.Result{Requeue: true}, nil
  186. }
  187. } else if controllerutil.ContainsFinalizer(&ps, pushSecretFinalizer) {
  188. // trigger a cleanup with no Synced Map
  189. badState, err := r.DeleteSecretFromProvidersV2(ctx, &ps, esapi.SyncedPushSecretsMap{}, finalStores)
  190. if err != nil {
  191. msg := fmt.Sprintf("Failed to Delete Secrets from Provider: %v", err)
  192. r.markAsFailed(msg, &ps, badState)
  193. return ctrl.Result{}, err
  194. }
  195. controllerutil.RemoveFinalizer(&ps, pushSecretFinalizer)
  196. if err := r.Client.Update(ctx, &ps, &client.UpdateOptions{}); err != nil {
  197. return ctrl.Result{}, fmt.Errorf("could not update finalizers: %w", err)
  198. }
  199. return ctrl.Result{}, nil
  200. }
  201. case esapi.PushSecretDeletionPolicyNone:
  202. if controllerutil.ContainsFinalizer(&ps, pushSecretFinalizer) {
  203. controllerutil.RemoveFinalizer(&ps, pushSecretFinalizer)
  204. if err := r.Client.Update(ctx, &ps, &client.UpdateOptions{}); err != nil {
  205. return ctrl.Result{}, fmt.Errorf(errCloudNotUpdateFinalizer, err)
  206. }
  207. }
  208. default:
  209. }
  210. timeSinceLastRefresh := 0 * time.Second
  211. if !ps.Status.RefreshTime.IsZero() {
  212. timeSinceLastRefresh = time.Since(ps.Status.RefreshTime.Time)
  213. }
  214. if !shouldRefresh(ps) {
  215. refreshInt = (ps.Spec.RefreshInterval.Duration - timeSinceLastRefresh) + 5*time.Second
  216. log.V(1).Info("skipping refresh", "rv", ctrlutil.GetResourceVersion(ps.ObjectMeta), "nr", refreshInt.Seconds())
  217. return ctrl.Result{RequeueAfter: refreshInt}, nil
  218. }
  219. // if no stores are managed by this controller
  220. if len(finalStores) == 0 {
  221. return ctrl.Result{}, nil
  222. }
  223. secrets, err := r.resolveSecrets(ctx, &ps)
  224. if err != nil {
  225. r.markAsFailed(errFailedGetSecret, &ps, nil)
  226. return ctrl.Result{}, err
  227. }
  228. allSyncedSecrets := make(esapi.SyncedPushSecretsMap)
  229. for _, secret := range secrets {
  230. if err := r.applyTemplate(ctx, &ps, &secret); err != nil {
  231. return ctrl.Result{}, err
  232. }
  233. syncedSecrets, err := r.PushSecretToProvidersV2(ctx, finalStores, ps, &secret, mgr)
  234. if err != nil {
  235. if errors.Is(err, locks.ErrConflict) {
  236. log.Info("retry to acquire lock to update the secret later", "error", err)
  237. return ctrl.Result{Requeue: true}, nil
  238. }
  239. totalSecrets := mergeSecretState(syncedSecrets, ps.Status.SyncedPushSecrets)
  240. msg := fmt.Sprintf(errFailedSetSecret, err)
  241. r.markAsFailed(msg, &ps, totalSecrets)
  242. return ctrl.Result{}, err
  243. }
  244. switch ps.Spec.DeletionPolicy {
  245. case esapi.PushSecretDeletionPolicyDelete:
  246. badSyncState, err := r.DeleteSecretFromProvidersV2(ctx, &ps, syncedSecrets, finalStores)
  247. if err != nil {
  248. msg := fmt.Sprintf("Failed to Delete Secrets from Provider: %v", err)
  249. r.markAsFailed(msg, &ps, badSyncState)
  250. return ctrl.Result{}, err
  251. }
  252. case esapi.PushSecretDeletionPolicyNone:
  253. default:
  254. }
  255. allSyncedSecrets = mergeSecretState(allSyncedSecrets, syncedSecrets)
  256. }
  257. r.markAsDone(&ps, allSyncedSecrets, start)
  258. return ctrl.Result{RequeueAfter: refreshInt}, nil
  259. }
  260. func shouldRefresh(ps esapi.PushSecret) bool {
  261. if ps.Status.SyncedResourceVersion != ctrlutil.GetResourceVersion(ps.ObjectMeta) {
  262. return true
  263. }
  264. if ps.Spec.RefreshInterval.Duration == 0 && ps.Status.SyncedResourceVersion != "" {
  265. return false
  266. }
  267. if ps.Status.RefreshTime.IsZero() {
  268. return true
  269. }
  270. return ps.Status.RefreshTime.Add(ps.Spec.RefreshInterval.Duration).Before(time.Now())
  271. }
  272. func (r *Reconciler) markAsFailed(msg string, ps *esapi.PushSecret, syncState esapi.SyncedPushSecretsMap) {
  273. cond := NewPushSecretCondition(esapi.PushSecretReady, v1.ConditionFalse, esapi.ReasonErrored, msg)
  274. SetPushSecretCondition(ps, *cond)
  275. if syncState != nil {
  276. r.setSecrets(ps, syncState)
  277. }
  278. r.recorder.Event(ps, v1.EventTypeWarning, esapi.ReasonErrored, msg)
  279. }
  280. func (r *Reconciler) markAsDone(ps *esapi.PushSecret, secrets esapi.SyncedPushSecretsMap, start time.Time) {
  281. msg := "PushSecret synced successfully"
  282. if ps.Spec.UpdatePolicy == esapi.PushSecretUpdatePolicyIfNotExists {
  283. msg += ". Existing secrets in providers unchanged."
  284. }
  285. cond := NewPushSecretCondition(esapi.PushSecretReady, v1.ConditionTrue, esapi.ReasonSynced, msg)
  286. SetPushSecretCondition(ps, *cond)
  287. r.setSecrets(ps, secrets)
  288. ps.Status.RefreshTime = metav1.NewTime(start)
  289. ps.Status.SyncedResourceVersion = ctrlutil.GetResourceVersion(ps.ObjectMeta)
  290. r.recorder.Event(ps, v1.EventTypeNormal, esapi.ReasonSynced, msg)
  291. }
  292. func (r *Reconciler) setSecrets(ps *esapi.PushSecret, status esapi.SyncedPushSecretsMap) {
  293. ps.Status.SyncedPushSecrets = status
  294. }
  295. func mergeSecretState(newMap, old esapi.SyncedPushSecretsMap) esapi.SyncedPushSecretsMap {
  296. if newMap == nil {
  297. return old
  298. }
  299. out := newMap.DeepCopy()
  300. for k, v := range old {
  301. _, ok := out[k]
  302. if !ok {
  303. out[k] = make(map[string]esapi.PushSecretData)
  304. }
  305. maps.Insert(out[k], maps.All(v))
  306. }
  307. return out
  308. }
  309. // DeleteSecretFromProviders removes secrets from providers that are no longer needed.
  310. // It compares the existing synced secrets in the PushSecret status with the new desired state,
  311. // and deletes any secrets that are no longer present in the new state.
  312. func (r *Reconciler) DeleteSecretFromProviders(ctx context.Context, ps *esapi.PushSecret, newMap esapi.SyncedPushSecretsMap, mgr *clientmanager.Manager) (esapi.SyncedPushSecretsMap, error) {
  313. out := mergeSecretState(newMap, ps.Status.SyncedPushSecrets)
  314. for storeName, oldData := range ps.Status.SyncedPushSecrets {
  315. storeRef := esv1.SecretStoreRef{
  316. Name: strings.Split(storeName, "/")[1],
  317. Kind: strings.Split(storeName, "/")[0],
  318. }
  319. client, err := mgr.Get(ctx, storeRef, ps.Namespace, nil)
  320. if err != nil {
  321. return out, fmt.Errorf("could not get secrets client for store %v: %w", storeName, err)
  322. }
  323. newData, ok := newMap[storeName]
  324. if !ok {
  325. err = r.DeleteAllSecretsFromStore(ctx, client, oldData)
  326. if err != nil {
  327. return out, err
  328. }
  329. delete(out, storeName)
  330. continue
  331. }
  332. for oldEntry, oldRef := range oldData {
  333. _, ok := newData[oldEntry]
  334. if !ok {
  335. err = r.DeleteSecretFromStore(ctx, client, oldRef)
  336. if err != nil {
  337. return out, err
  338. }
  339. delete(out[storeName], oldEntry)
  340. }
  341. }
  342. }
  343. return out, nil
  344. }
  345. // DeleteAllSecretsFromStore removes all secrets from a given secret store.
  346. func (r *Reconciler) DeleteAllSecretsFromStore(ctx context.Context, client esv1.SecretsClient, data map[string]esapi.PushSecretData) error {
  347. for _, v := range data {
  348. err := r.DeleteSecretFromStore(ctx, client, v)
  349. if err != nil {
  350. return err
  351. }
  352. }
  353. return nil
  354. }
  355. // DeleteSecretFromStore removes a specific secret from a given secret store.
  356. func (r *Reconciler) DeleteSecretFromStore(ctx context.Context, client esv1.SecretsClient, data esapi.PushSecretData) error {
  357. return client.DeleteSecret(ctx, data.Match.RemoteRef)
  358. }
  359. // PushSecretToProviders pushes the secret data to the specified secret stores.
  360. // It iterates over each store and handles the push operation according to the
  361. // defined update policies and conversion strategies.
  362. func (r *Reconciler) PushSecretToProviders(
  363. ctx context.Context,
  364. stores map[esapi.PushSecretStoreRef]esv1.GenericStore,
  365. ps esapi.PushSecret,
  366. secret *v1.Secret,
  367. mgr *clientmanager.Manager,
  368. ) (esapi.SyncedPushSecretsMap, error) {
  369. out := make(esapi.SyncedPushSecretsMap)
  370. for ref, store := range stores {
  371. out, err := r.handlePushSecretDataForStore(ctx, ps, secret, out, mgr, store.GetName(), ref.Kind)
  372. if err != nil {
  373. return out, err
  374. }
  375. }
  376. return out, nil
  377. }
  378. func (r *Reconciler) handlePushSecretDataForStore(
  379. ctx context.Context,
  380. ps esapi.PushSecret,
  381. secret *v1.Secret,
  382. out esapi.SyncedPushSecretsMap,
  383. mgr *clientmanager.Manager,
  384. storeName, refKind string,
  385. ) (esapi.SyncedPushSecretsMap, error) {
  386. storeKey := fmt.Sprintf("%v/%v", refKind, storeName)
  387. out[storeKey] = make(map[string]esapi.PushSecretData)
  388. storeRef := esv1.SecretStoreRef{
  389. Name: storeName,
  390. Kind: refKind,
  391. }
  392. originalSecretData := secret.Data
  393. secretClient, err := mgr.Get(ctx, storeRef, ps.GetNamespace(), nil)
  394. if err != nil {
  395. return out, fmt.Errorf("could not get secrets client for store %v: %w", storeName, err)
  396. }
  397. for _, data := range ps.Spec.Data {
  398. secretData, err := esutils.ReverseKeys(data.ConversionStrategy, originalSecretData)
  399. if err != nil {
  400. return nil, fmt.Errorf(errConvert, err)
  401. }
  402. secret.Data = secretData
  403. key := data.GetSecretKey()
  404. if !secretKeyExists(key, secret) {
  405. return out, fmt.Errorf("secret key %v does not exist", key)
  406. }
  407. switch ps.Spec.UpdatePolicy {
  408. case esapi.PushSecretUpdatePolicyIfNotExists:
  409. exists, err := secretClient.SecretExists(ctx, data.Match.RemoteRef)
  410. if err != nil {
  411. return out, fmt.Errorf("could not verify if secret exists in store: %w", err)
  412. } else if exists {
  413. out[storeKey][statusRef(data)] = data
  414. continue
  415. }
  416. case esapi.PushSecretUpdatePolicyReplace:
  417. default:
  418. }
  419. if err := secretClient.PushSecret(ctx, secret, data); err != nil {
  420. return out, fmt.Errorf(errSetSecretFailed, key, storeName, err)
  421. }
  422. out[storeKey][statusRef(data)] = data
  423. }
  424. return out, nil
  425. }
  426. func secretKeyExists(key string, secret *v1.Secret) bool {
  427. _, ok := secret.Data[key]
  428. return key == "" || ok
  429. }
  430. const defaultGeneratorStateKey = "__pushsecret"
  431. func (r *Reconciler) resolveSecrets(ctx context.Context, ps *esapi.PushSecret) ([]v1.Secret, error) {
  432. var err error
  433. generatorState := statemanager.New(ctx, r.Client, r.Scheme, ps.Namespace, ps)
  434. defer func() {
  435. if err != nil {
  436. if err := generatorState.Rollback(); err != nil {
  437. r.Log.Error(err, "error rolling back generator state")
  438. }
  439. return
  440. }
  441. if err := generatorState.Commit(); err != nil {
  442. r.Log.Error(err, "error committing generator state")
  443. }
  444. }()
  445. switch {
  446. case ps.Spec.Selector.Secret != nil && ps.Spec.Selector.Secret.Name != "":
  447. secretName := types.NamespacedName{Name: ps.Spec.Selector.Secret.Name, Namespace: ps.Namespace}
  448. secret := &v1.Secret{}
  449. if err := r.Client.Get(ctx, secretName, secret); err != nil {
  450. return nil, err
  451. }
  452. generatorState.EnqueueFlagLatestStateForGC(defaultGeneratorStateKey)
  453. return []v1.Secret{*secret}, nil
  454. case ps.Spec.Selector.GeneratorRef != nil:
  455. secret, err := r.resolveSecretFromGenerator(ctx, ps.Namespace, ps.Spec.Selector.GeneratorRef, generatorState)
  456. if err != nil {
  457. return nil, fmt.Errorf("could not resolve secret from generator ref %v: %w", ps.Spec.Selector.GeneratorRef, err)
  458. }
  459. return []v1.Secret{*secret}, nil
  460. case ps.Spec.Selector.Secret != nil && ps.Spec.Selector.Secret.Selector != nil:
  461. labelSelector, err := metav1.LabelSelectorAsSelector(ps.Spec.Selector.Secret.Selector)
  462. if err != nil {
  463. return nil, err
  464. }
  465. var secretList v1.SecretList
  466. err = r.List(ctx, &secretList, &client.ListOptions{LabelSelector: labelSelector, Namespace: ps.Namespace})
  467. if err != nil {
  468. return nil, err
  469. }
  470. return secretList.Items, err
  471. }
  472. return nil, errors.New("no secret selector provided")
  473. }
  474. func (r *Reconciler) resolveSecretFromGenerator(ctx context.Context, namespace string, generatorRef *esv1.GeneratorRef, generatorState *statemanager.Manager) (*v1.Secret, error) {
  475. gen, genResource, err := resolvers.GeneratorRef(ctx, r.Client, r.Scheme, namespace, generatorRef)
  476. if err != nil {
  477. return nil, fmt.Errorf("unable to resolve generator: %w", err)
  478. }
  479. var prevState *genv1alpha1.GeneratorState
  480. if generatorState != nil {
  481. prevState, err = generatorState.GetLatestState(defaultGeneratorStateKey)
  482. if err != nil {
  483. return nil, fmt.Errorf("unable to get latest state: %w", err)
  484. }
  485. }
  486. secretMap, newState, err := gen.Generate(ctx, genResource, r.Client, namespace)
  487. if err != nil {
  488. return nil, fmt.Errorf("unable to generate: %w", err)
  489. }
  490. if prevState != nil && generatorState != nil {
  491. generatorState.EnqueueMoveStateToGC(defaultGeneratorStateKey)
  492. }
  493. if generatorState != nil {
  494. generatorState.EnqueueSetLatest(ctx, defaultGeneratorStateKey, namespace, genResource, gen, newState)
  495. }
  496. return &v1.Secret{
  497. ObjectMeta: metav1.ObjectMeta{
  498. Name: "___generated-secret",
  499. Namespace: namespace,
  500. },
  501. Data: secretMap,
  502. }, err
  503. }
  504. // GetSecretStores retrieves the SecretStore and ClusterSecretStore resources
  505. // referenced in the PushSecret. It supports both direct references by name
  506. // and label selectors to find multiple stores.
  507. func (r *Reconciler) GetSecretStores(ctx context.Context, ps esapi.PushSecret) (map[esapi.PushSecretStoreRef]esv1.GenericStore, error) {
  508. stores := make(map[esapi.PushSecretStoreRef]esv1.GenericStore)
  509. for _, refStore := range ps.Spec.SecretStoreRefs {
  510. if refStore.LabelSelector != nil {
  511. labelSelector, err := metav1.LabelSelectorAsSelector(refStore.LabelSelector)
  512. if err != nil {
  513. return nil, fmt.Errorf("could not convert labels: %w", err)
  514. }
  515. if refStore.Kind == esv1.ClusterSecretStoreKind {
  516. clusterSecretStoreList := esv1.ClusterSecretStoreList{}
  517. err = r.List(ctx, &clusterSecretStoreList, &client.ListOptions{LabelSelector: labelSelector})
  518. if err != nil {
  519. return nil, fmt.Errorf("could not list cluster Secret Stores: %w", err)
  520. }
  521. for k, v := range clusterSecretStoreList.Items {
  522. key := esapi.PushSecretStoreRef{
  523. Name: v.Name,
  524. Kind: esv1.ClusterSecretStoreKind,
  525. }
  526. stores[key] = &clusterSecretStoreList.Items[k]
  527. }
  528. } else {
  529. secretStoreList := esv1.SecretStoreList{}
  530. err = r.List(ctx, &secretStoreList, &client.ListOptions{LabelSelector: labelSelector, Namespace: ps.Namespace})
  531. if err != nil {
  532. return nil, fmt.Errorf("could not list Secret Stores: %w", err)
  533. }
  534. for k, v := range secretStoreList.Items {
  535. key := esapi.PushSecretStoreRef{
  536. Name: v.Name,
  537. Kind: esv1.SecretStoreKind,
  538. }
  539. stores[key] = &secretStoreList.Items[k]
  540. }
  541. }
  542. } else {
  543. store, err := r.getSecretStoreFromName(ctx, refStore, ps.Namespace)
  544. if err != nil {
  545. return nil, err
  546. }
  547. stores[refStore] = store
  548. }
  549. }
  550. return stores, nil
  551. }
  552. func (r *Reconciler) getSecretStoreFromName(ctx context.Context, refStore esapi.PushSecretStoreRef, ns string) (esv1.GenericStore, error) {
  553. if refStore.Name == "" {
  554. return nil, errors.New("refStore Name must be provided")
  555. }
  556. ref := types.NamespacedName{
  557. Name: refStore.Name,
  558. }
  559. if refStore.Kind == esv1.ClusterSecretStoreKind {
  560. var store esv1.ClusterSecretStore
  561. err := r.Get(ctx, ref, &store)
  562. if err != nil {
  563. return nil, fmt.Errorf(errGetClusterSecretStore, ref.Name, err)
  564. }
  565. return &store, nil
  566. }
  567. ref.Namespace = ns
  568. var store esv1.SecretStore
  569. err := r.Get(ctx, ref, &store)
  570. if err != nil {
  571. return nil, fmt.Errorf(errGetSecretStore, ref.Name, err)
  572. }
  573. return &store, nil
  574. }
  575. // NewPushSecretCondition creates a new PushSecret condition.
  576. func NewPushSecretCondition(condType esapi.PushSecretConditionType, status v1.ConditionStatus, reason, message string) *esapi.PushSecretStatusCondition {
  577. return &esapi.PushSecretStatusCondition{
  578. Type: condType,
  579. Status: status,
  580. LastTransitionTime: metav1.Now(),
  581. Reason: reason,
  582. Message: message,
  583. }
  584. }
  585. // SetPushSecretCondition updates the PushSecret to include the provided condition.
  586. func SetPushSecretCondition(ps *esapi.PushSecret, condition esapi.PushSecretStatusCondition) {
  587. currentCond := GetPushSecretCondition(ps.Status.Conditions, condition.Type)
  588. if currentCond != nil && currentCond.Status == condition.Status &&
  589. currentCond.Reason == condition.Reason && currentCond.Message == condition.Message {
  590. psmetrics.UpdatePushSecretCondition(ps, &condition, 1.0)
  591. return
  592. }
  593. // Do not update lastTransitionTime if the status of the condition doesn't change.
  594. if currentCond != nil && currentCond.Status == condition.Status {
  595. condition.LastTransitionTime = currentCond.LastTransitionTime
  596. }
  597. ps.Status.Conditions = append(FilterOutCondition(ps.Status.Conditions, condition.Type), condition)
  598. if currentCond != nil {
  599. psmetrics.UpdatePushSecretCondition(ps, currentCond, 0.0)
  600. }
  601. psmetrics.UpdatePushSecretCondition(ps, &condition, 1.0)
  602. }
  603. // FilterOutCondition returns an empty set of conditions with the provided type.
  604. func FilterOutCondition(conditions []esapi.PushSecretStatusCondition, condType esapi.PushSecretConditionType) []esapi.PushSecretStatusCondition {
  605. newConditions := make([]esapi.PushSecretStatusCondition, 0, len(conditions))
  606. for _, c := range conditions {
  607. if c.Type == condType {
  608. continue
  609. }
  610. newConditions = append(newConditions, c)
  611. }
  612. return newConditions
  613. }
  614. // GetPushSecretCondition returns the condition with the provided type.
  615. func GetPushSecretCondition(conditions []esapi.PushSecretStatusCondition, condType esapi.PushSecretConditionType) *esapi.PushSecretStatusCondition {
  616. for i := range conditions {
  617. c := conditions[i]
  618. if c.Type == condType {
  619. return &c
  620. }
  621. }
  622. return nil
  623. }
  624. func statusRef(ref esv1.PushSecretData) string {
  625. if ref.GetProperty() != "" {
  626. return ref.GetRemoteKey() + "/" + ref.GetProperty()
  627. }
  628. return ref.GetRemoteKey()
  629. }
  630. // removeUnmanagedStores iterates over all SecretStore references and evaluates the controllerClass property.
  631. // Returns a map containing only managed stores.
  632. func removeUnmanagedStores(ctx context.Context, namespace string, r *Reconciler, ss map[esapi.PushSecretStoreRef]esv1.GenericStore) (map[esapi.PushSecretStoreRef]esv1.GenericStore, error) {
  633. for ref := range ss {
  634. var store esv1.GenericStore
  635. switch ref.Kind {
  636. case esv1.SecretStoreKind:
  637. store = &esv1.SecretStore{}
  638. case esv1.ClusterSecretStoreKind:
  639. store = &esv1.ClusterSecretStore{}
  640. namespace = ""
  641. }
  642. err := r.Client.Get(ctx, types.NamespacedName{
  643. Name: ref.Name,
  644. Namespace: namespace,
  645. }, store)
  646. if err != nil {
  647. return ss, err
  648. }
  649. class := store.GetSpec().Controller
  650. if class != "" && class != r.ControllerClass {
  651. delete(ss, ref)
  652. }
  653. }
  654. return ss, nil
  655. }