externalsecret_controller_manifest.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. /*
  2. Copyright © The ESO Authors
  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
  14. import (
  15. "context"
  16. "fmt"
  17. "maps"
  18. "github.com/go-logr/logr"
  19. v1 "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/apis/meta/v1/unstructured"
  23. "k8s.io/apimachinery/pkg/runtime/schema"
  24. "sigs.k8s.io/controller-runtime/pkg/client"
  25. "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
  26. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  27. "github.com/external-secrets/external-secrets/pkg/controllers/templating"
  28. "github.com/external-secrets/external-secrets/runtime/esutils"
  29. "github.com/external-secrets/external-secrets/runtime/template"
  30. )
  31. // isGenericTarget checks if the ExternalSecret targets a generic resource.
  32. func isGenericTarget(es *esv1.ExternalSecret) bool {
  33. return es.Spec.Target.Manifest != nil
  34. }
  35. // validateGenericTarget validates that generic targets are properly configured.
  36. func (r *Reconciler) validateGenericTarget(log logr.Logger, es *esv1.ExternalSecret) error {
  37. if !r.AllowGenericTargets {
  38. return fmt.Errorf("generic targets are disabled. Enable with --unsafe-allow-generic-targets flag")
  39. }
  40. manifest := es.Spec.Target.Manifest
  41. if manifest.APIVersion == "" {
  42. return fmt.Errorf("target.manifest.apiVersion is required")
  43. }
  44. if manifest.Kind == "" {
  45. return fmt.Errorf("target.manifest.kind is required")
  46. }
  47. log.Info("Warning: Using generic target. Make sure access policies and encryption are properly configured.",
  48. "apiVersion", manifest.APIVersion,
  49. "kind", manifest.Kind,
  50. "name", getTargetName(es))
  51. return nil
  52. }
  53. // getTargetGVK returns the GroupVersionKind for the target resource.
  54. func getTargetGVK(es *esv1.ExternalSecret) schema.GroupVersionKind {
  55. manifest := es.Spec.Target.Manifest
  56. gv, _ := schema.ParseGroupVersion(manifest.APIVersion)
  57. return schema.GroupVersionKind{
  58. Group: gv.Group,
  59. Version: gv.Version,
  60. Kind: manifest.Kind,
  61. }
  62. }
  63. // getTargetName returns the name of the target resource.
  64. func getTargetName(es *esv1.ExternalSecret) string {
  65. if es.Spec.Target.Name != "" {
  66. return es.Spec.Target.Name
  67. }
  68. return es.Name
  69. }
  70. // getGenericResource retrieves a generic resource using the controller-runtime client.
  71. func (r *Reconciler) getGenericResource(ctx context.Context, log logr.Logger, es *esv1.ExternalSecret) (*unstructured.Unstructured, error) {
  72. gvk := getTargetGVK(es)
  73. resource := &unstructured.Unstructured{}
  74. resource.SetGroupVersionKind(gvk)
  75. err := r.Client.Get(ctx, client.ObjectKey{
  76. Namespace: es.Namespace,
  77. Name: getTargetName(es),
  78. }, resource)
  79. if err != nil {
  80. if apierrors.IsNotFound(err) {
  81. log.V(1).Info("target resource does not exist", "gvk", gvk.String(), "name", getTargetName(es))
  82. return nil, err
  83. }
  84. return nil, fmt.Errorf("failed to get target resource: %w", err)
  85. }
  86. return resource, nil
  87. }
  88. func (r *Reconciler) createGenericResource(ctx context.Context, log logr.Logger, es *esv1.ExternalSecret, obj *unstructured.Unstructured) error {
  89. gvk := getTargetGVK(es)
  90. // Check if resource already exists
  91. existing := &unstructured.Unstructured{}
  92. existing.SetGroupVersionKind(gvk)
  93. err := r.Client.Get(ctx, client.ObjectKey{
  94. Namespace: es.Namespace,
  95. Name: getTargetName(es),
  96. }, existing)
  97. if err != nil {
  98. if !apierrors.IsNotFound(err) {
  99. return fmt.Errorf("failed to check if target resource exists: %w", err)
  100. }
  101. } else {
  102. return fmt.Errorf("target resource with name %s already exists", getTargetName(es))
  103. }
  104. log.Info("creating target resource", "gvk", gvk.String(), "name", getTargetName(es))
  105. err = r.Client.Create(ctx, obj)
  106. if err != nil {
  107. return fmt.Errorf("failed to create target resource: %w", err)
  108. }
  109. r.recorder.Event(es, v1.EventTypeNormal, "Created", fmt.Sprintf("Created %s %s", gvk.Kind, getTargetName(es)))
  110. return nil
  111. }
  112. func (r *Reconciler) updateGenericResource(ctx context.Context, log logr.Logger, es *esv1.ExternalSecret, existing *unstructured.Unstructured) error {
  113. gvk := getTargetGVK(es)
  114. log.Info("updating target resource", "gvk", gvk.String(), "name", getTargetName(es))
  115. err := r.Client.Update(ctx, existing)
  116. if err != nil {
  117. return fmt.Errorf("failed to update target resource: %w", err)
  118. }
  119. r.recorder.Event(es, v1.EventTypeNormal, "Updated", fmt.Sprintf("Updated %s %s", gvk.Kind, getTargetName(es)))
  120. return nil
  121. }
  122. // deleteGenericResource deletes a generic resource.
  123. func (r *Reconciler) deleteGenericResource(ctx context.Context, log logr.Logger, es *esv1.ExternalSecret) error {
  124. if !r.AllowGenericTargets || !isGenericTarget(es) {
  125. return nil
  126. }
  127. gvk := getTargetGVK(es)
  128. obj := &unstructured.Unstructured{}
  129. obj.SetGroupVersionKind(gvk)
  130. obj.SetNamespace(es.Namespace)
  131. obj.SetName(getTargetName(es))
  132. log.Info("deleting target resource", "gvk", gvk.String(), "name", getTargetName(es))
  133. err := r.Client.Delete(ctx, obj)
  134. if err != nil && !apierrors.IsNotFound(err) {
  135. return fmt.Errorf("failed to delete target resource: %w", err)
  136. }
  137. r.recorder.Event(es, v1.EventTypeNormal, "Deleted", fmt.Sprintf("Deleted %s %s", gvk.Kind, getTargetName(es)))
  138. return nil
  139. }
  140. // applyTemplateToManifest renders templates for generic resources and returns an unstructured object.
  141. // If existingObj is provided, templates will be applied to it (for merge behavior).
  142. // Otherwise, a new object is created.
  143. func (r *Reconciler) applyTemplateToManifest(ctx context.Context, es *esv1.ExternalSecret, dataMap map[string][]byte, existingObj *unstructured.Unstructured) (*unstructured.Unstructured, error) {
  144. var obj *unstructured.Unstructured
  145. if existingObj != nil {
  146. // use the existing object for merge behavior if it exists
  147. obj = existingObj.DeepCopy()
  148. } else {
  149. gvk := getTargetGVK(es)
  150. obj = &unstructured.Unstructured{}
  151. obj.SetGroupVersionKind(gvk)
  152. obj.SetName(getTargetName(es))
  153. obj.SetNamespace(es.Namespace)
  154. switch gvk.Kind {
  155. case "ConfigMap", "Secret":
  156. obj.Object["data"] = map[string]any{}
  157. default:
  158. obj.Object["spec"] = map[string]any{}
  159. }
  160. }
  161. labels := obj.GetLabels()
  162. if labels == nil {
  163. labels = make(map[string]string)
  164. }
  165. annotations := obj.GetAnnotations()
  166. if annotations == nil {
  167. annotations = make(map[string]string)
  168. }
  169. srcLabels, srcAnnotations := es.ObjectMeta.Labels, es.ObjectMeta.Annotations
  170. if es.Spec.Target.Template != nil {
  171. srcLabels = es.Spec.Target.Template.Metadata.Labels
  172. srcAnnotations = es.Spec.Target.Template.Metadata.Annotations
  173. }
  174. maps.Copy(labels, srcLabels)
  175. maps.Copy(annotations, srcAnnotations)
  176. labels[esv1.LabelManaged] = esv1.LabelManagedValue
  177. obj.SetLabels(labels)
  178. obj.SetAnnotations(annotations)
  179. var result *unstructured.Unstructured
  180. var err error
  181. if es.Spec.Target.Template == nil {
  182. result = r.createSimpleManifest(obj, dataMap)
  183. } else {
  184. result, err = r.renderTemplatedManifest(ctx, es, obj, dataMap)
  185. }
  186. if err != nil {
  187. return nil, err
  188. }
  189. ann := result.GetAnnotations()
  190. if ann == nil {
  191. ann = make(map[string]string)
  192. }
  193. hash, err := genericTargetContentHash(result)
  194. if err != nil {
  195. return nil, fmt.Errorf("failed to hash target %q content: %w", es.Spec.Target.Name, err)
  196. }
  197. ann[esv1.AnnotationDataHash] = hash
  198. result.SetAnnotations(ann)
  199. if err := r.applyOwnership(es, result); err != nil {
  200. return nil, err
  201. }
  202. return result, nil
  203. }
  204. // applyOwnership manages the owner reference and owner label on the target manifest resource.
  205. func (r *Reconciler) applyOwnership(es *esv1.ExternalSecret, result *unstructured.Unstructured) error {
  206. // get information about the current owner of the resource
  207. // - we ignore the API version as it can change over time
  208. // - we ignore the UID for consistency with the SetControllerReference function
  209. currentOwner := metav1.GetControllerOf(result)
  210. ownerIsESKind := false
  211. ownerIsCurrentES := false
  212. if currentOwner != nil {
  213. currentOwnerGK := schema.FromAPIVersionAndKind(currentOwner.APIVersion, currentOwner.Kind).GroupKind()
  214. ownerIsESKind = currentOwnerGK.String() == esv1.ExtSecretGroupKind
  215. ownerIsCurrentES = ownerIsESKind && currentOwner.Name == es.Name
  216. }
  217. // if another ExternalSecret is the owner, we should return an error
  218. // otherwise the controller will fight with itself to update the resource.
  219. // note, this does not prevent other controllers from owning the resource.
  220. if ownerIsESKind && !ownerIsCurrentES {
  221. return fmt.Errorf("%w: %s", ErrSecretIsOwned, currentOwner.Name)
  222. }
  223. // if the CreationPolicy is Owner, we should set ourselves as the owner of the resource
  224. if es.Spec.Target.CreationPolicy == esv1.CreatePolicyOwner {
  225. if err := controllerutil.SetControllerReference(es, result, r.Scheme); err != nil {
  226. return fmt.Errorf("%w: %w", ErrSecretSetCtrlRef, err)
  227. }
  228. }
  229. // if the creation policy is not Owner, we should remove ourselves as the owner
  230. // this could happen if the creation policy was changed after the resource was created
  231. if es.Spec.Target.CreationPolicy != esv1.CreatePolicyOwner && ownerIsCurrentES {
  232. if err := controllerutil.RemoveControllerReference(es, result, r.Scheme); err != nil {
  233. return fmt.Errorf("%w: %w", ErrSecretRemoveCtrlRef, err)
  234. }
  235. }
  236. labels := result.GetLabels()
  237. if labels == nil {
  238. labels = make(map[string]string)
  239. }
  240. // we also use a label to keep track of the owner of the resource
  241. // this lets us remove resources that are no longer needed if the target name changes
  242. // the label should not be set if the creation policy is not Owner
  243. if es.Spec.Target.CreationPolicy == esv1.CreatePolicyOwner {
  244. labels[esv1.LabelOwner] = esutils.ObjectHash(fmt.Sprintf("%v/%v", es.Namespace, es.Name))
  245. } else {
  246. delete(labels, esv1.LabelOwner)
  247. }
  248. result.SetLabels(labels)
  249. return nil
  250. }
  251. // createSimpleManifest creates a simple resource without templates (e.g., ConfigMap with data field).
  252. func (r *Reconciler) createSimpleManifest(obj *unstructured.Unstructured, dataMap map[string][]byte) *unstructured.Unstructured {
  253. // For ConfigMaps and similar resources, put data in .data field
  254. if obj.GetKind() == "ConfigMap" {
  255. data := make(map[string]string)
  256. for k, v := range dataMap {
  257. data[k] = string(v)
  258. }
  259. obj.Object["data"] = data
  260. return obj
  261. }
  262. // For other resources, put in spec.data or just data
  263. data := make(map[string]string)
  264. for k, v := range dataMap {
  265. data[k] = string(v)
  266. }
  267. if obj.Object["spec"] == nil {
  268. obj.Object["spec"] = make(map[string]any)
  269. }
  270. spec := obj.Object["spec"].(map[string]any)
  271. spec["data"] = data
  272. return obj
  273. }
  274. // renderTemplatedManifest renders templates for a custom resource.
  275. func (r *Reconciler) renderTemplatedManifest(ctx context.Context, es *esv1.ExternalSecret, obj *unstructured.Unstructured, dataMap map[string][]byte) (*unstructured.Unstructured, error) {
  276. execute, err := template.EngineForVersion(es.Spec.Target.Template.EngineVersion)
  277. if err != nil {
  278. return nil, fmt.Errorf("failed to get template engine: %w", err)
  279. }
  280. // Handle templateFrom entries
  281. for _, tplFrom := range es.Spec.Target.Template.TemplateFrom {
  282. targetPath := tplFrom.Target
  283. if targetPath == "" {
  284. targetPath = esv1.TemplateTargetData
  285. }
  286. if tplFrom.Literal != nil {
  287. // Execute template directly against the unstructured object
  288. out := make(map[string][]byte)
  289. out[*tplFrom.Literal] = []byte(*tplFrom.Literal)
  290. if err := execute(out, dataMap, esv1.TemplateScopeKeysAndValues, targetPath, obj); err != nil {
  291. return nil, fmt.Errorf("failed to execute literal template: %w", err)
  292. }
  293. }
  294. if tplFrom.ConfigMap != nil || tplFrom.Secret != nil {
  295. // Parser still uses v1.Secret, so collect data and apply via template engine to the end result.
  296. tempSecret := &v1.Secret{Data: make(map[string][]byte)}
  297. p := templating.Parser{
  298. Client: r.Client,
  299. TargetSecret: tempSecret,
  300. DataMap: dataMap,
  301. Exec: execute,
  302. }
  303. if tplFrom.ConfigMap != nil {
  304. if err := p.MergeConfigMap(ctx, es.Namespace, tplFrom); err != nil {
  305. return nil, fmt.Errorf("failed to merge configmap template: %w", err)
  306. }
  307. }
  308. if tplFrom.Secret != nil {
  309. if err := p.MergeSecret(ctx, es.Namespace, tplFrom); err != nil {
  310. return nil, fmt.Errorf("failed to merge secret template: %w", err)
  311. }
  312. }
  313. // apply collected data to the target object
  314. if err := execute(tempSecret.Data, dataMap, esv1.TemplateScopeValues, targetPath, obj); err != nil {
  315. return nil, fmt.Errorf("failed to apply merged templates to path %s: %w", targetPath, err)
  316. }
  317. }
  318. }
  319. // Handle template.data entries
  320. if len(es.Spec.Target.Template.Data) > 0 {
  321. tplMap := make(map[string][]byte)
  322. for k, v := range es.Spec.Target.Template.Data {
  323. tplMap[k] = []byte(v)
  324. }
  325. if err := execute(tplMap, dataMap, esv1.TemplateScopeValues, esv1.TemplateTargetData, obj); err != nil {
  326. return nil, fmt.Errorf("failed to execute template.data: %w", err)
  327. }
  328. }
  329. return obj, nil
  330. }