Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Decode input secrets used in template stringData #446

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 38 additions & 1 deletion pkg/generator/secret_template_reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package generator
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"time"

Expand Down Expand Up @@ -283,7 +284,12 @@ func evaluateTemplate(template *sg2v1alpha1.JSONPathTemplate, values map[string]
}

// Template Secret StringData
stringData, err := evaluate(template.StringData, values)
decodedValues, err := decodeSecrets(values)
Copy link
Contributor

@neil-hickey neil-hickey Sep 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a pre-processing step which ends up having to iterate all the values even if you never specify a SecretData field. We also don't have any test for the "else" case, i.e.

if obj.GetKind() == "Secret" { } else { // no tests }

Is there a way to fold this into our existing evaluate function or a new function evaluateStringData which could delegate to the existing evaluateBytes or similar for most of the work.

if err != nil {
return corev1.Secret{}, fmt.Errorf("decoding secrets: %w", err)
}

stringData, err := evaluate(template.StringData, decodedValues)
if err != nil {
return corev1.Secret{}, fmt.Errorf("templating stringData: %w", err)
}
Expand Down Expand Up @@ -349,3 +355,34 @@ func evaluateBytes(mapping map[string]string, values map[string]interface{}) (ma

return evaluatedMapping, nil
}

func decodeSecrets(values map[string]interface{}) (map[string]interface{}, error) {
decodedValues := make(map[string]interface{})
for valueKey, value := range values {
jsonData, err := json.Marshal(value)
if err != nil {
return nil, fmt.Errorf("failed to marshal values into JSON: %w", err)
}

obj := &unstructured.Unstructured{}
if err := json.Unmarshal(jsonData, obj); err != nil {
return nil, fmt.Errorf("failed to unmarshal JSON into Unstructured object: %w", err)
}

if obj.GetKind() == "Secret" {
data, _, _ := unstructured.NestedStringMap(obj.Object, "data")
for dataKey, encodedValue := range data {
decodedValue, err := base64.StdEncoding.DecodeString(string(encodedValue))
if err != nil {
return nil, fmt.Errorf("failed decoding base64 from a Secret: %w", err)
}
unstructured.SetNestedStringMap(obj.Object, map[string]string{dataKey: string(decodedValue)}, "data")
}
decodedValues[valueKey] = obj.Object
} else {
decodedValues[valueKey] = value
}
}

return decodedValues, nil
}
56 changes: 56 additions & 0 deletions pkg/generator/secret_template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,62 @@ func Test_SecretTemplate(t *testing.T) {
},
},
},
{
name: "reconciling secret template with input from another secret decoded in stringData",
template: sg2v1alpha1.SecretTemplate{
ObjectMeta: metav1.ObjectMeta{
Name: "secretTemplate",
Namespace: "test",
},
Spec: sg2v1alpha1.SecretTemplateSpec{
InputResources: []sg2v1alpha1.InputResource{{
Name: "creds",
Ref: sg2v1alpha1.InputResourceRef{
APIVersion: "v1",
Kind: "Secret",
Name: "existingSecret",
},
}},
JSONPathTemplate: &sg2v1alpha1.JSONPathTemplate{
Data: map[string]string{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

109-112 should be covered by the previous unit-test. This test is only for StringData

"key1": "$( .creds.data.inputKey1 )",
"key2": "$( .creds.data.inputKey2 )",
},
StringData: map[string]string{
"key3": "test-$( .creds.data.inputKey3 )",
},
},
},
},
existingObjects: []client.Object{
secret("existingSecret", map[string]string{
"inputKey1": "value1",
"inputKey2": "value2",
"inputKey3": "value3",
}),
},
expectedSecret: corev1.Secret{
TypeMeta: metav1.TypeMeta{
Kind: "Secret",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "secretTemplate",
Namespace: "test",
ResourceVersion: "1",
OwnerReferences: []metav1.OwnerReference{
secretTemplateOwnerRef("secretTemplate"),
},
},
Data: map[string][]byte{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

139-142 should be covered by the previous unit-test.

"key1": []byte("value1"),
"key2": []byte("value2"),
},
StringData: map[string]string{
"key3": "test-value3",
},
},
},
{
name: "reconciling secret template with input from two inputs with dynamic inputname",
template: sg2v1alpha1.SecretTemplate{
Expand Down