diff --git a/check/check.go b/check/check.go
index ab52f06..bc00f7b 100644
--- a/check/check.go
+++ b/check/check.go
@@ -310,4 +310,4 @@ func runAudit(audit string) (output string, err error) {
glog.V(3).Infof("Output:\n %q", output)
}
return output, err
-}
+}
\ No newline at end of file
diff --git a/check/controls.go b/check/controls.go
index 7725f56..7051ae8 100644
--- a/check/controls.go
+++ b/check/controls.go
@@ -33,7 +33,7 @@ const (
// UNKNOWN is when the AWS account can't be found
UNKNOWN = "Unknown"
// ARN for the AWS Security Hub service
- ARN = "arn:aws:securityhub:%s::product/khulnasoft-security/kube-bench"
+ ARN = "arn:aws:securityhub:%s::product/aqua-security/kube-bench"
// SCHEMA for the AWS Security Hub service
SCHEMA = "2018-10-08"
// TYPE is type of Security Hub finding
@@ -237,7 +237,7 @@ func (controls *Controls) ASFF() ([]types.AwsSecurityFinding, error) {
actualValue = check.ActualValue[0:1023]
}
- // Fix issue https://github.com/khulnasoft-lab/kube-bench/issues/903
+ // Fix issue https://github.com/aquasecurity/kube-bench/issues/903
if len(check.Remediation) > 512 {
remediation = check.Remediation[0:511]
}
@@ -327,4 +327,4 @@ func summarizeGroup(group *Group, state State) {
default:
glog.Warningf("Unrecognized state %s", state)
}
-}
+}
\ No newline at end of file
diff --git a/check/controls_test.go b/check/controls_test.go
index 8df0563..5e492c8 100644
--- a/check/controls_test.go
+++ b/check/controls_test.go
@@ -15,451 +15,231 @@
package check
import (
- "bytes"
- "encoding/json"
- "encoding/xml"
- "fmt"
- "io/ioutil"
- "os"
- "path/filepath"
- "reflect"
+ "strings"
"testing"
-
- "github.com/aws/aws-sdk-go-v2/aws"
- "github.com/aws/aws-sdk-go-v2/service/securityhub/types"
- "github.com/onsi/ginkgo/reporters"
- "github.com/spf13/viper"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/mock"
- "gopkg.in/yaml.v2"
)
-const cfgDir = "../cfg/"
-
-type mockRunner struct {
- mock.Mock
-}
-
-func (m *mockRunner) Run(c *Check) State {
- args := m.Called(c)
- return args.Get(0).(State)
-}
-
-// validate that the files we're shipping are valid YAML
-func TestYamlFiles(t *testing.T) {
- err := filepath.Walk(cfgDir, func(path string, info os.FileInfo, err error) error {
- if err != nil {
- t.Fatalf("failure accessing path %q: %v\n", path, err)
- }
- if !info.IsDir() {
- t.Logf("reading file: %s", path)
- in, err := ioutil.ReadFile(path)
- if err != nil {
- t.Fatalf("error opening file %s: %v", path, err)
- }
-
- c := new(Controls)
- err = yaml.Unmarshal(in, c)
- if err == nil {
- t.Logf("YAML file successfully unmarshalled: %s", path)
- } else {
- t.Fatalf("failed to load YAML from %s: %v", path, err)
- }
- }
- return nil
- })
- if err != nil {
- t.Fatalf("failure walking cfg dir: %v\n", err)
+func TestCheck_Run(t *testing.T) {
+ type TestCase struct {
+ name string
+ check Check
+ Expected State
}
-}
-
-func TestNewControls(t *testing.T) {
- t.Run("Should return error when node type is not specified", func(t *testing.T) {
- // given
- in := []byte(`
----
-controls:
-type: # not specified
-groups:
-`)
- // when
- _, err := NewControls(MASTER, in, "")
- // then
- assert.EqualError(t, err, "non-master controls file specified")
- })
-
- t.Run("Should return error when input YAML is invalid", func(t *testing.T) {
- // given
- in := []byte("BOOM")
- // when
- _, err := NewControls(MASTER, in, "")
- // then
- assert.EqualError(t, err, "failed to unmarshal YAML: yaml: unmarshal errors:\n line 1: cannot unmarshal !!str `BOOM` into check.Controls")
- })
-
-}
-
-func TestControls_RunChecks_SkippedCmd(t *testing.T) {
- t.Run("Should skip checks and groups specified by skipMap", func(t *testing.T) {
- // given
- normalRunner := &defaultRunner{}
- // and
- in := []byte(`
----
-type: "master"
-groups:
-- id: G1
- checks:
- - id: G1/C1
- - id: G1/C2
- - id: G1/C3
-- id: G2
- checks:
- - id: G2/C1
- - id: G2/C2
-`)
- controls, err := NewControls(MASTER, in, "")
- assert.NoError(t, err)
-
- var allChecks Predicate = func(group *Group, c *Check) bool {
- return true
- }
-
- skipMap := make(map[string]bool, 0)
- skipMap["G1"] = true
- skipMap["G2/C1"] = true
- skipMap["G2/C2"] = true
- controls.RunChecks(normalRunner, allChecks, skipMap)
- G1 := controls.Groups[0]
- assertEqualGroupSummary(t, 0, 0, 3, 0, G1)
-
- G2 := controls.Groups[1]
- assertEqualGroupSummary(t, 0, 0, 2, 0, G2)
- })
-}
-
-func TestControls_RunChecks_Skipped(t *testing.T) {
- t.Run("Should skip checks where the parent group is marked as skip", func(t *testing.T) {
- // given
- normalRunner := &defaultRunner{}
- // and
- in := []byte(`
----
-type: "master"
-groups:
-- id: G1
- type: skip
- checks:
- - id: G1/C1
-`)
- controls, err := NewControls(MASTER, in, "")
- assert.NoError(t, err)
-
- var allChecks Predicate = func(group *Group, c *Check) bool {
- return true
- }
- emptySkipList := make(map[string]bool, 0)
- controls.RunChecks(normalRunner, allChecks, emptySkipList)
-
- G1 := controls.Groups[0]
- assertEqualGroupSummary(t, 0, 0, 1, 0, G1)
- })
-}
-
-func TestControls_RunChecks(t *testing.T) {
- t.Run("Should run checks matching the filter and update summaries", func(t *testing.T) {
- // given
- runner := new(mockRunner)
- // and
- in := []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"
- compare:
- op: has
- value: "true"
- set: true
- remediation: |
- Edit the config file /this/is/a/file/path and set SomeSampleFlag to true.
- scored: true
-`)
- // and
- controls, err := NewControls(MASTER, in, "")
- assert.NoError(t, err)
- // and
- runner.On("Run", controls.Groups[0].Checks[0]).Return(PASS)
- runner.On("Run", controls.Groups[1].Checks[0]).Return(FAIL)
- // and
- var runAll Predicate = func(group *Group, c *Check) bool {
- return true
- }
- var emptySkipList = make(map[string]bool, 0)
- // when
- controls.RunChecks(runner, runAll, emptySkipList)
- // then
- assert.Equal(t, 2, len(controls.Groups))
- // and
- G1 := controls.Groups[0]
- assert.Equal(t, "G1", G1.ID)
- assert.Equal(t, "G1/C1", G1.Checks[0].ID)
- assertEqualGroupSummary(t, 1, 0, 0, 0, G1)
- // and
- G2 := controls.Groups[1]
- assert.Equal(t, "G2", G2.ID)
- assert.Equal(t, "G2/C1", G2.Checks[0].ID)
- assert.Equal(t, "has", G2.Checks[0].Tests.TestItems[0].Compare.Op)
- assert.Equal(t, "true", G2.Checks[0].Tests.TestItems[0].Compare.Value)
- assert.Equal(t, true, G2.Checks[0].Tests.TestItems[0].Set)
- assert.Equal(t, "SomeSampleFlag=true", G2.Checks[0].Tests.TestItems[0].Flag)
- assert.Equal(t, "Edit the config file /this/is/a/file/path and set SomeSampleFlag to true.\n", G2.Checks[0].Remediation)
- assert.Equal(t, true, G2.Checks[0].Scored)
- assertEqualGroupSummary(t, 0, 1, 0, 0, G2)
- // and
- assert.Equal(t, 1, controls.Summary.Pass)
- assert.Equal(t, 1, controls.Summary.Fail)
- assert.Equal(t, 0, controls.Summary.Info)
- assert.Equal(t, 0, controls.Summary.Warn)
- // and
- runner.AssertExpectations(t)
- })
-}
+ testCases := []TestCase{
+ {name: "Manual check should WARN", check: Check{Type: MANUAL}, Expected: WARN},
+ {name: "Skip check should INFO", check: Check{Type: "skip"}, Expected: INFO},
+ {name: "Unscored check (with no type) should WARN on failure", check: Check{Scored: false}, Expected: WARN},
+ {
+ name: "Unscored check that pass should PASS",
+ check: Check{
+ Scored: false,
+ Audit: "echo hello",
+ Tests: &tests{TestItems: []*testItem{{
+ Flag: "hello",
+ Set: true,
+ }}},
+ },
+ Expected: PASS,
+ },
-func TestControls_JUnitIncludesJSON(t *testing.T) {
- testCases := []struct {
- desc string
- input *Controls
- expect []byte
- }{
+ {name: "Check with no tests should WARN", check: Check{Scored: true}, Expected: WARN},
+ {name: "Scored check with empty tests should FAIL", check: Check{Scored: true, Tests: &tests{}}, Expected: FAIL},
+ {
+ name: "Scored check that doesn't pass should FAIL",
+ check: Check{
+ Scored: true,
+ Audit: "echo hello",
+ Tests: &tests{TestItems: []*testItem{{
+ Flag: "hello",
+ Set: false,
+ }}},
+ },
+ Expected: FAIL,
+ },
{
- desc: "Serializes to junit",
- input: &Controls{
- Groups: []*Group{
- {
- ID: "g1",
- Checks: []*Check{
- {ID: "check1id", Text: "check1text", State: PASS},
- },
- },
- },
+ name: "Scored checks that pass should PASS",
+ check: Check{
+ Scored: true,
+ Audit: "echo hello",
+ Tests: &tests{TestItems: []*testItem{{
+ Flag: "hello",
+ Set: true,
+ }}},
},
- expect: []byte(`
-
- {"test_number":"check1id","test_desc":"check1text","audit":"","AuditEnv":"","AuditConfig":"","type":"","remediation":"","test_info":null,"status":"PASS","actual_value":"","scored":false,"IsMultiple":false,"expected_result":""}
-
-`),
- }, {
- desc: "Summary values come from summary not checks",
- input: &Controls{
- Summary: Summary{
- Fail: 99,
- Pass: 100,
- Warn: 101,
- Info: 102,
- },
- Groups: []*Group{
- {
- ID: "g1",
- Checks: []*Check{
- {ID: "check1id", Text: "check1text", State: PASS},
- },
- },
- },
+ Expected: PASS,
+ },
+ {
+ name: "Scored checks that pass should PASS when config file is not present",
+ check: Check{
+ Scored: true,
+ Audit: "echo hello",
+ AuditConfig: "/test/config.yaml",
+ Tests: &tests{TestItems: []*testItem{{
+ Flag: "hello",
+ Set: true,
+ }}},
},
- expect: []byte(`
-
- {"test_number":"check1id","test_desc":"check1text","audit":"","AuditEnv":"","AuditConfig":"","type":"","remediation":"","test_info":null,"status":"PASS","actual_value":"","scored":false,"IsMultiple":false,"expected_result":""}
-
-`),
- }, {
- desc: "Warn and Info are considered skips and failed tests properly reported",
- input: &Controls{
- Groups: []*Group{
- {
- ID: "g1",
- Checks: []*Check{
- {ID: "check1id", Text: "check1text", State: PASS},
- {ID: "check2id", Text: "check2text", State: INFO},
- {ID: "check3id", Text: "check3text", State: WARN},
- {ID: "check4id", Text: "check4text", State: FAIL},
- },
- },
- },
+ Expected: PASS,
+ },
+ {
+ name: "Scored checks that pass should FAIL when config file is not present",
+ check: Check{
+ Scored: true,
+ AuditConfig: "/test/config.yaml",
+ Tests: &tests{TestItems: []*testItem{{
+ Flag: "hello",
+ Set: true,
+ }}},
},
- expect: []byte(`
-
- {"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":"","AuditEnv":"","AuditConfig":"","type":"","remediation":"","test_info":null,"status":"INFO","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":"","AuditEnv":"","AuditConfig":"","type":"","remediation":"","test_info":null,"status":"FAIL","actual_value":"","scored":false,"IsMultiple":false,"expected_result":""}
-
-`),
+ Expected: FAIL,
},
}
- for _, tc := range testCases {
- t.Run(tc.desc, func(t *testing.T) {
- junitBytes, err := tc.input.JUnit()
- if err != nil {
- t.Fatalf("Failed to serialize to JUnit: %v", err)
- }
- var out reporters.JUnitTestSuite
- if err := xml.Unmarshal(junitBytes, &out); err != nil {
- t.Fatalf("Unable to deserialize from resulting JUnit: %v", err)
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ testCase.check.run()
+ if testCase.check.State != testCase.Expected {
+ t.Errorf("expected %s, actual %s", testCase.Expected, testCase.check.State)
}
+ })
+ }
+}
+
+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],
+ }
- // Check that each check was serialized as json and stored as systemOut.
- for iGroup, group := range tc.input.Groups {
- for iCheck, check := range group.Checks {
- jsonBytes, err := json.Marshal(check)
- if err != nil {
- t.Fatalf("Failed to serialize to JUnit: %v", err)
- }
+ failingCases := []*Check{
+ controls.Groups[2].Checks[1],
+ controls.Groups[2].Checks[5],
+ controls.Groups[2].Checks[6],
+ }
- if out.TestCases[iGroup*iCheck+iCheck].SystemOut != string(jsonBytes) {
- t.Errorf("Expected\n\t%v\n\tbut got\n\t%v",
- out.TestCases[iGroup*iCheck+iCheck].SystemOut,
- string(jsonBytes),
- )
- }
- }
+ 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)
}
+ })
+ }
- if !bytes.Equal(junitBytes, tc.expect) {
- t.Errorf("Expected\n\t%v\n\tbut got\n\t%v",
- string(tc.expect),
- string(junitBytes),
- )
+ 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 assertEqualGroupSummary(t *testing.T, pass, fail, info, warn int, actual *Group) {
- t.Helper()
- assert.Equal(t, pass, actual.Pass)
- assert.Equal(t, fail, actual.Fail)
- assert.Equal(t, info, actual.Info)
- assert.Equal(t, warn, actual.Warn)
+func TestCheckAuditConfig(t *testing.T) {
+ passingCases := []*Check{
+ controls.Groups[1].Checks[0],
+ controls.Groups[1].Checks[3],
+ controls.Groups[1].Checks[5],
+ controls.Groups[1].Checks[7],
+ controls.Groups[1].Checks[9],
+ controls.Groups[1].Checks[15],
+ }
+
+ failingCases := []*Check{
+ controls.Groups[1].Checks[1],
+ controls.Groups[1].Checks[2],
+ controls.Groups[1].Checks[4],
+ controls.Groups[1].Checks[6],
+ controls.Groups[1].Checks[8],
+ controls.Groups[1].Checks[10],
+ controls.Groups[1].Checks[11],
+ controls.Groups[1].Checks[12],
+ controls.Groups[1].Checks[13],
+ controls.Groups[1].Checks[14],
+ controls.Groups[1].Checks[16],
+ }
+
+ 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 TestControls_ASFF(t *testing.T) {
- type fields struct {
- ID string
- Version string
- Text string
- Groups []*Group
- Summary Summary
+func Test_runAudit(t *testing.T) {
+ type args struct {
+ audit string
+ output string
}
tests := []struct {
- name string
- fields fields
- want []types.AwsSecurityFinding
- wantErr bool
+ name string
+ args args
+ errMsg string
+ output string
}{
{
- name: "Test simple conversion",
- fields: fields{
- ID: "test1",
- Version: "1",
- Text: "test runnner",
- Summary: Summary{
- Fail: 99,
- Pass: 100,
- Warn: 101,
- Info: 102,
- },
- Groups: []*Group{
- {
- ID: "g1",
- Text: "Group text",
- Checks: []*Check{
- {ID: "check1id",
- Text: "check1text",
- State: FAIL,
- Remediation: "fix me",
- Reason: "failed",
- ExpectedResult: "failed",
- ActualValue: "failed",
- },
- },
- },
- }},
- want: []types.AwsSecurityFinding{
- {
- AwsAccountId: aws.String("foo account"),
- Confidence: *aws.Int32(100),
- GeneratorId: aws.String(fmt.Sprintf("%s/cis-kubernetes-benchmark/%s/%s", fmt.Sprintf(ARN, "somewhere"), "1", "check1id")),
- Description: aws.String("check1text"),
- ProductArn: aws.String(fmt.Sprintf(ARN, "somewhere")),
- SchemaVersion: aws.String(SCHEMA),
- Title: aws.String(fmt.Sprintf("%s %s", "check1id", "check1text")),
- Types: []string{*aws.String(TYPE)},
- Severity: &types.Severity{
- Label: types.SeverityLabelHigh,
- },
- Remediation: &types.Remediation{
- Recommendation: &types.Recommendation{
- Text: aws.String("fix me"),
- },
- },
- ProductFields: map[string]string{
- "Reason": "failed",
- "Actual result": "failed",
- "Expected result": "failed",
- "Section": fmt.Sprintf("%s %s", "test1", "test runnner"),
- "Subsection": fmt.Sprintf("%s %s", "g1", "Group text"),
- },
- Resources: []types.Resource{
- {
- Id: aws.String("foo Cluster"),
- Type: aws.String(TYPE),
- },
- },
- },
+ name: "run success",
+ args: args{
+ audit: "echo 'hello world'",
},
- wantErr: false,
+ errMsg: "",
+ output: "hello world\n",
+ },
+ {
+ name: "run multiple lines script",
+ args: args{
+ audit: `
+hello() {
+ echo "hello world"
+}
+
+hello
+`,
+ },
+ errMsg: "",
+ output: "hello world\n",
+ },
+ {
+ name: "run failed",
+ args: args{
+ audit: "unknown_command",
+ },
+ errMsg: "failed to run: \"unknown_command\", output: \"/bin/sh: ",
+ output: "not found\n",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- viper.Set("AWS_ACCOUNT", "foo account")
- viper.Set("CLUSTER_ARN", "foo Cluster")
- viper.Set("AWS_REGION", "somewhere")
- controls := &Controls{
- ID: tt.fields.ID,
- Version: tt.fields.Version,
- Text: tt.fields.Text,
- Groups: tt.fields.Groups,
- Summary: tt.fields.Summary,
+ var errMsg string
+ output, err := runAudit(tt.args.audit)
+ if err != nil {
+ errMsg = err.Error()
+ }
+ if errMsg != "" && !strings.Contains(errMsg, tt.errMsg) {
+ t.Errorf("name %s errMsg = %q, want %q", tt.name, errMsg, tt.errMsg)
}
- got, _ := controls.ASFF()
- tt.want[0].CreatedAt = got[0].CreatedAt
- tt.want[0].UpdatedAt = got[0].UpdatedAt
- tt.want[0].Id = got[0].Id
- if !reflect.DeepEqual(got, tt.want) {
- t.Errorf("Controls.ASFF() = %v, want %v", got, tt.want)
+ if errMsg == "" && output != tt.output {
+ t.Errorf("name %s output = %q, want %q", tt.name, output, tt.output)
+ }
+ if errMsg != "" && !strings.Contains(output, tt.output) {
+ t.Errorf("name %s output = %q, want %q", tt.name, output, tt.output)
}
})
}
-}
+}
\ No newline at end of file
diff --git a/check/data b/check/data
index fa3c2fe..6a3f116 100644
--- a/check/data
+++ b/check/data
@@ -733,4 +733,4 @@ groups:
op: eq
value: "correct"
set: true
- scored: true
+ scored: true
\ No newline at end of file
diff --git a/check/test.go b/check/test.go
index 44ba464..6c777d4 100644
--- a/check/test.go
+++ b/check/test.go
@@ -443,4 +443,4 @@ func (t *testItem) UnmarshalYAML(unmarshal func(interface{}) error) error {
}
*t = testItem(newTestItem)
return nil
-}
+}
\ No newline at end of file
diff --git a/check/test_test.go b/check/test_test.go
index 7bbb721..5ab61bf 100644
--- a/check/test_test.go
+++ b/check/test_test.go
@@ -1405,4 +1405,4 @@ func TestExecuteJSONPathOnEncryptionConfig(t *testing.T) {
}
})
}
-}
+}
\ No newline at end of file