1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package livestate
16
17 import (
18 "context"
19 "fmt"
20 "strings"
21
22 corekccv1alpha1 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/core/v1alpha1"
23 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/dcl"
24 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/dcl/conversion"
25 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/dcl/extension"
26 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/dcl/kcclite"
27 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/deepcopy"
28 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/gcp"
29 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
30 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/servicemapping/servicemappingloader"
31 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/util"
32 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/util/pathslice"
33
34 mmdcl "github.com/GoogleCloudPlatform/declarative-resource-client-library/dcl"
35 dclunstruct "github.com/GoogleCloudPlatform/declarative-resource-client-library/unstructured"
36 "github.com/nasa9084/go-openapi"
37 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
38 "sigs.k8s.io/controller-runtime/pkg/client"
39 )
40
41
42
43
44 func FetchLiveState(ctx context.Context, resource *dcl.Resource, dclConfig *mmdcl.Config, converter *conversion.Converter, serviceMappingLoader *servicemappingloader.ServiceMappingLoader, kubeClient client.Client) (*unstructured.Unstructured, error) {
45 serverGeneratedIDNotConfigured, err := resource.HasServerGeneratedIDButNotConfigured()
46 if err != nil {
47 return nil, err
48 }
49 if serverGeneratedIDNotConfigured {
50
51
52
53
54 return nil, nil
55 }
56
57 var lite *unstructured.Unstructured
58 secretVersions := make(map[string]string)
59 if k8s.IsDeleted(&resource.ObjectMeta) {
60 lite, err = kcclite.ToKCCLiteBestEffort(resource, converter.MetadataLoader, converter.SchemaLoader, serviceMappingLoader, kubeClient)
61 } else {
62 lite, secretVersions, err = kcclite.ToKCCLiteAndSecretVersions(resource, converter.MetadataLoader, converter.SchemaLoader, serviceMappingLoader, kubeClient)
63 }
64 if err != nil {
65 return nil, fmt.Errorf("error converting to lite state: %w", err)
66 }
67
68 dclResource, err := converter.KRMObjectToDCLObject(lite)
69 if err != nil {
70 return nil, fmt.Errorf("error converting to DCL resource object: %w", err)
71 }
72
73 liveState, err := dclunstruct.Get(ctx, dclConfig, dclResource)
74 if err != nil {
75 if gcp.IsNotFoundError(err) {
76 return nil, nil
77 }
78 return nil, fmt.Errorf("error reading underlying resource: %w", err)
79 }
80 liveLite, err := converter.DCLObjectToKRMObject(liveState)
81 if err != nil {
82 return nil, fmt.Errorf("error converting DCL object to KRM resource obj: %w", err)
83 }
84
85 liveLite, err = withMutableButUnreadableFields(liveLite, resource, secretVersions, kubeClient)
86 if err != nil {
87 return nil, fmt.Errorf("error setting mutable-but-unreadable fields for live state: %w", err)
88 }
89
90 return liveLite, nil
91 }
92
93 func SetMutableButUnreadableFields(kccLite *unstructured.Unstructured, mutableButUnreadableSpec map[string]interface{}, path []string, schema *openapi.Schema, secretVersions map[string]string, namespace string, kubeClient client.Client) (*unstructured.Unstructured, error) {
94 if len(mutableButUnreadableSpec) == 0 {
95 return kccLite, nil
96 }
97
98 if schema.Type != "object" {
99 return nil, fmt.Errorf("wrong type for provided schema: %s, expect to have object", schema.Type)
100 }
101
102 for k, v := range mutableButUnreadableSpec {
103 path := append(path, k)
104
105 subSchema, ok := schema.Properties[k]
106 if !ok {
107 return nil, fmt.Errorf("unknown mutable-but-unreadable path '%v'", pathslice.ToString(path))
108 }
109 switch subSchema.Type {
110 case "integer":
111 v, err := dcl.CanonicalizeIntegerValue(v)
112 if err != nil {
113 return nil, fmt.Errorf("error canonicalizing the integer value for path '%v': %w", pathslice.ToString(path), err)
114 }
115
116 if err := unstructured.SetNestedField(kccLite.Object, v, path...); err != nil {
117 return nil, fmt.Errorf("error setting mutable-but-unreadable path '%v': %w", pathslice.ToString(path), err)
118 }
119 case "number":
120 v, err := dcl.CanonicalizeNumberValue(v)
121 if err != nil {
122 return nil, fmt.Errorf("error canonicalizing the number value for path '%v': %w", pathslice.ToString(path), err)
123 }
124
125 if err := unstructured.SetNestedField(kccLite.Object, v, path...); err != nil {
126 return nil, fmt.Errorf("error setting mutable-but-unreadable path '%v': %w", pathslice.ToString(path), err)
127 }
128
129 case "string":
130 isSensitive, err := extension.IsSensitiveField(subSchema)
131 if err != nil {
132 return nil, fmt.Errorf("error checking sensitivity for mutable-but-unreadable path '%v': %w", pathslice.ToString(path), err)
133 }
134
135 if !isSensitive {
136 if err := unstructured.SetNestedField(kccLite.Object, v, path...); err != nil {
137 return nil, fmt.Errorf("error setting mutable-but-unreadable path '%v': %w", pathslice.ToString(path), err)
138 }
139 continue
140 }
141
142 val, err := resolveSensitiveValueForLiveState(v, secretVersions, namespace, kubeClient)
143 if err != nil {
144 return nil, fmt.Errorf("error parsing the secret for mutable-but-unreadable path '%v': %w", pathslice.ToString(path), err)
145 }
146 valMap := make(map[string]interface{})
147 valMap["value"] = val
148 if err := unstructured.SetNestedField(kccLite.Object, valMap, path...); err != nil {
149 return nil, fmt.Errorf("error setting mutable-but-unreadable path '%v': %w", pathslice.ToString(path), err)
150 }
151 case "boolean":
152 if err := unstructured.SetNestedField(kccLite.Object, v, path...); err != nil {
153 return nil, fmt.Errorf("error setting mutable-but-unreadable path '%v': %w", pathslice.ToString(path), err)
154 }
155 case "array":
156 v, ok := v.([]interface{})
157 if !ok {
158 return nil, fmt.Errorf("wrong type for path '%v': %T, expect to have []interface{}", pathslice.ToString(path), v)
159 }
160
161 itemSchema := subSchema.Items
162 switch itemSchema.Type {
163 case "string", "boolean", "number", "integer":
164
165 path := strings.Split(pathslice.ToString(path), ".")
166 if err := unstructured.SetNestedField(kccLite.Object, deepcopy.DeepCopy(v), path...); err != nil {
167 return nil, fmt.Errorf("error setting mutable-but-unreadable path '%v': %w", pathslice.ToString(path), err)
168 }
169 case "array", "object":
170
171 return nil, fmt.Errorf("error setting mutable-but-unreadable path '%v': mutable-but-unreadable list/set of non-primitive types is not yet supported", pathslice.ToString(path))
172 default:
173
174 return nil, fmt.Errorf("unknown list/set type %T for mutable-but-unreadable path '%v'", itemSchema, pathslice.ToString(path))
175 }
176 case "object":
177 v, ok := v.(map[string]interface{})
178 if !ok {
179 return nil, fmt.Errorf("wrong type for path '%v': %T, expect to have map[string]interface{}", pathslice.ToString(path), v)
180 }
181
182 if subSchema.AdditionalProperties != nil {
183
184
185
186
187 if err := unstructured.SetNestedField(kccLite.Object, deepcopy.DeepCopy(v), path...); err != nil {
188 return nil, fmt.Errorf("error setting mutable-but-unreadable path '%v': %w", pathslice.ToString(path), err)
189 }
190 } else {
191
192 var err error
193 kccLite, err = SetMutableButUnreadableFields(kccLite, v, path, subSchema, secretVersions, namespace, kubeClient)
194 if err != nil {
195 return nil, fmt.Errorf("error setting nested mutable-but-unreadable path: %w", err)
196 }
197 }
198 default:
199
200 return nil, fmt.Errorf("unknown type %T for mutable-but-unreadable path '%v'", subSchema, pathslice.ToString(path))
201 }
202 }
203
204 return kccLite, nil
205 }
206
207 func resolveSensitiveValueForLiveState(value interface{}, secretVersions map[string]string, namespace string, kubeClient client.Client) (string, error) {
208 sensitiveField := corekccv1alpha1.SensitiveField{}
209 if err := util.Marshal(value, &sensitiveField); err != nil {
210 return "", fmt.Errorf("error parsing %v onto a SensitiveField struct: %v", value, err)
211 }
212
213 if sensitiveField.Value != nil {
214 return *sensitiveField.Value, nil
215 }
216
217 secretKeyRef := sensitiveField.ValueFrom.SecretKeyRef
218 secretVal, secretVer, err := k8s.GetSecretVal(secretKeyRef, namespace, kubeClient)
219 if err != nil {
220
221
222
223
224
225
226
227 return "", nil
228 }
229
230
231 prevSecretVer, ok := secretVersions[secretKeyRef.Name]
232 if ok && secretVer == prevSecretVer {
233 return secretVal, nil
234 }
235
236
237
238
239 return "", nil
240 }
241
242 func withMutableButUnreadableFields(kccLite *unstructured.Unstructured, resource *dcl.Resource, currSecretVersions map[string]string, kubeClient client.Client) (*unstructured.Unstructured, error) {
243 hasMutableButUnreadableFields, err := resource.HasMutableButUnreadableFields()
244 if err != nil {
245 return nil, fmt.Errorf("error checking if resource has mutable-but-unreadable fields: %w", err)
246 }
247 if !hasMutableButUnreadableFields {
248 return kccLite, nil
249 }
250
251 lastSeenValues, err := dcl.GetMutableButUnreadableFieldsFromAnnotations(resource)
252 if err != nil {
253 return nil, fmt.Errorf("error getting last-seen values of mutable-but-unreadable fields from annotations: %w", err)
254 }
255
256 updatedLite := kccLite.DeepCopy()
257 if len(lastSeenValues) == 0 {
258 return updatedLite, nil
259 }
260
261 secretVersions, err := k8s.GetSecretVersionsFromAnnotations(&resource.Resource)
262 if err != nil {
263 return nil, fmt.Errorf("error getting secret versions from annotations: %w", err)
264 }
265
266
267
268
269 if secretVersions == nil {
270 secretVersions = currSecretVersions
271 }
272
273 updatedLite, err = SetMutableButUnreadableFields(updatedLite, lastSeenValues["spec"].(map[string]interface{}), []string{"spec"}, resource.Schema, secretVersions, resource.GetNamespace(), kubeClient)
274 if err != nil {
275 return nil, err
276 }
277 return updatedLite, nil
278 }
279
View as plain text