externalsecret_controller.go 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698
  1. /*
  2. Licensed under the Apache License, Version 2.0 (the "License");
  3. you may not use this file except in compliance with the License.
  4. You may obtain a copy of the License at
  5. http://www.apache.org/licenses/LICENSE-2.0
  6. Unless required by applicable law or agreed to in writing, software
  7. distributed under the License is distributed on an "AS IS" BASIS,
  8. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9. See the License for the specific language governing permissions and
  10. limitations under the License.
  11. */
  12. package externalsecret
  13. import (
  14. "context"
  15. "encoding/json"
  16. "errors"
  17. "fmt"
  18. "maps"
  19. "slices"
  20. "strings"
  21. "time"
  22. "github.com/go-logr/logr"
  23. "github.com/prometheus/client_golang/prometheus"
  24. v1 "k8s.io/api/core/v1"
  25. "k8s.io/apimachinery/pkg/api/equality"
  26. apierrors "k8s.io/apimachinery/pkg/api/errors"
  27. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  28. "k8s.io/apimachinery/pkg/runtime"
  29. "k8s.io/apimachinery/pkg/types"
  30. "k8s.io/client-go/rest"
  31. "k8s.io/client-go/tools/record"
  32. ctrl "sigs.k8s.io/controller-runtime"
  33. "sigs.k8s.io/controller-runtime/pkg/builder"
  34. "sigs.k8s.io/controller-runtime/pkg/client"
  35. "sigs.k8s.io/controller-runtime/pkg/controller"
  36. "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
  37. "sigs.k8s.io/controller-runtime/pkg/handler"
  38. "sigs.k8s.io/controller-runtime/pkg/predicate"
  39. "sigs.k8s.io/controller-runtime/pkg/reconcile"
  40. esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
  41. // Metrics.
  42. "github.com/external-secrets/external-secrets/pkg/controllers/externalsecret/esmetrics"
  43. ctrlmetrics "github.com/external-secrets/external-secrets/pkg/controllers/metrics"
  44. "github.com/external-secrets/external-secrets/pkg/utils"
  45. // Loading registered generators.
  46. _ "github.com/external-secrets/external-secrets/pkg/generator/register"
  47. // Loading registered providers.
  48. _ "github.com/external-secrets/external-secrets/pkg/provider/register"
  49. )
  50. const (
  51. fieldOwnerTemplate = "externalsecrets.external-secrets.io/%v"
  52. errGetES = "could not get ExternalSecret"
  53. errConvert = "could not apply conversion strategy to keys: %v"
  54. errDecode = "could not apply decoding strategy to %v[%d]: %v"
  55. errGenerate = "could not generate [%d]: %w"
  56. errRewrite = "could not rewrite spec.dataFrom[%d]: %v"
  57. errInvalidKeys = "secret keys from spec.dataFrom.%v[%d] can only have alphanumeric,'-', '_' or '.' characters. Convert them using rewrite (https://external-secrets.io/latest/guides-datafrom-rewrite)"
  58. errUpdateSecret = "could not update Secret"
  59. errPatchStatus = "unable to patch status"
  60. errGetExistingSecret = "could not get existing secret: %w"
  61. errSetCtrlReference = "could not set ExternalSecret controller reference: %w"
  62. errFetchTplFrom = "error fetching templateFrom data: %w"
  63. errGetSecretData = "could not get secret data from provider"
  64. errDeleteSecret = "could not delete secret"
  65. errApplyTemplate = "could not apply template: %w"
  66. errExecTpl = "could not execute template: %w"
  67. errInvalidCreatePolicy = "invalid creationPolicy=%s. Can not delete secret i do not own"
  68. errPolicyMergeNotFound = "the desired secret %s was not found. With creationPolicy=Merge the secret won't be created"
  69. errPolicyMergeGetSecret = "unable to get secret %s: %w"
  70. errPolicyMergeMutate = "unable to mutate secret %s: %w"
  71. errPolicyMergePatch = "unable to patch secret %s: %w"
  72. )
  73. const externalSecretSecretNameKey = ".spec.target.name"
  74. // Reconciler reconciles a ExternalSecret object.
  75. type Reconciler struct {
  76. client.Client
  77. Log logr.Logger
  78. Scheme *runtime.Scheme
  79. RestConfig *rest.Config
  80. ControllerClass string
  81. RequeueInterval time.Duration
  82. ClusterSecretStoreEnabled bool
  83. EnableFloodGate bool
  84. recorder record.EventRecorder
  85. }
  86. // Reconcile implements the main reconciliation loop
  87. // for watched objects (ExternalSecret, ClusterSecretStore and SecretStore),
  88. // and updates/creates a Kubernetes secret based on them.
  89. func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
  90. log := r.Log.WithValues("ExternalSecret", req.NamespacedName)
  91. resourceLabels := ctrlmetrics.RefineNonConditionMetricLabels(map[string]string{"name": req.Name, "namespace": req.Namespace})
  92. start := time.Now()
  93. syncCallsError := esmetrics.GetCounterVec(esmetrics.SyncCallsErrorKey)
  94. // use closures to dynamically update resourceLabels
  95. defer func() {
  96. esmetrics.GetGaugeVec(esmetrics.ExternalSecretReconcileDurationKey).With(resourceLabels).Set(float64(time.Since(start)))
  97. esmetrics.GetCounterVec(esmetrics.SyncCallsKey).With(resourceLabels).Inc()
  98. }()
  99. var externalSecret esv1beta1.ExternalSecret
  100. err := r.Get(ctx, req.NamespacedName, &externalSecret)
  101. if err != nil {
  102. if apierrors.IsNotFound(err) {
  103. conditionSynced := NewExternalSecretCondition(esv1beta1.ExternalSecretDeleted, v1.ConditionFalse, esv1beta1.ConditionReasonSecretDeleted, "Secret was deleted")
  104. SetExternalSecretCondition(&esv1beta1.ExternalSecret{
  105. ObjectMeta: metav1.ObjectMeta{
  106. Name: req.Name,
  107. Namespace: req.Namespace,
  108. },
  109. }, *conditionSynced)
  110. return ctrl.Result{}, nil
  111. }
  112. log.Error(err, errGetES)
  113. syncCallsError.With(resourceLabels).Inc()
  114. return ctrl.Result{}, err
  115. }
  116. // See https://github.com/external-secrets/external-secrets/issues/3604
  117. // We fetch the ExternalSecret resource above, however the status subresource is inconsistent.
  118. // We have to explicitly fetch it, otherwise it may be missing and will cause
  119. // unexpected side effects.
  120. err = r.SubResource("status").Get(ctx, &externalSecret, &externalSecret)
  121. if err != nil {
  122. log.Error(err, "failed to get status subresource")
  123. return ctrl.Result{}, err
  124. }
  125. timeSinceLastRefresh := 0 * time.Second
  126. if !externalSecret.Status.RefreshTime.IsZero() {
  127. timeSinceLastRefresh = time.Since(externalSecret.Status.RefreshTime.Time)
  128. }
  129. // skip reconciliation if deletion timestamp is set on external secret
  130. if externalSecret.DeletionTimestamp != nil {
  131. log.Info("skipping as it is in deletion")
  132. return ctrl.Result{}, nil
  133. }
  134. // if extended metrics is enabled, refine the time series vector
  135. resourceLabels = ctrlmetrics.RefineLabels(resourceLabels, externalSecret.Labels)
  136. if shouldSkipClusterSecretStore(r, externalSecret) {
  137. log.Info("skipping cluster secret store as it is disabled")
  138. return ctrl.Result{}, nil
  139. }
  140. // skip when pointing to an unmanaged store
  141. skip, err := shouldSkipUnmanagedStore(ctx, req.Namespace, r, externalSecret)
  142. if skip {
  143. log.Info("skipping unmanaged store as it points to a unmanaged controllerClass")
  144. return ctrl.Result{}, nil
  145. }
  146. refreshInt := r.RequeueInterval
  147. if externalSecret.Spec.RefreshInterval != nil {
  148. refreshInt = externalSecret.Spec.RefreshInterval.Duration
  149. }
  150. // Target Secret Name should default to the ExternalSecret name if not explicitly specified
  151. secretName := externalSecret.Spec.Target.Name
  152. if secretName == "" {
  153. secretName = externalSecret.ObjectMeta.Name
  154. }
  155. // fetch external secret, we need to ensure that it exists, and it's hashmap corresponds
  156. var existingSecret v1.Secret
  157. err = r.Get(ctx, types.NamespacedName{
  158. Name: secretName,
  159. Namespace: externalSecret.Namespace,
  160. }, &existingSecret)
  161. if err != nil && !apierrors.IsNotFound(err) {
  162. log.Error(err, errGetExistingSecret)
  163. return ctrl.Result{}, err
  164. }
  165. // refresh should be skipped if
  166. // 1. resource generation hasn't changed
  167. // 2. refresh interval is 0
  168. // 3. if we're still within refresh-interval
  169. if !shouldRefresh(externalSecret) && isSecretValid(existingSecret) {
  170. refreshInt = (externalSecret.Spec.RefreshInterval.Duration - timeSinceLastRefresh) + 5*time.Second
  171. log.V(1).Info("skipping refresh", "rv", getResourceVersion(externalSecret), "nr", refreshInt.Seconds())
  172. return ctrl.Result{RequeueAfter: refreshInt}, nil
  173. }
  174. if !shouldReconcile(externalSecret) {
  175. log.V(1).Info("stopping reconciling", "rv", getResourceVersion(externalSecret))
  176. return ctrl.Result{}, nil
  177. }
  178. // patch status when done processing
  179. p := client.MergeFrom(externalSecret.DeepCopy())
  180. defer func() {
  181. err = r.Status().Patch(ctx, &externalSecret, p)
  182. if err != nil {
  183. log.Error(err, errPatchStatus)
  184. }
  185. }()
  186. secret := &v1.Secret{
  187. ObjectMeta: metav1.ObjectMeta{
  188. Name: secretName,
  189. Namespace: externalSecret.Namespace,
  190. },
  191. Immutable: &externalSecret.Spec.Target.Immutable,
  192. Data: make(map[string][]byte),
  193. }
  194. dataMap, err := r.getProviderSecretData(ctx, &externalSecret)
  195. if err != nil {
  196. r.markAsFailed(log, errGetSecretData, err, &externalSecret, syncCallsError.With(resourceLabels))
  197. return ctrl.Result{}, err
  198. }
  199. // secret data was not modified.
  200. if errors.Is(err, esv1beta1.NotModifiedErr) {
  201. log.Info("secret was not modified as a NotModified was returned by the provider")
  202. r.markAsDone(&externalSecret, start, log)
  203. return ctrl.Result{}, nil
  204. }
  205. // if no data was found we can delete the secret if needed.
  206. if len(dataMap) == 0 {
  207. switch externalSecret.Spec.Target.DeletionPolicy {
  208. // delete secret and return early.
  209. case esv1beta1.DeletionPolicyDelete:
  210. // safeguard that we only can delete secrets we own
  211. // this is also implemented in the es validation webhook
  212. if externalSecret.Spec.Target.CreationPolicy != esv1beta1.CreatePolicyOwner {
  213. err := fmt.Errorf(errInvalidCreatePolicy, externalSecret.Spec.Target.CreationPolicy)
  214. r.markAsFailed(log, errDeleteSecret, err, &externalSecret, syncCallsError.With(resourceLabels))
  215. return ctrl.Result{}, err
  216. }
  217. if err := r.Delete(ctx, secret); err != nil && !apierrors.IsNotFound(err) {
  218. r.markAsFailed(log, errDeleteSecret, err, &externalSecret, syncCallsError.With(resourceLabels))
  219. return ctrl.Result{}, err
  220. }
  221. conditionSynced := NewExternalSecretCondition(esv1beta1.ExternalSecretReady, v1.ConditionTrue, esv1beta1.ConditionReasonSecretDeleted, "secret deleted due to DeletionPolicy")
  222. SetExternalSecretCondition(&externalSecret, *conditionSynced)
  223. return ctrl.Result{RequeueAfter: refreshInt}, nil
  224. // In case provider secrets don't exist the kubernetes secret will be kept as-is.
  225. case esv1beta1.DeletionPolicyRetain:
  226. r.markAsDone(&externalSecret, start, log)
  227. return ctrl.Result{RequeueAfter: refreshInt}, nil
  228. // noop, handled below
  229. case esv1beta1.DeletionPolicyMerge:
  230. }
  231. }
  232. mutationFunc := func() error {
  233. if externalSecret.Spec.Target.CreationPolicy == esv1beta1.CreatePolicyOwner {
  234. err = controllerutil.SetControllerReference(&externalSecret, &secret.ObjectMeta, r.Scheme)
  235. if err != nil {
  236. return fmt.Errorf(errSetCtrlReference, err)
  237. }
  238. }
  239. if secret.Data == nil {
  240. secret.Data = make(map[string][]byte)
  241. }
  242. // diff existing keys
  243. keys, err := getManagedDataKeys(&existingSecret, externalSecret.Name)
  244. if err != nil {
  245. return err
  246. }
  247. // Sanitize data map for any updates on the ES
  248. for _, key := range keys {
  249. if dataMap[key] == nil {
  250. secret.Data[key] = nil
  251. // Sanitizing any templated / updated keys
  252. delete(secret.Data, key)
  253. }
  254. }
  255. err = r.applyTemplate(ctx, &externalSecret, secret, dataMap)
  256. if err != nil {
  257. return fmt.Errorf(errApplyTemplate, err)
  258. }
  259. if externalSecret.Spec.Target.CreationPolicy == esv1beta1.CreatePolicyOwner {
  260. lblValue := utils.ObjectHash(fmt.Sprintf("%v/%v", externalSecret.Namespace, externalSecret.Name))
  261. secret.Labels[esv1beta1.LabelOwner] = lblValue
  262. }
  263. secret.Annotations[esv1beta1.AnnotationDataHash] = r.computeDataHashAnnotation(&existingSecret, secret)
  264. return nil
  265. }
  266. switch externalSecret.Spec.Target.CreationPolicy { //nolint:exhaustive
  267. case esv1beta1.CreatePolicyMerge:
  268. err = r.patchSecret(ctx, secret, mutationFunc, &externalSecret)
  269. if err == nil {
  270. externalSecret.Status.Binding = v1.LocalObjectReference{Name: secret.Name}
  271. }
  272. case esv1beta1.CreatePolicyNone:
  273. log.V(1).Info("secret creation skipped due to creationPolicy=None")
  274. err = nil
  275. default:
  276. var created bool
  277. created, err = r.createOrUpdateSecret(ctx, secret, mutationFunc, &externalSecret)
  278. if err == nil {
  279. externalSecret.Status.Binding = v1.LocalObjectReference{Name: secret.Name}
  280. }
  281. // cleanup orphaned secrets
  282. if created {
  283. delErr := deleteOrphanedSecrets(ctx, r.Client, &externalSecret)
  284. if delErr != nil {
  285. msg := fmt.Sprintf("failed to clean up orphaned secrets: %v", delErr)
  286. r.markAsFailed(log, msg, delErr, &externalSecret, syncCallsError.With(resourceLabels))
  287. return ctrl.Result{}, delErr
  288. }
  289. }
  290. }
  291. if err != nil {
  292. r.markAsFailed(log, errUpdateSecret, err, &externalSecret, syncCallsError.With(resourceLabels))
  293. return ctrl.Result{}, err
  294. }
  295. r.markAsDone(&externalSecret, start, log)
  296. return ctrl.Result{
  297. RequeueAfter: refreshInt,
  298. }, nil
  299. }
  300. func (r *Reconciler) markAsDone(externalSecret *esv1beta1.ExternalSecret, start time.Time, log logr.Logger) {
  301. conditionSynced := NewExternalSecretCondition(esv1beta1.ExternalSecretReady, v1.ConditionTrue, esv1beta1.ConditionReasonSecretSynced, "Secret was synced")
  302. currCond := GetExternalSecretCondition(externalSecret.Status, esv1beta1.ExternalSecretReady)
  303. SetExternalSecretCondition(externalSecret, *conditionSynced)
  304. externalSecret.Status.RefreshTime = metav1.NewTime(start)
  305. externalSecret.Status.SyncedResourceVersion = getResourceVersion(*externalSecret)
  306. if currCond == nil || currCond.Status != conditionSynced.Status {
  307. log.Info("reconciled secret") // Log once if on success in any verbosity
  308. } else {
  309. log.V(1).Info("reconciled secret") // Log all reconciliation cycles if higher verbosity applied
  310. }
  311. }
  312. func (r *Reconciler) markAsFailed(log logr.Logger, msg string, err error, externalSecret *esv1beta1.ExternalSecret, counter prometheus.Counter) {
  313. log.Error(err, msg)
  314. r.recorder.Event(externalSecret, v1.EventTypeWarning, esv1beta1.ReasonUpdateFailed, err.Error())
  315. conditionSynced := NewExternalSecretCondition(esv1beta1.ExternalSecretReady, v1.ConditionFalse, esv1beta1.ConditionReasonSecretSyncedError, msg)
  316. SetExternalSecretCondition(externalSecret, *conditionSynced)
  317. counter.Inc()
  318. }
  319. func deleteOrphanedSecrets(ctx context.Context, cl client.Client, externalSecret *esv1beta1.ExternalSecret) error {
  320. secretList := v1.SecretList{}
  321. lblValue := utils.ObjectHash(fmt.Sprintf("%v/%v", externalSecret.Namespace, externalSecret.Name))
  322. ls := &metav1.LabelSelector{
  323. MatchLabels: map[string]string{
  324. esv1beta1.LabelOwner: lblValue,
  325. },
  326. }
  327. labelSelector, err := metav1.LabelSelectorAsSelector(ls)
  328. if err != nil {
  329. return err
  330. }
  331. err = cl.List(ctx, &secretList, &client.ListOptions{LabelSelector: labelSelector})
  332. if err != nil {
  333. return err
  334. }
  335. for key, secret := range secretList.Items {
  336. if externalSecret.Spec.Target.Name != "" && secret.Name != externalSecret.Spec.Target.Name {
  337. err = cl.Delete(ctx, &secretList.Items[key])
  338. if err != nil {
  339. return err
  340. }
  341. }
  342. }
  343. return nil
  344. }
  345. func (r *Reconciler) createOrUpdateSecret(ctx context.Context, secret *v1.Secret, mutationFunc func() error, es *esv1beta1.ExternalSecret) (bool, error) {
  346. fqdn := fmt.Sprintf(fieldOwnerTemplate, es.Name)
  347. key := client.ObjectKeyFromObject(secret)
  348. if err := r.Client.Get(ctx, key, secret); err != nil {
  349. if !apierrors.IsNotFound(err) {
  350. return false, err
  351. }
  352. if err := mutationFunc(); err != nil {
  353. return false, err
  354. }
  355. // Setting Field Owner even for CreationPolicy==Create
  356. if err := r.Client.Create(ctx, secret, client.FieldOwner(fqdn)); err != nil {
  357. return false, err
  358. }
  359. r.recorder.Event(es, v1.EventTypeNormal, esv1beta1.ReasonCreated, "Created Secret")
  360. return true, nil
  361. }
  362. existing := secret.DeepCopyObject()
  363. if err := mutationFunc(); err != nil {
  364. return false, err
  365. }
  366. if equality.Semantic.DeepEqual(existing, secret) {
  367. return false, nil
  368. }
  369. if err := r.Client.Update(ctx, secret, client.FieldOwner(fqdn)); err != nil {
  370. return false, err
  371. }
  372. r.recorder.Event(es, v1.EventTypeNormal, esv1beta1.ReasonUpdated, "Updated Secret")
  373. return false, nil
  374. }
  375. func (r *Reconciler) patchSecret(ctx context.Context, secret *v1.Secret, mutationFunc func() error, es *esv1beta1.ExternalSecret) error {
  376. fqdn := fmt.Sprintf(fieldOwnerTemplate, es.Name)
  377. err := r.Client.Get(ctx, client.ObjectKeyFromObject(secret), secret.DeepCopy())
  378. if apierrors.IsNotFound(err) {
  379. return fmt.Errorf(errPolicyMergeNotFound, secret.Name)
  380. }
  381. if err != nil {
  382. return fmt.Errorf(errPolicyMergeGetSecret, secret.Name, err)
  383. }
  384. existing := secret.DeepCopyObject()
  385. if err = mutationFunc(); err != nil {
  386. return fmt.Errorf(errPolicyMergeMutate, secret.Name, err)
  387. }
  388. // GVK is missing in the Secret, see:
  389. // https://github.com/kubernetes-sigs/controller-runtime/issues/526
  390. // https://github.com/kubernetes-sigs/controller-runtime/issues/1517
  391. // https://github.com/kubernetes/kubernetes/issues/80609
  392. // we need to manually set it before doing a Patch() as it depends on the GVK
  393. gvks, unversioned, err := r.Scheme.ObjectKinds(secret)
  394. if err != nil {
  395. return err
  396. }
  397. if !unversioned && len(gvks) == 1 {
  398. secret.SetGroupVersionKind(gvks[0])
  399. }
  400. if equality.Semantic.DeepEqual(existing, secret) {
  401. return nil
  402. }
  403. // Cleaning up Managed fields manually as to keep patch coherence
  404. secret.ObjectMeta.ManagedFields = nil
  405. // we're not able to resolve conflicts so we force ownership
  406. // see: https://kubernetes.io/docs/reference/using-api/server-side-apply/#using-server-side-apply-in-a-controller
  407. if err := r.Client.Patch(ctx, secret, client.Apply, client.FieldOwner(fqdn), client.ForceOwnership); err != nil {
  408. return fmt.Errorf(errPolicyMergePatch, secret.Name, err)
  409. }
  410. r.recorder.Event(es, v1.EventTypeNormal, esv1beta1.ReasonUpdated, "Updated Secret")
  411. return nil
  412. }
  413. func getManagedDataKeys(secret *v1.Secret, fieldOwner string) ([]string, error) {
  414. return getManagedFieldKeys(secret, fieldOwner, func(fields map[string]any) []string {
  415. dataFields := fields["f:data"]
  416. if dataFields == nil {
  417. return nil
  418. }
  419. df, ok := dataFields.(map[string]any)
  420. if !ok {
  421. return nil
  422. }
  423. return slices.Collect(maps.Keys(df))
  424. })
  425. }
  426. func getManagedFieldKeys(
  427. secret *v1.Secret,
  428. fieldOwner string,
  429. process func(fields map[string]any) []string,
  430. ) ([]string, error) {
  431. fqdn := fmt.Sprintf(fieldOwnerTemplate, fieldOwner)
  432. var keys []string
  433. for _, v := range secret.ObjectMeta.ManagedFields {
  434. if v.Manager != fqdn {
  435. continue
  436. }
  437. fields := make(map[string]any)
  438. err := json.Unmarshal(v.FieldsV1.Raw, &fields)
  439. if err != nil {
  440. return nil, fmt.Errorf("error unmarshaling managed fields: %w", err)
  441. }
  442. for _, key := range process(fields) {
  443. if key == "." {
  444. continue
  445. }
  446. keys = append(keys, strings.TrimPrefix(key, "f:"))
  447. }
  448. }
  449. return keys, nil
  450. }
  451. func getResourceVersion(es esv1beta1.ExternalSecret) string {
  452. return fmt.Sprintf("%d-%s", es.ObjectMeta.GetGeneration(), hashMeta(es.ObjectMeta))
  453. }
  454. func hashMeta(m metav1.ObjectMeta) string {
  455. type meta struct {
  456. annotations map[string]string
  457. labels map[string]string
  458. }
  459. return utils.ObjectHash(meta{
  460. annotations: m.Annotations,
  461. labels: m.Labels,
  462. })
  463. }
  464. func shouldSkipClusterSecretStore(r *Reconciler, es esv1beta1.ExternalSecret) bool {
  465. return !r.ClusterSecretStoreEnabled && es.Spec.SecretStoreRef.Kind == esv1beta1.ClusterSecretStoreKind
  466. }
  467. // shouldSkipUnmanagedStore iterates over all secretStore references in the externalSecret spec,
  468. // fetches the store and evaluates the controllerClass property.
  469. // Returns true if any storeRef points to store with a non-matching controllerClass.
  470. func shouldSkipUnmanagedStore(ctx context.Context, namespace string, r *Reconciler, es esv1beta1.ExternalSecret) (bool, error) {
  471. var storeList []esv1beta1.SecretStoreRef
  472. if es.Spec.SecretStoreRef.Name != "" {
  473. storeList = append(storeList, es.Spec.SecretStoreRef)
  474. }
  475. for _, ref := range es.Spec.Data {
  476. if ref.SourceRef != nil {
  477. storeList = append(storeList, ref.SourceRef.SecretStoreRef)
  478. }
  479. }
  480. for _, ref := range es.Spec.DataFrom {
  481. if ref.SourceRef != nil && ref.SourceRef.SecretStoreRef != nil {
  482. storeList = append(storeList, *ref.SourceRef.SecretStoreRef)
  483. }
  484. // verify that generator's controllerClass matches
  485. if ref.SourceRef != nil && ref.SourceRef.GeneratorRef != nil {
  486. genDef, err := r.getGeneratorDefinition(ctx, namespace, ref.SourceRef.GeneratorRef)
  487. if err != nil {
  488. return false, err
  489. }
  490. skipGenerator, err := shouldSkipGenerator(r, genDef)
  491. if err != nil {
  492. return false, err
  493. }
  494. if skipGenerator {
  495. return true, nil
  496. }
  497. }
  498. }
  499. for _, ref := range storeList {
  500. var store esv1beta1.GenericStore
  501. switch ref.Kind {
  502. case esv1beta1.SecretStoreKind, "":
  503. store = &esv1beta1.SecretStore{}
  504. case esv1beta1.ClusterSecretStoreKind:
  505. store = &esv1beta1.ClusterSecretStore{}
  506. namespace = ""
  507. }
  508. err := r.Client.Get(ctx, types.NamespacedName{
  509. Name: ref.Name,
  510. Namespace: namespace,
  511. }, store)
  512. if err != nil {
  513. return false, err
  514. }
  515. class := store.GetSpec().Controller
  516. if class != "" && class != r.ControllerClass {
  517. return true, nil
  518. }
  519. }
  520. return false, nil
  521. }
  522. func shouldRefresh(es esv1beta1.ExternalSecret) bool {
  523. // refresh if resource version changed
  524. if es.Status.SyncedResourceVersion != getResourceVersion(es) {
  525. return true
  526. }
  527. // skip refresh if refresh interval is 0
  528. if es.Spec.RefreshInterval.Duration == 0 && es.Status.SyncedResourceVersion != "" {
  529. return false
  530. }
  531. if es.Status.RefreshTime.IsZero() {
  532. return true
  533. }
  534. return es.Status.RefreshTime.Add(es.Spec.RefreshInterval.Duration).Before(time.Now())
  535. }
  536. func shouldReconcile(es esv1beta1.ExternalSecret) bool {
  537. if es.Spec.Target.Immutable && hasSyncedCondition(es) {
  538. return false
  539. }
  540. return true
  541. }
  542. func hasSyncedCondition(es esv1beta1.ExternalSecret) bool {
  543. for _, condition := range es.Status.Conditions {
  544. if condition.Reason == "SecretSynced" {
  545. return true
  546. }
  547. }
  548. return false
  549. }
  550. // isSecretValid checks if the secret exists, and it's data is consistent with the calculated hash.
  551. func isSecretValid(existingSecret v1.Secret) bool {
  552. // if target secret doesn't exist, or annotations as not set, we need to refresh
  553. if existingSecret.UID == "" || existingSecret.Annotations == nil {
  554. return false
  555. }
  556. // if the calculated hash is different from the calculation, then it's invalid
  557. if existingSecret.Annotations[esv1beta1.AnnotationDataHash] != utils.ObjectHash(existingSecret.Data) {
  558. return false
  559. }
  560. return true
  561. }
  562. // computeDataHashAnnotation generate a hash of the secret data combining the old key with the new keys to add or override.
  563. func (r *Reconciler) computeDataHashAnnotation(existing, secret *v1.Secret) string {
  564. data := make(map[string][]byte)
  565. maps.Insert(data, maps.All(existing.Data))
  566. maps.Insert(data, maps.All(secret.Data))
  567. return utils.ObjectHash(data)
  568. }
  569. // SetupWithManager returns a new controller builder that will be started by the provided Manager.
  570. func (r *Reconciler) SetupWithManager(mgr ctrl.Manager, opts controller.Options) error {
  571. r.recorder = mgr.GetEventRecorderFor("external-secrets")
  572. // Index .Spec.Target.Name to reconcile ExternalSecrets effectively when secrets have changed
  573. if err := mgr.GetFieldIndexer().IndexField(context.Background(), &esv1beta1.ExternalSecret{}, externalSecretSecretNameKey, func(obj client.Object) []string {
  574. es := obj.(*esv1beta1.ExternalSecret)
  575. if name := es.Spec.Target.Name; name != "" {
  576. return []string{name}
  577. }
  578. return []string{es.Name}
  579. }); err != nil {
  580. return err
  581. }
  582. return ctrl.NewControllerManagedBy(mgr).
  583. WithOptions(opts).
  584. For(&esv1beta1.ExternalSecret{}).
  585. // Cannot use Owns since the controller does not set owner reference when creation policy is not Owner
  586. Watches(
  587. &v1.Secret{},
  588. handler.EnqueueRequestsFromMapFunc(r.findObjectsForSecret),
  589. builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}),
  590. builder.OnlyMetadata,
  591. ).
  592. Complete(r)
  593. }
  594. func (r *Reconciler) findObjectsForSecret(ctx context.Context, secret client.Object) []reconcile.Request {
  595. var externalSecrets esv1beta1.ExternalSecretList
  596. err := r.List(
  597. ctx,
  598. &externalSecrets,
  599. client.InNamespace(secret.GetNamespace()),
  600. client.MatchingFields{externalSecretSecretNameKey: secret.GetName()},
  601. )
  602. if err != nil {
  603. return []reconcile.Request{}
  604. }
  605. requests := make([]reconcile.Request, len(externalSecrets.Items))
  606. for i := range externalSecrets.Items {
  607. requests[i] = reconcile.Request{
  608. NamespacedName: types.NamespacedName{
  609. Name: externalSecrets.Items[i].GetName(),
  610. Namespace: externalSecrets.Items[i].GetNamespace(),
  611. },
  612. }
  613. }
  614. return requests
  615. }