master
go 143 lines 3.46 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package secretresolver
4
5 import (
6 "context"
7 "fmt"
8 "regexp"
9 "strings"
10 )
11
12 type StoreRefResolver func(ctx context.Context, ref, original string) (string, error)
13
14 // reSecretRef matches ${...} references in strings.
15 var reSecretRef = regexp.MustCompile(`\$\{([^}]+)\}`)
16
17 // Resolve walks the config map and resolves all secret references in string values.
18 func (r *Resolver) Resolve(cfg map[string]any) error {
19 return r.ResolveWithStoreResolver(context.Background(), cfg, nil)
20 }
21
22 func (r *Resolver) ResolveWithStoreResolver(ctx context.Context, cfg map[string]any, storeResolver StoreRefResolver) error {
23 if r == nil {
24 return fmt.Errorf("secret resolver is nil")
25 }
26 if ctx == nil {
27 ctx = context.Background()
28 }
29 r.ensureDefaults()
30 return r.resolveMap(ctx, cfg, storeResolver)
31 }
32
33 func (r *Resolver) resolveMap(ctx context.Context, m map[string]any, storeResolver StoreRefResolver) error {
34 for k, v := range m {
35 // skip internal keys (__key__)
36 if isInternalKey(k) {
37 continue
38 }
39 resolved, err := r.resolveValue(ctx, v, storeResolver)
40 if err != nil {
41 return err
42 }
43 m[k] = resolved
44 }
45 return nil
46 }
47
48 func (r *Resolver) resolveMapAny(ctx context.Context, m map[any]any, storeResolver StoreRefResolver) error {
49 for k, v := range m {
50 s, ok := k.(string)
51 if ok && isInternalKey(s) {
52 continue
53 }
54 resolved, err := r.resolveValue(ctx, v, storeResolver)
55 if err != nil {
56 return err
57 }
58 m[k] = resolved
59 }
60 return nil
61 }
62
63 func (r *Resolver) resolveSlice(ctx context.Context, s []any, storeResolver StoreRefResolver) error {
64 for i, v := range s {
65 resolved, err := r.resolveValue(ctx, v, storeResolver)
66 if err != nil {
67 return err
68 }
69 s[i] = resolved
70 }
71 return nil
72 }
73
74 func (r *Resolver) resolveValue(ctx context.Context, v any, storeResolver StoreRefResolver) (any, error) {
75 switch val := v.(type) {
76 case string:
77 return r.resolveString(ctx, val, storeResolver)
78 case map[string]any:
79 return val, r.resolveMap(ctx, val, storeResolver)
80 case map[any]any:
81 return val, r.resolveMapAny(ctx, val, storeResolver)
82 case []any:
83 return val, r.resolveSlice(ctx, val, storeResolver)
84 default:
85 return v, nil
86 }
87 }
88
89 func (r *Resolver) resolveString(ctx context.Context, s string, storeResolver StoreRefResolver) (string, error) {
90 if !strings.Contains(s, "${") {
91 return s, nil
92 }
93
94 var resolveErr error
95
96 result := reSecretRef.ReplaceAllStringFunc(s, func(match string) string {
97 if resolveErr != nil {
98 return match
99 }
100
101 // extract inner content between ${ and }
102 inner := match[2 : len(match)-1]
103
104 val, err := r.resolveRef(ctx, inner, match, storeResolver)
105 if err != nil {
106 resolveErr = err
107 return match
108 }
109 return val
110 })
111
112 if resolveErr != nil {
113 return "", resolveErr
114 }
115 return result, nil
116 }
117
118 func (r *Resolver) resolveRef(ctx context.Context, ref, original string, storeResolver StoreRefResolver) (string, error) {
119 scheme, name, hasScheme := strings.Cut(ref, ":")
120
121 if !hasScheme {
122 // no scheme — leave unchanged
123 return original, nil
124 }
125
126 if scheme == "store" {
127 if storeResolver == nil {
128 return "", fmt.Errorf("resolving secret '%s': secretstore resolver is not configured", original)
129 }
130 return storeResolver(ctx, name, original)
131 }
132
133 provider, ok := r.providers[scheme]
134 if !ok {
135 return "", fmt.Errorf("resolving secret '%s': unknown secret provider '%s'", original, scheme)
136 }
137
138 return provider(ctx, name, original)
139 }
140
141 func isInternalKey(k string) bool {
142 return strings.HasPrefix(k, "__") && strings.HasSuffix(k, "__")
143 }