diff --git a/check/check.go b/check/check.go index ab6edb87cd4fb522b5f8360512d920b117012655..369ef2f54ca999536be9ca86b21c7d872b8c68d8 100644 --- a/check/check.go +++ b/check/check.go @@ -74,6 +74,7 @@ type Check struct { State `json:"status"` ActualValue string `json:"actual_value"` Scored bool `json:"scored"` + IsMultiple bool `yaml:"use_multiple_values"` ExpectedResult string `json:"expected_result"` Reason string `json:"reason,omitempty"` } @@ -125,7 +126,7 @@ func (c *Check) run() State { lastCommand := c.Audit hasAuditConfig := c.AuditConfig != "" - state, finalOutput, retErrmsgs := performTest(c.Audit, c.Tests) + state, finalOutput, retErrmsgs := performTest(c.Audit, c.Tests, c.IsMultiple) if len(state) > 0 { c.Reason = retErrmsgs c.State = state @@ -161,7 +162,7 @@ func (c *Check) run() State { currentTests.TestItems[i] = nti } - state, finalOutput, retErrmsgs = performTest(c.AuditConfig, currentTests) + state, finalOutput, retErrmsgs = performTest(c.AuditConfig, currentTests, c.IsMultiple) if len(state) > 0 { c.Reason = retErrmsgs c.State = state @@ -195,7 +196,7 @@ func (c *Check) run() State { return c.State } -func performTest(audit string, tests *tests) (State, *testOutput, string) { +func performTest(audit string, tests *tests, isMultipleOutput bool) (State, *testOutput, string) { if len(strings.TrimSpace(audit)) == 0 { return "", failTestItem("missing command"), "missing audit command" } @@ -203,7 +204,7 @@ func performTest(audit string, tests *tests) (State, *testOutput, string) { var out bytes.Buffer errmsgs := runAudit(audit, &out) - finalOutput := tests.execute(out.String()) + finalOutput := tests.execute(out.String(), isMultipleOutput) if finalOutput == nil { errmsgs += fmt.Sprintf("Final output is <<EMPTY>>. Failed to run: %s\n", audit) } diff --git a/check/check_test.go b/check/check_test.go index 45c37ecac3ed14e6538a10c79da3f61146a9ee5a..c6b93f865c94a62c8de37f356acb1ed61326324f 100644 --- a/check/check_test.go +++ b/check/check_test.go @@ -103,6 +103,18 @@ func TestCheckAuditConfig(t *testing.T) { controls.Groups[1].Checks[8], "FAIL", }, + { + controls.Groups[1].Checks[9], + "PASS", + }, + { + controls.Groups[1].Checks[10], + "FAIL", + }, + { + controls.Groups[1].Checks[11], + "FAIL", + }, } for _, c := range cases { diff --git a/check/controls_test.go b/check/controls_test.go index 973dcc590a8930ffd51b455d128aff62f2b41bb9..fd49686a1bd9edbcf55dc7fd39360937b80952c3 100644 --- a/check/controls_test.go +++ b/check/controls_test.go @@ -184,7 +184,7 @@ func TestControls_JUnitIncludesJSON(t *testing.T) { }, expect: []byte(`<testsuite name="" tests="0" failures="0" errors="0" time="0"> <testcase name="check1id check1text" classname="" time="0"> - <system-out>{"test_number":"check1id","test_desc":"check1text","audit":"","AuditConfig":"","type":"","remediation":"","test_info":null,"status":"PASS","actual_value":"","scored":false,"expected_result":""}</system-out> + <system-out>{"test_number":"check1id","test_desc":"check1text","audit":"","AuditConfig":"","type":"","remediation":"","test_info":null,"status":"PASS","actual_value":"","scored":false,"IsMultiple":false,"expected_result":""}</system-out> </testcase> </testsuite>`), }, { @@ -207,7 +207,7 @@ func TestControls_JUnitIncludesJSON(t *testing.T) { }, expect: []byte(`<testsuite name="" tests="402" failures="99" errors="0" time="0"> <testcase name="check1id check1text" classname="" time="0"> - <system-out>{"test_number":"check1id","test_desc":"check1text","audit":"","AuditConfig":"","type":"","remediation":"","test_info":null,"status":"PASS","actual_value":"","scored":false,"expected_result":""}</system-out> + <system-out>{"test_number":"check1id","test_desc":"check1text","audit":"","AuditConfig":"","type":"","remediation":"","test_info":null,"status":"PASS","actual_value":"","scored":false,"IsMultiple":false,"expected_result":""}</system-out> </testcase> </testsuite>`), }, { @@ -227,19 +227,19 @@ func TestControls_JUnitIncludesJSON(t *testing.T) { }, expect: []byte(`<testsuite name="" tests="0" failures="0" errors="0" time="0"> <testcase name="check1id check1text" classname="" time="0"> - <system-out>{"test_number":"check1id","test_desc":"check1text","audit":"","AuditConfig":"","type":"","remediation":"","test_info":null,"status":"PASS","actual_value":"","scored":false,"expected_result":""}</system-out> + <system-out>{"test_number":"check1id","test_desc":"check1text","audit":"","AuditConfig":"","type":"","remediation":"","test_info":null,"status":"PASS","actual_value":"","scored":false,"IsMultiple":false,"expected_result":""}</system-out> </testcase> <testcase name="check2id check2text" classname="" time="0"> <skipped></skipped> - <system-out>{"test_number":"check2id","test_desc":"check2text","audit":"","AuditConfig":"","type":"","remediation":"","test_info":null,"status":"INFO","actual_value":"","scored":false,"expected_result":""}</system-out> + <system-out>{"test_number":"check2id","test_desc":"check2text","audit":"","AuditConfig":"","type":"","remediation":"","test_info":null,"status":"INFO","actual_value":"","scored":false,"IsMultiple":false,"expected_result":""}</system-out> </testcase> <testcase name="check3id check3text" classname="" time="0"> <skipped></skipped> - <system-out>{"test_number":"check3id","test_desc":"check3text","audit":"","AuditConfig":"","type":"","remediation":"","test_info":null,"status":"WARN","actual_value":"","scored":false,"expected_result":""}</system-out> + <system-out>{"test_number":"check3id","test_desc":"check3text","audit":"","AuditConfig":"","type":"","remediation":"","test_info":null,"status":"WARN","actual_value":"","scored":false,"IsMultiple":false,"expected_result":""}</system-out> </testcase> <testcase name="check4id check4text" classname="" time="0"> <failure type=""></failure> - <system-out>{"test_number":"check4id","test_desc":"check4text","audit":"","AuditConfig":"","type":"","remediation":"","test_info":null,"status":"FAIL","actual_value":"","scored":false,"expected_result":""}</system-out> + <system-out>{"test_number":"check4id","test_desc":"check4text","audit":"","AuditConfig":"","type":"","remediation":"","test_info":null,"status":"FAIL","actual_value":"","scored":false,"IsMultiple":false,"expected_result":""}</system-out> </testcase> </testsuite>`), }, diff --git a/check/data b/check/data index 1930341691e97887525e4a118e54c7363a502185..33d6ce8dffcc64422abf1311ff2df2e0d46223ed 100644 --- a/check/data +++ b/check/data @@ -440,3 +440,39 @@ groups: value: "correct" set: true scored: true + - id: 9 + text: "test use_multiple_values is correct -> pass" + audit: "printf 'permissions=600\npermissions=600\npermissions=600'" + use_multiple_values: true + tests: + test_items: + - flag: "permissions" + compare: + op: bitmask + value: "600" + set: true + scored: true + - id: 10 + text: "test use_multiple_values is wrong -> fail" + audit: "printf 'permissions=600\npermissions=600\npermissions=644'" + use_multiple_values: true + tests: + test_items: + - flag: "permissions" + compare: + op: bitmask + value: "600" + set: true + scored: true + - id: 11 + text: "test use_multiple_values include empty value -> fail" + audit: "printf 'permissions=600\n\npermissions=600'" + use_multiple_values: true + tests: + test_items: + - flag: "permissions" + compare: + op: bitmask + value: "600" + set: true + scored: true diff --git a/check/test.go b/check/test.go index b6bed048fac16b2ec33ed72992236c7242f9b905..47381c24f12176a5f64a9977f85969562dae7f2b 100644 --- a/check/test.go +++ b/check/test.go @@ -67,7 +67,28 @@ func failTestItem(s string) *testOutput { return &testOutput{testResult: false, actualResult: s} } -func (t *testItem) execute(s string) *testOutput { +func (t *testItem) execute(s string, isMultipleOutput bool) *testOutput { + result := &testOutput{} + s = strings.TrimRight(s, " \n") + + // If the test has output that should be evaluated for each row + if isMultipleOutput { + output := strings.Split(s, "\n") + for _, op := range output { + result = t.evaluate(op) + // If the test failed for the current row, no need to keep testing for this output + if !result.testResult { + break + } + } + } else { + result = t.evaluate(s) + } + + return result +} + +func (t *testItem) evaluate(s string) *testOutput { result := &testOutput{} var match bool var flagVal string @@ -310,7 +331,7 @@ type tests struct { BinOp binOp `yaml:"bin_op"` } -func (ts *tests) execute(s string) *testOutput { +func (ts *tests) execute(s string, isMultipleOutput bool) *testOutput { finalOutput := &testOutput{} // If no tests are defined return with empty finalOutput. @@ -327,7 +348,7 @@ func (ts *tests) execute(s string) *testOutput { expectedResultArr := make([]string, len(res)) for i, t := range ts.TestItems { - res[i] = *(t.execute(s)) + res[i] = *(t.execute(s, isMultipleOutput)) expectedResultArr[i] = res[i].ExpectedResult } diff --git a/check/test_test.go b/check/test_test.go index 55c3e1e9cc5a2208c163bdba2a51f805f77c04d4..933d4515cbd0cb76f694f90b64d55a00f6971f9d 100644 --- a/check/test_test.go +++ b/check/test_test.go @@ -183,7 +183,7 @@ func TestTestExecute(t *testing.T) { } for _, c := range cases { - res := c.Tests.execute(c.str).testResult + res := c.Tests.execute(c.str, c.IsMultiple).testResult if !res { t.Errorf("%s, expected:%v, got:%v\n", c.Text, true, res) } @@ -219,7 +219,7 @@ func TestTestExecuteExceptions(t *testing.T) { } for _, c := range cases { - res := c.Tests.execute(c.str).testResult + res := c.Tests.execute(c.str, c.IsMultiple).testResult if res { t.Errorf("%s, expected:%v, got:%v\n", c.Text, false, res) }