diff --git a/cfg/cis-1.6/etcd.yaml b/cfg/cis-1.6/etcd.yaml index f17ab16..8106148 100644 --- a/cfg/cis-1.6/etcd.yaml +++ b/cfg/cis-1.6/etcd.yaml @@ -15,7 +15,9 @@ groups: bin_op: and test_items: - flag: "--cert-file" + env: "ETCD_CERT_FILE" - flag: "--key-file" + env: "ETCD_KEY_FILE" remediation: | Follow the etcd service documentation and configure TLS encryption. Then, edit the etcd pod specification file /etc/kubernetes/manifests/etcd.yaml @@ -30,6 +32,7 @@ groups: tests: test_items: - flag: "--client-cert-auth" + env: "ETCD_CLIENT_CERT_AUTH" compare: op: eq value: true @@ -46,8 +49,10 @@ groups: bin_op: or test_items: - flag: "--auto-tls" + env: "ETCD_AUTO_TLS" set: false - flag: "--auto-tls" + env: "ETCD_AUTO_TLS" compare: op: eq value: false @@ -65,7 +70,9 @@ groups: bin_op: and test_items: - flag: "--peer-cert-file" + env: "ETCD_PEER_CERT_FILE" - flag: "--peer-key-file" + env: "ETCD_PEER_KEY_FILE" remediation: | Follow the etcd service documentation and configure peer TLS encryption as appropriate for your etcd cluster. @@ -81,6 +88,7 @@ groups: tests: test_items: - flag: "--peer-client-cert-auth" + env: "ETCD_PEER_CLIENT_CERT_AUTH" compare: op: eq value: true @@ -97,8 +105,10 @@ groups: bin_op: or test_items: - flag: "--peer-auto-tls" + env: "ETCD_PEER_AUTO_TLS" set: false - flag: "--peer-auto-tls" + env: "ETCD_PEER_AUTO_TLS" compare: op: eq value: false @@ -114,6 +124,7 @@ groups: tests: test_items: - flag: "--trusted-ca-file" + env: "ETCD_TRUSTED_CA_FILE" remediation: | [Manual test] Follow the etcd documentation and create a dedicated certificate authority setup for the diff --git a/check/check.go b/check/check.go index 463afa8..eb24bff 100644 --- a/check/check.go +++ b/check/check.go @@ -68,6 +68,7 @@ type Check struct { ID string `yaml:"id" json:"test_number"` Text string `json:"test_desc"` Audit string `json:"audit"` + AuditEnv string `yaml:"audit_env"` AuditConfig string `yaml:"audit_config"` Type string `json:"type"` Tests *tests `json:"-"` @@ -81,7 +82,9 @@ type Check struct { ExpectedResult string `json:"expected_result"` Reason string `json:"reason,omitempty"` AuditOutput string `json:"-"` + AuditEnvOutput string `json:"-"` AuditConfigOutput string `json:"-"` + DisableEnvTesting bool `json:"-"` } // Runner wraps the basic Run method. @@ -184,6 +187,14 @@ func (c *Check) run() State { } func (c *Check) runAuditCommands() (lastCommand string, err error) { + // Always run auditEnvOutput if needed + if c.AuditEnv != "" { + c.AuditEnvOutput, err = runAudit(c.AuditEnv) + if err != nil { + return c.AuditEnv, err + } + } + // Run the audit command and auditConfig commands, if present c.AuditOutput, err = runAudit(c.Audit) if err != nil { @@ -207,11 +218,15 @@ func (c *Check) execute() (finalOutput *testOutput, err error) { t.isMultipleOutput = c.IsMultiple // Try with the auditOutput first, and if that's not found, try the auditConfigOutput - t.isConfigSetting = false + t.auditUsed = AuditCommand result := *(t.execute(c.AuditOutput)) - if !result.flagFound { - t.isConfigSetting = true + if !result.found { + t.auditUsed = AuditConfig result = *(t.execute(c.AuditConfigOutput)) + if !result.found && t.Env != "" { + t.auditUsed = AuditEnv + result = *(t.execute(c.AuditEnvOutput)) + } } res[i] = result expectedResultArr[i] = res[i].ExpectedResult diff --git a/check/check_test.go b/check/check_test.go index df68197..a5afc75 100644 --- a/check/check_test.go +++ b/check/check_test.go @@ -81,6 +81,39 @@ func TestCheck_Run(t *testing.T) { } } +func TestCheckAuditEnv(t *testing.T){ + passingCases := []*Check{ + controls.Groups[2].Checks[0], + controls.Groups[2].Checks[2], + controls.Groups[2].Checks[3], + controls.Groups[2].Checks[4], + } + + failingCases := []*Check{ + controls.Groups[2].Checks[1], + controls.Groups[2].Checks[5], + controls.Groups[2].Checks[6], + } + + for _, c := range passingCases { + t.Run(c.Text, func(t *testing.T) { + c.run() + if c.State != "PASS" { + t.Errorf("Should PASS, got: %v", c.State) + } + }) + } + + for _, c := range failingCases { + t.Run(c.Text, func(t *testing.T) { + c.run() + if c.State != "FAIL" { + t.Errorf("Should FAIL, got: %v", c.State) + } + }) + } +} + func TestCheckAuditConfig(t *testing.T) { passingCases := []*Check{ diff --git a/check/controls_test.go b/check/controls_test.go index de8b4ab..3a5710f 100644 --- a/check/controls_test.go +++ b/check/controls_test.go @@ -256,7 +256,7 @@ func TestControls_JUnitIncludesJSON(t *testing.T) { }, expect: []byte(` - {"test_number":"check1id","test_desc":"check1text","audit":"","AuditConfig":"","type":"","remediation":"","test_info":null,"status":"PASS","actual_value":"","scored":false,"IsMultiple":false,"expected_result":""} + {"test_number":"check1id","test_desc":"check1text","audit":"","AuditEnv":"","AuditConfig":"","type":"","remediation":"","test_info":null,"status":"PASS","actual_value":"","scored":false,"IsMultiple":false,"expected_result":""} `), }, { @@ -279,7 +279,7 @@ func TestControls_JUnitIncludesJSON(t *testing.T) { }, expect: []byte(` - {"test_number":"check1id","test_desc":"check1text","audit":"","AuditConfig":"","type":"","remediation":"","test_info":null,"status":"PASS","actual_value":"","scored":false,"IsMultiple":false,"expected_result":""} + {"test_number":"check1id","test_desc":"check1text","audit":"","AuditEnv":"","AuditConfig":"","type":"","remediation":"","test_info":null,"status":"PASS","actual_value":"","scored":false,"IsMultiple":false,"expected_result":""} `), }, { @@ -299,19 +299,19 @@ func TestControls_JUnitIncludesJSON(t *testing.T) { }, expect: []byte(` - {"test_number":"check1id","test_desc":"check1text","audit":"","AuditConfig":"","type":"","remediation":"","test_info":null,"status":"PASS","actual_value":"","scored":false,"IsMultiple":false,"expected_result":""} + {"test_number":"check1id","test_desc":"check1text","audit":"","AuditEnv":"","AuditConfig":"","type":"","remediation":"","test_info":null,"status":"PASS","actual_value":"","scored":false,"IsMultiple":false,"expected_result":""} - {"test_number":"check2id","test_desc":"check2text","audit":"","AuditConfig":"","type":"","remediation":"","test_info":null,"status":"INFO","actual_value":"","scored":false,"IsMultiple":false,"expected_result":""} + {"test_number":"check2id","test_desc":"check2text","audit":"","AuditEnv":"","AuditConfig":"","type":"","remediation":"","test_info":null,"status":"INFO","actual_value":"","scored":false,"IsMultiple":false,"expected_result":""} - {"test_number":"check3id","test_desc":"check3text","audit":"","AuditConfig":"","type":"","remediation":"","test_info":null,"status":"WARN","actual_value":"","scored":false,"IsMultiple":false,"expected_result":""} + {"test_number":"check3id","test_desc":"check3text","audit":"","AuditEnv":"","AuditConfig":"","type":"","remediation":"","test_info":null,"status":"WARN","actual_value":"","scored":false,"IsMultiple":false,"expected_result":""} - {"test_number":"check4id","test_desc":"check4text","audit":"","AuditConfig":"","type":"","remediation":"","test_info":null,"status":"FAIL","actual_value":"","scored":false,"IsMultiple":false,"expected_result":""} + {"test_number":"check4id","test_desc":"check4text","audit":"","AuditEnv":"","AuditConfig":"","type":"","remediation":"","test_info":null,"status":"FAIL","actual_value":"","scored":false,"IsMultiple":false,"expected_result":""} `), }, diff --git a/check/data b/check/data index 6df4cf2..4e0b214 100644 --- a/check/data +++ b/check/data @@ -327,6 +327,53 @@ groups: op: eq value: false set: true + - id: 29 + text: "flag is set (via env)" + tests: + test_items: + - flag: "--allow-privileged" + env: "ALLOW_PRIVILEGED" + set: true + + - id: 30 + text: "flag is not set (via env)" + tests: + test_items: + - flag: "--basic-auth" + env: "BASIC_AUTH" + set: false + + - id: 31 + text: "flag value is set to some value (via env)" + tests: + test_items: + - flag: "--insecure-port" + env: "INSECURE_PORT" + compare: + op: eq + value: 0 + set: true + + - id: 32 + text: "flag value is greater than or equal some number (via env)" + tests: + test_items: + - flag: "--audit-log-maxage" + env: "AUDIT_LOG_MAXAGE" + compare: + op: gte + value: 30 + set: true + + - id: 33 + text: "flag value is less than some number (via env)" + tests: + test_items: + - env: "MAX_BACKLOG" + compare: + op: lt + value: 30 + set: true - id: 2.1 text: "audit and audit_config commands" @@ -557,3 +604,104 @@ groups: path: '{.readOnlyPort}' set: false scored: true + +- id: 3.1 + text: "audit_env commands" + checks: + - id: 0 + text: "audit fails to find flag, audit_env finds flag -> pass" + audit: "echo in=incorrect" + audit_env: "echo flag=correct" + tests: + test_items: + - flag: "flag" + env: "flag" + compare: + op: eq + value: "correct" + set: true + scored: true + - id: 1 + text: "audit fails to find flag, audit_env finds flag and fails -> fail" + audit: "echo in=wrong" + audit_env: "echo flag=wrong" + tests: + test_items: + - flag: "flag" + env: "flag" + compare: + op: eq + value: "correct" + set: true + scored: true + - id: 2 + text: "audit finds correct flag, audit_env is incorrect -> pass" + audit: "echo flag=correct" + audit_env: "echo flag=incorrect" + tests: + test_items: + - flag: "flag" + env: "flag" + compare: + op: eq + value: "correct" + set: true + scored: true + - id: 3 + text: "audit doesn't flag flag, audit_config finds it and passes, audit_env is not present -> pass" + audit: "echo in=correct" + audit_config: "echo 'flag: correct'" + tests: + test_items: + - flag: "flag" + path: "{.flag}" + compare: + op: eq + value: "correct" + set: true + scored: true + - id: 4 + text: "audit doesn't flag flag, audit_config doesn't find flag, audit_env finds and passes -> pass" + audit: "echo in=correct" + audit_config: "echo 'in: correct'" + audit_env: "echo flag=correct" + tests: + test_items: + - flag: "flag" + path: "{.flag}" + env: "flag" + compare: + op: eq + value: "correct" + set: true + scored: true + - id: 5 + text: "audit doesn't find flag, audit_config doesn't find flag, audit_env finds and fails -> fails" + audit: "echo in=correct" + audit_config: "echo 'in: correct'" + audit_env: "echo flag=incorrect" + tests: + test_items: + - flag: "flag" + path: "{.flag}" + env: "flag" + compare: + op: eq + value: "correct" + set: true + scored: true + - id: 6 + text: "audit finds flag and fails, audit_config finds flag and fails, audit_env finds and passes -> fails" + audit: "echo flag=incorrect" + audit_config: "echo 'flag: incorrect'" + audit_env: "echo flag=correct" + tests: + test_items: + - flag: "flag" + path: "{.flag}" + env: "flag" + compare: + op: eq + value: "correct" + set: true + scored: true diff --git a/check/test.go b/check/test.go index bb16f73..7664cd0 100644 --- a/check/test.go +++ b/check/test.go @@ -48,17 +48,27 @@ type tests struct { BinOp binOp `yaml:"bin_op"` } +type AuditUsed string + +const ( + AuditCommand AuditUsed = "auditCommand" + AuditConfig AuditUsed = "auditConfig" + AuditEnv AuditUsed = "auditEnv" +) + type testItem struct { Flag string + Env string Path string Output string Value string Set bool Compare compare isMultipleOutput bool - isConfigSetting bool + auditUsed AuditUsed } +type envTestItem testItem type pathTestItem testItem type flagTestItem testItem @@ -69,7 +79,7 @@ type compare struct { type testOutput struct { testResult bool - flagFound bool + found bool actualResult string ExpectedResult string } @@ -78,16 +88,25 @@ func failTestItem(s string) *testOutput { return &testOutput{testResult: false, actualResult: s} } -func (t testItem) flagValue() string { - if t.isConfigSetting { +func (t testItem) value() string { + if t.auditUsed == AuditConfig { return t.Path } + if t.auditUsed == AuditEnv { + return t.Env + } + return t.Flag } func (t testItem) findValue(s string) (match bool, value string, err error) { - if t.isConfigSetting { + if t.auditUsed == AuditEnv { + et := envTestItem(t) + return et.findValue(s) + } + + if t.auditUsed == AuditConfig { pt := pathTestItem(t) return pt.findValue(s) } @@ -145,10 +164,28 @@ func (t pathTestItem) findValue(s string) (match bool, value string, err error) } glog.V(3).Infof("In pathTestItem.findValue %s", value) - match = (value != "") + match = value != "" return match, value, err } +func (t envTestItem) findValue(s string) (match bool, value string, err error) { + if s != "" && t.Env != "" { + r, _ := regexp.Compile(fmt.Sprintf("%s=.*(?:$|\\n)", t.Env)) + out := r.FindString(s) + out = strings.Replace(out, "\n", "", 1) + out = strings.Replace(out, fmt.Sprintf("%s=", t.Env), "", 1) + + if len(out) > 0 { + match = true + value = out + }else{ + match = false + value = "" + } + } + return match, value, nil +} + func (t testItem) execute(s string) *testOutput { result := &testOutput{} s = strings.TrimRight(s, " \n") @@ -186,16 +223,16 @@ func (t testItem) evaluate(s string) *testOutput { if match && t.Compare.Op != "" { result.ExpectedResult, result.testResult = compareOp(t.Compare.Op, value, t.Compare.Value) } else { - result.ExpectedResult = fmt.Sprintf("'%s' is present", t.flagValue()) + result.ExpectedResult = fmt.Sprintf("'%s' is present", t.value()) result.testResult = match } } else { - result.ExpectedResult = fmt.Sprintf("'%s' is not present", t.flagValue()) + result.ExpectedResult = fmt.Sprintf("'%s' is not present", t.value()) result.testResult = !match } - result.flagFound = match - glog.V(3).Info(fmt.Sprintf("flagFound %v", result.flagFound)) + result.found = match + glog.V(3).Info(fmt.Sprintf("found %v", result.found)) return result } diff --git a/check/test_test.go b/check/test_test.go index c191637..cafc7a2 100644 --- a/check/test_test.go +++ b/check/test_test.go @@ -51,168 +51,231 @@ func TestTestExecute(t *testing.T) { *Check str string strConfig string + strEnv string }{ { controls.Groups[0].Checks[0], "2:45 ../kubernetes/kube-apiserver --allow-privileged=false --option1=20,30,40", "", + "", }, { controls.Groups[0].Checks[1], "2:45 ../kubernetes/kube-apiserver --allow-privileged=false", "", + "", }, { controls.Groups[0].Checks[2], "niinai 13617 2635 99 19:26 pts/20 00:03:08 ./kube-apiserver --insecure-port=0 --anonymous-auth", "", + "", }, { controls.Groups[0].Checks[3], "2:45 ../kubernetes/kube-apiserver --secure-port=0 --audit-log-maxage=40 --option", "", + "", }, { controls.Groups[0].Checks[4], "2:45 ../kubernetes/kube-apiserver --max-backlog=20 --secure-port=0 --audit-log-maxage=40 --option", "", + "", }, { controls.Groups[0].Checks[5], "2:45 ../kubernetes/kube-apiserver --option --admission-control=WebHook,RBAC ---audit-log-maxage=40", "", + "", }, { controls.Groups[0].Checks[6], "2:45 .. --kubelet-clientkey=foo --kubelet-client-certificate=bar --admission-control=Webhook,RBAC", "", + "", }, { controls.Groups[0].Checks[7], "2:45 .. --secure-port=0 --kubelet-client-certificate=bar --admission-control=Webhook,RBAC", "", + "", }, { controls.Groups[0].Checks[8], "644", "", + "", }, { controls.Groups[0].Checks[9], "640", "", + "", }, { controls.Groups[0].Checks[9], "600", "", + "", }, { controls.Groups[0].Checks[10], "2:45 ../kubernetes/kube-apiserver --option --admission-control=WebHook,RBAC ---audit-log-maxage=40", "", + "", }, { controls.Groups[0].Checks[11], "2:45 ../kubernetes/kube-apiserver --option --admission-control=WebHook,RBAC ---audit-log-maxage=40", "", + "", }, { controls.Groups[0].Checks[12], "2:45 ../kubernetes/kube-apiserver --option --admission-control=WebHook,Something,RBAC ---audit-log-maxage=40", "", + "", }, { controls.Groups[0].Checks[13], "2:45 ../kubernetes/kube-apiserver --option --admission-control=Something ---audit-log-maxage=40", "", + "", }, { // check for ':' as argument-value separator, with space between arg and val controls.Groups[0].Checks[14], "2:45 kube-apiserver some-arg: some-val --admission-control=Something ---audit-log-maxage=40", "", + "", }, { // check for ':' as argument-value separator, with no space between arg and val controls.Groups[0].Checks[14], "2:45 kube-apiserver some-arg:some-val --admission-control=Something ---audit-log-maxage=40", "", + "", }, { controls.Groups[0].Checks[15], "", "{\"readOnlyPort\": 15000}", + "", }, { controls.Groups[0].Checks[16], "", "{\"stringValue\": \"WebHook,Something,RBAC\"}", + "", }, { controls.Groups[0].Checks[17], "", "{\"trueValue\": true}", + "", }, { controls.Groups[0].Checks[18], "", "{\"readOnlyPort\": 15000}", + "", }, { controls.Groups[0].Checks[19], "", "{\"authentication\": { \"anonymous\": {\"enabled\": false}}}", + "", }, { controls.Groups[0].Checks[20], "", "readOnlyPort: 15000", + "", }, { controls.Groups[0].Checks[21], "", "readOnlyPort: 15000", + "", }, { controls.Groups[0].Checks[22], "", "authentication:\n anonymous:\n enabled: false", + "", }, { controls.Groups[0].Checks[26], "", "currentMasterVersion: 1.12.7", + "", }, { controls.Groups[0].Checks[27], "--peer-client-cert-auth", "", + "", }, { controls.Groups[0].Checks[27], "--abc=true --peer-client-cert-auth --efg=false", "", + "", }, { controls.Groups[0].Checks[27], "--abc --peer-client-cert-auth --efg", "", + "", }, { controls.Groups[0].Checks[27], "--peer-client-cert-auth=true", "", + "", }, { controls.Groups[0].Checks[27], "--abc --peer-client-cert-auth=true --efg", "", + "", }, { controls.Groups[0].Checks[28], "--abc --peer-client-cert-auth=false --efg", "", + "", + }, + { + controls.Groups[0].Checks[29], + "2:45 ../kubernetes/kube-apiserver --option1=20,30,40", + "", + "SOME_OTHER_ENV=true\nALLOW_PRIVILEGED=false", + }, + { + controls.Groups[0].Checks[30], + "2:45 ../kubernetes/kube-apiserver --option1=20,30,40", + "", + "", + }, + { + controls.Groups[0].Checks[31], + "2:45 ../kubernetes/kube-apiserver --option1=20,30,40", + "", + "INSECURE_PORT=0", + }, + { + controls.Groups[0].Checks[32], + "2:45 ../kubernetes/kube-apiserver --option1=20,30,40", + "", + "AUDIT_LOG_MAXAGE=40", + }, + { + controls.Groups[0].Checks[33], + "2:45 ../kubernetes/kube-apiserver --option1=20,30,40", + "", + "MAX_BACKLOG=20", }, } @@ -220,6 +283,7 @@ func TestTestExecute(t *testing.T) { t.Run(c.Text, func(t *testing.T) { c.Check.AuditOutput = c.str c.Check.AuditConfigOutput = c.strConfig + c.Check.AuditEnvOutput = c.strEnv res, err := c.Check.execute() if err != nil { t.Errorf(err.Error()) diff --git a/cmd/common.go b/cmd/common.go index 9f17ff9..3d0c0a4 100644 --- a/cmd/common.go +++ b/cmd/common.go @@ -100,11 +100,11 @@ func runChecks(nodetype check.NodeType, testYamlFile string) { // Variable substitutions. Replace all occurrences of variables in controls files. s := string(in) - s = makeSubstitutions(s, "bin", binmap) - s = makeSubstitutions(s, "conf", confmap) - s = makeSubstitutions(s, "svc", svcmap) - s = makeSubstitutions(s, "kubeconfig", kubeconfmap) - s = makeSubstitutions(s, "cafile", cafilemap) + s, binSubs := makeSubstitutions(s, "bin", binmap) + s, _ = makeSubstitutions(s, "conf", confmap) + s, _ = makeSubstitutions(s, "svc", svcmap) + s, _ = makeSubstitutions(s, "kubeconfig", kubeconfmap) + s, _ = makeSubstitutions(s, "cafile", cafilemap) controls, err := check.NewControls(nodetype, []byte(s)) if err != nil { @@ -117,10 +117,36 @@ func runChecks(nodetype check.NodeType, testYamlFile string) { exitWithError(fmt.Errorf("error setting up run filter: %v", err)) } + generateDefaultEnvAudit(controls, binSubs) + controls.RunChecks(runner, filter, parseSkipIds(skipIds)) controlsCollection = append(controlsCollection, controls) } +func generateDefaultEnvAudit(controls *check.Controls, binSubs []string){ + for _, group := range controls.Groups { + for _, checkItem := range group.Checks { + if checkItem.Tests != nil && !checkItem.DisableEnvTesting { + for _, test := range checkItem.Tests.TestItems { + if test.Env != "" && checkItem.AuditEnv == "" { + binPath := "" + + if len(binSubs) == 1 { + binPath = binSubs[0] + } else { + fmt.Printf("AuditEnv not explicit for check (%s), where bin path cannot be determined\n", checkItem.ID) + } + + if test.Env != "" && checkItem.AuditEnv == "" { + checkItem.AuditEnv = fmt.Sprintf("cat \"/proc/$(/bin/ps -C %s -o pid= | tr -d ' ')/environ\" | tr '\\0' '\\n'", binPath) + } + } + } + } + } + } +} + func parseSkipIds(skipIds string) map[string]bool { var skipIdMap = make(map[string]bool, 0) if skipIds != "" { diff --git a/cmd/common_test.go b/cmd/common_test.go index 725ae0b..2f800f8 100644 --- a/cmd/common_test.go +++ b/cmd/common_test.go @@ -559,6 +559,41 @@ func TestExitCodeSelection(t *testing.T){ assert.Equal(t, 10, exitCodeFailure) } +func TestGenerationDefaultEnvAudit(t *testing.T) { + input := []byte(` +--- +type: "master" +groups: +- id: G1 + checks: + - id: G1/C1 +- id: G2 + checks: + - id: G2/C1 + text: "Verify that the SomeSampleFlag argument is set to true" + audit: "grep -B1 SomeSampleFlag=true /this/is/a/file/path" + tests: + test_items: + - flag: "SomeSampleFlag=true" + env: "SOME_SAMPLE_FLAG" + compare: + op: has + value: "true" + set: true + remediation: | + Edit the config file /this/is/a/file/path and set SomeSampleFlag to true. + scored: true +`) + controls, err := check.NewControls(check.MASTER, input) + assert.NoError(t, err) + + binSubs := []string {"TestBinPath"} + generateDefaultEnvAudit(controls, binSubs) + + expectedAuditEnv := fmt.Sprintf("cat \"/proc/$(/bin/ps -C %s -o pid= | tr -d ' ')/environ\" | tr '\\0' '\\n'", binSubs[0]) + assert.Equal(t, expectedAuditEnv, controls.Groups[1].Checks[0].AuditEnv) +} + func parseControlsJsonFile(filepath string) ([]*check.Controls, error) { var result []*check.Controls diff --git a/cmd/util.go b/cmd/util.go index 62f7d81..5c42ae6 100644 --- a/cmd/util.go +++ b/cmd/util.go @@ -353,7 +353,8 @@ func getVersionFromKubeletOutput(s string) string { return subs[1] } -func makeSubstitutions(s string, ext string, m map[string]string) string { +func makeSubstitutions(s string, ext string, m map[string]string) (string, []string) { + substitutions := make([]string, 0) for k, v := range m { subst := "$" + k + ext if v == "" { @@ -361,10 +362,14 @@ func makeSubstitutions(s string, ext string, m map[string]string) string { continue } glog.V(2).Info(fmt.Sprintf("Substituting %s with '%s'\n", subst, v)) + beforeS := s s = multiWordReplace(s, subst, v) + if beforeS != s { + substitutions = append(substitutions, v) + } } - return s + return s, substitutions } func isEmpty(str string) bool { diff --git a/cmd/util_test.go b/cmd/util_test.go index ea9045b..78a8658 100644 --- a/cmd/util_test.go +++ b/cmd/util_test.go @@ -15,6 +15,7 @@ package cmd import ( + "github.com/magiconair/properties/assert" "io/ioutil" "os" "path/filepath" @@ -387,17 +388,19 @@ func TestMakeSubsitutions(t *testing.T) { input string subst map[string]string exp string + expectedSubs []string }{ - {input: "Replace $thisbin", subst: map[string]string{"this": "that"}, exp: "Replace that"}, - {input: "Replace $thisbin", subst: map[string]string{"this": "that", "here": "there"}, exp: "Replace that"}, - {input: "Replace $thisbin and $herebin", subst: map[string]string{"this": "that", "here": "there"}, exp: "Replace that and there"}, + {input: "Replace $thisbin", subst: map[string]string{"this": "that"}, exp: "Replace that", expectedSubs: []string{"that"}}, + {input: "Replace $thisbin", subst: map[string]string{"this": "that", "here": "there"}, exp: "Replace that", expectedSubs: []string{"that"}}, + {input: "Replace $thisbin and $herebin", subst: map[string]string{"this": "that", "here": "there"}, exp: "Replace that and there", expectedSubs: []string{"that", "there"}}, } for _, c := range cases { t.Run(c.input, func(t *testing.T) { - s := makeSubstitutions(c.input, "bin", c.subst) + s, subs := makeSubstitutions(c.input, "bin", c.subst) if s != c.exp { t.Fatalf("Got %s expected %s", s, c.exp) } + assert.Equal(t, c.expectedSubs, subs) }) } } diff --git a/docs/README.md b/docs/README.md index 5faeb2b..423093b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -150,8 +150,8 @@ pass a check. This criteria is made up of keywords extracted from the output of the `audit` command and operations that compare these keywords against values expected by the CIS Kubernetes Benchmark. -There are two ways to extract keywords from the output of the `audit` command, -`flag` and `path`. +There are three ways to extract keywords from the output of the `audit` command, +`flag`, `path`, `env`. `flag` is used when the keyword is a command-line flag. The associated `audit` command is usually a `ps` command and a `grep` for the binary whose flag we are @@ -186,6 +186,23 @@ tests: # ... ``` +`env` is used to check if the value is present within a specified environment variable. The presence of `env` is treated as an OR operation, if both `flag` and `env` are supplied it will use either to attempt pass the check. +The command used for checking the environment variables of a process **is generated by default**. + +If the command being generated is causing errors, you can override the command used by setting `auditEnv` on the check. +Similarly, if you don't want the environment checking command to be generated or run at all, specify `disableEnvTesting` as true on the check. + +The example below will check if the flag `--auto-tls` is equal to false *OR* `ETCD_AUTO_TLS` is equal to false + +```yml + test_items: + - flag: "--auto-tls" + env: "ETCD_AUTO_TLS" + compare: + op: eq + value: false +``` + `test_item` compares the output of the audit command and keywords using the `set` and `compare` fields.