diff --git a/check/check.go b/check/check.go index ab6edb8..369ef2f 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 <>. Failed to run: %s\n", audit) } diff --git a/check/check_test.go b/check/check_test.go index 45c37ec..c6b93f8 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 973dcc5..fd49686 100644 --- a/check/controls_test.go +++ b/check/controls_test.go @@ -184,7 +184,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,"expected_result":""} + {"test_number":"check1id","test_desc":"check1text","audit":"","AuditConfig":"","type":"","remediation":"","test_info":null,"status":"PASS","actual_value":"","scored":false,"IsMultiple":false,"expected_result":""} `), }, { @@ -207,7 +207,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,"expected_result":""} + {"test_number":"check1id","test_desc":"check1text","audit":"","AuditConfig":"","type":"","remediation":"","test_info":null,"status":"PASS","actual_value":"","scored":false,"IsMultiple":false,"expected_result":""} `), }, { @@ -227,19 +227,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,"expected_result":""} + {"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":"check2id","test_desc":"check2text","audit":"","AuditConfig":"","type":"","remediation":"","test_info":null,"status":"INFO","actual_value":"","scored":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":"check3id","test_desc":"check3text","audit":"","AuditConfig":"","type":"","remediation":"","test_info":null,"status":"WARN","actual_value":"","scored":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":"check4id","test_desc":"check4text","audit":"","AuditConfig":"","type":"","remediation":"","test_info":null,"status":"FAIL","actual_value":"","scored":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":""} `), }, diff --git a/check/data b/check/data index 1930341..33d6ce8 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 b6bed04..47381c2 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 55c3e1e..933d451 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) }