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

fix: obfuscate secret() calls, resolve sensitive config #5862

Merged
merged 4 commits into from
Sep 23, 2024
Merged
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
1 change: 1 addition & 0 deletions cmd/testworkflow-init/constants/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package constants
const (
EnvGroupActions = "01"
EnvGroupDebug = "00"
EnvGroupSecrets = "02"

EnvNodeName = "TKI_N"
EnvPodName = "TKI_P"
Expand Down
21 changes: 20 additions & 1 deletion cmd/testworkflow-init/orchestration/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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") == "" {
Expand Down
39 changes: 10 additions & 29 deletions pkg/expressions/libs/fs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,47 +9,28 @@ 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)
_ = afero.WriteFile(fsys.Fs, "else/file1.txt", nil, 0644)
_ = 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) {
fsys := &afero.IOFS{Fs: afero.NewMemMapFs()}
_ = 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"))
}
44 changes: 0 additions & 44 deletions pkg/expressions/libs/secret.go

This file was deleted.

26 changes: 0 additions & 26 deletions pkg/expressions/libs/secret_test.go

This file was deleted.

19 changes: 19 additions & 0 deletions pkg/expressions/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
4 changes: 2 additions & 2 deletions pkg/testworkflows/testworkflowprocessor/processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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...)
Expand Down Expand Up @@ -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)

Expand Down
55 changes: 55 additions & 0 deletions pkg/testworkflows/testworkflowprocessor/secretmachine.go
Original file line number Diff line number Diff line change
@@ -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
})

}
42 changes: 42 additions & 0 deletions pkg/testworkflows/testworkflowprocessor/secretmachine_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
4 changes: 2 additions & 2 deletions pkg/testworkflows/testworkflowresolver/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}

Expand Down
Loading