clusterexternalsecret_controller.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  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 clusterexternalsecret
  13. import (
  14. "context"
  15. "errors"
  16. "fmt"
  17. "reflect"
  18. "slices"
  19. "sort"
  20. "time"
  21. "github.com/go-logr/logr"
  22. v1 "k8s.io/api/core/v1"
  23. apierrors "k8s.io/apimachinery/pkg/api/errors"
  24. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  25. "k8s.io/apimachinery/pkg/labels"
  26. "k8s.io/apimachinery/pkg/runtime"
  27. "k8s.io/apimachinery/pkg/types"
  28. ctrl "sigs.k8s.io/controller-runtime"
  29. "sigs.k8s.io/controller-runtime/pkg/builder"
  30. "sigs.k8s.io/controller-runtime/pkg/client"
  31. "sigs.k8s.io/controller-runtime/pkg/controller"
  32. "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
  33. "sigs.k8s.io/controller-runtime/pkg/event"
  34. "sigs.k8s.io/controller-runtime/pkg/handler"
  35. "sigs.k8s.io/controller-runtime/pkg/predicate"
  36. "sigs.k8s.io/controller-runtime/pkg/reconcile"
  37. esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
  38. "github.com/external-secrets/external-secrets/pkg/controllers/clusterexternalsecret/cesmetrics"
  39. ctrlmetrics "github.com/external-secrets/external-secrets/pkg/controllers/metrics"
  40. )
  41. // Reconciler reconciles a ClusterExternalSecret object.
  42. type Reconciler struct {
  43. client.Client
  44. Log logr.Logger
  45. Scheme *runtime.Scheme
  46. RequeueInterval time.Duration
  47. }
  48. const (
  49. errGetCES = "could not get ClusterExternalSecret"
  50. errPatchStatus = "unable to patch status"
  51. errConvertLabelSelector = "unable to convert labelselector"
  52. errGetExistingES = "could not get existing ExternalSecret"
  53. errNamespacesFailed = "one or more namespaces failed"
  54. )
  55. func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
  56. log := r.Log.WithValues("ClusterExternalSecret", req.NamespacedName)
  57. resourceLabels := ctrlmetrics.RefineNonConditionMetricLabels(map[string]string{"name": req.Name, "namespace": req.Namespace})
  58. start := time.Now()
  59. externalSecretReconcileDuration := cesmetrics.GetGaugeVec(cesmetrics.ClusterExternalSecretReconcileDurationKey)
  60. defer func() { externalSecretReconcileDuration.With(resourceLabels).Set(float64(time.Since(start))) }()
  61. var clusterExternalSecret esv1beta1.ClusterExternalSecret
  62. err := r.Get(ctx, req.NamespacedName, &clusterExternalSecret)
  63. if err != nil {
  64. if apierrors.IsNotFound(err) {
  65. cesmetrics.RemoveMetrics(req.Namespace, req.Name)
  66. return ctrl.Result{}, nil
  67. }
  68. log.Error(err, errGetCES)
  69. return ctrl.Result{}, err
  70. }
  71. // skip reconciliation if deletion timestamp is set on cluster external secret
  72. if clusterExternalSecret.DeletionTimestamp != nil {
  73. log.Info("skipping as it is in deletion")
  74. return ctrl.Result{}, nil
  75. }
  76. p := client.MergeFrom(clusterExternalSecret.DeepCopy())
  77. defer r.deferPatch(ctx, log, &clusterExternalSecret, p)
  78. return r.reconcile(ctx, log, &clusterExternalSecret)
  79. }
  80. func (r *Reconciler) reconcile(ctx context.Context, log logr.Logger, clusterExternalSecret *esv1beta1.ClusterExternalSecret) (ctrl.Result, error) {
  81. refreshInt := r.RequeueInterval
  82. if clusterExternalSecret.Spec.RefreshInterval != nil {
  83. refreshInt = clusterExternalSecret.Spec.RefreshInterval.Duration
  84. }
  85. esName := clusterExternalSecret.Spec.ExternalSecretName
  86. if esName == "" {
  87. esName = clusterExternalSecret.ObjectMeta.Name
  88. }
  89. if prevName := clusterExternalSecret.Status.ExternalSecretName; prevName != esName {
  90. // ExternalSecretName has changed, so remove the old ones
  91. if err := r.removeOldSecrets(ctx, log, clusterExternalSecret, prevName); err != nil {
  92. return ctrl.Result{}, err
  93. }
  94. }
  95. clusterExternalSecret.Status.ExternalSecretName = esName
  96. namespaces, err := r.getTargetNamespaces(ctx, clusterExternalSecret)
  97. if err != nil {
  98. log.Error(err, "failed to get target Namespaces")
  99. failedNamespaces := map[string]error{
  100. "unknown": err,
  101. }
  102. condition := NewClusterExternalSecretCondition(failedNamespaces)
  103. SetClusterExternalSecretCondition(clusterExternalSecret, *condition)
  104. clusterExternalSecret.Status.FailedNamespaces = toNamespaceFailures(failedNamespaces)
  105. return ctrl.Result{}, err
  106. }
  107. failedNamespaces := r.deleteOutdatedExternalSecrets(ctx, namespaces, esName, clusterExternalSecret.Name, clusterExternalSecret.Status.ProvisionedNamespaces)
  108. provisionedNamespaces := r.gatherProvisionedNamespaces(ctx, log, clusterExternalSecret, namespaces, esName, failedNamespaces)
  109. condition := NewClusterExternalSecretCondition(failedNamespaces)
  110. SetClusterExternalSecretCondition(clusterExternalSecret, *condition)
  111. clusterExternalSecret.Status.FailedNamespaces = toNamespaceFailures(failedNamespaces)
  112. sort.Strings(provisionedNamespaces)
  113. clusterExternalSecret.Status.ProvisionedNamespaces = provisionedNamespaces
  114. return ctrl.Result{RequeueAfter: refreshInt}, nil
  115. }
  116. func (r *Reconciler) gatherProvisionedNamespaces(
  117. ctx context.Context,
  118. log logr.Logger,
  119. clusterExternalSecret *esv1beta1.ClusterExternalSecret,
  120. namespaces []v1.Namespace,
  121. esName string,
  122. failedNamespaces map[string]error,
  123. ) []string {
  124. var provisionedNamespaces []string //nolint:prealloc // we don't know the size
  125. for _, namespace := range namespaces {
  126. var existingES esv1beta1.ExternalSecret
  127. err := r.Get(ctx, types.NamespacedName{
  128. Name: esName,
  129. Namespace: namespace.Name,
  130. }, &existingES)
  131. if err != nil && !apierrors.IsNotFound(err) {
  132. log.Error(err, errGetExistingES)
  133. failedNamespaces[namespace.Name] = err
  134. continue
  135. }
  136. if err == nil && !isExternalSecretOwnedBy(&existingES, clusterExternalSecret.Name) {
  137. failedNamespaces[namespace.Name] = errors.New("external secret already exists in namespace")
  138. continue
  139. }
  140. if err := r.createOrUpdateExternalSecret(ctx, clusterExternalSecret, namespace, esName, clusterExternalSecret.Spec.ExternalSecretMetadata); err != nil {
  141. log.Error(err, "failed to create or update external secret")
  142. failedNamespaces[namespace.Name] = err
  143. continue
  144. }
  145. provisionedNamespaces = append(provisionedNamespaces, namespace.Name)
  146. }
  147. return provisionedNamespaces
  148. }
  149. func (r *Reconciler) removeOldSecrets(ctx context.Context, log logr.Logger, clusterExternalSecret *esv1beta1.ClusterExternalSecret, prevName string) error {
  150. var (
  151. failedNamespaces = map[string]error{}
  152. lastErr error
  153. )
  154. for _, ns := range clusterExternalSecret.Status.ProvisionedNamespaces {
  155. if err := r.deleteExternalSecret(ctx, prevName, clusterExternalSecret.Name, ns); err != nil {
  156. log.Error(err, "could not delete ExternalSecret")
  157. failedNamespaces[ns] = err
  158. lastErr = err
  159. }
  160. }
  161. if len(failedNamespaces) > 0 {
  162. condition := NewClusterExternalSecretCondition(failedNamespaces)
  163. SetClusterExternalSecretCondition(clusterExternalSecret, *condition)
  164. clusterExternalSecret.Status.FailedNamespaces = toNamespaceFailures(failedNamespaces)
  165. return lastErr
  166. }
  167. return nil
  168. }
  169. func (r *Reconciler) getTargetNamespaces(ctx context.Context, ces *esv1beta1.ClusterExternalSecret) ([]v1.Namespace, error) {
  170. var selectors []*metav1.LabelSelector //nolint:prealloc // ces.Spec.NamespaceSelector might be empty.
  171. if s := ces.Spec.NamespaceSelector; s != nil {
  172. selectors = append(selectors, s)
  173. }
  174. for _, ns := range ces.Spec.Namespaces {
  175. selectors = append(selectors, &metav1.LabelSelector{
  176. MatchLabels: map[string]string{
  177. "kubernetes.io/metadata.name": ns,
  178. },
  179. })
  180. }
  181. selectors = append(selectors, ces.Spec.NamespaceSelectors...)
  182. var namespaces []v1.Namespace
  183. namespaceSet := make(map[string]struct{})
  184. for _, selector := range selectors {
  185. labelSelector, err := metav1.LabelSelectorAsSelector(selector)
  186. if err != nil {
  187. return nil, fmt.Errorf("failed to convert label selector %s: %w", selector, err)
  188. }
  189. var nl v1.NamespaceList
  190. err = r.List(ctx, &nl, &client.ListOptions{LabelSelector: labelSelector})
  191. if err != nil {
  192. return nil, fmt.Errorf("failed to list namespaces by label selector %s: %w", selector, err)
  193. }
  194. for _, n := range nl.Items {
  195. if _, exist := namespaceSet[n.Name]; exist {
  196. continue
  197. }
  198. namespaceSet[n.Name] = struct{}{}
  199. namespaces = append(namespaces, n)
  200. }
  201. }
  202. return namespaces, nil
  203. }
  204. func (r *Reconciler) createOrUpdateExternalSecret(ctx context.Context, clusterExternalSecret *esv1beta1.ClusterExternalSecret, namespace v1.Namespace, esName string, esMetadata esv1beta1.ExternalSecretMetadata) error {
  205. externalSecret := &esv1beta1.ExternalSecret{
  206. ObjectMeta: metav1.ObjectMeta{
  207. Namespace: namespace.Name,
  208. Name: esName,
  209. },
  210. }
  211. mutateFunc := func() error {
  212. externalSecret.Labels = esMetadata.Labels
  213. externalSecret.Annotations = esMetadata.Annotations
  214. externalSecret.Spec = clusterExternalSecret.Spec.ExternalSecretSpec
  215. if err := controllerutil.SetControllerReference(clusterExternalSecret, externalSecret, r.Scheme); err != nil {
  216. return fmt.Errorf("could not set the controller owner reference %w", err)
  217. }
  218. return nil
  219. }
  220. if _, err := ctrl.CreateOrUpdate(ctx, r.Client, externalSecret, mutateFunc); err != nil {
  221. return fmt.Errorf("could not create or update ExternalSecret: %w", err)
  222. }
  223. return nil
  224. }
  225. func (r *Reconciler) deleteExternalSecret(ctx context.Context, esName, cesName, namespace string) error {
  226. var existingES esv1beta1.ExternalSecret
  227. err := r.Get(ctx, types.NamespacedName{
  228. Name: esName,
  229. Namespace: namespace,
  230. }, &existingES)
  231. if err != nil {
  232. // If we can't find it then just leave
  233. if apierrors.IsNotFound(err) {
  234. return nil
  235. }
  236. return err
  237. }
  238. if !isExternalSecretOwnedBy(&existingES, cesName) {
  239. return nil
  240. }
  241. err = r.Delete(ctx, &existingES, &client.DeleteOptions{})
  242. if err != nil {
  243. return fmt.Errorf("external secret in non matching namespace could not be deleted: %w", err)
  244. }
  245. return nil
  246. }
  247. func (r *Reconciler) deferPatch(ctx context.Context, log logr.Logger, clusterExternalSecret *esv1beta1.ClusterExternalSecret, p client.Patch) {
  248. if err := r.Status().Patch(ctx, clusterExternalSecret, p); err != nil {
  249. log.Error(err, errPatchStatus)
  250. }
  251. }
  252. func (r *Reconciler) deleteOutdatedExternalSecrets(ctx context.Context, namespaces []v1.Namespace, esName, cesName string, provisionedNamespaces []string) map[string]error {
  253. failedNamespaces := map[string]error{}
  254. // Loop through existing namespaces first to make sure they still have our labels
  255. for _, namespace := range getRemovedNamespaces(namespaces, provisionedNamespaces) {
  256. err := r.deleteExternalSecret(ctx, esName, cesName, namespace)
  257. if err != nil {
  258. r.Log.Error(err, "unable to delete external secret")
  259. failedNamespaces[namespace] = err
  260. }
  261. }
  262. return failedNamespaces
  263. }
  264. func isExternalSecretOwnedBy(es *esv1beta1.ExternalSecret, cesName string) bool {
  265. owner := metav1.GetControllerOf(es)
  266. return owner != nil && owner.APIVersion == esv1beta1.SchemeGroupVersion.String() && owner.Kind == esv1beta1.ClusterExtSecretKind && owner.Name == cesName
  267. }
  268. func getRemovedNamespaces(currentNSs []v1.Namespace, provisionedNSs []string) []string {
  269. currentNSSet := map[string]struct{}{}
  270. for _, currentNs := range currentNSs {
  271. currentNSSet[currentNs.Name] = struct{}{}
  272. }
  273. var removedNSs []string
  274. for _, ns := range provisionedNSs {
  275. if _, ok := currentNSSet[ns]; !ok {
  276. removedNSs = append(removedNSs, ns)
  277. }
  278. }
  279. return removedNSs
  280. }
  281. func toNamespaceFailures(failedNamespaces map[string]error) []esv1beta1.ClusterExternalSecretNamespaceFailure {
  282. namespaceFailures := make([]esv1beta1.ClusterExternalSecretNamespaceFailure, len(failedNamespaces))
  283. i := 0
  284. for namespace, err := range failedNamespaces {
  285. namespaceFailures[i] = esv1beta1.ClusterExternalSecretNamespaceFailure{
  286. Namespace: namespace,
  287. Reason: err.Error(),
  288. }
  289. i++
  290. }
  291. sort.Slice(namespaceFailures, func(i, j int) bool { return namespaceFailures[i].Namespace < namespaceFailures[j].Namespace })
  292. return namespaceFailures
  293. }
  294. // SetupWithManager sets up the controller with the Manager.
  295. func (r *Reconciler) SetupWithManager(mgr ctrl.Manager, opts controller.Options) error {
  296. return ctrl.NewControllerManagedBy(mgr).
  297. WithOptions(opts).
  298. For(&esv1beta1.ClusterExternalSecret{}).
  299. Owns(&esv1beta1.ExternalSecret{}).
  300. Watches(
  301. &v1.Namespace{},
  302. handler.EnqueueRequestsFromMapFunc(r.findObjectsForNamespace),
  303. builder.WithPredicates(namespacePredicate()),
  304. ).
  305. Complete(r)
  306. }
  307. func (r *Reconciler) findObjectsForNamespace(ctx context.Context, namespace client.Object) []reconcile.Request {
  308. var clusterExternalSecrets esv1beta1.ClusterExternalSecretList
  309. if err := r.List(ctx, &clusterExternalSecrets); err != nil {
  310. r.Log.Error(err, errGetCES)
  311. return []reconcile.Request{}
  312. }
  313. return r.queueRequestsForItem(&clusterExternalSecrets, namespace)
  314. }
  315. func (r *Reconciler) queueRequestsForItem(clusterExternalSecrets *esv1beta1.ClusterExternalSecretList, namespace client.Object) []reconcile.Request {
  316. var requests []reconcile.Request
  317. for i := range clusterExternalSecrets.Items {
  318. clusterExternalSecret := clusterExternalSecrets.Items[i]
  319. var selectors []*metav1.LabelSelector
  320. if s := clusterExternalSecret.Spec.NamespaceSelector; s != nil {
  321. selectors = append(selectors, s)
  322. }
  323. selectors = append(selectors, clusterExternalSecret.Spec.NamespaceSelectors...)
  324. var selected bool
  325. for _, selector := range selectors {
  326. labelSelector, err := metav1.LabelSelectorAsSelector(selector)
  327. if err != nil {
  328. r.Log.Error(err, errConvertLabelSelector)
  329. continue
  330. }
  331. if labelSelector.Matches(labels.Set(namespace.GetLabels())) {
  332. requests = append(requests, reconcile.Request{
  333. NamespacedName: types.NamespacedName{
  334. Name: clusterExternalSecret.GetName(),
  335. Namespace: clusterExternalSecret.GetNamespace(),
  336. },
  337. })
  338. selected = true
  339. break
  340. }
  341. }
  342. // Prevent the object from being added twice if it happens to be listed
  343. // by Namespaces selector as well.
  344. if selected {
  345. continue
  346. }
  347. if slices.Contains(clusterExternalSecret.Spec.Namespaces, namespace.GetName()) {
  348. requests = append(requests, reconcile.Request{
  349. NamespacedName: types.NamespacedName{
  350. Name: clusterExternalSecret.GetName(),
  351. Namespace: clusterExternalSecret.GetNamespace(),
  352. },
  353. })
  354. }
  355. }
  356. return requests
  357. }
  358. func namespacePredicate() predicate.Predicate {
  359. return predicate.Funcs{
  360. CreateFunc: func(e event.CreateEvent) bool {
  361. return true
  362. },
  363. UpdateFunc: func(e event.UpdateEvent) bool {
  364. if e.ObjectOld == nil || e.ObjectNew == nil {
  365. return false
  366. }
  367. return !reflect.DeepEqual(e.ObjectOld.GetLabels(), e.ObjectNew.GetLabels())
  368. },
  369. DeleteFunc: func(deleteEvent event.DeleteEvent) bool {
  370. return true
  371. },
  372. }
  373. }