diff --git a/sysdig/cfn_preprocess_template.go b/sysdig/cfn_preprocess_template.go index 0934c501..7abbbaa3 100644 --- a/sysdig/cfn_preprocess_template.go +++ b/sysdig/cfn_preprocess_template.go @@ -11,6 +11,7 @@ package sysdig import ( "context" "fmt" + "unicode" "github.com/Jeffail/gabs/v2" "github.com/rs/zerolog/log" @@ -27,11 +28,50 @@ func GetValueFromTemplate(what *gabs.Container) (string, *gabs.Container) { fallback = nil default: fallback = what - result = "placeholder: " + what.String() + result = what.String() } return result, fallback } +func capitalize(key string) string { + r := []rune(key) + r[0] = unicode.ToUpper(r[0]) + return string(r) +} + +// updateKey recursively capitalizes the first letter of each key in the input object +func updateKeys(inputMap gabs.Container) error { + // in this case, the object is probably an array, so update each child + if len(inputMap.ChildrenMap()) == 0 { + for _, child := range inputMap.Children() { + err := updateKeys(*child) + if err != nil { + return err + } + } + } else { + for key, child := range inputMap.ChildrenMap() { + _, err := inputMap.Set(child.Data(), capitalize(key)) + if err != nil { + return fmt.Errorf("failed to update new key %s", capitalize(key)) + } + + err = inputMap.Delete(key) + if err != nil { + return fmt.Errorf("failed to delete old key %s" + key) + } + + // recurse to update child's keys + err = updateKeys(*child) + if err != nil { + return err + } + } + } + + return nil +} + func terraformPreModifications(ctx context.Context, patchedStack []byte) ([]byte, error) { l := log.Ctx(ctx) template, err := gabs.ParseJSON(patchedStack) @@ -56,8 +96,8 @@ func terraformPreModifications(ctx context.Context, patchedStack []byte) ([]byte } } - if container.Exists("Environment") { - for _, env := range container.S("Environment").Children() { + if container.Exists("environment") { + for _, env := range container.S("environment").Children() { if env.Exists("name") { name, _ := env.S("name").Data().(string) err = env.Delete("name") @@ -81,15 +121,26 @@ func terraformPreModifications(ctx context.Context, patchedStack []byte) ([]byte } } } + + passthrough, _ := GetValueFromTemplate(container.S("environment")) + parsedPassthrough, _ := gabs.ParseJSON([]byte(passthrough)) + _, err = container.Set(parsedPassthrough, "Environment") + if err != nil { + return nil, fmt.Errorf("Could not update Environment field: %v", err) + } + + err = container.Delete("environment") + if err != nil { + return nil, fmt.Errorf("could not delete environment in the Container definition: %w", err) + } } if container.Exists("entryPoint") { - for _, arg := range container.S("entryPoint").Children() { - passthrough, _ := GetValueFromTemplate(arg) - err = container.ArrayAppend(passthrough, "EntryPoint") - if err != nil { - return nil, fmt.Errorf("Could not append entrypoint values to EntryPoint: %v", err) - } + passthrough, _ := GetValueFromTemplate(container.S("entryPoint")) + parsedPassthrough, _ := gabs.ParseJSON([]byte(passthrough)) + _, err = container.Set(parsedPassthrough, "EntryPoint") + if err != nil { + return nil, fmt.Errorf("Could not update EntryPoint field: %v", err) } err = container.Delete("entryPoint") @@ -97,6 +148,58 @@ func terraformPreModifications(ctx context.Context, patchedStack []byte) ([]byte return nil, fmt.Errorf("could not delete entryPoint in the Container definition: %w", err) } } + + if container.Exists("command") { + passthrough, _ := GetValueFromTemplate(container.S("command")) + parsedPassthrough, _ := gabs.ParseJSON([]byte(passthrough)) + _, err = container.Set(parsedPassthrough, "Command") + if err != nil { + return nil, fmt.Errorf("Could not update Command field: %v", err) + } + + err = container.Delete("command") + if err != nil { + return nil, fmt.Errorf("could not delete command in the Container definition: %w", err) + } + } + + if container.Exists("volumesFrom") { + err = updateKeys(*container.S("volumesFrom")) + if err != nil { + return nil, fmt.Errorf("could not update volumesFrom items: %v", err) + } + + passthrough, _ := GetValueFromTemplate(container.S("volumesFrom")) + parsedPassthrough, _ := gabs.ParseJSON([]byte(passthrough)) + _, err = container.Set(parsedPassthrough, "VolumesFrom") + if err != nil { + return nil, fmt.Errorf("could not update VolumesFrom field: %v", err) + } + + err = container.Delete("volumesFrom") + if err != nil { + return nil, fmt.Errorf("could not delete volumesFrom in the container definition: %w", err) + } + } + + if container.Exists("linuxParameters") { + err = updateKeys(*container.S("linuxParameters")) + if err != nil { + return nil, fmt.Errorf("could not update linuxParameters items: %v", err) + } + + passthrough, _ := GetValueFromTemplate(container.S("linuxParameters")) + parsedPassthrough, _ := gabs.ParseJSON([]byte(passthrough)) + _, err = container.Set(parsedPassthrough, "LinuxParameters") + if err != nil { + return nil, fmt.Errorf("could not update LinuxParameters field: %v", err) + } + + err = container.Delete("linuxParameters") + if err != nil { + return nil, fmt.Errorf("could not delete linuxParameters in the COntainer definition: %w", err) + } + } } } diff --git a/sysdig/data_source_sysdig_fargate_ECS_test.go b/sysdig/data_source_sysdig_fargate_ECS_test.go index 040306a4..13a9e31b 100644 --- a/sysdig/data_source_sysdig_fargate_ECS_test.go +++ b/sysdig/data_source_sysdig_fargate_ECS_test.go @@ -4,12 +4,54 @@ import ( "context" "encoding/json" "io/ioutil" + "sort" "testing" + "github.com/Jeffail/gabs/v2" "github.com/falcosecurity/kilt/runtimes/cloudformation/cfnpatcher" "github.com/stretchr/testify/assert" ) +var ( + testKiltDefinition = KiltRecipeConfig{ + SysdigAccessKey: "sysdig_access_key", + AgentImage: "workload_agent_image", + OrchestratorHost: "orchestrator_host", + OrchestratorPort: "orchestrator_port", + CollectorHost: "collector_host", + CollectorPort: "collector_port", + } + + testContainerDefinitionFiles = []string{ + "fargate_entrypoint_test", + "fargate_env_test", + "fargate_cmd_test", + "fargate_linuxparameters_test", + "fargate_combined_test", + } +) + +// sortContainerEnv goes into a container definition and sorts the environment variables +func sortContainerEnv(json []byte) string { + jsonObject, _ := gabs.ParseJSON(json) + containers, _ := jsonObject.Data().([]interface{}) + for _, container := range containers { + if env, ok := container.(map[string]interface{})["Environment"]; ok { + envSort := env.([]interface{}) + sort.Slice(envSort, func(i, j int) bool { + return gabs.Wrap(envSort[i]).S("Name").Data().(string) < gabs.Wrap(envSort[j]).S("Name").Data().(string) + }) + } + } + return jsonObject.String() +} + +func sortAndCompare(t *testing.T, expected []byte, actual []byte) { + expectedJSON := sortContainerEnv(expected) + actualJSON := sortContainerEnv(actual) + assert.JSONEq(t, expectedJSON, actualJSON) +} + func TestECStransformation(t *testing.T) { inputfile, err := ioutil.ReadFile("testfiles/ECSinput.json") @@ -87,3 +129,24 @@ func TestECStransformation(t *testing.T) { assert.Equal(t, expectedContainerDefinitions[0].EntryPoint, patchedContainerDefinitions[0].EntryPoint) assert.Equal(t, patchedContainerDefinitions[0].EntryPoint2, "") } + +func TestTransform(t *testing.T) { + for _, testName := range testContainerDefinitionFiles { + t.Run(testName, func(t *testing.T) { + jsonConfig, _ := json.Marshal(testKiltDefinition) + kiltConfig := &cfnpatcher.Configuration{ + Kilt: agentinoKiltDefinition, + ImageAuthSecret: "image_auth_secret", + OptIn: false, + UseRepositoryHints: true, + RecipeConfig: string(jsonConfig), + } + + inputContainerDefinition, _ := ioutil.ReadFile("testfiles/" + testName + ".json") + patched, _ := patchFargateTaskDefinition(context.Background(), string(inputContainerDefinition), kiltConfig) + expectedContainerDefinition, _ := ioutil.ReadFile("testfiles/" + testName + "_expected.json") + + sortAndCompare(t, expectedContainerDefinition, []byte(*patched)) + }) + } +} diff --git a/sysdig/testfiles/ECSinput.json b/sysdig/testfiles/ECSinput.json index 6e963a17..53d07140 100644 --- a/sysdig/testfiles/ECSinput.json +++ b/sysdig/testfiles/ECSinput.json @@ -2,8 +2,8 @@ { "Environment": [ { - "name": "pmet", - "value": "temp" + "Name": "pmet", + "Value": "temp" }, { "Name": "SYSDIG_ENDPOINT", diff --git a/sysdig/testfiles/fargate_cmd_test.json b/sysdig/testfiles/fargate_cmd_test.json new file mode 100644 index 00000000..bf4bf713 --- /dev/null +++ b/sysdig/testfiles/fargate_cmd_test.json @@ -0,0 +1,14 @@ +[ + { + "name": "test", + "image": "test_image:latest", + "entryPoint": [ + "/bin/test" + ], + "command": [ + "test", + "--test-arg", + "test-arg-value" + ] + } +] \ No newline at end of file diff --git a/sysdig/testfiles/fargate_cmd_test_expected.json b/sysdig/testfiles/fargate_cmd_test_expected.json new file mode 100644 index 00000000..8953164a --- /dev/null +++ b/sysdig/testfiles/fargate_cmd_test_expected.json @@ -0,0 +1,64 @@ +[ + { + "name": "test", + "Image": "test_image:latest", + "EntryPoint": [ + "/opt/draios/bin/instrument" + ], + "Command": [ + "/bin/test", + "test", + "--test-arg", + "test-arg-value" + ], + "Environment": [ + { + "Name": "SYSDIG_ORCHESTRATOR_PORT", + "Value": "orchestrator_port" + }, + { + "Name": "SYSDIG_COLLECTOR", + "Value": "collector_host" + }, + { + "Name": "SYSDIG_COLLECTOR_PORT", + "Value": "collector_port" + }, + { + "Name": "SYSDIG_ACCESS_KEY", + "Value": "sysdig_access_key" + }, + { + "Name": "SYSDIG_LOGGING", + "Value": "" + }, + { + "Name": "SYSDIG_ORCHESTRATOR", + "Value": "orchestrator_host" + } + ], + "LinuxParameters": { + "Capabilities": { + "Add": [ + "SYS_PTRACE" + ] + } + }, + "VolumesFrom": [ + { + "ReadOnly": true, + "SourceContainer": "SysdigInstrumentation" + } + ] + }, + { + "EntryPoint": [ + "/opt/draios/bin/logwriter" + ], + "Image": "workload_agent_image", + "Name": "SysdigInstrumentation", + "RepositoryCredentials": { + "CredentialsParameter": "image_auth_secret" + } + } +] \ No newline at end of file diff --git a/sysdig/testfiles/fargate_combined_test.json b/sysdig/testfiles/fargate_combined_test.json new file mode 100644 index 00000000..082bff6e --- /dev/null +++ b/sysdig/testfiles/fargate_combined_test.json @@ -0,0 +1,30 @@ +[ + { + "name": "test", + "image": "test_image:latest", + "entryPoint": [ + "/bin/test" + ], + "command": [ + "test", + "--test-arg", + "test-arg-value" + ], + "environment": [ + { + "name": "TMP", + "value": "temporary" + }, + { + "name": "SYSDIG_CUSTOM", + "value": "custom" + } + ], + "volumesFrom": [ + { + "sourceContainer": "test_container", + "readOnly": true + } + ] + } +] \ No newline at end of file diff --git a/sysdig/testfiles/fargate_combined_test_expected.json b/sysdig/testfiles/fargate_combined_test_expected.json new file mode 100644 index 00000000..a9619d93 --- /dev/null +++ b/sysdig/testfiles/fargate_combined_test_expected.json @@ -0,0 +1,76 @@ +[ + { + "name": "test", + "Image": "test_image:latest", + "EntryPoint": [ + "/opt/draios/bin/instrument" + ], + "Command": [ + "/bin/test", + "test", + "--test-arg", + "test-arg-value" + ], + "Environment": [ + { + "Name": "SYSDIG_ORCHESTRATOR_PORT", + "Value": "orchestrator_port" + }, + { + "Name": "SYSDIG_COLLECTOR", + "Value": "collector_host" + }, + { + "Name": "SYSDIG_COLLECTOR_PORT", + "Value": "collector_port" + }, + { + "Name": "SYSDIG_ACCESS_KEY", + "Value": "sysdig_access_key" + }, + { + "Name": "SYSDIG_LOGGING", + "Value": "" + }, + { + "Name": "SYSDIG_ORCHESTRATOR", + "Value": "orchestrator_host" + }, + { + "Name": "TMP", + "Value": "temporary" + }, + { + "Name": "SYSDIG_CUSTOM", + "Value": "custom" + } + ], + "LinuxParameters": { + "Capabilities": { + "Add": [ + "SYS_PTRACE" + ] + } + }, + "VolumesFrom": [ + { + "ReadOnly": true, + "SourceContainer": "test_container" + }, + { + "ReadOnly": true, + "SourceContainer": "SysdigInstrumentation" + } + ] + }, + { + "EntryPoint": [ + "/opt/draios/bin/logwriter" + ], + "Image": "workload_agent_image", + "Name": "SysdigInstrumentation", + "RepositoryCredentials": { + "CredentialsParameter": "image_auth_secret" + } + } +] \ No newline at end of file diff --git a/sysdig/testfiles/fargate_entrypoint_test.json b/sysdig/testfiles/fargate_entrypoint_test.json new file mode 100644 index 00000000..318310f2 --- /dev/null +++ b/sysdig/testfiles/fargate_entrypoint_test.json @@ -0,0 +1,9 @@ +[ + { + "name": "test", + "image": "test_image:latest", + "entryPoint": [ + "/bin/test" + ] + } +] \ No newline at end of file diff --git a/sysdig/testfiles/fargate_entrypoint_test_expected.json b/sysdig/testfiles/fargate_entrypoint_test_expected.json new file mode 100644 index 00000000..d3c912f8 --- /dev/null +++ b/sysdig/testfiles/fargate_entrypoint_test_expected.json @@ -0,0 +1,61 @@ +[ + { + "name": "test", + "Image": "test_image:latest", + "EntryPoint": [ + "/opt/draios/bin/instrument" + ], + "Command": [ + "/bin/test" + ], + "Environment": [ + { + "Name": "SYSDIG_ORCHESTRATOR_PORT", + "Value": "orchestrator_port" + }, + { + "Name": "SYSDIG_COLLECTOR", + "Value": "collector_host" + }, + { + "Name": "SYSDIG_COLLECTOR_PORT", + "Value": "collector_port" + }, + { + "Name": "SYSDIG_ACCESS_KEY", + "Value": "sysdig_access_key" + }, + { + "Name": "SYSDIG_LOGGING", + "Value": "" + }, + { + "Name": "SYSDIG_ORCHESTRATOR", + "Value": "orchestrator_host" + } + ], + "LinuxParameters": { + "Capabilities": { + "Add": [ + "SYS_PTRACE" + ] + } + }, + "VolumesFrom": [ + { + "ReadOnly": true, + "SourceContainer": "SysdigInstrumentation" + } + ] + }, + { + "EntryPoint": [ + "/opt/draios/bin/logwriter" + ], + "Image": "workload_agent_image", + "Name": "SysdigInstrumentation", + "RepositoryCredentials": { + "CredentialsParameter": "image_auth_secret" + } + } +] \ No newline at end of file diff --git a/sysdig/testfiles/fargate_env_test.json b/sysdig/testfiles/fargate_env_test.json new file mode 100644 index 00000000..0b5b3f9e --- /dev/null +++ b/sysdig/testfiles/fargate_env_test.json @@ -0,0 +1,19 @@ +[ + { + "name": "test", + "image": "test_image:latest", + "entryPoint": [ + "/bin/test" + ], + "environment": [ + { + "name": "TMP", + "value": "temporary" + }, + { + "name": "SYSDIG_CUSTOM", + "value": "custom" + } + ] + } +] \ No newline at end of file diff --git a/sysdig/testfiles/fargate_env_test_expected.json b/sysdig/testfiles/fargate_env_test_expected.json new file mode 100644 index 00000000..1c0a6cd7 --- /dev/null +++ b/sysdig/testfiles/fargate_env_test_expected.json @@ -0,0 +1,69 @@ +[ + { + "name": "test", + "Image": "test_image:latest", + "EntryPoint": [ + "/opt/draios/bin/instrument" + ], + "Command": [ + "/bin/test" + ], + "Environment": [ + { + "Name": "SYSDIG_ORCHESTRATOR_PORT", + "Value": "orchestrator_port" + }, + { + "Name": "SYSDIG_COLLECTOR", + "Value": "collector_host" + }, + { + "Name": "SYSDIG_COLLECTOR_PORT", + "Value": "collector_port" + }, + { + "Name": "SYSDIG_ACCESS_KEY", + "Value": "sysdig_access_key" + }, + { + "Name": "SYSDIG_LOGGING", + "Value": "" + }, + { + "Name": "SYSDIG_ORCHESTRATOR", + "Value": "orchestrator_host" + }, + { + "Name": "TMP", + "Value": "temporary" + }, + { + "Name": "SYSDIG_CUSTOM", + "Value": "custom" + } + ], + "LinuxParameters": { + "Capabilities": { + "Add": [ + "SYS_PTRACE" + ] + } + }, + "VolumesFrom": [ + { + "ReadOnly": true, + "SourceContainer": "SysdigInstrumentation" + } + ] + }, + { + "EntryPoint": [ + "/opt/draios/bin/logwriter" + ], + "Image": "workload_agent_image", + "Name": "SysdigInstrumentation", + "RepositoryCredentials": { + "CredentialsParameter": "image_auth_secret" + } + } +] \ No newline at end of file diff --git a/sysdig/testfiles/fargate_linuxparameters_test.json b/sysdig/testfiles/fargate_linuxparameters_test.json new file mode 100644 index 00000000..967c732d --- /dev/null +++ b/sysdig/testfiles/fargate_linuxparameters_test.json @@ -0,0 +1,16 @@ +[ + { + "name": "test", + "image": "test_image:latest", + "entryPoint": [ + "/bin/test" + ], + "linuxParameters": { + "capabilities": { + "drop": [ + "BLOCK_SUSPEND" + ] + } + } + } +] \ No newline at end of file diff --git a/sysdig/testfiles/fargate_linuxparameters_test_expected.json b/sysdig/testfiles/fargate_linuxparameters_test_expected.json new file mode 100644 index 00000000..102ce082 --- /dev/null +++ b/sysdig/testfiles/fargate_linuxparameters_test_expected.json @@ -0,0 +1,64 @@ +[ + { + "name": "test", + "Image": "test_image:latest", + "EntryPoint": [ + "/opt/draios/bin/instrument" + ], + "Command": [ + "/bin/test" + ], + "Environment": [ + { + "Name": "SYSDIG_ORCHESTRATOR_PORT", + "Value": "orchestrator_port" + }, + { + "Name": "SYSDIG_COLLECTOR", + "Value": "collector_host" + }, + { + "Name": "SYSDIG_COLLECTOR_PORT", + "Value": "collector_port" + }, + { + "Name": "SYSDIG_ACCESS_KEY", + "Value": "sysdig_access_key" + }, + { + "Name": "SYSDIG_LOGGING", + "Value": "" + }, + { + "Name": "SYSDIG_ORCHESTRATOR", + "Value": "orchestrator_host" + } + ], + "LinuxParameters": { + "Capabilities": { + "Add": [ + "SYS_PTRACE" + ], + "Drop": [ + "BLOCK_SUSPEND" + ] + } + }, + "VolumesFrom": [ + { + "ReadOnly": true, + "SourceContainer": "SysdigInstrumentation" + } + ] + }, + { + "EntryPoint": [ + "/opt/draios/bin/logwriter" + ], + "Image": "workload_agent_image", + "Name": "SysdigInstrumentation", + "RepositoryCredentials": { + "CredentialsParameter": "image_auth_secret" + } + } +] \ No newline at end of file