externalsecret_controller.go 60 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534
  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 externalsecret implements the controller for managing ExternalSecret resources
  14. package externalsecret
  15. import (
  16. "context"
  17. "encoding/json"
  18. "errors"
  19. "fmt"
  20. "maps"
  21. "slices"
  22. "strings"
  23. "time"
  24. "github.com/go-logr/logr"
  25. "github.com/prometheus/client_golang/prometheus"
  26. robfigcron "github.com/robfig/cron/v3"
  27. v1 "k8s.io/api/core/v1"
  28. "k8s.io/apimachinery/pkg/api/equality"
  29. apierrors "k8s.io/apimachinery/pkg/api/errors"
  30. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  31. "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
  32. "k8s.io/apimachinery/pkg/fields"
  33. "k8s.io/apimachinery/pkg/labels"
  34. "k8s.io/apimachinery/pkg/runtime"
  35. "k8s.io/apimachinery/pkg/runtime/schema"
  36. "k8s.io/apimachinery/pkg/types"
  37. "k8s.io/client-go/rest"
  38. "k8s.io/client-go/tools/record"
  39. "k8s.io/utils/ptr"
  40. ctrl "sigs.k8s.io/controller-runtime"
  41. "sigs.k8s.io/controller-runtime/pkg/builder"
  42. "sigs.k8s.io/controller-runtime/pkg/client"
  43. "sigs.k8s.io/controller-runtime/pkg/controller"
  44. "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
  45. "sigs.k8s.io/controller-runtime/pkg/event"
  46. "sigs.k8s.io/controller-runtime/pkg/handler"
  47. "sigs.k8s.io/controller-runtime/pkg/predicate"
  48. "sigs.k8s.io/controller-runtime/pkg/reconcile"
  49. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  50. // Metrics.
  51. "github.com/external-secrets/external-secrets/pkg/controllers/externalsecret/esmetrics"
  52. ctrlmetrics "github.com/external-secrets/external-secrets/pkg/controllers/metrics"
  53. ctrlutil "github.com/external-secrets/external-secrets/pkg/controllers/util"
  54. "github.com/external-secrets/external-secrets/runtime/esutils"
  55. "github.com/external-secrets/external-secrets/runtime/esutils/resolvers"
  56. // Loading registered generators.
  57. _ "github.com/external-secrets/external-secrets/pkg/register"
  58. )
  59. const (
  60. fieldOwnerTemplate = "externalsecrets.external-secrets.io/%v"
  61. fieldOwnerTemplateSha = "externalsecrets.external-secrets.io/sha3/%x"
  62. // ExternalSecretFinalizer is the finalizer for ExternalSecret resources.
  63. ExternalSecretFinalizer = "externalsecrets.external-secrets.io/externalsecret-cleanup"
  64. // condition messages for "SecretSynced" reason.
  65. msgSynced = "secret synced"
  66. msgSyncedRetain = "secret retained due to DeletionPolicy=Retain"
  67. // condition messages for "SecretDeleted" reason.
  68. msgDeleted = "secret deleted due to DeletionPolicy=Delete"
  69. // condition messages for "SecretMissing" reason.
  70. msgMissing = "secret will not be created due to CreationPolicy=Merge"
  71. // condition messages for "SecretSyncedError" reason.
  72. msgErrorGetSecretData = "could not get secret data from provider"
  73. msgErrorDeleteSecret = "could not delete secret"
  74. msgErrorDeleteOrphaned = "could not delete orphaned secrets"
  75. msgErrorUpdateSecret = "could not update secret"
  76. msgErrorUpdateImmutable = "could not update secret, target is immutable"
  77. msgErrorBecomeOwner = "failed to take ownership of target secret"
  78. msgErrorIsOwned = "target is owned by another ExternalSecret"
  79. // log messages.
  80. logErrorGetES = "unable to get ExternalSecret"
  81. logErrorUpdateESStatus = "unable to update ExternalSecret status"
  82. logErrorGetSecret = "unable to get Secret"
  83. logErrorPatchSecret = "unable to patch Secret"
  84. logErrorSecretCacheNotSynced = "controller caches for Secret are not in sync"
  85. logErrorUnmanagedStore = "unable to determine if store is managed"
  86. // log messages for mutating / destructive secret operations, emitted at
  87. // V(1) so they are opt-in. They only ever carry key names, never values.
  88. logSecretDeleted = "deleted secret"
  89. logManagedSecretDeleted = "deleted managed secret"
  90. logSecretDeletedOrphaned = "deleted orphaned secret"
  91. logSecretDataChanged = "secret data keys changed"
  92. // error formats.
  93. errConvert = "error applying conversion strategy %s to keys: %w"
  94. errRewrite = "error applying rewrite to keys: %w"
  95. errDecode = "error applying decoding strategy %s to data: %w"
  96. errGenerate = "error using generator: %w"
  97. errInvalidKeys = "invalid secret keys (TIP: use rewrite or conversionStrategy to change keys): %w"
  98. errFetchTplFrom = "error fetching templateFrom data: %w"
  99. errApplyTemplate = "could not apply template: %w"
  100. errExecTpl = "could not execute template: %w"
  101. errMutate = "unable to mutate secret %s: %w"
  102. errUpdate = "unable to update secret %s: %w"
  103. errUpdateNotFound = "unable to update secret %s: not found"
  104. errDeleteCreatePolicy = "unable to delete secret %s: creationPolicy=%s is not Owner"
  105. errSecretCachesNotSynced = "controller caches for secret %s are not in sync"
  106. // event messages.
  107. eventCreated = "secret created"
  108. eventUpdated = "secret updated"
  109. eventDeleted = "secret deleted due to DeletionPolicy=Delete"
  110. eventDeletedOrphaned = "secret deleted because it was orphaned"
  111. eventMissingProviderSecret = "secret does not exist at provider using spec.dataFrom[%d]"
  112. eventMissingProviderSecretKey = "secret does not exist at provider using spec.dataFrom[%d] (key=%s)"
  113. // cacheSyncRetryDelay is used when partial and full secret caches are temporarily out of sync.
  114. cacheSyncRetryDelay = 200 * time.Millisecond
  115. )
  116. // these errors are explicitly defined so we can detect them with `errors.Is()`.
  117. var (
  118. ErrSecretImmutable = fmt.Errorf("secret is immutable")
  119. ErrSecretIsOwned = fmt.Errorf("secret is owned by another ExternalSecret")
  120. ErrSecretSetCtrlRef = fmt.Errorf("could not set controller reference on secret")
  121. ErrSecretRemoveCtrlRef = fmt.Errorf("could not remove controller reference on secret")
  122. )
  123. const (
  124. indexESTargetSecretNameField = ".metadata.targetSecretName"
  125. indexESTargetResourceField = ".spec.target.resource"
  126. )
  127. // Reconciler reconciles a ExternalSecret object.
  128. type Reconciler struct {
  129. client.Client
  130. SecretClient client.Client
  131. APIReader client.Reader
  132. EnableSecretAPIReadOnCacheMismatch bool
  133. Log logr.Logger
  134. Scheme *runtime.Scheme
  135. RestConfig *rest.Config
  136. ControllerClass string
  137. RequeueInterval time.Duration
  138. ClusterSecretStoreEnabled bool
  139. EnableFloodGate bool
  140. EnableGeneratorState bool
  141. AllowGenericTargets bool
  142. recorder record.EventRecorder
  143. // informerManager manages dynamic informers for generic targets
  144. informerManager InformerManager
  145. }
  146. // Reconcile implements the main reconciliation loop
  147. // for watched objects (ExternalSecret, ClusterSecretStore and SecretStore),
  148. // and updates/creates a Kubernetes secret based on them.
  149. func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, err error) {
  150. log := r.Log.WithValues("ExternalSecret", req.NamespacedName)
  151. resourceLabels := ctrlmetrics.RefineNonConditionMetricLabels(map[string]string{"name": req.Name, "namespace": req.Namespace})
  152. start := time.Now()
  153. syncCallsError := esmetrics.GetCounterVec(esmetrics.SyncCallsErrorKey)
  154. // use closures to dynamically update resourceLabels
  155. defer func() {
  156. esmetrics.GetGaugeVec(esmetrics.ExternalSecretReconcileDurationKey).With(resourceLabels).Set(float64(time.Since(start)))
  157. esmetrics.GetCounterVec(esmetrics.SyncCallsKey).With(resourceLabels).Inc()
  158. }()
  159. externalSecret := &esv1.ExternalSecret{}
  160. err = r.Get(ctx, req.NamespacedName, externalSecret)
  161. if err != nil {
  162. if apierrors.IsNotFound(err) {
  163. // NOTE: this does not actually set the condition on the ExternalSecret, because it does not exist
  164. // this is a hack to disable metrics for deleted ExternalSecrets, see:
  165. // https://github.com/external-secrets/external-secrets/pull/612
  166. conditionSynced := NewExternalSecretCondition(esv1.ExternalSecretDeleted, v1.ConditionFalse, esv1.ConditionReasonSecretDeleted, "Secret was deleted")
  167. SetExternalSecretCondition(&esv1.ExternalSecret{
  168. ObjectMeta: metav1.ObjectMeta{
  169. Name: req.Name,
  170. Namespace: req.Namespace,
  171. },
  172. }, *conditionSynced)
  173. return ctrl.Result{}, nil
  174. }
  175. log.Error(err, logErrorGetES)
  176. syncCallsError.With(resourceLabels).Inc()
  177. return ctrl.Result{}, err
  178. }
  179. // Handle deletion with finalizer
  180. if !externalSecret.GetDeletionTimestamp().IsZero() {
  181. // Always attempt cleanup to handle edge case where finalizer might be removed externally
  182. if err := r.cleanupManagedSecrets(ctx, log, externalSecret); err != nil {
  183. log.Error(err, "failed to cleanup managed secrets")
  184. return ctrl.Result{}, err
  185. }
  186. // Release informer for generic targets
  187. if isGenericTarget(externalSecret) && r.informerManager != nil {
  188. gvk := getTargetGVK(externalSecret)
  189. esName := types.NamespacedName{Name: externalSecret.Name, Namespace: externalSecret.Namespace}
  190. if err := r.informerManager.ReleaseInformer(ctx, gvk, esName); err != nil {
  191. log.Error(err, "failed to release informer for generic target",
  192. "group", gvk.Group,
  193. "version", gvk.Version,
  194. "kind", gvk.Kind)
  195. }
  196. }
  197. // Remove finalizer if it exists
  198. // Use Patch instead of Update to avoid claiming ownership of spec fields like refreshInterval
  199. patch := client.MergeFrom(externalSecret.DeepCopy())
  200. if updated := controllerutil.RemoveFinalizer(externalSecret, ExternalSecretFinalizer); updated {
  201. if err := r.Patch(ctx, externalSecret, patch); err != nil {
  202. return ctrl.Result{}, err
  203. }
  204. }
  205. return ctrl.Result{}, nil
  206. }
  207. // Add finalizer if it doesn't exist
  208. // Use Patch instead of Update to avoid claiming ownership of spec fields like refreshInterval
  209. patch := client.MergeFrom(externalSecret.DeepCopy())
  210. if updated := controllerutil.AddFinalizer(externalSecret, ExternalSecretFinalizer); updated {
  211. if err := r.Patch(ctx, externalSecret, patch); err != nil {
  212. return ctrl.Result{}, err
  213. }
  214. }
  215. // if extended metrics is enabled, refine the time series vector
  216. resourceLabels = ctrlmetrics.RefineLabels(resourceLabels, externalSecret.Labels)
  217. // skip this ExternalSecret if it uses a ClusterSecretStore and the feature is disabled
  218. if shouldSkipClusterSecretStore(r, externalSecret) {
  219. log.V(1).Info("skipping ExternalSecret, ClusterSecretStore feature is disabled")
  220. return ctrl.Result{}, nil
  221. }
  222. // skip this ExternalSecret if it uses any SecretStore not managed by this controller
  223. skip, err := shouldSkipUnmanagedStore(ctx, req.Namespace, r, externalSecret)
  224. if err != nil {
  225. log.Error(err, logErrorUnmanagedStore)
  226. syncCallsError.With(resourceLabels).Inc()
  227. return ctrl.Result{}, err
  228. }
  229. if skip {
  230. log.V(1).Info("skipping ExternalSecret, uses unmanaged SecretStore")
  231. return ctrl.Result{}, nil
  232. }
  233. // if this is a generic target, use a different reconciliation path
  234. if isGenericTarget(externalSecret) {
  235. // update the status of the ExternalSecret when this function returns, if needed
  236. currentStatus := *externalSecret.Status.DeepCopy()
  237. defer func() {
  238. if equality.Semantic.DeepEqual(currentStatus, externalSecret.Status) {
  239. return
  240. }
  241. updateErr := r.Status().Update(ctx, externalSecret)
  242. if updateErr != nil && !apierrors.IsConflict(updateErr) {
  243. log.Error(updateErr, logErrorUpdateESStatus)
  244. }
  245. }()
  246. // validate generic target configuration early
  247. if err := r.validateGenericTarget(log, externalSecret); err != nil {
  248. r.markAsFailed("invalid generic target", ctrlutil.Safe(err), externalSecret, syncCallsError.With(resourceLabels), esv1.ConditionReasonSecretSyncedError)
  249. return ctrl.Result{}, nil // don't requeue as this is a configuration error that is not recoverable
  250. }
  251. return r.reconcileGenericTarget(ctx, externalSecret, log, start, resourceLabels, syncCallsError)
  252. }
  253. // the target secret name defaults to the ExternalSecret name, if not explicitly set
  254. secretName := externalSecret.Spec.Target.Name
  255. if secretName == "" {
  256. secretName = externalSecret.Name
  257. }
  258. // fetch the existing secret (from the partial cache)
  259. // - please note that the ~partial cache~ is different from the ~full cache~
  260. // so there can be race conditions between the two caches
  261. // - the WatchesMetadata(v1.Secret{}) in SetupWithManager() is using the partial cache
  262. // so we might receive a reconcile request before the full cache is updated
  263. // - furthermore, when `--enable-managed-secrets-caching` is true, the full cache
  264. // will ONLY include secrets with the "managed" label, so we cant use the full cache
  265. // to reliably determine if a secret exists or not
  266. secretPartial := &metav1.PartialObjectMetadata{}
  267. secretPartial.SetGroupVersionKind(v1.SchemeGroupVersion.WithKind("Secret"))
  268. err = r.Get(ctx, client.ObjectKey{Name: secretName, Namespace: externalSecret.Namespace}, secretPartial)
  269. if err != nil && !apierrors.IsNotFound(err) {
  270. log.Error(err, logErrorGetSecret, "secretName", secretName, "secretNamespace", externalSecret.Namespace)
  271. syncCallsError.With(resourceLabels).Inc()
  272. return ctrl.Result{}, err
  273. }
  274. // if the secret exists but does not have the "managed" label, add the label
  275. // using a PATCH so it is visible in the cache, then requeue immediately
  276. if secretPartial.UID != "" && secretPartial.Labels[esv1.LabelManaged] != esv1.LabelManagedValue {
  277. fqdn := fqdnFor(externalSecret.Name)
  278. patch := client.MergeFrom(secretPartial.DeepCopy())
  279. if secretPartial.Labels == nil {
  280. secretPartial.Labels = make(map[string]string)
  281. }
  282. secretPartial.Labels[esv1.LabelManaged] = esv1.LabelManagedValue
  283. err = r.Patch(ctx, secretPartial, patch, client.FieldOwner(fqdn))
  284. if err != nil {
  285. log.Error(err, logErrorPatchSecret, "secretName", secretName, "secretNamespace", externalSecret.Namespace)
  286. syncCallsError.With(resourceLabels).Inc()
  287. return ctrl.Result{}, err
  288. }
  289. return ctrl.Result{Requeue: true}, nil
  290. }
  291. // fetch existing secret (from the full cache)
  292. // NOTE: we are using the `r.SecretClient` which we only use for managed secrets.
  293. // when `enableManagedSecretsCache` is true, this is a cached client that only sees our managed secrets,
  294. // otherwise it will be the normal controller-runtime client which may be cached or make direct API calls,
  295. // depending on if `enabledSecretCache` is true or false.
  296. existingSecret := &v1.Secret{}
  297. err = r.SecretClient.Get(ctx, client.ObjectKey{Name: secretName, Namespace: externalSecret.Namespace}, existingSecret)
  298. if err != nil && !apierrors.IsNotFound(err) {
  299. log.Error(err, logErrorGetSecret, "secretName", secretName, "secretNamespace", externalSecret.Namespace)
  300. syncCallsError.With(resourceLabels).Inc()
  301. return ctrl.Result{}, err
  302. }
  303. // ensure the full cache is up-to-date
  304. // NOTE: this prevents race conditions between the partial and full cache.
  305. // if enabled, we verify against the API server before retrying to avoid unnecessary error backoff
  306. // when the cache is temporarily stale.
  307. existingSecret, cacheNotSynced, getErr := r.resolveSecretCacheMismatch(ctx, client.ObjectKey{Name: secretName, Namespace: externalSecret.Namespace}, secretPartial, existingSecret)
  308. if getErr != nil && !apierrors.IsNotFound(getErr) {
  309. log.Error(getErr, logErrorGetSecret, "secretName", secretName, "secretNamespace", externalSecret.Namespace)
  310. syncCallsError.With(resourceLabels).Inc()
  311. return ctrl.Result{}, getErr
  312. }
  313. if cacheNotSynced {
  314. log.V(1).Info(logErrorSecretCacheNotSynced, "secretName", secretName, "secretNamespace", externalSecret.Namespace)
  315. return ctrl.Result{RequeueAfter: cacheSyncRetryDelay}, nil
  316. }
  317. // refresh will be skipped if ALL the following conditions are met:
  318. // 1. refresh interval is not 0
  319. // 2. resource generation of the ExternalSecret has not changed
  320. // 3. the last refresh time of the ExternalSecret is within the refresh interval
  321. // 4. the target secret is valid:
  322. // - it exists
  323. // - it has the correct "managed" label
  324. // - it has the correct "data-hash" annotation
  325. if !shouldRefresh(externalSecret) && isSecretValid(existingSecret, externalSecret) {
  326. log.V(1).Info("skipping refresh")
  327. return r.getRequeueResult(externalSecret), nil
  328. }
  329. // update status of the ExternalSecret when this function returns, if needed.
  330. // NOTE: we use the ability of deferred functions to update named return values `result` and `err`
  331. // NOTE: we dereference the DeepCopy of the status field because status fields are NOT pointers,
  332. // so otherwise the `equality.Semantic.DeepEqual` will always return false.
  333. currentStatus := *externalSecret.Status.DeepCopy()
  334. defer func() {
  335. // if the status has not changed, we don't need to update it
  336. if equality.Semantic.DeepEqual(currentStatus, externalSecret.Status) {
  337. return
  338. }
  339. // update the status of the ExternalSecret, storing any error in a new variable
  340. // if there was no new error, we don't need to change the `result` or `err` values
  341. updateErr := r.Status().Update(ctx, externalSecret)
  342. if updateErr == nil {
  343. return
  344. }
  345. // if we got an update conflict, we should requeue immediately
  346. if apierrors.IsConflict(updateErr) {
  347. log.V(1).Info("conflict while updating status, will requeue")
  348. // we only explicitly request a requeue if the main function did not return an `err`.
  349. // otherwise, we get an annoying log saying that results are ignored when there is an error,
  350. // as errors are always retried.
  351. if err == nil {
  352. result = ctrl.Result{Requeue: true}
  353. }
  354. return
  355. }
  356. // for other errors, log and update the `err` variable if there is no error already
  357. // so the reconciler will requeue the request
  358. log.Error(updateErr, logErrorUpdateESStatus)
  359. if err == nil {
  360. err = updateErr
  361. }
  362. }()
  363. // retrieve the provider secret data.
  364. dataMap, err := r.GetProviderSecretData(ctx, externalSecret)
  365. if err != nil {
  366. r.markAsFailed(msgErrorGetSecretData, err, externalSecret, syncCallsError.With(resourceLabels), esv1.ConditionReasonSecretSyncedError)
  367. return ctrl.Result{}, err
  368. }
  369. // if no data was found we can delete the secret if needed.
  370. if len(dataMap) == 0 {
  371. switch externalSecret.Spec.Target.DeletionPolicy {
  372. // delete secret and return early.
  373. case esv1.DeletionPolicyDelete:
  374. // safeguard that we only can delete secrets we own.
  375. // this is also implemented in the es validation webhook.
  376. // NOTE: this error cant be fixed by retrying so we don't return an error (which would requeue immediately)
  377. creationPolicy := externalSecret.Spec.Target.CreationPolicy
  378. if creationPolicy != esv1.CreatePolicyOwner {
  379. err = fmt.Errorf(errDeleteCreatePolicy, secretName, creationPolicy)
  380. r.markAsFailed(msgErrorDeleteSecret, ctrlutil.Safe(err), externalSecret, syncCallsError.With(resourceLabels), esv1.ConditionReasonSecretSyncedError)
  381. return ctrl.Result{}, nil
  382. }
  383. // delete the secret, if it exists
  384. if existingSecret.UID != "" {
  385. err = r.Delete(ctx, existingSecret)
  386. if err != nil && !apierrors.IsNotFound(err) {
  387. r.markAsFailed(msgErrorDeleteSecret, ctrlutil.Safe(err), externalSecret, syncCallsError.With(resourceLabels), esv1.ConditionReasonSecretSyncedError)
  388. return ctrl.Result{}, err
  389. }
  390. log.V(1).Info(logSecretDeleted, "secret", secretName, "namespace", externalSecret.Namespace, "reason", "DeletionPolicy=Delete and provider returned no data")
  391. r.recorder.Event(externalSecret, v1.EventTypeNormal, esv1.ReasonDeleted, eventDeleted)
  392. }
  393. r.markAsDone(externalSecret, start, log, esv1.ConditionReasonSecretDeleted, msgDeleted)
  394. return r.getRequeueResult(externalSecret), nil
  395. // In case provider secrets don't exist the kubernetes secret will be kept as-is.
  396. case esv1.DeletionPolicyRetain:
  397. r.markAsDone(externalSecret, start, log, esv1.ConditionReasonSecretSynced, msgSyncedRetain)
  398. return r.getRequeueResult(externalSecret), nil
  399. // noop, handled below
  400. case esv1.DeletionPolicyMerge:
  401. }
  402. }
  403. // mutationFunc is a function which can be applied to a secret to make it match the desired state.
  404. mutationFunc := func(secret *v1.Secret) error {
  405. // initialize maps within the secret so it's safe to set values
  406. if secret.Annotations == nil {
  407. secret.Annotations = make(map[string]string)
  408. }
  409. if secret.Labels == nil {
  410. secret.Labels = make(map[string]string)
  411. }
  412. if secret.Data == nil {
  413. secret.Data = make(map[string][]byte)
  414. }
  415. // set the immutable flag on the secret if requested by the ExternalSecret
  416. if externalSecret.Spec.Target.Immutable {
  417. secret.Immutable = new(true)
  418. }
  419. // only apply the template if the secret is mutable or if the secret is new (has no UID)
  420. // otherwise we would mutate an object that is immutable and already exists
  421. objectDoesNotExistOrCanBeMutated := secret.GetUID() == "" || !externalSecret.Spec.Target.Immutable
  422. if objectDoesNotExistOrCanBeMutated {
  423. // get the list of keys that are managed by this ExternalSecret
  424. keys, err := getManagedDataKeys(secret, externalSecret.Name)
  425. if err != nil {
  426. return err
  427. }
  428. // remove any data keys that are managed by this ExternalSecret, so we can re-add them
  429. // this ensures keys added by templates are not left behind when they are removed from the template
  430. for _, key := range keys {
  431. delete(secret.Data, key)
  432. }
  433. // WARNING: this will remove any labels or annotations managed by this ExternalSecret
  434. // so any updates to labels and annotations should be done AFTER this point
  435. err = r.ApplyTemplate(ctx, externalSecret, secret, dataMap)
  436. if err != nil {
  437. return fmt.Errorf(errApplyTemplate, err)
  438. }
  439. }
  440. if err := r.applyOwnership(externalSecret, secret); err != nil {
  441. return err
  442. }
  443. secret.Labels[esv1.LabelManaged] = esv1.LabelManagedValue
  444. secret.Annotations[esv1.AnnotationDataHash] = esutils.ObjectHash(secret.Data)
  445. return nil
  446. }
  447. switch externalSecret.Spec.Target.CreationPolicy {
  448. case esv1.CreatePolicyNone:
  449. log.V(1).Info("secret creation skipped due to CreationPolicy=None")
  450. err = nil
  451. case esv1.CreatePolicyMerge:
  452. // update the secret, if it exists
  453. if existingSecret.UID != "" {
  454. err = r.updateSecret(ctx, log, existingSecret, mutationFunc, externalSecret, secretName)
  455. } else {
  456. // if the secret does not exist, we wait until the next refresh interval
  457. // rather than returning an error which would requeue immediately
  458. r.markAsDone(externalSecret, start, log, esv1.ConditionReasonSecretMissing, msgMissing)
  459. return r.getRequeueResult(externalSecret), nil
  460. }
  461. case esv1.CreatePolicyOrphan, esv1.CreatePolicyCreateOrMerge:
  462. // create the secret if it does not exist, otherwise update it.
  463. // CreateOrMerge behaves like Orphan here (create-or-update, no
  464. // ownerReference); it differs only in that ApplyTemplate keeps existing
  465. // keys for it (see externalsecret_controller_template.go).
  466. if existingSecret.UID == "" {
  467. err = r.createSecret(ctx, mutationFunc, externalSecret, secretName)
  468. } else {
  469. // if the secret exists, we should update it
  470. err = r.updateSecret(ctx, log, existingSecret, mutationFunc, externalSecret, secretName)
  471. }
  472. case esv1.CreatePolicyOwner:
  473. // we may have orphaned secrets to clean up,
  474. // for example, if the target secret name was changed
  475. err = r.deleteOrphanedSecrets(ctx, log, externalSecret, secretName)
  476. if err != nil {
  477. r.markAsFailed(msgErrorDeleteOrphaned, ctrlutil.Safe(err), externalSecret, syncCallsError.With(resourceLabels), esv1.ConditionReasonSecretSyncedError)
  478. return ctrl.Result{}, err
  479. }
  480. // create the secret, if it does not exist
  481. if existingSecret.UID == "" {
  482. err = r.createSecret(ctx, mutationFunc, externalSecret, secretName)
  483. } else {
  484. // if the secret exists, we should update it
  485. err = r.updateSecret(ctx, log, existingSecret, mutationFunc, externalSecret, secretName)
  486. }
  487. }
  488. if err != nil {
  489. // if we got an update conflict, we should requeue immediately
  490. if apierrors.IsConflict(err) {
  491. log.V(1).Info("conflict while updating secret, will requeue")
  492. return ctrl.Result{Requeue: true}, nil
  493. }
  494. // detect errors indicating that we failed to set ourselves as the owner of the secret
  495. // NOTE: this error cant be fixed by retrying so we don't return an error (which would requeue immediately)
  496. if errors.Is(err, ErrSecretSetCtrlRef) {
  497. r.markAsFailed(msgErrorBecomeOwner, ctrlutil.Safe(err), externalSecret, syncCallsError.With(resourceLabels), esv1.ConditionReasonSecretSyncedError)
  498. return ctrl.Result{}, nil
  499. }
  500. // detect errors indicating that the secret has another ExternalSecret as owner
  501. // NOTE: this error cant be fixed by retrying so we don't return an error (which would requeue immediately)
  502. if errors.Is(err, ErrSecretIsOwned) {
  503. r.markAsFailed(msgErrorIsOwned, ctrlutil.Safe(err), externalSecret, syncCallsError.With(resourceLabels), esv1.ConditionReasonSecretOwnedByOther)
  504. return ctrl.Result{}, nil
  505. }
  506. // detect errors indicating that the secret is immutable
  507. // NOTE: this error cant be fixed by retrying so we don't return an error (which would requeue immediately)
  508. if errors.Is(err, ErrSecretImmutable) {
  509. r.markAsFailed(msgErrorUpdateImmutable, ctrlutil.Safe(err), externalSecret, syncCallsError.With(resourceLabels), esv1.ConditionReasonSecretImmutable)
  510. return ctrl.Result{}, nil
  511. }
  512. // not marked safe here: this path also carries template errors, which can
  513. // echo rendered values. createSecret / updateSecret mark their own API errors.
  514. r.markAsFailed(msgErrorUpdateSecret, err, externalSecret, syncCallsError.With(resourceLabels), esv1.ConditionReasonSecretSyncedError)
  515. return ctrl.Result{}, err
  516. }
  517. r.markAsDone(externalSecret, start, log, esv1.ConditionReasonSecretSynced, msgSynced)
  518. return r.getRequeueResult(externalSecret), nil
  519. }
  520. // reconcileGenericTarget handles reconciliation for generic targets (ConfigMaps, Custom Resources).
  521. func (r *Reconciler) reconcileGenericTarget(
  522. ctx context.Context,
  523. externalSecret *esv1.ExternalSecret,
  524. log logr.Logger,
  525. start time.Time,
  526. resourceLabels map[string]string,
  527. syncCallsError *prometheus.CounterVec,
  528. ) (ctrl.Result, error) {
  529. var existing *unstructured.Unstructured
  530. if externalSecret.Spec.Target.CreationPolicy == esv1.CreatePolicyMerge ||
  531. externalSecret.Spec.Target.CreationPolicy == esv1.CreatePolicyOrphan ||
  532. externalSecret.Spec.Target.CreationPolicy == esv1.CreatePolicyOwner ||
  533. externalSecret.Spec.Target.CreationPolicy == esv1.CreatePolicyCreateOrMerge {
  534. var getErr error
  535. existing, getErr = r.getGenericResource(ctx, log, externalSecret)
  536. if getErr != nil && !apierrors.IsNotFound(getErr) {
  537. r.markAsFailed("could not get target resource", ctrlutil.Safe(getErr), externalSecret, syncCallsError.With(resourceLabels), esv1.ConditionReasonResourceSyncedError)
  538. return ctrl.Result{}, getErr
  539. }
  540. }
  541. valid, err := isGenericTargetValid(existing, externalSecret)
  542. if err != nil {
  543. log.V(1).Info("unable to validate target", "error", err)
  544. return ctrl.Result{}, err
  545. }
  546. if !shouldRefresh(externalSecret) && valid {
  547. log.V(1).Info("skipping refresh of generic target")
  548. return r.getRequeueResult(externalSecret), nil
  549. }
  550. dataMap, err := r.GetProviderSecretData(ctx, externalSecret)
  551. if err != nil {
  552. r.markAsFailed(msgErrorGetSecretData, err, externalSecret, syncCallsError.With(resourceLabels), esv1.ConditionReasonResourceSyncedError)
  553. return ctrl.Result{}, err
  554. }
  555. if len(dataMap) == 0 {
  556. switch externalSecret.Spec.Target.DeletionPolicy {
  557. case esv1.DeletionPolicyDelete:
  558. creationPolicy := externalSecret.Spec.Target.CreationPolicy
  559. if creationPolicy != esv1.CreatePolicyOwner {
  560. err = fmt.Errorf("unable to delete resource: creationPolicy=%s is not Owner", creationPolicy)
  561. r.markAsFailed("could not delete resource", ctrlutil.Safe(err), externalSecret, syncCallsError.With(resourceLabels), esv1.ConditionReasonResourceSyncedError)
  562. return ctrl.Result{}, nil
  563. }
  564. err = r.deleteGenericResource(ctx, log, externalSecret)
  565. if err != nil {
  566. r.markAsFailed("could not delete resource", ctrlutil.Safe(err), externalSecret, syncCallsError.With(resourceLabels), esv1.ConditionReasonResourceSyncedError)
  567. return ctrl.Result{}, err
  568. }
  569. r.markAsDone(externalSecret, start, log, esv1.ConditionReasonResourceDeleted, msgDeleted)
  570. return r.getRequeueResult(externalSecret), nil
  571. case esv1.DeletionPolicyRetain:
  572. r.markAsDone(externalSecret, start, log, esv1.ConditionReasonResourceSynced, msgSyncedRetain)
  573. return r.getRequeueResult(externalSecret), nil
  574. case esv1.DeletionPolicyMerge:
  575. }
  576. }
  577. // For Merge and CreateOrMerge with an existing resource, pass it to
  578. // applyTemplateToManifest so templates are applied to the existing resource
  579. // instead of creating a new one.
  580. var baseObj *unstructured.Unstructured
  581. if (externalSecret.Spec.Target.CreationPolicy == esv1.CreatePolicyMerge ||
  582. externalSecret.Spec.Target.CreationPolicy == esv1.CreatePolicyCreateOrMerge) && existing != nil {
  583. baseObj = existing
  584. }
  585. // render the template for the manifest
  586. obj, err := r.applyTemplateToManifest(ctx, externalSecret, dataMap, baseObj)
  587. if err != nil {
  588. // applyTemplateToManifest also applies ownership, so the same dead-end
  589. // conflicts the Secret lane reports can surface here. Retrying does not
  590. // fix either, hence no returned error.
  591. switch {
  592. case errors.Is(err, ErrSecretIsOwned):
  593. r.markAsFailed(msgErrorIsOwned, ctrlutil.Safe(err), externalSecret, syncCallsError.With(resourceLabels), esv1.ConditionReasonSecretOwnedByOther)
  594. return ctrl.Result{}, nil
  595. case errors.Is(err, ErrSecretSetCtrlRef):
  596. r.markAsFailed(msgErrorBecomeOwner, ctrlutil.Safe(err), externalSecret, syncCallsError.With(resourceLabels), esv1.ConditionReasonResourceSyncedError)
  597. return ctrl.Result{}, nil
  598. }
  599. // template errors stay generic: rendering can echo secret values.
  600. r.markAsFailed("could not apply template to manifest", err, externalSecret, syncCallsError.With(resourceLabels), esv1.ConditionReasonResourceSyncedError)
  601. return ctrl.Result{}, err
  602. }
  603. // handle creation policies
  604. switch externalSecret.Spec.Target.CreationPolicy {
  605. case esv1.CreatePolicyNone:
  606. log.V(1).Info("resource creation skipped due to CreationPolicy=None")
  607. err = nil
  608. case esv1.CreatePolicyMerge:
  609. // for Merge policy, only update if resource exists
  610. if existing == nil || existing.GetUID() == "" {
  611. r.markAsDone(externalSecret, start, log, esv1.ConditionReasonResourceMissing, "resource will not be created due to CreationPolicy=Merge")
  612. return r.getRequeueResult(externalSecret), nil
  613. }
  614. obj.SetResourceVersion(existing.GetResourceVersion())
  615. obj.SetUID(existing.GetUID())
  616. // update the existing resource
  617. err = r.updateGenericResource(ctx, log, externalSecret, obj)
  618. case esv1.CreatePolicyOrphan, esv1.CreatePolicyOwner, esv1.CreatePolicyCreateOrMerge:
  619. if existing != nil {
  620. obj.SetResourceVersion(existing.GetResourceVersion())
  621. obj.SetUID(existing.GetUID())
  622. err = r.updateGenericResource(ctx, log, externalSecret, obj)
  623. } else {
  624. err = r.createGenericResource(ctx, log, externalSecret, obj)
  625. }
  626. }
  627. if err != nil {
  628. // if we got an update conflict, requeue immediately
  629. if apierrors.IsConflict(err) {
  630. log.V(1).Info("conflict while updating resource, will requeue")
  631. return ctrl.Result{RequeueAfter: 1 * time.Second}, nil
  632. }
  633. // the template was already applied above, so err here is only from the
  634. // Kubernetes API call that created or updated the target resource.
  635. r.markAsFailed(msgErrorUpdateSecret, ctrlutil.Safe(err), externalSecret, syncCallsError.With(resourceLabels), esv1.ConditionReasonResourceSyncedError)
  636. return ctrl.Result{}, err
  637. }
  638. if externalSecret.Spec.Target.CreationPolicy != esv1.CreatePolicyNone {
  639. gvk := getTargetGVK(externalSecret)
  640. esName := types.NamespacedName{Name: externalSecret.Name, Namespace: externalSecret.Namespace}
  641. if _, err := r.informerManager.EnsureInformer(ctx, gvk, esName); err != nil {
  642. log.Error(err, "failed to register informer for generic target, drift detection may not work",
  643. "group", gvk.Group,
  644. "version", gvk.Version,
  645. "kind", gvk.Kind)
  646. }
  647. }
  648. r.markAsDone(externalSecret, start, log, esv1.ConditionReasonResourceSynced, msgSynced)
  649. return r.getRequeueResult(externalSecret), nil
  650. }
  651. // getRequeueResult create a result with requeueAfter based on the ExternalSecret refresh interval.
  652. func (r *Reconciler) getRequeueResult(externalSecret *esv1.ExternalSecret) ctrl.Result {
  653. // default to the global requeue interval
  654. // note, this will never be used because the CRD has a default value of 1 hour
  655. refreshInterval := r.RequeueInterval
  656. if externalSecret.Spec.RefreshInterval != nil {
  657. refreshInterval = externalSecret.Spec.RefreshInterval.Duration
  658. }
  659. // if the refresh interval is <= 0, we should not requeue
  660. if refreshInterval <= 0 {
  661. return ctrl.Result{}
  662. }
  663. // if the last refresh time is not set, requeue after the refresh interval
  664. // note, this should not happen, as we only call this function on ExternalSecrets
  665. // that have been reconciled at least once
  666. if externalSecret.Status.RefreshTime.IsZero() {
  667. return ctrl.Result{RequeueAfter: refreshInterval}
  668. }
  669. timeSinceLastRefresh := time.Since(externalSecret.Status.RefreshTime.Time)
  670. // if the last refresh time is in the future, we should requeue immediately
  671. // note, this should not happen, as we always refresh an ExternalSecret
  672. // that has a last refresh time in the future
  673. if timeSinceLastRefresh < 0 {
  674. return ctrl.Result{Requeue: true}
  675. }
  676. // if there is time remaining, requeue after the remaining time
  677. if timeSinceLastRefresh < refreshInterval {
  678. return ctrl.Result{RequeueAfter: refreshInterval - timeSinceLastRefresh}
  679. }
  680. // otherwise, requeue immediately
  681. return ctrl.Result{Requeue: true}
  682. }
  683. func (r *Reconciler) markAsDone(externalSecret *esv1.ExternalSecret, start time.Time, log logr.Logger, reason, msg string) {
  684. oldReadyCondition := esv1.GetExternalSecretCondition(externalSecret.Status, esv1.ExternalSecretReady)
  685. newReadyCondition := NewExternalSecretCondition(esv1.ExternalSecretReady, v1.ConditionTrue, reason, msg)
  686. SetExternalSecretCondition(externalSecret, *newReadyCondition)
  687. externalSecret.Status.RefreshTime = metav1.NewTime(start)
  688. externalSecret.Status.SyncedResourceVersion = ctrlutil.GetResourceVersion(externalSecret.ObjectMeta)
  689. // if the status or reason has changed, log at the appropriate verbosity level
  690. if oldReadyCondition == nil || oldReadyCondition.Status != newReadyCondition.Status || oldReadyCondition.Reason != newReadyCondition.Reason {
  691. if newReadyCondition.Reason == esv1.ConditionReasonSecretDeleted {
  692. log.Info("deleted secret")
  693. } else {
  694. log.Info("reconciled secret")
  695. }
  696. } else {
  697. log.V(1).Info("reconciled secret")
  698. }
  699. }
  700. func (r *Reconciler) markAsFailed(msg string, err error, externalSecret *esv1.ExternalSecret, counter prometheus.Counter, reason string) {
  701. r.recorder.Event(externalSecret, v1.EventTypeWarning, esv1.ReasonUpdateFailed, err.Error())
  702. // only errors explicitly marked safe are detailed here; provider errors keep
  703. // the generic message because they may carry secret payloads.
  704. if detail := ctrlutil.SafeMessage(err); detail != "" {
  705. msg = fmt.Sprintf("%s: %s", msg, detail)
  706. }
  707. conditionSynced := NewExternalSecretCondition(esv1.ExternalSecretReady, v1.ConditionFalse, reason, msg)
  708. SetExternalSecretCondition(externalSecret, *conditionSynced)
  709. counter.Inc()
  710. }
  711. // applyOwnership handles ownership-related logic for target resources (Secrets and generic targets).
  712. // It detects ownership conflicts with other ExternalSecrets, sets or removes the controller reference
  713. // based on CreationPolicy, and manages the LabelOwner label.
  714. func (r *Reconciler) applyOwnership(es *esv1.ExternalSecret, target client.Object) error {
  715. // get information about the current owner of the target
  716. // - we ignore the API version as it can change over time
  717. // - we ignore the UID for consistency with the SetControllerReference function
  718. currentOwner := metav1.GetControllerOf(target)
  719. ownerIsESKind := false
  720. ownerIsCurrentES := false
  721. if currentOwner != nil {
  722. currentOwnerGK := schema.FromAPIVersionAndKind(currentOwner.APIVersion, currentOwner.Kind).GroupKind()
  723. ownerIsESKind = currentOwnerGK.String() == esv1.ExtSecretGroupKind
  724. ownerIsCurrentES = ownerIsESKind && currentOwner.Name == es.Name
  725. }
  726. // if another ExternalSecret is the owner, we should return an error
  727. // otherwise the controller will fight with itself to update the target.
  728. // note, this does not prevent other controllers from owning the target.
  729. if ownerIsESKind && !ownerIsCurrentES {
  730. return fmt.Errorf("%w: %s", ErrSecretIsOwned, currentOwner.Name)
  731. }
  732. // if the CreationPolicy is Owner, we should set ourselves as the owner of the target
  733. if es.Spec.Target.CreationPolicy == esv1.CreatePolicyOwner {
  734. if err := controllerutil.SetControllerReference(es, target, r.Scheme); err != nil {
  735. return fmt.Errorf("%w: %w", ErrSecretSetCtrlRef, err)
  736. }
  737. }
  738. // if the creation policy is not Owner, we should remove ourselves as the owner
  739. // this could happen if the creation policy was changed after the target was created
  740. if es.Spec.Target.CreationPolicy != esv1.CreatePolicyOwner && ownerIsCurrentES {
  741. if err := controllerutil.RemoveControllerReference(es, target, r.Scheme); err != nil {
  742. return fmt.Errorf("%w: %w", ErrSecretRemoveCtrlRef, err)
  743. }
  744. }
  745. // we also use a label to keep track of the owner of the target
  746. // this lets us remove targets that are no longer needed if the target name changes
  747. labels := target.GetLabels()
  748. if labels == nil {
  749. labels = make(map[string]string)
  750. }
  751. if es.Spec.Target.CreationPolicy == esv1.CreatePolicyOwner {
  752. labels[esv1.LabelOwner] = esutils.ObjectHash(fmt.Sprintf("%v/%v", es.Namespace, es.Name))
  753. } else {
  754. delete(labels, esv1.LabelOwner)
  755. }
  756. target.SetLabels(labels)
  757. return nil
  758. }
  759. func (r *Reconciler) cleanupManagedSecrets(ctx context.Context, log logr.Logger, externalSecret *esv1.ExternalSecret) error {
  760. // Only delete resources if DeletionPolicy is Delete
  761. if externalSecret.Spec.Target.DeletionPolicy != esv1.DeletionPolicyDelete {
  762. log.V(1).Info("skipping resource deletion due to DeletionPolicy", "policy", externalSecret.Spec.Target.DeletionPolicy)
  763. return nil
  764. }
  765. // if this is a generic target, use deleteGenericResource
  766. if isGenericTarget(externalSecret) {
  767. return r.deleteGenericResource(ctx, log, externalSecret)
  768. }
  769. // handle Secret deletion
  770. secretName := externalSecret.Spec.Target.Name
  771. if secretName == "" {
  772. secretName = externalSecret.Name
  773. }
  774. var secret v1.Secret
  775. err := r.Get(ctx, types.NamespacedName{Name: secretName, Namespace: externalSecret.Namespace}, &secret)
  776. if err != nil {
  777. if apierrors.IsNotFound(err) {
  778. return nil
  779. }
  780. return err
  781. }
  782. // Only delete if we own it
  783. if metav1.IsControlledBy(&secret, externalSecret) {
  784. if err := r.Delete(ctx, &secret); err != nil && !apierrors.IsNotFound(err) {
  785. return err
  786. }
  787. log.V(1).Info(logManagedSecretDeleted, "secret", secretName, "namespace", externalSecret.Namespace, "reason", "ExternalSecret deleted")
  788. }
  789. return nil
  790. }
  791. func (r *Reconciler) deleteOrphanedSecrets(ctx context.Context, log logr.Logger, externalSecret *esv1.ExternalSecret, secretName string) error {
  792. ownerLabel := esutils.ObjectHash(fmt.Sprintf("%v/%v", externalSecret.Namespace, externalSecret.Name))
  793. // we use a PartialObjectMetadataList to avoid loading the full secret objects
  794. // and because the Secrets partials are always cached due to WatchesMetadata() in SetupWithManager()
  795. secretListPartial := &metav1.PartialObjectMetadataList{}
  796. secretListPartial.SetGroupVersionKind(v1.SchemeGroupVersion.WithKind("SecretList"))
  797. listOpts := &client.ListOptions{
  798. LabelSelector: labels.SelectorFromSet(map[string]string{
  799. esv1.LabelOwner: ownerLabel,
  800. }),
  801. Namespace: externalSecret.Namespace,
  802. }
  803. if err := r.List(ctx, secretListPartial, listOpts); err != nil {
  804. return err
  805. }
  806. // delete all secrets that are not the target secret
  807. for _, secretPartial := range secretListPartial.Items {
  808. if secretPartial.GetName() != secretName {
  809. err := r.Delete(ctx, &secretPartial)
  810. if err != nil && !apierrors.IsNotFound(err) {
  811. return err
  812. }
  813. log.V(1).Info(logSecretDeletedOrphaned, "secret", secretPartial.GetName(), "namespace", externalSecret.Namespace)
  814. r.recorder.Event(externalSecret, v1.EventTypeNormal, esv1.ReasonDeleted, eventDeletedOrphaned)
  815. }
  816. }
  817. return nil
  818. }
  819. // createSecret creates a new secret with the given mutation function.
  820. func (r *Reconciler) createSecret(ctx context.Context, mutationFunc func(secret *v1.Secret) error, es *esv1.ExternalSecret, secretName string) error {
  821. fqdn := fqdnFor(es.Name)
  822. // define and mutate the new secret
  823. newSecret := &v1.Secret{
  824. ObjectMeta: metav1.ObjectMeta{
  825. Name: secretName,
  826. Namespace: es.Namespace,
  827. Labels: map[string]string{},
  828. Annotations: map[string]string{},
  829. },
  830. Data: make(map[string][]byte),
  831. }
  832. if err := mutationFunc(newSecret); err != nil {
  833. return err
  834. }
  835. // note, we set field owner even for Create
  836. if err := r.Create(ctx, newSecret, client.FieldOwner(fqdn)); err != nil {
  837. return ctrlutil.Safe(err)
  838. }
  839. // set the binding reference to the secret
  840. // https://github.com/external-secrets/external-secrets/pull/2263
  841. es.Status.Binding = v1.LocalObjectReference{Name: newSecret.Name}
  842. r.recorder.Event(es, v1.EventTypeNormal, esv1.ReasonCreated, eventCreated)
  843. return nil
  844. }
  845. func (r *Reconciler) updateSecret(ctx context.Context, log logr.Logger, existingSecret *v1.Secret, mutationFunc func(secret *v1.Secret) error, es *esv1.ExternalSecret, secretName string) error {
  846. fqdn := fqdnFor(es.Name)
  847. // fail if the secret does not exist
  848. // this should never happen because we check this before calling this function
  849. if existingSecret.UID == "" {
  850. return ctrlutil.Safe(fmt.Errorf(errUpdateNotFound, secretName))
  851. }
  852. // set the binding reference to the secret
  853. // https://github.com/external-secrets/external-secrets/pull/2263
  854. es.Status.Binding = v1.LocalObjectReference{Name: secretName}
  855. // mutate a copy of the existing secret with the mutation function
  856. updatedSecret := existingSecret.DeepCopy()
  857. if err := mutationFunc(updatedSecret); err != nil {
  858. return fmt.Errorf(errMutate, updatedSecret.Name, err)
  859. }
  860. // if the secret does not need to be updated, return early
  861. if equality.Semantic.DeepEqual(existingSecret, updatedSecret) {
  862. return nil
  863. }
  864. // if the existing secret is immutable, we can only update the object metadata
  865. if ptr.Deref(existingSecret.Immutable, false) {
  866. // check if the metadata was changed
  867. metadataChanged := !equality.Semantic.DeepEqual(existingSecret.ObjectMeta, updatedSecret.ObjectMeta)
  868. // check if the immutable data/type was changed
  869. var dataChanged bool
  870. if metadataChanged {
  871. // update the `existingSecret` object with the metadata from `updatedSecret`
  872. // this lets us compare the objects to see if the immutable data/type was changed
  873. existingSecret.ObjectMeta = *updatedSecret.ObjectMeta.DeepCopy()
  874. dataChanged = !equality.Semantic.DeepEqual(existingSecret, updatedSecret)
  875. // because we use labels and annotations to keep track of the secret,
  876. // we need to update the metadata, regardless of if the immutable data was changed
  877. // NOTE: we are using the `existingSecret` object here, as we ONLY want to update the metadata,
  878. // and we previously copied the metadata from the `updatedSecret` object
  879. if err := r.Update(ctx, existingSecret, client.FieldOwner(fqdn)); err != nil {
  880. // if we get a conflict, we should return early to requeue immediately
  881. // note, we don't wrap this error so we can handle it in the caller
  882. if apierrors.IsConflict(err) {
  883. return err
  884. }
  885. return ctrlutil.Safe(fmt.Errorf(errUpdate, existingSecret.Name, err))
  886. }
  887. } else {
  888. // we know there was some change in the secret (or we would have returned early)
  889. // we know the metadata was NOT changed (metadataChanged == false)
  890. // so, the only thing that could have changed is the immutable data/type fields
  891. dataChanged = true
  892. }
  893. // if the immutable data was changed, we should return an error
  894. if dataChanged {
  895. return ctrlutil.Safe(fmt.Errorf(errUpdate, existingSecret.Name, ErrSecretImmutable))
  896. }
  897. }
  898. // update the secret
  899. if err := r.Update(ctx, updatedSecret, client.FieldOwner(fqdn)); err != nil {
  900. // if we get a conflict, we should return early to requeue immediately
  901. // note, we don't wrap this error so we can handle it in the caller
  902. if apierrors.IsConflict(err) {
  903. return err
  904. }
  905. return ctrlutil.Safe(fmt.Errorf(errUpdate, updatedSecret.Name, err))
  906. }
  907. // only compute the key diff when debug verbosity is active (--loglevel=debug /
  908. // log.level=debug in Helm). skipping it by default avoids per-reconcile
  909. // allocation on every managed secret. we only ever log key names, never values.
  910. if log.V(1).Enabled() {
  911. added, updated, removed, emptied := diffSecretDataKeys(existingSecret.Data, updatedSecret.Data)
  912. if len(added) > 0 || len(updated) > 0 || len(removed) > 0 {
  913. log.V(1).Info(logSecretDataChanged,
  914. "secret", secretName,
  915. "namespace", es.Namespace,
  916. "added", added,
  917. "updated", updated,
  918. "removed", removed,
  919. "emptied", emptied,
  920. )
  921. }
  922. }
  923. r.recorder.Event(es, v1.EventTypeNormal, esv1.ReasonUpdated, eventUpdated)
  924. return nil
  925. }
  926. // getManagedDataKeys returns the list of data keys in a secret which are managed by a specified owner.
  927. func getManagedDataKeys(secret *v1.Secret, fieldOwner string) ([]string, error) {
  928. return getManagedFieldKeys(secret, fieldOwner, func(fields map[string]any) []string {
  929. dataFields := fields["f:data"]
  930. if dataFields == nil {
  931. return nil
  932. }
  933. df, ok := dataFields.(map[string]any)
  934. if !ok {
  935. return nil
  936. }
  937. return slices.Collect(maps.Keys(df))
  938. })
  939. }
  940. func getManagedFieldKeys(
  941. secret *v1.Secret,
  942. fieldOwner string,
  943. process func(fields map[string]any) []string,
  944. ) ([]string, error) {
  945. fqdn := fqdnFor(fieldOwner)
  946. var keys []string
  947. for _, v := range secret.ObjectMeta.ManagedFields {
  948. if v.Manager != fqdn {
  949. continue
  950. }
  951. fields := make(map[string]any)
  952. err := json.Unmarshal(v.FieldsV1.Raw, &fields)
  953. if err != nil {
  954. return nil, fmt.Errorf("error unmarshaling managed fields: %w", err)
  955. }
  956. for _, key := range process(fields) {
  957. if key == "." {
  958. continue
  959. }
  960. keys = append(keys, strings.TrimPrefix(key, "f:"))
  961. }
  962. }
  963. return keys, nil
  964. }
  965. func shouldSkipClusterSecretStore(r *Reconciler, es *esv1.ExternalSecret) bool {
  966. return !r.ClusterSecretStoreEnabled && es.Spec.SecretStoreRef.Kind == esv1.ClusterSecretStoreKind
  967. }
  968. // shouldSkipUnmanagedStore iterates over all secretStore references in the externalSecret spec,
  969. // fetches the store and evaluates the controllerClass property.
  970. // Returns true if any storeRef points to store with a non-matching controllerClass.
  971. func shouldSkipUnmanagedStore(ctx context.Context, namespace string, r *Reconciler, es *esv1.ExternalSecret) (bool, error) {
  972. var storeList []esv1.SecretStoreRef
  973. if es.Spec.SecretStoreRef.Name != "" {
  974. storeList = append(storeList, es.Spec.SecretStoreRef)
  975. }
  976. for _, ref := range es.Spec.Data {
  977. if ref.SourceRef != nil {
  978. storeList = append(storeList, ref.SourceRef.SecretStoreRef)
  979. }
  980. }
  981. for _, ref := range es.Spec.DataFrom {
  982. if ref.SourceRef != nil && ref.SourceRef.SecretStoreRef != nil {
  983. storeList = append(storeList, *ref.SourceRef.SecretStoreRef)
  984. }
  985. // verify that generator's controllerClass matches
  986. if ref.SourceRef != nil && ref.SourceRef.GeneratorRef != nil {
  987. _, obj, err := resolvers.GeneratorRef(ctx, r.Client, r.Scheme, namespace, ref.SourceRef.GeneratorRef)
  988. if err != nil {
  989. if apierrors.IsNotFound(err) {
  990. // skip non-existent generators
  991. continue
  992. }
  993. if errors.Is(err, resolvers.ErrUnableToGetGenerator) {
  994. // skip generators that we can't get (e.g. due to being invalid)
  995. continue
  996. }
  997. return false, err
  998. }
  999. skipGenerator, err := shouldSkipGenerator(r, obj)
  1000. if err != nil {
  1001. return false, err
  1002. }
  1003. if skipGenerator {
  1004. return true, nil
  1005. }
  1006. }
  1007. }
  1008. for _, ref := range storeList {
  1009. var store esv1.GenericStore
  1010. switch ref.Kind {
  1011. case esv1.SecretStoreKind, "":
  1012. store = &esv1.SecretStore{}
  1013. case esv1.ClusterSecretStoreKind:
  1014. store = &esv1.ClusterSecretStore{}
  1015. namespace = ""
  1016. default:
  1017. return false, fmt.Errorf("unsupported secret store kind: %s", ref.Kind)
  1018. }
  1019. err := r.Get(ctx, types.NamespacedName{
  1020. Name: ref.Name,
  1021. Namespace: namespace,
  1022. }, store)
  1023. if err != nil {
  1024. if apierrors.IsNotFound(err) {
  1025. // skip non-existent stores
  1026. continue
  1027. }
  1028. return false, err
  1029. }
  1030. class := store.GetSpec().Controller
  1031. if class != "" && class != r.ControllerClass {
  1032. return true, nil
  1033. }
  1034. }
  1035. return false, nil
  1036. }
  1037. // isWithinSyncWindow reports whether 'at' falls inside the window that opened
  1038. // at the most-recent firing of 'sched' before 'at'. robfig's Next() is strictly
  1039. // exclusive, so we back up by (duration + 1s) to find that firing.
  1040. func isWithinSyncWindow(sched robfigcron.Schedule, duration time.Duration, at time.Time) bool {
  1041. prev := sched.Next(at.Add(-duration - time.Second))
  1042. return !prev.IsZero() && !prev.After(at) && !at.After(prev.Add(duration))
  1043. }
  1044. // cronParser is the standard 5-field (no-seconds) parser shared across all
  1045. // sync-window checks within this controller.
  1046. var cronParser = robfigcron.NewParser(
  1047. robfigcron.Minute | robfigcron.Hour | robfigcron.Dom |
  1048. robfigcron.Month | robfigcron.Dow | robfigcron.Descriptor,
  1049. )
  1050. // isPeriodicRefreshAllowedByWindows returns true when the SyncWindows on 'es'
  1051. // collectively permit a periodic refresh at time 'at'.
  1052. //
  1053. // - No windows: always allow.
  1054. // - kind=deny: deny when any window is active; allow otherwise.
  1055. // - kind=allow: allow when at least one window is active; deny otherwise.
  1056. //
  1057. // Windows with an unparseable Schedule are silently ignored (treated as
  1058. // inactive) so a typo does not permanently block syncs.
  1059. func isPeriodicRefreshAllowedByWindows(es *esv1.ExternalSecret, at time.Time) bool {
  1060. sw := es.Spec.SyncWindows
  1061. if sw == nil || len(sw.Windows) == 0 {
  1062. return true
  1063. }
  1064. anyActive := false
  1065. for _, w := range sw.Windows {
  1066. sched, err := cronParser.Parse(w.Schedule)
  1067. if err != nil {
  1068. // A schedule that fails to parse is skipped rather than aborting the
  1069. // whole evaluation. The kubebuilder pattern marker rejects malformed
  1070. // schedules at admission, so this is a defensive log for any value
  1071. // that slips past validation (e.g. a parser/regex mismatch).
  1072. ctrl.Log.V(1).Info("ignoring unparseable sync window schedule",
  1073. "ExternalSecret", es.Namespace+"/"+es.Name,
  1074. "schedule", w.Schedule,
  1075. "error", err.Error())
  1076. continue
  1077. }
  1078. if isWithinSyncWindow(sched, w.Duration.Duration, at) {
  1079. anyActive = true
  1080. break
  1081. }
  1082. }
  1083. allowed := true
  1084. switch sw.Kind {
  1085. case esv1.SyncWindowDeny:
  1086. allowed = !anyActive
  1087. case esv1.SyncWindowAllow:
  1088. allowed = anyActive
  1089. }
  1090. if !allowed {
  1091. ctrl.Log.V(1).Info("periodic refresh blocked by SyncWindow",
  1092. "ExternalSecret", es.Namespace+"/"+es.Name,
  1093. "kind", sw.Kind)
  1094. }
  1095. return allowed
  1096. }
  1097. func shouldRefresh(es *esv1.ExternalSecret) bool {
  1098. switch es.Spec.RefreshPolicy {
  1099. case esv1.RefreshPolicyCreatedOnce:
  1100. if es.Status.SyncedResourceVersion == "" || es.Status.RefreshTime.IsZero() {
  1101. return true
  1102. }
  1103. return false
  1104. case esv1.RefreshPolicyOnChange:
  1105. if es.Status.SyncedResourceVersion == "" || es.Status.RefreshTime.IsZero() {
  1106. return true
  1107. }
  1108. return es.Status.SyncedResourceVersion != ctrlutil.GetResourceVersion(es.ObjectMeta)
  1109. case esv1.RefreshPolicyPeriodic:
  1110. return shouldRefreshPeriodic(es)
  1111. default:
  1112. return shouldRefreshPeriodic(es)
  1113. }
  1114. }
  1115. func shouldRefreshPeriodic(es *esv1.ExternalSecret) bool {
  1116. // if the refresh interval is 0, and we have synced previously, we should not refresh
  1117. if es.Spec.RefreshInterval.Duration <= 0 && es.Status.SyncedResourceVersion != "" {
  1118. return false
  1119. }
  1120. // if the ExternalSecret has been updated, we should refresh
  1121. if es.Status.SyncedResourceVersion != ctrlutil.GetResourceVersion(es.ObjectMeta) {
  1122. return true
  1123. }
  1124. // if the last refresh time is zero, we should refresh
  1125. if es.Status.RefreshTime.IsZero() {
  1126. return true
  1127. }
  1128. now := time.Now()
  1129. // if the last refresh time is in the future, we should refresh
  1130. if es.Status.RefreshTime.Time.After(now) {
  1131. return true
  1132. }
  1133. // if the last refresh time + refresh interval is before now, we should refresh
  1134. if !es.Status.RefreshTime.Add(es.Spec.RefreshInterval.Duration).Before(now) {
  1135. return false
  1136. }
  1137. // check sync windows before triggering a refresh
  1138. return isPeriodicRefreshAllowedByWindows(es, now)
  1139. }
  1140. // isSecretValid checks if the secret exists, and it's data is consistent with the calculated hash.
  1141. func isSecretValid(existingSecret *v1.Secret, es *esv1.ExternalSecret) bool {
  1142. // Secret is always valid with `CreationPolicy=Orphan`
  1143. if es.Spec.Target.CreationPolicy == esv1.CreatePolicyOrphan {
  1144. return true
  1145. }
  1146. if existingSecret.UID == "" {
  1147. return false
  1148. }
  1149. // if the managed label is missing or incorrect, then it's invalid
  1150. if existingSecret.Labels[esv1.LabelManaged] != esv1.LabelManagedValue {
  1151. return false
  1152. }
  1153. // if the data-hash annotation is missing or incorrect, then it's invalid
  1154. // this is how we know if the data has chanced since we last updated the secret
  1155. if existingSecret.Annotations[esv1.AnnotationDataHash] != esutils.ObjectHash(existingSecret.Data) {
  1156. return false
  1157. }
  1158. return true
  1159. }
  1160. func isGenericTargetValid(existingTarget *unstructured.Unstructured, es *esv1.ExternalSecret) (bool, error) {
  1161. if es.Spec.Target.CreationPolicy == esv1.CreatePolicyOrphan {
  1162. return true, nil
  1163. }
  1164. if existingTarget == nil || existingTarget.GetUID() == "" {
  1165. return false, nil
  1166. }
  1167. if existingTarget.GetLabels()[esv1.LabelManaged] != esv1.LabelManagedValue {
  1168. return false, nil
  1169. }
  1170. hash, err := genericTargetContentHash(existingTarget)
  1171. if err != nil {
  1172. return false, fmt.Errorf("failed to hash target: %w", err)
  1173. }
  1174. if existingTarget.GetAnnotations()[esv1.AnnotationDataHash] != hash {
  1175. return false, nil
  1176. }
  1177. return true, nil
  1178. }
  1179. // genericTargetContentHash computes a hash over the hashable content of an unstructured object.
  1180. // It uses the "spec" field if present, otherwise falls back to "data".
  1181. func genericTargetContentHash(obj *unstructured.Unstructured) (string, error) {
  1182. content := obj.Object
  1183. switch {
  1184. case content["spec"] != nil:
  1185. return esutils.ObjectHash(content["spec"]), nil
  1186. case content["data"] != nil:
  1187. return esutils.ObjectHash(content["data"]), nil
  1188. default:
  1189. return "", errors.New("generic target content does not have a spec or data field for content hashing")
  1190. }
  1191. }
  1192. // SetupWithManager returns a new controller builder that will be started by the provided Manager.
  1193. func (r *Reconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, opts controller.Options) error {
  1194. r.recorder = mgr.GetEventRecorderFor("external-secrets")
  1195. if r.APIReader == nil {
  1196. r.APIReader = mgr.GetAPIReader()
  1197. }
  1198. // Initialize informer manager only if generic targets are allowed
  1199. if r.AllowGenericTargets && r.informerManager == nil {
  1200. r.informerManager = NewInformerManager(ctx, mgr.GetCache(), r.Client, r.Log.WithName("informer-manager"))
  1201. }
  1202. // index ExternalSecrets based on the target secret name,
  1203. // this lets us quickly find all ExternalSecrets which target a specific Secret
  1204. if err := mgr.GetFieldIndexer().IndexField(ctx, &esv1.ExternalSecret{}, indexESTargetSecretNameField, func(obj client.Object) []string {
  1205. es := obj.(*esv1.ExternalSecret)
  1206. // Don't index generic targets here (they use indexESTargetResourceField)
  1207. if isGenericTarget(es) {
  1208. return nil
  1209. }
  1210. // if the target name is set, use that as the index
  1211. if es.Spec.Target.Name != "" {
  1212. return []string{es.Spec.Target.Name}
  1213. }
  1214. // otherwise, use the ExternalSecret name
  1215. return []string{es.Name}
  1216. }); err != nil {
  1217. return err
  1218. }
  1219. // index ExternalSecrets based on the target resource (GVK + name)
  1220. // this lets us quickly find all ExternalSecrets which target a specific generic resource
  1221. if err := mgr.GetFieldIndexer().IndexField(ctx, &esv1.ExternalSecret{}, indexESTargetResourceField, func(obj client.Object) []string {
  1222. es := obj.(*esv1.ExternalSecret)
  1223. if !r.AllowGenericTargets || !isGenericTarget(es) {
  1224. return nil
  1225. }
  1226. gvk := getTargetGVK(es)
  1227. targetName := getTargetName(es)
  1228. // Index format: "group/version/kind/name"
  1229. return []string{fmt.Sprintf("%s/%s/%s/%s", gvk.Group, gvk.Version, gvk.Kind, targetName)}
  1230. }); err != nil {
  1231. return err
  1232. }
  1233. // predicate function to ignore secret events unless they have the "managed" label
  1234. secretHasESLabel := predicate.NewPredicateFuncs(func(object client.Object) bool {
  1235. value, hasLabel := object.GetLabels()[esv1.LabelManaged]
  1236. return hasLabel && value == esv1.LabelManagedValue
  1237. })
  1238. // filter ExternalSecret updates to avoid requeueing on status-only changes.
  1239. externalSecretPredicate := predicate.Funcs{
  1240. UpdateFunc: func(e event.UpdateEvent) bool {
  1241. return shouldEnqueueExternalSecretUpdate(e.ObjectOld, e.ObjectNew)
  1242. },
  1243. }
  1244. // Build the controller
  1245. builder := ctrl.NewControllerManagedBy(mgr).
  1246. WithOptions(opts).
  1247. For(&esv1.ExternalSecret{}, builder.WithPredicates(externalSecretPredicate)).
  1248. // we cant use Owns(), as we don't set ownerReferences when the creationPolicy is not Owner.
  1249. // we use WatchesMetadata() to reduce memory usage, as otherwise we have to process full secret objects.
  1250. WatchesMetadata(
  1251. &v1.Secret{},
  1252. handler.EnqueueRequestsFromMapFunc(r.findObjectsForSecret),
  1253. builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}, secretHasESLabel),
  1254. )
  1255. // Watch generic targets dynamically via the informer manager
  1256. // Only add this watch source if the feature is enabled
  1257. if r.AllowGenericTargets {
  1258. builder = builder.WatchesRawSource(r.informerManager.Source())
  1259. }
  1260. return builder.Complete(r)
  1261. }
  1262. // shouldEnqueueExternalSecretUpdate returns true for spec/metadata updates that can affect reconciliation behavior,
  1263. // while ignoring status-only updates.
  1264. func shouldEnqueueExternalSecretUpdate(oldObj, newObj client.Object) bool {
  1265. oldES, oldOK := oldObj.(*esv1.ExternalSecret)
  1266. newES, newOK := newObj.(*esv1.ExternalSecret)
  1267. if !oldOK || !newOK {
  1268. return true
  1269. }
  1270. if oldES.GetGeneration() != newES.GetGeneration() {
  1271. return true
  1272. }
  1273. if !equality.Semantic.DeepEqual(oldES.GetLabels(), newES.GetLabels()) {
  1274. return true
  1275. }
  1276. if !equality.Semantic.DeepEqual(oldES.GetAnnotations(), newES.GetAnnotations()) {
  1277. return true
  1278. }
  1279. if !equality.Semantic.DeepEqual(oldES.GetFinalizers(), newES.GetFinalizers()) {
  1280. return true
  1281. }
  1282. oldDeletion := oldES.GetDeletionTimestamp()
  1283. newDeletion := newES.GetDeletionTimestamp()
  1284. if oldDeletion == nil && newDeletion == nil {
  1285. return false
  1286. }
  1287. if oldDeletion == nil || newDeletion == nil {
  1288. return true
  1289. }
  1290. return !oldDeletion.Equal(newDeletion)
  1291. }
  1292. // resolveSecretCacheMismatch optionally uses a direct API read when the partial
  1293. // and full secret caches disagree. It returns the secret to continue with,
  1294. // whether the caches should still be treated as out of sync, and any read error.
  1295. func (r *Reconciler) resolveSecretCacheMismatch(ctx context.Context, key client.ObjectKey, secretPartial *metav1.PartialObjectMetadata, existingSecret *v1.Secret) (*v1.Secret, bool, error) {
  1296. if secretPartial.UID == existingSecret.UID && secretPartial.ResourceVersion == existingSecret.ResourceVersion {
  1297. return existingSecret, false, nil
  1298. }
  1299. if !r.EnableSecretAPIReadOnCacheMismatch {
  1300. return nil, true, nil
  1301. }
  1302. authoritativeSecret := &v1.Secret{}
  1303. secretReader := r.APIReader
  1304. if secretReader == nil {
  1305. secretReader = r.SecretClient
  1306. }
  1307. err := secretReader.Get(ctx, key, authoritativeSecret)
  1308. if err != nil && !apierrors.IsNotFound(err) {
  1309. return nil, false, err
  1310. }
  1311. if secretPartial.UID != authoritativeSecret.UID || secretPartial.ResourceVersion != authoritativeSecret.ResourceVersion {
  1312. return nil, true, nil
  1313. }
  1314. return authoritativeSecret, false, nil
  1315. }
  1316. func (r *Reconciler) findObjectsForSecret(ctx context.Context, secret client.Object) []reconcile.Request {
  1317. externalSecretsList := &esv1.ExternalSecretList{}
  1318. listOps := &client.ListOptions{
  1319. FieldSelector: fields.OneTermEqualSelector(indexESTargetSecretNameField, secret.GetName()),
  1320. Namespace: secret.GetNamespace(),
  1321. }
  1322. err := r.List(ctx, externalSecretsList, listOps)
  1323. if err != nil {
  1324. return []reconcile.Request{}
  1325. }
  1326. requests := make([]reconcile.Request, len(externalSecretsList.Items))
  1327. for i := range externalSecretsList.Items {
  1328. requests[i] = reconcile.Request{
  1329. NamespacedName: types.NamespacedName{
  1330. Name: externalSecretsList.Items[i].GetName(),
  1331. Namespace: externalSecretsList.Items[i].GetNamespace(),
  1332. },
  1333. }
  1334. }
  1335. return requests
  1336. }