externalsecret_controller.go 50 KB

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