diff --git a/cmd/testworkflow-init/constants/commands.go b/cmd/testworkflow-init/constants/commands.go index de826192651..f0bfd7ef699 100644 --- a/cmd/testworkflow-init/constants/commands.go +++ b/cmd/testworkflow-init/constants/commands.go @@ -3,6 +3,7 @@ package constants const ( EnvGroupActions = "01" EnvGroupDebug = "00" + EnvGroupSecrets = "02" EnvNodeName = "TKI_N" EnvPodName = "TKI_P" diff --git a/cmd/testworkflow-init/orchestration/setup.go b/cmd/testworkflow-init/orchestration/setup.go index 2df6e6ca23d..35c627f153f 100644 --- a/cmd/testworkflow-init/orchestration/setup.go +++ b/cmd/testworkflow-init/orchestration/setup.go @@ -17,7 +17,7 @@ import ( ) var ( - scopedRegex = regexp.MustCompile(`^_(00|01|\d|[1-9]\d*)(C)?(S?)_`) + scopedRegex = regexp.MustCompile(`^_(00|01|02|\d|[1-9]\d*)(C)?(S?)_`) Setup = newSetup() defaultWorkingDir = getWorkingDir() commonSensitiveVariables = []string{ @@ -130,6 +130,16 @@ func (c *setup) GetSensitiveWords() []string { words = append(words, value) } } + // TODO(TKC-2585): Avoid adding the secrets to all the groups without isolation + for k := range c.envGroups[constants.EnvGroupSecrets] { + value := os.Getenv(k) + if len(value) < c.minSensitiveWordLength { + continue + } + if _, ok := c.envGroupsSensitive[constants.EnvGroupSecrets][k]; ok { + words = append(words, value) + } + } return words } @@ -159,6 +169,15 @@ func (c *setup) UseEnv(group string) { } } + // TODO(TKC-2585): Avoid adding the secrets to all the groups without isolation + for k, v := range c.envGroups[constants.EnvGroupSecrets] { + if _, ok := c.envGroupsComputed[constants.EnvGroupSecrets][k]; ok { + envTemplates[k] = v + } else { + os.Setenv(k, v) + } + } + // Configure PWD variable, to make it similar to shell environment variables cwd := getWorkingDir() if os.Getenv("PWD") == "" { diff --git a/pkg/expressions/libs/fs_test.go b/pkg/expressions/libs/fs_test.go index 8e513f7717f..4a6e7c981ce 100644 --- a/pkg/expressions/libs/fs_test.go +++ b/pkg/expressions/libs/fs_test.go @@ -9,25 +9,6 @@ import ( "github.com/kubeshop/testkube/pkg/expressions" ) -func MustCall(m expressions.Machine, name string, args ...interface{}) interface{} { - list := make([]expressions.StaticValue, len(args)) - for i, v := range args { - if vv, ok := v.(expressions.StaticValue); ok { - list[i] = vv - } else { - list[i] = expressions.NewValue(v) - } - } - v, ok, err := m.Call(name, list...) - if err != nil { - panic(err) - } - if !ok { - panic("not recognized") - } - return v.Static().Value() -} - func TestFsLibGlob(t *testing.T) { fsys := &afero.IOFS{Fs: afero.NewMemMapFs()} _ = afero.WriteFile(fsys.Fs, "etc/file1.txt", nil, 0644) @@ -35,12 +16,12 @@ func TestFsLibGlob(t *testing.T) { _ = afero.WriteFile(fsys.Fs, "another-file.txt", nil, 0644) _ = afero.WriteFile(fsys.Fs, "etc/nested/file2.json", nil, 0644) machine := NewFsMachine(fsys, "/etc") - assert.Equal(t, []string{"/etc/file1.txt", "/etc/nested/file2.json"}, MustCall(machine, "glob", "**/*")) - assert.Equal(t, []string{"/etc/file1.txt"}, MustCall(machine, "glob", "*")) - assert.Equal(t, []string{"/etc/nested/file2.json"}, MustCall(machine, "glob", "**/*.json")) - assert.Equal(t, []string{"/etc/file1.txt", "/etc/nested/file2.json"}, MustCall(machine, "glob", "**/*.json", "*.txt")) - assert.Equal(t, []string{"/another-file.txt", "/else/file1.txt", "/etc/file1.txt"}, MustCall(machine, "glob", "/**/*.txt")) - assert.Equal(t, []string{"/another-file.txt", "/etc/file1.txt"}, MustCall(machine, "glob", "/**/*.txt", "!/else/**/*")) + assert.Equal(t, []string{"/etc/file1.txt", "/etc/nested/file2.json"}, expressions.MustCall(machine, "glob", "**/*")) + assert.Equal(t, []string{"/etc/file1.txt"}, expressions.MustCall(machine, "glob", "*")) + assert.Equal(t, []string{"/etc/nested/file2.json"}, expressions.MustCall(machine, "glob", "**/*.json")) + assert.Equal(t, []string{"/etc/file1.txt", "/etc/nested/file2.json"}, expressions.MustCall(machine, "glob", "**/*.json", "*.txt")) + assert.Equal(t, []string{"/another-file.txt", "/else/file1.txt", "/etc/file1.txt"}, expressions.MustCall(machine, "glob", "/**/*.txt")) + assert.Equal(t, []string{"/another-file.txt", "/etc/file1.txt"}, expressions.MustCall(machine, "glob", "/**/*.txt", "!/else/**/*")) } func TestFsLibRead(t *testing.T) { @@ -48,8 +29,8 @@ func TestFsLibRead(t *testing.T) { _ = afero.WriteFile(fsys.Fs, "etc/file1.txt", []byte("foo"), 0644) _ = afero.WriteFile(fsys.Fs, "another-file.txt", []byte("bar"), 0644) machine := NewFsMachine(fsys, "/etc") - assert.Equal(t, "foo", MustCall(machine, "file", "file1.txt")) - assert.Equal(t, "foo", MustCall(machine, "file", "/etc/file1.txt")) - assert.Equal(t, "bar", MustCall(machine, "file", "../another-file.txt")) - assert.Equal(t, "bar", MustCall(machine, "file", "/another-file.txt")) + assert.Equal(t, "foo", expressions.MustCall(machine, "file", "file1.txt")) + assert.Equal(t, "foo", expressions.MustCall(machine, "file", "/etc/file1.txt")) + assert.Equal(t, "bar", expressions.MustCall(machine, "file", "../another-file.txt")) + assert.Equal(t, "bar", expressions.MustCall(machine, "file", "/another-file.txt")) } diff --git a/pkg/expressions/libs/secret.go b/pkg/expressions/libs/secret.go deleted file mode 100644 index 1ea52a59cb4..00000000000 --- a/pkg/expressions/libs/secret.go +++ /dev/null @@ -1,44 +0,0 @@ -package libs - -import ( - "fmt" - "strings" - - corev1 "k8s.io/api/core/v1" - - "github.com/kubeshop/testkube/pkg/expressions" -) - -func NewSecretMachine(mapEnvs map[string]corev1.EnvVarSource) expressions.Machine { - return expressions.NewMachine(). - RegisterFunction("secret", func(values ...expressions.StaticValue) (interface{}, bool, error) { - if len(values) != 2 { - return nil, true, fmt.Errorf(`"secret" function expects 2 arguments, %d provided`, len(values)) - } - - secretName, _ := values[0].StringValue() - keyName, _ := values[1].StringValue() - strs := []string{secretName, keyName} - for i := range strs { - j := 0 - for _, char := range []string{"-", "."} { - for ; strings.Contains(strs[i], char); j++ { - strs[i] = strings.Replace(strs[i], char, fmt.Sprintf("_%d_", j), 1) - } - } - } - - envName := fmt.Sprintf("S_N_%s_K_%s", strs[0], strs[1]) - mapEnvs[envName] = corev1.EnvVarSource{ - SecretKeyRef: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: secretName, - }, - Key: keyName, - }, - } - - return expressions.NewValue(fmt.Sprintf("{{%senv.%s}}", expressions.InternalFnCall, envName)), true, nil - }) - -} diff --git a/pkg/expressions/libs/secret_test.go b/pkg/expressions/libs/secret_test.go deleted file mode 100644 index d42a987daa6..00000000000 --- a/pkg/expressions/libs/secret_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package libs - -import ( - "testing" - - "github.com/stretchr/testify/assert" - corev1 "k8s.io/api/core/v1" - - "github.com/kubeshop/testkube/pkg/expressions" -) - -func TestSecret(t *testing.T) { - mapEnvs := make(map[string]corev1.EnvVarSource) - machine := NewSecretMachine(mapEnvs) - assert.Equal(t, "{{"+expressions.InternalFnCall+"env.S_N_name_0_one_1_two_K_key_0_three_1_four}}", MustCall(machine, "secret", "name-one.two", "key-three.four")) - assert.EqualValues(t, map[string]corev1.EnvVarSource{ - "S_N_name_0_one_1_two_K_key_0_three_1_four": { - SecretKeyRef: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: "name-one.two", - }, - Key: "key-three.four", - }, - }, - }, mapEnvs) -} diff --git a/pkg/expressions/utils.go b/pkg/expressions/utils.go index f7c99af4707..be0033e1524 100644 --- a/pkg/expressions/utils.go +++ b/pkg/expressions/utils.go @@ -85,3 +85,22 @@ func Escape(str string) string { func EscapeLabelKeyForVarName(key string) string { return strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(key, ".", "_"), "-", "_"), "/", "_") } + +func MustCall(m Machine, name string, args ...interface{}) interface{} { + list := make([]StaticValue, len(args)) + for i, v := range args { + if vv, ok := v.(StaticValue); ok { + list[i] = vv + } else { + list[i] = NewValue(v) + } + } + v, ok, err := m.Call(name, list...) + if err != nil { + panic(err) + } + if !ok { + panic("not recognized") + } + return v.Static().Value() +} diff --git a/pkg/testworkflows/testworkflowprocessor/processor.go b/pkg/testworkflows/testworkflowprocessor/processor.go index 67c3b307821..36e093295b9 100644 --- a/pkg/testworkflows/testworkflowprocessor/processor.go +++ b/pkg/testworkflows/testworkflowprocessor/processor.go @@ -15,7 +15,6 @@ import ( testworkflowsv1 "github.com/kubeshop/testkube-operator/api/testworkflows/v1" "github.com/kubeshop/testkube/internal/common" "github.com/kubeshop/testkube/pkg/expressions" - "github.com/kubeshop/testkube/pkg/expressions/libs" "github.com/kubeshop/testkube/pkg/imageinspector" "github.com/kubeshop/testkube/pkg/testworkflows/testworkflowprocessor/action" "github.com/kubeshop/testkube/pkg/testworkflows/testworkflowprocessor/action/actiontypes" @@ -112,7 +111,7 @@ func (p *processor) Bundle(ctx context.Context, workflow *testworkflowsv1.TestWo AppendVolumeMounts(layer.AddEmptyDirVolume(nil, constants.DefaultDataPath)) mapEnv := make(map[string]corev1.EnvVarSource) - extendedMachines := append(machines, libs.NewSecretMachine(mapEnv)) + extendedMachines := append(machines, createSecretMachine(mapEnv)) // Fetch resource root and resource ID resourceRoot, err := expressions.EvalExpression("resource.root", extendedMachines...) @@ -395,6 +394,7 @@ func (p *processor) Bundle(ctx context.Context, workflow *testworkflowsv1.TestWo } jobSpec.Spec.Template = podSpec + // TODO(TKC-2585): Avoid adding the secrets to all the groups without isolation addEnvVarToContainerSpec(mapEnv, jobSpec.Spec.Template.Spec.InitContainers) addEnvVarToContainerSpec(mapEnv, jobSpec.Spec.Template.Spec.Containers) diff --git a/pkg/testworkflows/testworkflowprocessor/secretmachine.go b/pkg/testworkflows/testworkflowprocessor/secretmachine.go new file mode 100644 index 00000000000..6fb50ec8326 --- /dev/null +++ b/pkg/testworkflows/testworkflowprocessor/secretmachine.go @@ -0,0 +1,55 @@ +package testworkflowprocessor + +import ( + "fmt" + "strings" + + corev1 "k8s.io/api/core/v1" + + "github.com/kubeshop/testkube/cmd/testworkflow-init/constants" + "github.com/kubeshop/testkube/pkg/expressions" + "github.com/kubeshop/testkube/pkg/testworkflows/testworkflowprocessor/action/actiontypes" +) + +func createSecretMachine(mapEnvs map[string]corev1.EnvVarSource) expressions.Machine { + return expressions.NewMachine(). + RegisterFunction("secret", func(values ...expressions.StaticValue) (interface{}, bool, error) { + computed := false + if len(values) == 3 { + if values[2].IsBool() { + computed, _ = values[2].BoolValue() + } else { + return nil, true, fmt.Errorf(`"secret" function expects 3rd argument to be boolean, %s provided`, values[3].String()) + } + } else if len(values) != 2 { + return nil, true, fmt.Errorf(`"secret" function expects 2-3 arguments, %d provided`, len(values)) + } + + secretName, _ := values[0].StringValue() + keyName, _ := values[1].StringValue() + strs := []string{secretName, keyName} + for i := range strs { + j := 0 + for _, char := range []string{"-", "."} { + for ; strings.Contains(strs[i], char); j++ { + strs[i] = strings.Replace(strs[i], char, fmt.Sprintf("_%d_", j), 1) + } + } + } + + // TODO(TKC-2585): Avoid adding the secrets to all the groups with virtual 02 group + envName := fmt.Sprintf("%s_K_%s", strs[0], strs[1]) + internalName := actiontypes.EnvName(constants.EnvGroupSecrets, computed, true, envName) + mapEnvs[internalName] = corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: secretName, + }, + Key: keyName, + }, + } + + return expressions.NewValue(fmt.Sprintf("{{%senv.%s}}", expressions.InternalFnCall, envName)), true, nil + }) + +} diff --git a/pkg/testworkflows/testworkflowprocessor/secretmachine_test.go b/pkg/testworkflows/testworkflowprocessor/secretmachine_test.go new file mode 100644 index 00000000000..f0f5f6bcd0f --- /dev/null +++ b/pkg/testworkflows/testworkflowprocessor/secretmachine_test.go @@ -0,0 +1,42 @@ +package testworkflowprocessor + +import ( + "testing" + + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + + "github.com/kubeshop/testkube/pkg/expressions" +) + +func TestSecret(t *testing.T) { + mapEnvs := make(map[string]corev1.EnvVarSource) + machine := createSecretMachine(mapEnvs) + assert.Equal(t, "{{"+expressions.InternalFnCall+"env.name_0_one_1_two_K_key_0_three_1_four}}", expressions.MustCall(machine, "secret", "name-one.two", "key-three.four")) + assert.EqualValues(t, map[string]corev1.EnvVarSource{ + "_02S_name_0_one_1_two_K_key_0_three_1_four": { + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "name-one.two", + }, + Key: "key-three.four", + }, + }, + }, mapEnvs) +} + +func TestSecretComputed(t *testing.T) { + mapEnvs := make(map[string]corev1.EnvVarSource) + machine := createSecretMachine(mapEnvs) + assert.Equal(t, "{{"+expressions.InternalFnCall+"env.name_0_one_1_two_K_key_0_three_1_four}}", expressions.MustCall(machine, "secret", "name-one.two", "key-three.four", true)) + assert.EqualValues(t, map[string]corev1.EnvVarSource{ + "_02CS_name_0_one_1_two_K_key_0_three_1_four": { + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "name-one.two", + }, + Key: "key-three.four", + }, + }, + }, mapEnvs) +} diff --git a/pkg/testworkflows/testworkflowresolver/config.go b/pkg/testworkflows/testworkflowresolver/config.go index 3770533f943..4c6c66270e4 100644 --- a/pkg/testworkflows/testworkflowresolver/config.go +++ b/pkg/testworkflows/testworkflowresolver/config.go @@ -78,12 +78,12 @@ func createConfigMachine(cfg map[string]intstr.IntOrString, schema map[string]te func getSecretCallExpression(expr expressions.Expression, k string, externalize func(key, value string) (*corev1.EnvVarSource, error)) ( expressions.Expression, error) { - envVar, err := externalize(k, expr.String()) + envVar, err := externalize(k, expr.Template()) if err != nil { return nil, errors.Wrap(err, "config."+k) } if envVar.SecretKeyRef != nil { - expr = expressions.NewValue(fmt.Sprintf("{{%ssecret(\"%s\", \"%s\")}}", expressions.InternalFnCall, + expr = expressions.NewValue(fmt.Sprintf("{{%ssecret(\"%s\", \"%s\", true)}}", expressions.InternalFnCall, envVar.SecretKeyRef.Name, envVar.SecretKeyRef.Key)) }