externalsecret_controller.go 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677
  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. "strings"
  19. "time"
  20. "github.com/go-logr/logr"
  21. "github.com/prometheus/client_golang/prometheus"
  22. "golang.org/x/exp/slices"
  23. v1 "k8s.io/api/core/v1"
  24. "k8s.io/apimachinery/pkg/api/equality"
  25. apierrors "k8s.io/apimachinery/pkg/api/errors"
  26. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  27. "k8s.io/apimachinery/pkg/runtime"
  28. "k8s.io/apimachinery/pkg/types"
  29. "k8s.io/client-go/tools/record"
  30. ctrl "sigs.k8s.io/controller-runtime"
  31. "sigs.k8s.io/controller-runtime/pkg/builder"
  32. "sigs.k8s.io/controller-runtime/pkg/client"
  33. "sigs.k8s.io/controller-runtime/pkg/controller"
  34. "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
  35. esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
  36. "github.com/external-secrets/external-secrets/pkg/controllers/secretstore"
  37. // Loading registered providers.
  38. _ "github.com/external-secrets/external-secrets/pkg/provider/register"
  39. "github.com/external-secrets/external-secrets/pkg/utils"
  40. )
  41. const (
  42. requeueAfter = time.Second * 30
  43. fieldOwnerTemplate = "externalsecrets.external-secrets.io/%v"
  44. errGetES = "could not get ExternalSecret"
  45. errConvert = "could not apply conversion strategy to keys: %v"
  46. errDecode = "could not apply decoding strategy to %v[%d]: %v"
  47. errRewrite = "could not rewrite spec.dataFrom[%d]: %v"
  48. 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)"
  49. errUpdateSecret = "could not update Secret"
  50. errPatchStatus = "unable to patch status"
  51. errGetSecretStore = "could not get SecretStore %q, %w"
  52. errSecretStoreNotReady = "the desired SecretStore %s is not ready"
  53. errGetClusterSecretStore = "could not get ClusterSecretStore %q, %w"
  54. errStoreRef = "could not get store reference"
  55. errStoreUsability = "could not use store reference"
  56. errStoreProvider = "could not get store provider"
  57. errStoreClient = "could not get provider client"
  58. errGetExistingSecret = "could not get existing secret: %w"
  59. errClusterStoreMismatch = "using cluster store %q is not allowed from namespace %q: denied by spec.condition"
  60. errCloseStoreClient = "could not close provider client"
  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. errTplCMMissingKey = "error in configmap %s: missing key %s"
  73. errTplSecMissingKey = "error in secret %s: missing key %s"
  74. )
  75. // Reconciler reconciles a ExternalSecret object.
  76. type Reconciler struct {
  77. client.Client
  78. Log logr.Logger
  79. Scheme *runtime.Scheme
  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. syncCallsMetricLabels := prometheus.Labels{"name": req.Name, "namespace": req.Namespace}
  92. start := time.Now()
  93. defer externalSecretReconcileDuration.With(prometheus.Labels{
  94. "name": req.Name,
  95. "namespace": req.Namespace,
  96. }).Set(float64(time.Since(start)))
  97. var externalSecret esv1beta1.ExternalSecret
  98. err := r.Get(ctx, req.NamespacedName, &externalSecret)
  99. if apierrors.IsNotFound(err) {
  100. syncCallsTotal.With(syncCallsMetricLabels).Inc()
  101. conditionSynced := NewExternalSecretCondition(esv1beta1.ExternalSecretDeleted, v1.ConditionFalse, esv1beta1.ConditionReasonSecretDeleted, "Secret was deleted")
  102. SetExternalSecretCondition(&esv1beta1.ExternalSecret{
  103. ObjectMeta: metav1.ObjectMeta{
  104. Name: req.Name,
  105. Namespace: req.Namespace,
  106. },
  107. }, *conditionSynced)
  108. return ctrl.Result{}, nil
  109. } else if err != nil {
  110. log.Error(err, errGetES)
  111. syncCallsError.With(syncCallsMetricLabels).Inc()
  112. return ctrl.Result{}, nil
  113. }
  114. if shouldSkipClusterSecretStore(r, externalSecret) {
  115. log.Info("skipping cluster secret store as it is disabled")
  116. return ctrl.Result{}, nil
  117. }
  118. // patch status when done processing
  119. p := client.MergeFrom(externalSecret.DeepCopy())
  120. defer func() {
  121. err = r.Status().Patch(ctx, &externalSecret, p)
  122. if err != nil {
  123. log.Error(err, errPatchStatus)
  124. }
  125. }()
  126. store, err := r.getStore(ctx, &externalSecret)
  127. if err != nil {
  128. log.Error(err, errStoreRef)
  129. r.recorder.Event(&externalSecret, v1.EventTypeWarning, esv1beta1.ReasonInvalidStoreRef, err.Error())
  130. conditionSynced := NewExternalSecretCondition(esv1beta1.ExternalSecretReady, v1.ConditionFalse, esv1beta1.ConditionReasonSecretSyncedError, errStoreRef)
  131. SetExternalSecretCondition(&externalSecret, *conditionSynced)
  132. syncCallsError.With(syncCallsMetricLabels).Inc()
  133. return ctrl.Result{}, err
  134. }
  135. log = log.WithValues("SecretStore", store.GetNamespacedName())
  136. // check if store should be handled by this controller instance
  137. if !secretstore.ShouldProcessStore(store, r.ControllerClass) {
  138. log.Info("skipping unmanaged store")
  139. return ctrl.Result{}, nil
  140. }
  141. // when using ClusterSecretStore, validate the ClusterSecretStore namespace conditions
  142. shouldProcess, err := r.ShouldProcessSecret(ctx, store, externalSecret.Namespace)
  143. if err != nil || !shouldProcess {
  144. if err == nil && !shouldProcess {
  145. err = fmt.Errorf(errClusterStoreMismatch, store.GetName(), externalSecret.Namespace)
  146. }
  147. log.Error(err, err.Error())
  148. r.recorder.Event(&externalSecret, v1.EventTypeWarning, esv1beta1.ReasonInvalidStoreRef, err.Error())
  149. conditionSynced := NewExternalSecretCondition(esv1beta1.ExternalSecretReady, v1.ConditionFalse, esv1beta1.ConditionReasonSecretSyncedError, errStoreUsability)
  150. SetExternalSecretCondition(&externalSecret, *conditionSynced)
  151. syncCallsError.With(syncCallsMetricLabels).Inc()
  152. return ctrl.Result{}, err
  153. }
  154. if r.EnableFloodGate {
  155. if err = assertStoreIsUsable(store); err != nil {
  156. log.Error(err, errStoreUsability)
  157. r.recorder.Event(&externalSecret, v1.EventTypeWarning, esv1beta1.ReasonUnavailableStore, err.Error())
  158. conditionSynced := NewExternalSecretCondition(esv1beta1.ExternalSecretReady, v1.ConditionFalse, esv1beta1.ConditionReasonSecretSyncedError, errStoreUsability)
  159. SetExternalSecretCondition(&externalSecret, *conditionSynced)
  160. syncCallsError.With(syncCallsMetricLabels).Inc()
  161. return ctrl.Result{}, err
  162. }
  163. }
  164. storeProvider, err := esv1beta1.GetProvider(store)
  165. if err != nil {
  166. log.Error(err, errStoreProvider)
  167. syncCallsError.With(syncCallsMetricLabels).Inc()
  168. return ctrl.Result{RequeueAfter: requeueAfter}, nil
  169. }
  170. refreshInt := r.RequeueInterval
  171. if externalSecret.Spec.RefreshInterval != nil {
  172. refreshInt = externalSecret.Spec.RefreshInterval.Duration
  173. }
  174. // Target Secret Name should default to the ExternalSecret name if not explicitly specified
  175. secretName := externalSecret.Spec.Target.Name
  176. if secretName == "" {
  177. secretName = externalSecret.ObjectMeta.Name
  178. }
  179. // fetch external secret, we need to ensure that it exists, and it's hashmap corresponds
  180. var existingSecret v1.Secret
  181. err = r.Get(ctx, types.NamespacedName{
  182. Name: secretName,
  183. Namespace: externalSecret.Namespace,
  184. }, &existingSecret)
  185. if err != nil && !apierrors.IsNotFound(err) {
  186. log.Error(err, errGetExistingSecret)
  187. }
  188. // refresh should be skipped if
  189. // 1. resource generation hasn't changed
  190. // 2. refresh interval is 0
  191. // 3. if we're still within refresh-interval
  192. if !shouldRefresh(externalSecret) && isSecretValid(existingSecret) {
  193. log.V(1).Info("skipping refresh", "rv", getResourceVersion(externalSecret))
  194. return ctrl.Result{RequeueAfter: refreshInt}, nil
  195. }
  196. if !shouldReconcile(externalSecret) {
  197. log.V(1).Info("stopping reconciling", "rv", getResourceVersion(externalSecret))
  198. return ctrl.Result{
  199. RequeueAfter: 0,
  200. Requeue: false,
  201. }, nil
  202. }
  203. // secret client is created only if we are going to refresh
  204. // this skip an unnecessary check/request in the case we are not going to do anything
  205. secretClient, err := storeProvider.NewClient(ctx, store, r.Client, req.Namespace)
  206. if err != nil {
  207. log.Error(err, errStoreClient)
  208. conditionSynced := NewExternalSecretCondition(esv1beta1.ExternalSecretReady, v1.ConditionFalse, esv1beta1.ConditionReasonSecretSyncedError, errStoreClient)
  209. SetExternalSecretCondition(&externalSecret, *conditionSynced)
  210. r.recorder.Event(&externalSecret, v1.EventTypeWarning, esv1beta1.ReasonProviderClientConfig, err.Error())
  211. syncCallsError.With(syncCallsMetricLabels).Inc()
  212. return ctrl.Result{RequeueAfter: requeueAfter}, nil
  213. }
  214. defer func() {
  215. err = secretClient.Close(ctx)
  216. if err != nil {
  217. log.Error(err, errCloseStoreClient)
  218. }
  219. }()
  220. secret := &v1.Secret{
  221. ObjectMeta: metav1.ObjectMeta{
  222. Name: secretName,
  223. Namespace: externalSecret.Namespace,
  224. },
  225. Immutable: &externalSecret.Spec.Target.Immutable,
  226. Data: make(map[string][]byte),
  227. }
  228. dataMap, err := r.getProviderSecretData(ctx, secretClient, &externalSecret)
  229. if err != nil {
  230. log.Error(err, errGetSecretData)
  231. r.recorder.Event(&externalSecret, v1.EventTypeWarning, esv1beta1.ReasonUpdateFailed, err.Error())
  232. conditionSynced := NewExternalSecretCondition(esv1beta1.ExternalSecretReady, v1.ConditionFalse, esv1beta1.ConditionReasonSecretSyncedError, errGetSecretData)
  233. SetExternalSecretCondition(&externalSecret, *conditionSynced)
  234. syncCallsError.With(syncCallsMetricLabels).Inc()
  235. return ctrl.Result{RequeueAfter: requeueAfter}, nil
  236. }
  237. // if no data was found we can delete the secret if needed.
  238. if len(dataMap) == 0 {
  239. switch externalSecret.Spec.Target.DeletionPolicy {
  240. // delete secret and return early.
  241. case esv1beta1.DeletionPolicyDelete:
  242. // safeguard that we only can delete secrets we own
  243. // this is also implemented in the es validation webhook
  244. if externalSecret.Spec.Target.CreationPolicy != esv1beta1.CreatePolicyOwner {
  245. err := fmt.Errorf(errInvalidCreatePolicy, externalSecret.Spec.Target.CreationPolicy)
  246. log.Error(err, errDeleteSecret)
  247. r.recorder.Event(&externalSecret, v1.EventTypeWarning, esv1beta1.ReasonUpdateFailed, err.Error())
  248. conditionSynced := NewExternalSecretCondition(esv1beta1.ExternalSecretReady, v1.ConditionFalse, esv1beta1.ConditionReasonSecretSyncedError, errDeleteSecret)
  249. SetExternalSecretCondition(&externalSecret, *conditionSynced)
  250. syncCallsError.With(syncCallsMetricLabels).Inc()
  251. return ctrl.Result{RequeueAfter: requeueAfter}, nil
  252. }
  253. err = r.Delete(ctx, secret)
  254. if err != nil && !apierrors.IsNotFound(err) {
  255. log.Error(err, errDeleteSecret)
  256. r.recorder.Event(&externalSecret, v1.EventTypeWarning, esv1beta1.ReasonUpdateFailed, err.Error())
  257. conditionSynced := NewExternalSecretCondition(esv1beta1.ExternalSecretReady, v1.ConditionFalse, esv1beta1.ConditionReasonSecretSyncedError, errDeleteSecret)
  258. SetExternalSecretCondition(&externalSecret, *conditionSynced)
  259. syncCallsError.With(syncCallsMetricLabels).Inc()
  260. }
  261. conditionSynced := NewExternalSecretCondition(esv1beta1.ExternalSecretReady, v1.ConditionTrue, esv1beta1.ConditionReasonSecretDeleted, "secret deleted due to DeletionPolicy")
  262. SetExternalSecretCondition(&externalSecret, *conditionSynced)
  263. return ctrl.Result{RequeueAfter: requeueAfter}, nil
  264. case esv1beta1.DeletionPolicyMerge:
  265. // noop, handled below
  266. // In case provider secrets don't exist the kubernetes secret will be kept as-is.
  267. case esv1beta1.DeletionPolicyRetain:
  268. return ctrl.Result{RequeueAfter: requeueAfter}, nil
  269. }
  270. }
  271. mutationFunc := func() error {
  272. if externalSecret.Spec.Target.CreationPolicy == esv1beta1.CreatePolicyOwner {
  273. err = controllerutil.SetControllerReference(&externalSecret, &secret.ObjectMeta, r.Scheme)
  274. if err != nil {
  275. return fmt.Errorf(errSetCtrlReference, err)
  276. }
  277. }
  278. if secret.Data == nil {
  279. secret.Data = make(map[string][]byte)
  280. }
  281. err = r.applyTemplate(ctx, &externalSecret, secret, dataMap)
  282. if err != nil {
  283. return fmt.Errorf(errApplyTemplate, err)
  284. }
  285. // diff existing keys
  286. if externalSecret.Spec.Target.DeletionPolicy == esv1beta1.DeletionPolicyMerge {
  287. keys, err := getManagedKeys(&existingSecret, externalSecret.Name)
  288. if err != nil {
  289. return err
  290. }
  291. for _, key := range keys {
  292. if dataMap[key] == nil {
  293. secret.Data[key] = nil
  294. }
  295. }
  296. }
  297. return nil
  298. }
  299. //nolint
  300. switch externalSecret.Spec.Target.CreationPolicy {
  301. case esv1beta1.CreatePolicyMerge:
  302. err = patchSecret(ctx, r.Client, r.Scheme, secret, mutationFunc, externalSecret.Name)
  303. case esv1beta1.CreatePolicyNone:
  304. log.V(1).Info("secret creation skipped due to creationPolicy=None")
  305. err = nil
  306. default:
  307. _, err = ctrl.CreateOrUpdate(ctx, r.Client, secret, mutationFunc)
  308. }
  309. if err != nil {
  310. log.Error(err, errUpdateSecret)
  311. r.recorder.Event(&externalSecret, v1.EventTypeWarning, esv1beta1.ReasonUpdateFailed, err.Error())
  312. conditionSynced := NewExternalSecretCondition(esv1beta1.ExternalSecretReady, v1.ConditionFalse, esv1beta1.ConditionReasonSecretSyncedError, errUpdateSecret)
  313. SetExternalSecretCondition(&externalSecret, *conditionSynced)
  314. syncCallsError.With(syncCallsMetricLabels).Inc()
  315. return ctrl.Result{}, err
  316. }
  317. r.recorder.Event(&externalSecret, v1.EventTypeNormal, esv1beta1.ReasonUpdated, "Updated Secret")
  318. conditionSynced := NewExternalSecretCondition(esv1beta1.ExternalSecretReady, v1.ConditionTrue, esv1beta1.ConditionReasonSecretSynced, "Secret was synced")
  319. currCond := GetExternalSecretCondition(externalSecret.Status, esv1beta1.ExternalSecretReady)
  320. SetExternalSecretCondition(&externalSecret, *conditionSynced)
  321. externalSecret.Status.RefreshTime = metav1.NewTime(time.Now())
  322. externalSecret.Status.SyncedResourceVersion = getResourceVersion(externalSecret)
  323. syncCallsTotal.With(syncCallsMetricLabels).Inc()
  324. if currCond == nil || currCond.Status != conditionSynced.Status {
  325. log.Info("reconciled secret") // Log once if on success in any verbosity
  326. } else {
  327. log.V(1).Info("reconciled secret") // Log all reconciliation cycles if higher verbosity applied
  328. }
  329. return ctrl.Result{
  330. RequeueAfter: refreshInt,
  331. }, nil
  332. }
  333. func patchSecret(ctx context.Context, c client.Client, scheme *runtime.Scheme, secret *v1.Secret, mutationFunc func() error, fieldOwner string) error {
  334. fqdn := fmt.Sprintf(fieldOwnerTemplate, fieldOwner)
  335. err := c.Get(ctx, client.ObjectKeyFromObject(secret), secret.DeepCopy())
  336. if apierrors.IsNotFound(err) {
  337. return fmt.Errorf(errPolicyMergeNotFound, secret.Name)
  338. }
  339. if err != nil {
  340. return fmt.Errorf(errPolicyMergeGetSecret, secret.Name, err)
  341. }
  342. existing := secret.DeepCopyObject()
  343. err = mutationFunc()
  344. if err != nil {
  345. return fmt.Errorf(errPolicyMergeMutate, secret.Name, err)
  346. }
  347. // GVK is missing in the Secret, see:
  348. // https://github.com/kubernetes-sigs/controller-runtime/issues/526
  349. // https://github.com/kubernetes-sigs/controller-runtime/issues/1517
  350. // https://github.com/kubernetes/kubernetes/issues/80609
  351. // we need to manually set it before doing a Patch() as it depends on the GVK
  352. gvks, unversioned, err := scheme.ObjectKinds(secret)
  353. if err != nil {
  354. return err
  355. }
  356. if !unversioned && len(gvks) == 1 {
  357. secret.SetGroupVersionKind(gvks[0])
  358. }
  359. if equality.Semantic.DeepEqual(existing, secret) {
  360. return nil
  361. }
  362. // we're not able to resolve conflicts so we force ownership
  363. // see: https://kubernetes.io/docs/reference/using-api/server-side-apply/#using-server-side-apply-in-a-controller
  364. err = c.Patch(ctx, secret, client.Apply, client.FieldOwner(fqdn), client.ForceOwnership)
  365. if err != nil {
  366. return fmt.Errorf(errPolicyMergePatch, secret.Name, err)
  367. }
  368. return nil
  369. }
  370. func getManagedKeys(secret *v1.Secret, fieldOwner string) ([]string, error) {
  371. fqdn := fmt.Sprintf(fieldOwnerTemplate, fieldOwner)
  372. var keys []string
  373. for _, v := range secret.ObjectMeta.ManagedFields {
  374. if v.Manager != fqdn {
  375. continue
  376. }
  377. fields := make(map[string]interface{})
  378. err := json.Unmarshal(v.FieldsV1.Raw, &fields)
  379. if err != nil {
  380. return nil, fmt.Errorf("error unmarshaling managed fields: %w", err)
  381. }
  382. dataFields := fields["f:data"]
  383. if dataFields == nil {
  384. continue
  385. }
  386. df, ok := dataFields.(map[string]string)
  387. if !ok {
  388. continue
  389. }
  390. for k := range df {
  391. if k == "." {
  392. continue
  393. }
  394. keys = append(keys, strings.TrimPrefix(k, "f:"))
  395. }
  396. }
  397. return keys, nil
  398. }
  399. func getResourceVersion(es esv1beta1.ExternalSecret) string {
  400. return fmt.Sprintf("%d-%s", es.ObjectMeta.GetGeneration(), hashMeta(es.ObjectMeta))
  401. }
  402. func hashMeta(m metav1.ObjectMeta) string {
  403. type meta struct {
  404. annotations map[string]string
  405. labels map[string]string
  406. }
  407. return utils.ObjectHash(meta{
  408. annotations: m.Annotations,
  409. labels: m.Labels,
  410. })
  411. }
  412. func shouldSkipClusterSecretStore(r *Reconciler, es esv1beta1.ExternalSecret) bool {
  413. return !r.ClusterSecretStoreEnabled && es.Spec.SecretStoreRef.Kind == esv1beta1.ClusterSecretStoreKind
  414. }
  415. func shouldRefresh(es esv1beta1.ExternalSecret) bool {
  416. // refresh if resource version changed
  417. if es.Status.SyncedResourceVersion != getResourceVersion(es) {
  418. return true
  419. }
  420. // skip refresh if refresh interval is 0
  421. if es.Spec.RefreshInterval.Duration == 0 && es.Status.SyncedResourceVersion != "" {
  422. return false
  423. }
  424. if es.Status.RefreshTime.IsZero() {
  425. return true
  426. }
  427. return es.Status.RefreshTime.Add(es.Spec.RefreshInterval.Duration).Before(time.Now())
  428. }
  429. func shouldReconcile(es esv1beta1.ExternalSecret) bool {
  430. if es.Spec.Target.Immutable && hasSyncedCondition(es) {
  431. return false
  432. }
  433. return true
  434. }
  435. func hasSyncedCondition(es esv1beta1.ExternalSecret) bool {
  436. for _, condition := range es.Status.Conditions {
  437. if condition.Reason == "SecretSynced" {
  438. return true
  439. }
  440. }
  441. return false
  442. }
  443. // isSecretValid checks if the secret exists, and it's data is consistent with the calculated hash.
  444. func isSecretValid(existingSecret v1.Secret) bool {
  445. // if target secret doesn't exist, or annotations as not set, we need to refresh
  446. if existingSecret.UID == "" || existingSecret.Annotations == nil {
  447. return false
  448. }
  449. // if the calculated hash is different from the calculation, then it's invalid
  450. if existingSecret.Annotations[esv1beta1.AnnotationDataHash] != utils.ObjectHash(existingSecret.Data) {
  451. return false
  452. }
  453. return true
  454. }
  455. // assertStoreIsUsable assert that the store is ready to use.
  456. func assertStoreIsUsable(store esv1beta1.GenericStore) error {
  457. condition := secretstore.GetSecretStoreCondition(store.GetStatus(), esv1beta1.SecretStoreReady)
  458. if condition == nil || condition.Status != v1.ConditionTrue {
  459. return fmt.Errorf(errSecretStoreNotReady, store.GetName())
  460. }
  461. return nil
  462. }
  463. func (r *Reconciler) getStore(ctx context.Context, externalSecret *esv1beta1.ExternalSecret) (esv1beta1.GenericStore, error) {
  464. ref := types.NamespacedName{
  465. Name: externalSecret.Spec.SecretStoreRef.Name,
  466. }
  467. if externalSecret.Spec.SecretStoreRef.Kind == esv1beta1.ClusterSecretStoreKind {
  468. var store esv1beta1.ClusterSecretStore
  469. err := r.Get(ctx, ref, &store)
  470. if err != nil {
  471. return nil, fmt.Errorf(errGetClusterSecretStore, ref.Name, err)
  472. }
  473. return &store, nil
  474. }
  475. ref.Namespace = externalSecret.Namespace
  476. var store esv1beta1.SecretStore
  477. err := r.Get(ctx, ref, &store)
  478. if err != nil {
  479. return nil, fmt.Errorf(errGetSecretStore, ref.Name, err)
  480. }
  481. return &store, nil
  482. }
  483. func (r *Reconciler) ShouldProcessSecret(ctx context.Context, store esv1beta1.GenericStore, ns string) (bool, error) {
  484. if store.GetKind() != esv1beta1.ClusterSecretStoreKind {
  485. return true, nil
  486. }
  487. if len(store.GetSpec().Conditions) == 0 {
  488. return true, nil
  489. }
  490. namespaceList := &v1.NamespaceList{}
  491. for _, condition := range store.GetSpec().Conditions {
  492. if condition.NamespaceSelector != nil {
  493. namespaceSelector, err := metav1.LabelSelectorAsSelector(condition.NamespaceSelector)
  494. if err != nil {
  495. return false, err
  496. }
  497. if err := r.Client.List(context.Background(), namespaceList, client.MatchingLabelsSelector{Selector: namespaceSelector}); err != nil {
  498. return false, err
  499. }
  500. for _, namespace := range namespaceList.Items {
  501. if namespace.GetName() == ns {
  502. return true, nil // namespace matches the labelselector
  503. }
  504. }
  505. }
  506. if condition.Namespaces != nil {
  507. if slices.Contains(condition.Namespaces, ns) {
  508. return true, nil // namespace in the namespaces list
  509. }
  510. }
  511. }
  512. return false, nil
  513. }
  514. // getProviderSecretData returns the provider's secret data with the provided ExternalSecret.
  515. func (r *Reconciler) getProviderSecretData(ctx context.Context, providerClient esv1beta1.SecretsClient, externalSecret *esv1beta1.ExternalSecret) (map[string][]byte, error) {
  516. providerData := make(map[string][]byte)
  517. for i, remoteRef := range externalSecret.Spec.DataFrom {
  518. var secretMap map[string][]byte
  519. var err error
  520. if remoteRef.Find != nil {
  521. secretMap, err = providerClient.GetAllSecrets(ctx, *remoteRef.Find)
  522. if errors.Is(err, esv1beta1.NoSecretErr) && externalSecret.Spec.Target.DeletionPolicy != esv1beta1.DeletionPolicyRetain {
  523. r.recorder.Event(externalSecret, v1.EventTypeNormal, esv1beta1.ReasonDeleted, fmt.Sprintf("secret does not exist at provider using .dataFrom[%d]", i))
  524. continue
  525. }
  526. if err != nil {
  527. return nil, err
  528. }
  529. secretMap, err = utils.RewriteMap(remoteRef.Rewrite, secretMap)
  530. if err != nil {
  531. return nil, fmt.Errorf(errRewrite, i, err)
  532. }
  533. if len(remoteRef.Rewrite) == 0 {
  534. // ConversionStrategy is deprecated. Use RewriteMap instead.
  535. r.recorder.Event(externalSecret, v1.EventTypeWarning, esv1beta1.ReasonDeprecated, fmt.Sprintf("dataFrom[%d].find.conversionStrategy=%v is deprecated and will be removed in further releases. Use dataFrom.rewrite instead", i, remoteRef.Find.ConversionStrategy))
  536. secretMap, err = utils.ConvertKeys(remoteRef.Find.ConversionStrategy, secretMap)
  537. if err != nil {
  538. return nil, fmt.Errorf(errConvert, err)
  539. }
  540. }
  541. if !utils.ValidateKeys(secretMap) {
  542. return nil, fmt.Errorf(errInvalidKeys, "find", i)
  543. }
  544. secretMap, err = utils.DecodeMap(remoteRef.Find.DecodingStrategy, secretMap)
  545. if err != nil {
  546. return nil, fmt.Errorf(errDecode, "spec.dataFrom", i, err)
  547. }
  548. } else if remoteRef.Extract != nil {
  549. secretMap, err = providerClient.GetSecretMap(ctx, *remoteRef.Extract)
  550. if errors.Is(err, esv1beta1.NoSecretErr) && externalSecret.Spec.Target.DeletionPolicy != esv1beta1.DeletionPolicyRetain {
  551. r.recorder.Event(externalSecret, v1.EventTypeNormal, esv1beta1.ReasonDeleted, fmt.Sprintf("secret does not exist at provider using .dataFrom[%d]", i))
  552. continue
  553. }
  554. if err != nil {
  555. return nil, err
  556. }
  557. secretMap, err = utils.RewriteMap(remoteRef.Rewrite, secretMap)
  558. if err != nil {
  559. return nil, fmt.Errorf(errRewrite, i, err)
  560. }
  561. if len(remoteRef.Rewrite) == 0 {
  562. secretMap, err = utils.ConvertKeys(remoteRef.Extract.ConversionStrategy, secretMap)
  563. if err != nil {
  564. return nil, fmt.Errorf(errConvert, err)
  565. }
  566. }
  567. if !utils.ValidateKeys(secretMap) {
  568. return nil, fmt.Errorf(errInvalidKeys, "extract", i)
  569. }
  570. secretMap, err = utils.DecodeMap(remoteRef.Extract.DecodingStrategy, secretMap)
  571. if err != nil {
  572. return nil, fmt.Errorf(errDecode, "spec.dataFrom", i, err)
  573. }
  574. }
  575. providerData = utils.MergeByteMap(providerData, secretMap)
  576. }
  577. for i, secretRef := range externalSecret.Spec.Data {
  578. secretData, err := providerClient.GetSecret(ctx, secretRef.RemoteRef)
  579. if errors.Is(err, esv1beta1.NoSecretErr) && externalSecret.Spec.Target.DeletionPolicy != esv1beta1.DeletionPolicyRetain {
  580. r.recorder.Event(externalSecret, v1.EventTypeNormal, esv1beta1.ReasonDeleted, fmt.Sprintf("secret does not exist at provider using .data[%d] key=%s", i, secretRef.RemoteRef.Key))
  581. continue
  582. }
  583. if err != nil {
  584. return nil, err
  585. }
  586. secretData, err = utils.Decode(secretRef.RemoteRef.DecodingStrategy, secretData)
  587. if err != nil {
  588. return nil, fmt.Errorf(errDecode, "spec.data", i, err)
  589. }
  590. providerData[secretRef.SecretKey] = secretData
  591. }
  592. return providerData, nil
  593. }
  594. // SetupWithManager returns a new controller builder that will be started by the provided Manager.
  595. func (r *Reconciler) SetupWithManager(mgr ctrl.Manager, opts controller.Options) error {
  596. r.recorder = mgr.GetEventRecorderFor("external-secrets")
  597. return ctrl.NewControllerManagedBy(mgr).
  598. WithOptions(opts).
  599. For(&esv1beta1.ExternalSecret{}).
  600. Owns(&v1.Secret{}, builder.OnlyMetadata).
  601. Complete(r)
  602. }