template.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  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 template
  14. import (
  15. "bytes"
  16. "fmt"
  17. "maps"
  18. "strings"
  19. tpl "text/template"
  20. "github.com/Masterminds/sprig/v3"
  21. "github.com/spf13/pflag"
  22. corev1 "k8s.io/api/core/v1"
  23. "k8s.io/apimachinery/pkg/runtime"
  24. "k8s.io/apimachinery/pkg/util/yaml"
  25. "sigs.k8s.io/controller-runtime/pkg/client"
  26. esapi "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  27. "github.com/external-secrets/external-secrets/runtime/feature"
  28. )
  29. var tplFuncs = tpl.FuncMap{
  30. "pkcs12key": pkcs12key,
  31. "pkcs12keyPass": pkcs12keyPass,
  32. "pkcs12cert": pkcs12cert,
  33. "pkcs12certPass": pkcs12certPass,
  34. "pemToPkcs12": pemToPkcs12,
  35. "pemToPkcs12Pass": pemToPkcs12Pass,
  36. "fullPemToPkcs12": fullPemToPkcs12,
  37. "fullPemToPkcs12Pass": fullPemToPkcs12Pass,
  38. "pemTruststoreToPKCS12": pemTruststoreToPKCS12,
  39. "pemTruststoreToPKCS12Pass": pemTruststoreToPKCS12Pass,
  40. "filterPEM": filterPEM,
  41. "filterCertChain": filterCertChain,
  42. "certSANs": certSANs,
  43. "jwkPublicKeyPem": jwkPublicKeyPem,
  44. "jwkPrivateKeyPem": jwkPrivateKeyPem,
  45. "toYaml": toYAML,
  46. "fromYaml": fromYAML,
  47. "rsaDecrypt": rsaDecrypt,
  48. }
  49. var leftDelim, rightDelim string
  50. var (
  51. errConvertingToUnstructured = "failed to convert object to unstructured: %w"
  52. errConvertingToObject = "failed to convert unstructured to object: %w"
  53. )
  54. // FuncMap returns the template function map so other templating calls can use the same extra functions.
  55. func FuncMap() tpl.FuncMap {
  56. return tplFuncs
  57. }
  58. const (
  59. errParse = "unable to parse template at key %s: %s"
  60. errExecute = "unable to execute template at key %s: %s"
  61. errDecodePKCS12WithPass = "unable to decode pkcs12 with password: %s"
  62. errDecodeCertWithPass = "unable to decode pkcs12 certificate with password: %s"
  63. errParsePrivKey = "unable to parse private key type"
  64. pemTypeCertificate = "CERTIFICATE"
  65. pemTypeKey = "PRIVATE KEY"
  66. )
  67. func init() {
  68. sprigFuncs := sprig.TxtFuncMap()
  69. delete(sprigFuncs, "env")
  70. delete(sprigFuncs, "expandenv")
  71. delete(sprigFuncs, "getHostByName")
  72. maps.Copy(tplFuncs, sprigFuncs)
  73. fs := pflag.NewFlagSet("template", pflag.ExitOnError)
  74. fs.StringVar(&leftDelim, "template-left-delimiter", "{{", "templating left delimiter")
  75. fs.StringVar(&rightDelim, "template-right-delimiter", "}}", "templating right delimiter")
  76. feature.Register(feature.Feature{
  77. Flags: fs,
  78. })
  79. }
  80. func applyToTarget(k string, val []byte, target string, obj client.Object) error {
  81. target = strings.ToLower(target)
  82. switch target {
  83. case "annotations":
  84. annotations := obj.GetAnnotations()
  85. if annotations == nil {
  86. annotations = make(map[string]string)
  87. }
  88. annotations[k] = string(val)
  89. obj.SetAnnotations(annotations)
  90. case "labels":
  91. labels := obj.GetLabels()
  92. if labels == nil {
  93. labels = make(map[string]string)
  94. }
  95. labels[k] = string(val)
  96. obj.SetLabels(labels)
  97. case "data":
  98. if err := setField(obj, "data", k, val); err != nil {
  99. return fmt.Errorf("failed to set data field on object: %w", err)
  100. }
  101. case "spec":
  102. if err := setField(obj, "spec", k, val); err != nil {
  103. return fmt.Errorf("failed to set data field on object: %w", err)
  104. }
  105. default:
  106. parts := strings.Split(target, ".")
  107. if len(parts) == 0 {
  108. return fmt.Errorf("invalid path: %s", target)
  109. }
  110. unstructured, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
  111. if err != nil {
  112. return fmt.Errorf(errConvertingToUnstructured, err)
  113. }
  114. // Navigate to the parent of the target field
  115. current := unstructured
  116. for i := range len(parts) - 1 {
  117. part := parts[i]
  118. if current[part] == nil {
  119. current[part] = make(map[string]any)
  120. }
  121. next, ok := current[part].(map[string]any)
  122. if !ok {
  123. return fmt.Errorf("path %s is not a map at segment %s", target, part)
  124. }
  125. current = next
  126. }
  127. // Set the value at the final key
  128. // Convert []byte to string to avoid base64 encoding when serializing
  129. lastPart := parts[len(parts)-1]
  130. current[lastPart] = tryParseYAML(string(val))
  131. // Convert back to the original object type
  132. if err := runtime.DefaultUnstructuredConverter.FromUnstructured(unstructured, obj); err != nil {
  133. return fmt.Errorf(errConvertingToObject, err)
  134. }
  135. }
  136. // all fields have been nilled out if they weren't set.
  137. if obj.GetLabels() == nil {
  138. obj.SetLabels(make(map[string]string))
  139. }
  140. if obj.GetAnnotations() == nil {
  141. obj.SetAnnotations(make(map[string]string))
  142. }
  143. return nil
  144. }
  145. func valueScopeApply(tplMap, data map[string][]byte, target string, secret client.Object) error {
  146. for k, v := range tplMap {
  147. val, err := execute(k, string(v), data)
  148. if err != nil {
  149. return fmt.Errorf(errExecute, k, err)
  150. }
  151. if err := applyToTarget(k, val, target, secret); err != nil {
  152. return fmt.Errorf("failed to apply to target: %w", err)
  153. }
  154. }
  155. return nil
  156. }
  157. func mapScopeApply(tpl string, data map[string][]byte, target string, secret client.Object) error {
  158. val, err := execute(tpl, tpl, data)
  159. if err != nil {
  160. return fmt.Errorf(errExecute, tpl, err)
  161. }
  162. target = strings.ToLower(target)
  163. switch target {
  164. case "annotations", "labels", "data":
  165. // normal route
  166. src := make(map[string]string)
  167. err = yaml.Unmarshal(val, &src)
  168. if err != nil {
  169. return fmt.Errorf("could not unmarshal template to 'map[string][]byte': %w", err)
  170. }
  171. for k, val := range src {
  172. if err := applyToTarget(k, []byte(val), target, secret); err != nil {
  173. return fmt.Errorf("failed to apply to target: %w", err)
  174. }
  175. }
  176. // we are done
  177. return nil
  178. }
  179. // for more complex path, we need to navigate to the last element of the path
  180. // creating objects in that path if they don't exist and then apply the parsed
  181. // structure at that location to the entire object.
  182. var parsed any
  183. if err := yaml.Unmarshal(val, &parsed); err != nil {
  184. return fmt.Errorf("could not unmarshal template YAML: %w", err)
  185. }
  186. return applyParsedToPath(parsed, target, secret)
  187. }
  188. // Execute renders the secret data as template. If an error occurs processing is stopped immediately.
  189. func Execute(tpl, data map[string][]byte, scope esapi.TemplateScope, target string, secret client.Object) error {
  190. if tpl == nil {
  191. return nil
  192. }
  193. switch scope {
  194. case esapi.TemplateScopeKeysAndValues:
  195. for _, v := range tpl {
  196. err := mapScopeApply(string(v), data, target, secret)
  197. if err != nil {
  198. return err
  199. }
  200. }
  201. case esapi.TemplateScopeValues:
  202. err := valueScopeApply(tpl, data, target, secret)
  203. if err != nil {
  204. return err
  205. }
  206. default:
  207. return fmt.Errorf("unknown scope '%v': expected 'Values' or 'KeysAndValues'", scope)
  208. }
  209. return nil
  210. }
  211. func execute(k, val string, data map[string][]byte) ([]byte, error) {
  212. strValData := make(map[string]string, len(data))
  213. for k := range data {
  214. strValData[k] = string(data[k])
  215. }
  216. t, err := tpl.New(k).
  217. Option("missingkey=error").
  218. Funcs(tplFuncs).
  219. Delims(leftDelim, rightDelim).
  220. Parse(val)
  221. if err != nil {
  222. return nil, fmt.Errorf(errParse, k, err)
  223. }
  224. buf := bytes.NewBuffer(nil)
  225. err = t.Execute(buf, strValData)
  226. if err != nil {
  227. return nil, fmt.Errorf(errExecute, k, err)
  228. }
  229. return buf.Bytes(), nil
  230. }
  231. // setData sets the data field of the object.
  232. func setField(obj client.Object, field, k string, val []byte) error {
  233. m, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
  234. if err != nil {
  235. return fmt.Errorf(errConvertingToUnstructured, err)
  236. }
  237. _, ok := m[field]
  238. if !ok {
  239. m[field] = map[string]any{}
  240. }
  241. specMap, ok := m[field].(map[string]any)
  242. if !ok {
  243. return fmt.Errorf("failed to convert data to map[string][]byte")
  244. }
  245. // Secrets require base64-encoded []byte values in the data field
  246. // Other resources (ConfigMaps, custom resources) need plain string values
  247. _, isSecret := obj.(*corev1.Secret)
  248. if isSecret {
  249. // For Secrets, keep as []byte (will be base64-encoded during serialization)
  250. specMap[k] = val
  251. } else {
  252. // For generic (ConfigMaps, custom resources), use plain strings
  253. specMap[k] = string(val)
  254. }
  255. m[field] = specMap
  256. // Convert back to the original object type
  257. if err := runtime.DefaultUnstructuredConverter.FromUnstructured(m, obj); err != nil {
  258. return fmt.Errorf(errConvertingToObject, err)
  259. }
  260. return nil
  261. }
  262. // tryParseYAML attempts to parse a string value as YAML, returns original value if parsing fails.
  263. func tryParseYAML(value any) any {
  264. str, ok := value.(string)
  265. if !ok {
  266. return value
  267. }
  268. var parsed any
  269. if err := yaml.Unmarshal([]byte(str), &parsed); err == nil {
  270. return parsed
  271. }
  272. return value
  273. }
  274. // applyParsedToPath applies a parsed YAML structure to a specific path in the object.
  275. func applyParsedToPath(parsed any, target string, obj client.Object) error {
  276. unstructured, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
  277. if err != nil {
  278. return fmt.Errorf(errConvertingToUnstructured, err)
  279. }
  280. parts := strings.Split(target, ".")
  281. if len(parts) == 0 {
  282. return fmt.Errorf("invalid path: %s", target)
  283. }
  284. // single value, aka "spec"
  285. if len(parts) == 1 {
  286. unstructured[parts[0]] = parsed
  287. } else {
  288. // navigate to the last element of the path and apply the entire struct at that location.
  289. // build up the entire map structure that we are eventually going to apply.
  290. current := unstructured
  291. // this STOPS at the last part! That is important. for _, part := range parts does _include_ the last part
  292. for i := 0; i < len(parts)-1; i++ {
  293. part := parts[i]
  294. if current[part] == nil {
  295. current[part] = make(map[string]any)
  296. }
  297. next, ok := current[part].(map[string]any)
  298. if !ok {
  299. return fmt.Errorf("path %s is not a map at segment %s", target, part)
  300. }
  301. current = next
  302. }
  303. // once we constructed the entire segment, we finally apply our parsed object
  304. // MERGE the parsed content into existing content instead of replacing it
  305. lastPart := parts[len(parts)-1]
  306. if existing, exists := current[lastPart]; exists {
  307. // if both existing and new values are maps, merge them
  308. existingMap, existingOk := existing.(map[string]any)
  309. parsedMap, parsedOk := parsed.(map[string]any)
  310. if existingOk && parsedOk {
  311. maps.Copy(existingMap, parsedMap)
  312. current[lastPart] = existingMap
  313. } else {
  314. // existing or parsed value is not a map, replace entirely.
  315. // this might break if people are trying to overwrite
  316. // fields that aren't supposed to do that. but that's
  317. // on the user to keep in mind. If they are trying to
  318. // update a number field with a complex value, that's
  319. // going to error on update anyway.
  320. current[lastPart] = parsed
  321. }
  322. } else {
  323. // field doesn't exist yet, create it
  324. current[lastPart] = parsed
  325. }
  326. }
  327. // convert back to original object
  328. if err := runtime.DefaultUnstructuredConverter.FromUnstructured(unstructured, obj); err != nil {
  329. return fmt.Errorf(errConvertingToObject, err)
  330. }
  331. return nil
  332. }