template.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  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. "strconv"
  19. "strings"
  20. tpl "text/template"
  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. "github.com/external-secrets/external-secrets/runtime/template/v2/sprig"
  29. )
  30. var tplFuncs = tpl.FuncMap{
  31. "pkcs12key": pkcs12key,
  32. "pkcs12keyPass": pkcs12keyPass,
  33. "pkcs12cert": pkcs12cert,
  34. "pkcs12certPass": pkcs12certPass,
  35. "pemToPkcs12": pemToPkcs12,
  36. "pemToPkcs12Pass": pemToPkcs12Pass,
  37. "fullPemToPkcs12": fullPemToPkcs12,
  38. "fullPemToPkcs12Pass": fullPemToPkcs12Pass,
  39. "pemTruststoreToPKCS12": pemTruststoreToPKCS12,
  40. "pemTruststoreToPKCS12Pass": pemTruststoreToPKCS12Pass,
  41. "filterPEM": filterPEM,
  42. "filterCertChain": filterCertChain,
  43. "certSANs": certSANs,
  44. "jwkPublicKeyPem": jwkPublicKeyPem,
  45. "jwkPrivateKeyPem": jwkPrivateKeyPem,
  46. "toYaml": toYAML,
  47. "fromYaml": fromYAML,
  48. "rsaDecrypt": rsaDecrypt,
  49. }
  50. var leftDelim, rightDelim string
  51. var (
  52. errConvertingToUnstructured = "failed to convert object to unstructured: %w"
  53. errConvertingToObject = "failed to convert unstructured to object: %w"
  54. )
  55. // FuncMap returns the template function map so other templating calls can use the same extra functions.
  56. func FuncMap() tpl.FuncMap {
  57. return tplFuncs
  58. }
  59. const (
  60. errParse = "unable to parse template at key %s: %s"
  61. errExecute = "unable to execute template at key %s: %s"
  62. errDecodePKCS12WithPass = "unable to decode pkcs12 with password: %s"
  63. errDecodeCertWithPass = "unable to decode pkcs12 certificate with password: %s"
  64. errParsePrivKey = "unable to parse private key type"
  65. pemTypeCertificate = "CERTIFICATE"
  66. pemTypeKey = "PRIVATE KEY"
  67. )
  68. func init() {
  69. maps.Copy(tplFuncs, sprig.TxtFuncMap())
  70. fs := pflag.NewFlagSet("template", pflag.ExitOnError)
  71. fs.StringVar(&leftDelim, "template-left-delimiter", "{{", "templating left delimiter")
  72. fs.StringVar(&rightDelim, "template-right-delimiter", "}}", "templating right delimiter")
  73. feature.Register(feature.Feature{
  74. Flags: fs,
  75. })
  76. }
  77. func applyToTarget(k string, val []byte, target string, obj client.Object) error {
  78. // Match the well-known top-level targets case-insensitively, but keep the
  79. // original case of target so nested custom-resource paths preserve
  80. // mixed-case segments (e.g. spec.headers.customRequestHeaders). Issue #6458.
  81. switch strings.ToLower(target) {
  82. case "annotations":
  83. annotations := obj.GetAnnotations()
  84. if annotations == nil {
  85. annotations = make(map[string]string)
  86. }
  87. annotations[k] = string(val)
  88. obj.SetAnnotations(annotations)
  89. case "labels":
  90. labels := obj.GetLabels()
  91. if labels == nil {
  92. labels = make(map[string]string)
  93. }
  94. labels[k] = string(val)
  95. obj.SetLabels(labels)
  96. case "data":
  97. if err := setField(obj, "data", k, val); err != nil {
  98. return fmt.Errorf("failed to set data field on object: %w", err)
  99. }
  100. case "spec":
  101. if err := setField(obj, "spec", k, val); err != nil {
  102. return fmt.Errorf("failed to set spec field on object: %w", err)
  103. }
  104. default:
  105. tokens, err := parseTargetPath(target)
  106. if err != nil {
  107. return err
  108. }
  109. // Set the value at the target path, converting []byte to string to avoid
  110. // base64 encoding when serializing.
  111. leaf := func(any) any { return tryParseYAML(string(val)) }
  112. if err := applyAtPath(obj, target, tokens, leaf); err != nil {
  113. return err
  114. }
  115. }
  116. // all fields have been nilled out if they weren't set.
  117. if obj.GetLabels() == nil {
  118. obj.SetLabels(make(map[string]string))
  119. }
  120. if obj.GetAnnotations() == nil {
  121. obj.SetAnnotations(make(map[string]string))
  122. }
  123. return nil
  124. }
  125. func valueScopeApply(tplMap, data map[string][]byte, target string, secret client.Object) error {
  126. for k, v := range tplMap {
  127. val, err := execute(k, string(v), data)
  128. if err != nil {
  129. return fmt.Errorf(errExecute, k, err)
  130. }
  131. if err := applyToTarget(k, val, target, secret); err != nil {
  132. return fmt.Errorf("failed to apply to target: %w", err)
  133. }
  134. }
  135. return nil
  136. }
  137. func mapScopeApply(tpl string, data map[string][]byte, target string, secret client.Object) error {
  138. val, err := execute(tpl, tpl, data)
  139. if err != nil {
  140. return fmt.Errorf(errExecute, tpl, err)
  141. }
  142. // See applyToTarget: switch on the lowercased target but keep the original
  143. // case so nested paths are not lowercased (issue #6458).
  144. switch strings.ToLower(target) {
  145. case "annotations", "labels", "data":
  146. // normal route
  147. src := make(map[string]string)
  148. err = yaml.Unmarshal(val, &src)
  149. if err != nil {
  150. return fmt.Errorf("could not unmarshal template to 'map[string][]byte': %w", err)
  151. }
  152. for k, val := range src {
  153. if err := applyToTarget(k, []byte(val), target, secret); err != nil {
  154. return fmt.Errorf("failed to apply to target: %w", err)
  155. }
  156. }
  157. // we are done
  158. return nil
  159. }
  160. // for more complex path, we need to navigate to the last element of the path
  161. // creating objects in that path if they don't exist and then apply the parsed
  162. // structure at that location to the entire object.
  163. var parsed any
  164. if err := yaml.Unmarshal(val, &parsed); err != nil {
  165. return fmt.Errorf("could not unmarshal template YAML: %w", err)
  166. }
  167. return applyParsedToPath(parsed, target, secret)
  168. }
  169. // Execute renders the secret data as template. If an error occurs processing is stopped immediately.
  170. func Execute(tpl, data map[string][]byte, scope esapi.TemplateScope, target string, secret client.Object) error {
  171. if tpl == nil {
  172. return nil
  173. }
  174. switch scope {
  175. case esapi.TemplateScopeKeysAndValues:
  176. for _, v := range tpl {
  177. err := mapScopeApply(string(v), data, target, secret)
  178. if err != nil {
  179. return err
  180. }
  181. }
  182. case esapi.TemplateScopeValues:
  183. err := valueScopeApply(tpl, data, target, secret)
  184. if err != nil {
  185. return err
  186. }
  187. default:
  188. return fmt.Errorf("unknown scope '%v': expected 'Values' or 'KeysAndValues'", scope)
  189. }
  190. return nil
  191. }
  192. func execute(k, val string, data map[string][]byte) ([]byte, error) {
  193. strValData := make(map[string]string, len(data))
  194. for k := range data {
  195. strValData[k] = string(data[k])
  196. }
  197. t, err := tpl.New(k).
  198. Option("missingkey=error").
  199. Funcs(tplFuncs).
  200. Delims(leftDelim, rightDelim).
  201. Parse(val)
  202. if err != nil {
  203. return nil, fmt.Errorf(errParse, k, err)
  204. }
  205. buf := bytes.NewBuffer(nil)
  206. err = t.Execute(buf, strValData)
  207. if err != nil {
  208. return nil, fmt.Errorf(errExecute, k, err)
  209. }
  210. return buf.Bytes(), nil
  211. }
  212. // setData sets the data field of the object.
  213. func setField(obj client.Object, field, k string, val []byte) error {
  214. m, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
  215. if err != nil {
  216. return fmt.Errorf(errConvertingToUnstructured, err)
  217. }
  218. _, ok := m[field]
  219. if !ok {
  220. m[field] = map[string]any{}
  221. }
  222. specMap, ok := m[field].(map[string]any)
  223. if !ok {
  224. return fmt.Errorf("failed to convert data to map[string][]byte")
  225. }
  226. // Secrets require base64-encoded []byte values in the data field
  227. // Other resources (ConfigMaps, custom resources) need plain string values
  228. _, isSecret := obj.(*corev1.Secret)
  229. if isSecret {
  230. // For Secrets, keep as []byte (will be base64-encoded during serialization)
  231. specMap[k] = val
  232. } else {
  233. // For generic (ConfigMaps, custom resources), use plain strings
  234. specMap[k] = string(val)
  235. }
  236. m[field] = specMap
  237. // Convert back to the original object type
  238. if err := runtime.DefaultUnstructuredConverter.FromUnstructured(m, obj); err != nil {
  239. return fmt.Errorf(errConvertingToObject, err)
  240. }
  241. return nil
  242. }
  243. // tryParseYAML attempts to parse a string value as YAML, returns original value if parsing fails.
  244. func tryParseYAML(value any) any {
  245. str, ok := value.(string)
  246. if !ok {
  247. return value
  248. }
  249. var parsed any
  250. if err := yaml.Unmarshal([]byte(str), &parsed); err == nil {
  251. return parsed
  252. }
  253. return value
  254. }
  255. // applyParsedToPath applies a parsed YAML structure to a specific path in the object.
  256. func applyParsedToPath(parsed any, target string, obj client.Object) error {
  257. tokens, err := parseTargetPath(target)
  258. if err != nil {
  259. return err
  260. }
  261. // navigate to the last element of the path and apply the entire struct at that location.
  262. // MERGE the parsed content into existing map content instead of replacing it.
  263. leaf := func(existing any) any {
  264. existingMap, existingOk := existing.(map[string]any)
  265. parsedMap, parsedOk := parsed.(map[string]any)
  266. if existingOk && parsedOk {
  267. maps.Copy(existingMap, parsedMap)
  268. return existingMap
  269. }
  270. // existing or parsed value is not a map, replace entirely.
  271. // this might break if people are trying to overwrite
  272. // fields that aren't supposed to do that. but that's
  273. // on the user to keep in mind. If they are trying to
  274. // update a number field with a complex value, that's
  275. // going to error on update anyway.
  276. return parsed
  277. }
  278. // single value, aka "spec": replace entirely to preserve historical behavior.
  279. if len(tokens) == 1 && tokens[0].kind == tokenKey {
  280. leaf = func(any) any { return parsed }
  281. }
  282. return applyAtPath(obj, target, tokens, leaf)
  283. }
  284. // applyAtPath applies leaf at the token path within obj's unstructured form and
  285. // writes the result back into obj. target is used only for error messages.
  286. func applyAtPath(obj client.Object, target string, tokens []pathToken, leaf func(existing any) any) error {
  287. unstructured, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
  288. if err != nil {
  289. return fmt.Errorf(errConvertingToUnstructured, err)
  290. }
  291. updated, err := setAtPath(unstructured, tokens, leaf)
  292. if err != nil {
  293. return fmt.Errorf("failed to set path %s: %w", target, err)
  294. }
  295. updatedMap, ok := updated.(map[string]any)
  296. if !ok {
  297. return fmt.Errorf("failed to set path %s: expected object root, got %T", target, updated)
  298. }
  299. if err := runtime.DefaultUnstructuredConverter.FromUnstructured(updatedMap, obj); err != nil {
  300. return fmt.Errorf(errConvertingToObject, err)
  301. }
  302. return nil
  303. }
  304. // pathTokenKind distinguishes a map-key access from a slice-index access in a target path.
  305. type pathTokenKind int
  306. const (
  307. tokenKey pathTokenKind = iota
  308. tokenIndex
  309. )
  310. // pathToken is a single step in a parsed target path.
  311. type pathToken struct {
  312. kind pathTokenKind
  313. name string
  314. idx int
  315. }
  316. // maxArrayIndex limits the memory assignation for setAtPath.
  317. const maxArrayIndex = 10000
  318. // parseTargetPath is a pure function parsing and validating a dotted target path, with optional slice notation, into tokens.
  319. // Each step is a pathToken of kind "tokenKey" or "tokenIndex".
  320. //
  321. // e.g.
  322. //
  323. // input: "spec.rules[0].from[0].source.notRemoteIpBlocks"
  324. // output: [
  325. // {key, "spec"},
  326. // {key, "rules"},
  327. // {index, 0},
  328. // {key, "from"},
  329. // {index, 0},
  330. // {key, "source"},
  331. // {key, "notRemoteIpBlocks"},
  332. // ]
  333. func parseTargetPath(target string) ([]pathToken, error) {
  334. var tokens []pathToken
  335. for seg := range strings.SplitSeq(target, ".") {
  336. if seg == "" {
  337. return nil, fmt.Errorf("invalid path %q: empty segment", target)
  338. }
  339. name, rest := seg, ""
  340. if i := strings.IndexByte(seg, '['); i >= 0 {
  341. name, rest = seg[:i], seg[i:]
  342. }
  343. if name != "" {
  344. tokens = append(tokens, pathToken{kind: tokenKey, name: name})
  345. } else if rest == "" {
  346. return nil, fmt.Errorf("invalid path %q: empty segment", target)
  347. }
  348. for rest != "" {
  349. if rest[0] != '[' {
  350. return nil, fmt.Errorf("invalid path %q: expected '[' near %q", target, rest)
  351. }
  352. end := strings.IndexByte(rest, ']')
  353. if end < 0 {
  354. return nil, fmt.Errorf("invalid path %q: unterminated '['", target)
  355. }
  356. idx, err := strconv.Atoi(rest[1:end])
  357. if err != nil {
  358. return nil, fmt.Errorf("invalid path %q: bad index %q", target, rest[1:end])
  359. }
  360. if idx < 0 {
  361. return nil, fmt.Errorf("invalid path %q: negative index %d", target, idx)
  362. }
  363. if idx > maxArrayIndex {
  364. return nil, fmt.Errorf("invalid path %q: index %d exceeds maximum (10000)", target, idx)
  365. }
  366. tokens = append(tokens, pathToken{kind: tokenIndex, idx: idx})
  367. rest = rest[end+1:]
  368. }
  369. }
  370. if len(tokens) == 0 {
  371. return nil, fmt.Errorf("invalid path: %s", target)
  372. }
  373. if tokens[0].kind != tokenKey {
  374. return nil, fmt.Errorf("invalid path %q: path must start with a key", target)
  375. }
  376. return tokens, nil
  377. }
  378. // setAtPath walks node following tokens, creating intermediate maps and slices as needed,
  379. // and applies leaf to the value at the final location. It returns the possibly-reallocated
  380. // node, since growing a slice replaces its header.
  381. func setAtPath(node any, tokens []pathToken, leaf func(existing any) any) (any, error) {
  382. tok := tokens[0]
  383. switch tok.kind {
  384. case tokenKey:
  385. return setMap(node, tokens, leaf, tok)
  386. case tokenIndex:
  387. return setIndex(node, tokens, leaf, tok)
  388. default:
  389. return nil, fmt.Errorf("unknown path token kind %d", tok.kind)
  390. }
  391. }
  392. // setIndex sets a value in a slice at index.
  393. func setIndex(node any, tokens []pathToken, leaf func(existing any) any, tok pathToken) (any, error) {
  394. s, ok := node.([]any)
  395. if !ok && node != nil {
  396. return nil, fmt.Errorf("expected array at index %d but found %T", tok.idx, node)
  397. }
  398. if tok.idx >= len(s) {
  399. grown := make([]any, tok.idx+1)
  400. copy(grown, s)
  401. s = grown
  402. }
  403. if err := setLeaf(
  404. func() any { return s[tok.idx] },
  405. func(v any) { s[tok.idx] = v },
  406. tokens, leaf,
  407. ); err != nil {
  408. return nil, err
  409. }
  410. return s, nil
  411. }
  412. // setMap sets a value in a map at token name.
  413. func setMap(node any, tokens []pathToken, leaf func(existing any) any, tok pathToken) (any, error) {
  414. m, ok := node.(map[string]any)
  415. if !ok && node != nil {
  416. return nil, fmt.Errorf("expected map at key %q but found %T", tok.name, node)
  417. }
  418. if len(m) == 0 {
  419. m = make(map[string]any)
  420. }
  421. if err := setLeaf(
  422. func() any { return m[tok.name] },
  423. func(v any) { m[tok.name] = v },
  424. tokens, leaf,
  425. ); err != nil {
  426. return nil, err
  427. }
  428. return m, nil
  429. }
  430. // setLeaf writes leaf into a container via get/set closures, recursing when
  431. // the path continues. The closures capture the container (slice or map).
  432. // set is called to set a value in a container, be that a slice or a map.
  433. // get is called to retrieve a value from a container, either a slice or a map.
  434. // This abstraction is necessary because there are no shared slice/map operation even
  435. // in Typed Parameters, so these cannot be reasonably abstracted. At least, it makes
  436. // the entire logic a bit more readable.
  437. func setLeaf(get func() any, set func(any), tokens []pathToken, leaf func(existing any) any) error {
  438. if len(tokens) == 1 {
  439. set(leaf(get()))
  440. return nil
  441. }
  442. child, err := setAtPath(get(), tokens[1:], leaf)
  443. if err != nil {
  444. return err
  445. }
  446. set(child)
  447. return nil
  448. }