externalsecret_controller.go 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  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. "fmt"
  16. "time"
  17. "github.com/go-logr/logr"
  18. "github.com/prometheus/client_golang/prometheus"
  19. corev1 "k8s.io/api/core/v1"
  20. apierrors "k8s.io/apimachinery/pkg/api/errors"
  21. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  22. "k8s.io/apimachinery/pkg/runtime"
  23. "k8s.io/apimachinery/pkg/types"
  24. ctrl "sigs.k8s.io/controller-runtime"
  25. "sigs.k8s.io/controller-runtime/pkg/client"
  26. "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
  27. esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
  28. "github.com/external-secrets/external-secrets/pkg/provider"
  29. // Loading registered providers.
  30. _ "github.com/external-secrets/external-secrets/pkg/provider/register"
  31. schema "github.com/external-secrets/external-secrets/pkg/provider/schema"
  32. "github.com/external-secrets/external-secrets/pkg/template"
  33. utils "github.com/external-secrets/external-secrets/pkg/utils"
  34. )
  35. const (
  36. requeueAfter = time.Second * 30
  37. )
  38. // Reconciler reconciles a ExternalSecret object.
  39. type Reconciler struct {
  40. client.Client
  41. Log logr.Logger
  42. Scheme *runtime.Scheme
  43. ControllerClass string
  44. }
  45. // Reconcile implements the main reconciliation loop
  46. // for watched objects (ExternalSecret, ClusterSecretStore and SecretStore),
  47. // and updates/creates a Kubernetes secret based on them.
  48. func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
  49. log := r.Log.WithValues("ExternalSecret", req.NamespacedName)
  50. syncCallsMetricLabels := prometheus.Labels{"name": req.Name, "namespace": req.Namespace}
  51. var externalSecret esv1alpha1.ExternalSecret
  52. err := r.Get(ctx, req.NamespacedName, &externalSecret)
  53. if apierrors.IsNotFound(err) {
  54. syncCallsTotal.With(syncCallsMetricLabels).Inc()
  55. return ctrl.Result{}, nil
  56. } else if err != nil {
  57. log.Error(err, "could not get ExternalSecret")
  58. syncCallsError.With(syncCallsMetricLabels).Inc()
  59. return ctrl.Result{}, nil
  60. }
  61. store, err := r.getStore(ctx, &externalSecret)
  62. if err != nil {
  63. log.Error(err, "could not get store reference")
  64. conditionSynced := NewExternalSecretCondition(esv1alpha1.ExternalSecretReady, corev1.ConditionFalse, esv1alpha1.ConditionReasonSecretSyncedError, err.Error())
  65. SetExternalSecretCondition(&externalSecret, *conditionSynced)
  66. err = r.Status().Update(ctx, &externalSecret)
  67. syncCallsError.With(syncCallsMetricLabels).Inc()
  68. return ctrl.Result{RequeueAfter: requeueAfter}, nil
  69. }
  70. log = log.WithValues("SecretStore", store.GetNamespacedName())
  71. // check if store should be handled by this controller instance
  72. if !shouldProcessStore(store, r.ControllerClass) {
  73. log.Info("skippig unmanaged store")
  74. return ctrl.Result{}, nil
  75. }
  76. storeProvider, err := schema.GetProvider(store)
  77. if err != nil {
  78. log.Error(err, "could not get store provider")
  79. syncCallsError.With(syncCallsMetricLabels).Inc()
  80. return ctrl.Result{RequeueAfter: requeueAfter}, nil
  81. }
  82. secretClient, err := storeProvider.NewClient(ctx, store, r.Client, req.Namespace)
  83. if err != nil {
  84. log.Error(err, "could not get provider client")
  85. conditionSynced := NewExternalSecretCondition(esv1alpha1.ExternalSecretReady, corev1.ConditionFalse, esv1alpha1.ConditionReasonSecretSyncedError, err.Error())
  86. SetExternalSecretCondition(&externalSecret, *conditionSynced)
  87. err = r.Status().Update(ctx, &externalSecret)
  88. syncCallsError.With(syncCallsMetricLabels).Inc()
  89. return ctrl.Result{RequeueAfter: requeueAfter}, nil
  90. }
  91. secret := &corev1.Secret{
  92. ObjectMeta: metav1.ObjectMeta{
  93. Name: externalSecret.Spec.Target.Name,
  94. Namespace: externalSecret.Namespace,
  95. },
  96. Data: make(map[string][]byte),
  97. }
  98. _, err = ctrl.CreateOrUpdate(ctx, r.Client, secret, func() error {
  99. err = controllerutil.SetControllerReference(&externalSecret, &secret.ObjectMeta, r.Scheme)
  100. if err != nil {
  101. return fmt.Errorf("could not set ExternalSecret controller reference: %w", err)
  102. }
  103. mergeTemplate(secret, externalSecret)
  104. data, err := r.getProviderSecretData(ctx, secretClient, &externalSecret)
  105. if err != nil {
  106. return fmt.Errorf("could not get secret data from provider: %w", err)
  107. }
  108. // overwrite data
  109. for k, v := range data {
  110. secret.Data[k] = v
  111. }
  112. err = template.Execute(externalSecret.Spec.Target.Template, secret, data)
  113. if err != nil {
  114. return fmt.Errorf("could not execute template: %w", err)
  115. }
  116. return nil
  117. })
  118. if err != nil {
  119. log.Error(err, "could not reconcile ExternalSecret")
  120. conditionSynced := NewExternalSecretCondition(esv1alpha1.ExternalSecretReady, corev1.ConditionFalse, esv1alpha1.ConditionReasonSecretSyncedError, err.Error())
  121. SetExternalSecretCondition(&externalSecret, *conditionSynced)
  122. err = r.Status().Update(ctx, &externalSecret)
  123. if err != nil {
  124. log.Error(err, "unable to update status")
  125. }
  126. syncCallsError.With(syncCallsMetricLabels).Inc()
  127. return ctrl.Result{RequeueAfter: requeueAfter}, nil
  128. }
  129. dur := time.Hour
  130. if externalSecret.Spec.RefreshInterval != nil {
  131. dur = externalSecret.Spec.RefreshInterval.Duration
  132. }
  133. conditionSynced := NewExternalSecretCondition(esv1alpha1.ExternalSecretReady, corev1.ConditionTrue, esv1alpha1.ConditionReasonSecretSynced, "Secret was synced")
  134. SetExternalSecretCondition(&externalSecret, *conditionSynced)
  135. externalSecret.Status.RefreshTime = metav1.NewTime(time.Now())
  136. err = r.Status().Update(ctx, &externalSecret)
  137. if err != nil {
  138. log.Error(err, "unable to update status")
  139. }
  140. syncCallsTotal.With(syncCallsMetricLabels).Inc()
  141. return ctrl.Result{
  142. RequeueAfter: dur,
  143. }, nil
  144. }
  145. // shouldProcessStore returns true if the store should be processed.
  146. func shouldProcessStore(store esv1alpha1.GenericStore, class string) bool {
  147. if store.GetSpec().Controller == "" || store.GetSpec().Controller == class {
  148. return true
  149. }
  150. return false
  151. }
  152. // we do not want to force-override the label/annotations
  153. // and only copy the necessary key/value pairs.
  154. func mergeTemplate(secret *corev1.Secret, externalSecret esv1alpha1.ExternalSecret) {
  155. if secret.ObjectMeta.Labels == nil {
  156. secret.ObjectMeta.Labels = make(map[string]string)
  157. }
  158. if secret.ObjectMeta.Annotations == nil {
  159. secret.ObjectMeta.Annotations = make(map[string]string)
  160. }
  161. if externalSecret.Spec.Target.Template == nil {
  162. mergeMap(secret.ObjectMeta.Labels, externalSecret.ObjectMeta.Labels)
  163. mergeMap(secret.ObjectMeta.Annotations, externalSecret.ObjectMeta.Annotations)
  164. return
  165. }
  166. // if template is defined: use those labels/annotations
  167. secret.Type = externalSecret.Spec.Target.Template.Type
  168. mergeMap(secret.ObjectMeta.Labels, externalSecret.Spec.Target.Template.Metadata.Labels)
  169. mergeMap(secret.ObjectMeta.Annotations, externalSecret.Spec.Target.Template.Metadata.Annotations)
  170. }
  171. // mergeMap performs a deep clone from src to dest.
  172. func mergeMap(dest, src map[string]string) {
  173. for k, v := range src {
  174. dest[k] = v
  175. }
  176. }
  177. // getStore returns the store with the provided ExternalSecret.
  178. func (r *Reconciler) getStore(ctx context.Context, externalSecret *esv1alpha1.ExternalSecret) (esv1alpha1.GenericStore, error) {
  179. ref := types.NamespacedName{
  180. Name: externalSecret.Spec.SecretStoreRef.Name,
  181. }
  182. if externalSecret.Spec.SecretStoreRef.Kind == esv1alpha1.ClusterSecretStoreKind {
  183. var store esv1alpha1.ClusterSecretStore
  184. err := r.Get(ctx, ref, &store)
  185. if err != nil {
  186. return nil, fmt.Errorf("could not get ClusterSecretStore %q, %w", ref.Name, err)
  187. }
  188. return &store, nil
  189. }
  190. ref.Namespace = externalSecret.Namespace
  191. var store esv1alpha1.SecretStore
  192. err := r.Get(ctx, ref, &store)
  193. if err != nil {
  194. return nil, fmt.Errorf("could not get SecretStore %q, %w", ref.Name, err)
  195. }
  196. return &store, nil
  197. }
  198. // getProviderSecretData returns the provider's secret data with the provided ExternalSecret.
  199. func (r *Reconciler) getProviderSecretData(ctx context.Context, providerClient provider.SecretsClient, externalSecret *esv1alpha1.ExternalSecret) (map[string][]byte, error) {
  200. providerData := make(map[string][]byte)
  201. for _, remoteRef := range externalSecret.Spec.DataFrom {
  202. secretMap, err := providerClient.GetSecretMap(ctx, remoteRef)
  203. if err != nil {
  204. return nil, fmt.Errorf("key %q from ExternalSecret %q: %w", remoteRef.Key, externalSecret.Name, err)
  205. }
  206. providerData = utils.Merge(providerData, secretMap)
  207. }
  208. for _, secretRef := range externalSecret.Spec.Data {
  209. secretData, err := providerClient.GetSecret(ctx, secretRef.RemoteRef)
  210. if err != nil {
  211. return nil, fmt.Errorf("key %q from ExternalSecret %q: %w", secretRef.RemoteRef.Key, externalSecret.Name, err)
  212. }
  213. providerData[secretRef.SecretKey] = secretData
  214. }
  215. return providerData, nil
  216. }
  217. // SetupWithManager returns a new controller builder that will be started by the provided Manager.
  218. func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
  219. return ctrl.NewControllerManagedBy(mgr).
  220. For(&esv1alpha1.ExternalSecret{}).
  221. Owns(&corev1.Secret{}).
  222. Complete(r)
  223. }