Allow for environment variables to be checked in tests (#755)

* Initial commit for checking environment variables for etcd

* Revert config changes

* Remove redundant struct data

* Fix issues with failing tests

* Initial changes based on code review

* Add option to disable envTesting + Update docs

* Initial tests

* Finished testing

* Fix broken tests
pull/761/head^2
Wicked 3 years ago committed by GitHub
parent 28192bb7ab
commit a19b65127e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -15,7 +15,9 @@ groups:
bin_op: and bin_op: and
test_items: test_items:
- flag: "--cert-file" - flag: "--cert-file"
env: "ETCD_CERT_FILE"
- flag: "--key-file" - flag: "--key-file"
env: "ETCD_KEY_FILE"
remediation: | remediation: |
Follow the etcd service documentation and configure TLS encryption. Follow the etcd service documentation and configure TLS encryption.
Then, edit the etcd pod specification file /etc/kubernetes/manifests/etcd.yaml Then, edit the etcd pod specification file /etc/kubernetes/manifests/etcd.yaml
@ -30,6 +32,7 @@ groups:
tests: tests:
test_items: test_items:
- flag: "--client-cert-auth" - flag: "--client-cert-auth"
env: "ETCD_CLIENT_CERT_AUTH"
compare: compare:
op: eq op: eq
value: true value: true
@ -46,8 +49,10 @@ groups:
bin_op: or bin_op: or
test_items: test_items:
- flag: "--auto-tls" - flag: "--auto-tls"
env: "ETCD_AUTO_TLS"
set: false set: false
- flag: "--auto-tls" - flag: "--auto-tls"
env: "ETCD_AUTO_TLS"
compare: compare:
op: eq op: eq
value: false value: false
@ -65,7 +70,9 @@ groups:
bin_op: and bin_op: and
test_items: test_items:
- flag: "--peer-cert-file" - flag: "--peer-cert-file"
env: "ETCD_PEER_CERT_FILE"
- flag: "--peer-key-file" - flag: "--peer-key-file"
env: "ETCD_PEER_KEY_FILE"
remediation: | remediation: |
Follow the etcd service documentation and configure peer TLS encryption as appropriate Follow the etcd service documentation and configure peer TLS encryption as appropriate
for your etcd cluster. for your etcd cluster.
@ -81,6 +88,7 @@ groups:
tests: tests:
test_items: test_items:
- flag: "--peer-client-cert-auth" - flag: "--peer-client-cert-auth"
env: "ETCD_PEER_CLIENT_CERT_AUTH"
compare: compare:
op: eq op: eq
value: true value: true
@ -97,8 +105,10 @@ groups:
bin_op: or bin_op: or
test_items: test_items:
- flag: "--peer-auto-tls" - flag: "--peer-auto-tls"
env: "ETCD_PEER_AUTO_TLS"
set: false set: false
- flag: "--peer-auto-tls" - flag: "--peer-auto-tls"
env: "ETCD_PEER_AUTO_TLS"
compare: compare:
op: eq op: eq
value: false value: false
@ -114,6 +124,7 @@ groups:
tests: tests:
test_items: test_items:
- flag: "--trusted-ca-file" - flag: "--trusted-ca-file"
env: "ETCD_TRUSTED_CA_FILE"
remediation: | remediation: |
[Manual test] [Manual test]
Follow the etcd documentation and create a dedicated certificate authority setup for the Follow the etcd documentation and create a dedicated certificate authority setup for the

@ -68,6 +68,7 @@ type Check struct {
ID string `yaml:"id" json:"test_number"` ID string `yaml:"id" json:"test_number"`
Text string `json:"test_desc"` Text string `json:"test_desc"`
Audit string `json:"audit"` Audit string `json:"audit"`
AuditEnv string `yaml:"audit_env"`
AuditConfig string `yaml:"audit_config"` AuditConfig string `yaml:"audit_config"`
Type string `json:"type"` Type string `json:"type"`
Tests *tests `json:"-"` Tests *tests `json:"-"`
@ -81,7 +82,9 @@ type Check struct {
ExpectedResult string `json:"expected_result"` ExpectedResult string `json:"expected_result"`
Reason string `json:"reason,omitempty"` Reason string `json:"reason,omitempty"`
AuditOutput string `json:"-"` AuditOutput string `json:"-"`
AuditEnvOutput string `json:"-"`
AuditConfigOutput string `json:"-"` AuditConfigOutput string `json:"-"`
DisableEnvTesting bool `json:"-"`
} }
// Runner wraps the basic Run method. // Runner wraps the basic Run method.
@ -184,6 +187,14 @@ func (c *Check) run() State {
} }
func (c *Check) runAuditCommands() (lastCommand string, err error) { 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 // Run the audit command and auditConfig commands, if present
c.AuditOutput, err = runAudit(c.Audit) c.AuditOutput, err = runAudit(c.Audit)
if err != nil { if err != nil {
@ -207,11 +218,15 @@ func (c *Check) execute() (finalOutput *testOutput, err error) {
t.isMultipleOutput = c.IsMultiple t.isMultipleOutput = c.IsMultiple
// Try with the auditOutput first, and if that's not found, try the auditConfigOutput // 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)) result := *(t.execute(c.AuditOutput))
if !result.flagFound { if !result.found {
t.isConfigSetting = true t.auditUsed = AuditConfig
result = *(t.execute(c.AuditConfigOutput)) result = *(t.execute(c.AuditConfigOutput))
if !result.found && t.Env != "" {
t.auditUsed = AuditEnv
result = *(t.execute(c.AuditEnvOutput))
}
} }
res[i] = result res[i] = result
expectedResultArr[i] = res[i].ExpectedResult expectedResultArr[i] = res[i].ExpectedResult

@ -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) { func TestCheckAuditConfig(t *testing.T) {
passingCases := []*Check{ passingCases := []*Check{

@ -256,7 +256,7 @@ func TestControls_JUnitIncludesJSON(t *testing.T) {
}, },
expect: []byte(`<testsuite name="" tests="0" failures="0" errors="0" time="0"> expect: []byte(`<testsuite name="" tests="0" failures="0" errors="0" time="0">
<testcase name="check1id check1text" classname="" time="0"> <testcase name="check1id check1text" classname="" time="0">
<system-out>{&#34;test_number&#34;:&#34;check1id&#34;,&#34;test_desc&#34;:&#34;check1text&#34;,&#34;audit&#34;:&#34;&#34;,&#34;AuditConfig&#34;:&#34;&#34;,&#34;type&#34;:&#34;&#34;,&#34;remediation&#34;:&#34;&#34;,&#34;test_info&#34;:null,&#34;status&#34;:&#34;PASS&#34;,&#34;actual_value&#34;:&#34;&#34;,&#34;scored&#34;:false,&#34;IsMultiple&#34;:false,&#34;expected_result&#34;:&#34;&#34;}</system-out> <system-out>{&#34;test_number&#34;:&#34;check1id&#34;,&#34;test_desc&#34;:&#34;check1text&#34;,&#34;audit&#34;:&#34;&#34;,&#34;AuditEnv&#34;:&#34;&#34;,&#34;AuditConfig&#34;:&#34;&#34;,&#34;type&#34;:&#34;&#34;,&#34;remediation&#34;:&#34;&#34;,&#34;test_info&#34;:null,&#34;status&#34;:&#34;PASS&#34;,&#34;actual_value&#34;:&#34;&#34;,&#34;scored&#34;:false,&#34;IsMultiple&#34;:false,&#34;expected_result&#34;:&#34;&#34;}</system-out>
</testcase> </testcase>
</testsuite>`), </testsuite>`),
}, { }, {
@ -279,7 +279,7 @@ func TestControls_JUnitIncludesJSON(t *testing.T) {
}, },
expect: []byte(`<testsuite name="" tests="402" failures="99" errors="0" time="0"> expect: []byte(`<testsuite name="" tests="402" failures="99" errors="0" time="0">
<testcase name="check1id check1text" classname="" time="0"> <testcase name="check1id check1text" classname="" time="0">
<system-out>{&#34;test_number&#34;:&#34;check1id&#34;,&#34;test_desc&#34;:&#34;check1text&#34;,&#34;audit&#34;:&#34;&#34;,&#34;AuditConfig&#34;:&#34;&#34;,&#34;type&#34;:&#34;&#34;,&#34;remediation&#34;:&#34;&#34;,&#34;test_info&#34;:null,&#34;status&#34;:&#34;PASS&#34;,&#34;actual_value&#34;:&#34;&#34;,&#34;scored&#34;:false,&#34;IsMultiple&#34;:false,&#34;expected_result&#34;:&#34;&#34;}</system-out> <system-out>{&#34;test_number&#34;:&#34;check1id&#34;,&#34;test_desc&#34;:&#34;check1text&#34;,&#34;audit&#34;:&#34;&#34;,&#34;AuditEnv&#34;:&#34;&#34;,&#34;AuditConfig&#34;:&#34;&#34;,&#34;type&#34;:&#34;&#34;,&#34;remediation&#34;:&#34;&#34;,&#34;test_info&#34;:null,&#34;status&#34;:&#34;PASS&#34;,&#34;actual_value&#34;:&#34;&#34;,&#34;scored&#34;:false,&#34;IsMultiple&#34;:false,&#34;expected_result&#34;:&#34;&#34;}</system-out>
</testcase> </testcase>
</testsuite>`), </testsuite>`),
}, { }, {
@ -299,19 +299,19 @@ func TestControls_JUnitIncludesJSON(t *testing.T) {
}, },
expect: []byte(`<testsuite name="" tests="0" failures="0" errors="0" time="0"> expect: []byte(`<testsuite name="" tests="0" failures="0" errors="0" time="0">
<testcase name="check1id check1text" classname="" time="0"> <testcase name="check1id check1text" classname="" time="0">
<system-out>{&#34;test_number&#34;:&#34;check1id&#34;,&#34;test_desc&#34;:&#34;check1text&#34;,&#34;audit&#34;:&#34;&#34;,&#34;AuditConfig&#34;:&#34;&#34;,&#34;type&#34;:&#34;&#34;,&#34;remediation&#34;:&#34;&#34;,&#34;test_info&#34;:null,&#34;status&#34;:&#34;PASS&#34;,&#34;actual_value&#34;:&#34;&#34;,&#34;scored&#34;:false,&#34;IsMultiple&#34;:false,&#34;expected_result&#34;:&#34;&#34;}</system-out> <system-out>{&#34;test_number&#34;:&#34;check1id&#34;,&#34;test_desc&#34;:&#34;check1text&#34;,&#34;audit&#34;:&#34;&#34;,&#34;AuditEnv&#34;:&#34;&#34;,&#34;AuditConfig&#34;:&#34;&#34;,&#34;type&#34;:&#34;&#34;,&#34;remediation&#34;:&#34;&#34;,&#34;test_info&#34;:null,&#34;status&#34;:&#34;PASS&#34;,&#34;actual_value&#34;:&#34;&#34;,&#34;scored&#34;:false,&#34;IsMultiple&#34;:false,&#34;expected_result&#34;:&#34;&#34;}</system-out>
</testcase> </testcase>
<testcase name="check2id check2text" classname="" time="0"> <testcase name="check2id check2text" classname="" time="0">
<skipped></skipped> <skipped></skipped>
<system-out>{&#34;test_number&#34;:&#34;check2id&#34;,&#34;test_desc&#34;:&#34;check2text&#34;,&#34;audit&#34;:&#34;&#34;,&#34;AuditConfig&#34;:&#34;&#34;,&#34;type&#34;:&#34;&#34;,&#34;remediation&#34;:&#34;&#34;,&#34;test_info&#34;:null,&#34;status&#34;:&#34;INFO&#34;,&#34;actual_value&#34;:&#34;&#34;,&#34;scored&#34;:false,&#34;IsMultiple&#34;:false,&#34;expected_result&#34;:&#34;&#34;}</system-out> <system-out>{&#34;test_number&#34;:&#34;check2id&#34;,&#34;test_desc&#34;:&#34;check2text&#34;,&#34;audit&#34;:&#34;&#34;,&#34;AuditEnv&#34;:&#34;&#34;,&#34;AuditConfig&#34;:&#34;&#34;,&#34;type&#34;:&#34;&#34;,&#34;remediation&#34;:&#34;&#34;,&#34;test_info&#34;:null,&#34;status&#34;:&#34;INFO&#34;,&#34;actual_value&#34;:&#34;&#34;,&#34;scored&#34;:false,&#34;IsMultiple&#34;:false,&#34;expected_result&#34;:&#34;&#34;}</system-out>
</testcase> </testcase>
<testcase name="check3id check3text" classname="" time="0"> <testcase name="check3id check3text" classname="" time="0">
<skipped></skipped> <skipped></skipped>
<system-out>{&#34;test_number&#34;:&#34;check3id&#34;,&#34;test_desc&#34;:&#34;check3text&#34;,&#34;audit&#34;:&#34;&#34;,&#34;AuditConfig&#34;:&#34;&#34;,&#34;type&#34;:&#34;&#34;,&#34;remediation&#34;:&#34;&#34;,&#34;test_info&#34;:null,&#34;status&#34;:&#34;WARN&#34;,&#34;actual_value&#34;:&#34;&#34;,&#34;scored&#34;:false,&#34;IsMultiple&#34;:false,&#34;expected_result&#34;:&#34;&#34;}</system-out> <system-out>{&#34;test_number&#34;:&#34;check3id&#34;,&#34;test_desc&#34;:&#34;check3text&#34;,&#34;audit&#34;:&#34;&#34;,&#34;AuditEnv&#34;:&#34;&#34;,&#34;AuditConfig&#34;:&#34;&#34;,&#34;type&#34;:&#34;&#34;,&#34;remediation&#34;:&#34;&#34;,&#34;test_info&#34;:null,&#34;status&#34;:&#34;WARN&#34;,&#34;actual_value&#34;:&#34;&#34;,&#34;scored&#34;:false,&#34;IsMultiple&#34;:false,&#34;expected_result&#34;:&#34;&#34;}</system-out>
</testcase> </testcase>
<testcase name="check4id check4text" classname="" time="0"> <testcase name="check4id check4text" classname="" time="0">
<failure type=""></failure> <failure type=""></failure>
<system-out>{&#34;test_number&#34;:&#34;check4id&#34;,&#34;test_desc&#34;:&#34;check4text&#34;,&#34;audit&#34;:&#34;&#34;,&#34;AuditConfig&#34;:&#34;&#34;,&#34;type&#34;:&#34;&#34;,&#34;remediation&#34;:&#34;&#34;,&#34;test_info&#34;:null,&#34;status&#34;:&#34;FAIL&#34;,&#34;actual_value&#34;:&#34;&#34;,&#34;scored&#34;:false,&#34;IsMultiple&#34;:false,&#34;expected_result&#34;:&#34;&#34;}</system-out> <system-out>{&#34;test_number&#34;:&#34;check4id&#34;,&#34;test_desc&#34;:&#34;check4text&#34;,&#34;audit&#34;:&#34;&#34;,&#34;AuditEnv&#34;:&#34;&#34;,&#34;AuditConfig&#34;:&#34;&#34;,&#34;type&#34;:&#34;&#34;,&#34;remediation&#34;:&#34;&#34;,&#34;test_info&#34;:null,&#34;status&#34;:&#34;FAIL&#34;,&#34;actual_value&#34;:&#34;&#34;,&#34;scored&#34;:false,&#34;IsMultiple&#34;:false,&#34;expected_result&#34;:&#34;&#34;}</system-out>
</testcase> </testcase>
</testsuite>`), </testsuite>`),
}, },

@ -327,6 +327,53 @@ groups:
op: eq op: eq
value: false value: false
set: true 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 - id: 2.1
text: "audit and audit_config commands" text: "audit and audit_config commands"
@ -557,3 +604,104 @@ groups:
path: '{.readOnlyPort}' path: '{.readOnlyPort}'
set: false set: false
scored: true 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

@ -48,17 +48,27 @@ type tests struct {
BinOp binOp `yaml:"bin_op"` BinOp binOp `yaml:"bin_op"`
} }
type AuditUsed string
const (
AuditCommand AuditUsed = "auditCommand"
AuditConfig AuditUsed = "auditConfig"
AuditEnv AuditUsed = "auditEnv"
)
type testItem struct { type testItem struct {
Flag string Flag string
Env string
Path string Path string
Output string Output string
Value string Value string
Set bool Set bool
Compare compare Compare compare
isMultipleOutput bool isMultipleOutput bool
isConfigSetting bool auditUsed AuditUsed
} }
type envTestItem testItem
type pathTestItem testItem type pathTestItem testItem
type flagTestItem testItem type flagTestItem testItem
@ -69,7 +79,7 @@ type compare struct {
type testOutput struct { type testOutput struct {
testResult bool testResult bool
flagFound bool found bool
actualResult string actualResult string
ExpectedResult string ExpectedResult string
} }
@ -78,16 +88,25 @@ func failTestItem(s string) *testOutput {
return &testOutput{testResult: false, actualResult: s} return &testOutput{testResult: false, actualResult: s}
} }
func (t testItem) flagValue() string { func (t testItem) value() string {
if t.isConfigSetting { if t.auditUsed == AuditConfig {
return t.Path return t.Path
} }
if t.auditUsed == AuditEnv {
return t.Env
}
return t.Flag return t.Flag
} }
func (t testItem) findValue(s string) (match bool, value string, err error) { 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) pt := pathTestItem(t)
return pt.findValue(s) 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) glog.V(3).Infof("In pathTestItem.findValue %s", value)
match = (value != "") match = value != ""
return match, value, err 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 { func (t testItem) execute(s string) *testOutput {
result := &testOutput{} result := &testOutput{}
s = strings.TrimRight(s, " \n") s = strings.TrimRight(s, " \n")
@ -186,16 +223,16 @@ func (t testItem) evaluate(s string) *testOutput {
if match && t.Compare.Op != "" { if match && t.Compare.Op != "" {
result.ExpectedResult, result.testResult = compareOp(t.Compare.Op, value, t.Compare.Value) result.ExpectedResult, result.testResult = compareOp(t.Compare.Op, value, t.Compare.Value)
} else { } else {
result.ExpectedResult = fmt.Sprintf("'%s' is present", t.flagValue()) result.ExpectedResult = fmt.Sprintf("'%s' is present", t.value())
result.testResult = match result.testResult = match
} }
} else { } 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.testResult = !match
} }
result.flagFound = match result.found = match
glog.V(3).Info(fmt.Sprintf("flagFound %v", result.flagFound)) glog.V(3).Info(fmt.Sprintf("found %v", result.found))
return result return result
} }

@ -51,168 +51,231 @@ func TestTestExecute(t *testing.T) {
*Check *Check
str string str string
strConfig string strConfig string
strEnv string
}{ }{
{ {
controls.Groups[0].Checks[0], controls.Groups[0].Checks[0],
"2:45 ../kubernetes/kube-apiserver --allow-privileged=false --option1=20,30,40", "2:45 ../kubernetes/kube-apiserver --allow-privileged=false --option1=20,30,40",
"", "",
"",
}, },
{ {
controls.Groups[0].Checks[1], controls.Groups[0].Checks[1],
"2:45 ../kubernetes/kube-apiserver --allow-privileged=false", "2:45 ../kubernetes/kube-apiserver --allow-privileged=false",
"", "",
"",
}, },
{ {
controls.Groups[0].Checks[2], controls.Groups[0].Checks[2],
"niinai 13617 2635 99 19:26 pts/20 00:03:08 ./kube-apiserver --insecure-port=0 --anonymous-auth", "niinai 13617 2635 99 19:26 pts/20 00:03:08 ./kube-apiserver --insecure-port=0 --anonymous-auth",
"", "",
"",
}, },
{ {
controls.Groups[0].Checks[3], controls.Groups[0].Checks[3],
"2:45 ../kubernetes/kube-apiserver --secure-port=0 --audit-log-maxage=40 --option", "2:45 ../kubernetes/kube-apiserver --secure-port=0 --audit-log-maxage=40 --option",
"", "",
"",
}, },
{ {
controls.Groups[0].Checks[4], controls.Groups[0].Checks[4],
"2:45 ../kubernetes/kube-apiserver --max-backlog=20 --secure-port=0 --audit-log-maxage=40 --option", "2:45 ../kubernetes/kube-apiserver --max-backlog=20 --secure-port=0 --audit-log-maxage=40 --option",
"", "",
"",
}, },
{ {
controls.Groups[0].Checks[5], controls.Groups[0].Checks[5],
"2:45 ../kubernetes/kube-apiserver --option --admission-control=WebHook,RBAC ---audit-log-maxage=40", "2:45 ../kubernetes/kube-apiserver --option --admission-control=WebHook,RBAC ---audit-log-maxage=40",
"", "",
"",
}, },
{ {
controls.Groups[0].Checks[6], controls.Groups[0].Checks[6],
"2:45 .. --kubelet-clientkey=foo --kubelet-client-certificate=bar --admission-control=Webhook,RBAC", "2:45 .. --kubelet-clientkey=foo --kubelet-client-certificate=bar --admission-control=Webhook,RBAC",
"", "",
"",
}, },
{ {
controls.Groups[0].Checks[7], controls.Groups[0].Checks[7],
"2:45 .. --secure-port=0 --kubelet-client-certificate=bar --admission-control=Webhook,RBAC", "2:45 .. --secure-port=0 --kubelet-client-certificate=bar --admission-control=Webhook,RBAC",
"", "",
"",
}, },
{ {
controls.Groups[0].Checks[8], controls.Groups[0].Checks[8],
"644", "644",
"", "",
"",
}, },
{ {
controls.Groups[0].Checks[9], controls.Groups[0].Checks[9],
"640", "640",
"", "",
"",
}, },
{ {
controls.Groups[0].Checks[9], controls.Groups[0].Checks[9],
"600", "600",
"", "",
"",
}, },
{ {
controls.Groups[0].Checks[10], controls.Groups[0].Checks[10],
"2:45 ../kubernetes/kube-apiserver --option --admission-control=WebHook,RBAC ---audit-log-maxage=40", "2:45 ../kubernetes/kube-apiserver --option --admission-control=WebHook,RBAC ---audit-log-maxage=40",
"", "",
"",
}, },
{ {
controls.Groups[0].Checks[11], controls.Groups[0].Checks[11],
"2:45 ../kubernetes/kube-apiserver --option --admission-control=WebHook,RBAC ---audit-log-maxage=40", "2:45 ../kubernetes/kube-apiserver --option --admission-control=WebHook,RBAC ---audit-log-maxage=40",
"", "",
"",
}, },
{ {
controls.Groups[0].Checks[12], controls.Groups[0].Checks[12],
"2:45 ../kubernetes/kube-apiserver --option --admission-control=WebHook,Something,RBAC ---audit-log-maxage=40", "2:45 ../kubernetes/kube-apiserver --option --admission-control=WebHook,Something,RBAC ---audit-log-maxage=40",
"", "",
"",
}, },
{ {
controls.Groups[0].Checks[13], controls.Groups[0].Checks[13],
"2:45 ../kubernetes/kube-apiserver --option --admission-control=Something ---audit-log-maxage=40", "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 // check for ':' as argument-value separator, with space between arg and val
controls.Groups[0].Checks[14], controls.Groups[0].Checks[14],
"2:45 kube-apiserver some-arg: some-val --admission-control=Something ---audit-log-maxage=40", "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 // check for ':' as argument-value separator, with no space between arg and val
controls.Groups[0].Checks[14], controls.Groups[0].Checks[14],
"2:45 kube-apiserver some-arg:some-val --admission-control=Something ---audit-log-maxage=40", "2:45 kube-apiserver some-arg:some-val --admission-control=Something ---audit-log-maxage=40",
"", "",
"",
}, },
{ {
controls.Groups[0].Checks[15], controls.Groups[0].Checks[15],
"", "",
"{\"readOnlyPort\": 15000}", "{\"readOnlyPort\": 15000}",
"",
}, },
{ {
controls.Groups[0].Checks[16], controls.Groups[0].Checks[16],
"", "",
"{\"stringValue\": \"WebHook,Something,RBAC\"}", "{\"stringValue\": \"WebHook,Something,RBAC\"}",
"",
}, },
{ {
controls.Groups[0].Checks[17], controls.Groups[0].Checks[17],
"", "",
"{\"trueValue\": true}", "{\"trueValue\": true}",
"",
}, },
{ {
controls.Groups[0].Checks[18], controls.Groups[0].Checks[18],
"", "",
"{\"readOnlyPort\": 15000}", "{\"readOnlyPort\": 15000}",
"",
}, },
{ {
controls.Groups[0].Checks[19], controls.Groups[0].Checks[19],
"", "",
"{\"authentication\": { \"anonymous\": {\"enabled\": false}}}", "{\"authentication\": { \"anonymous\": {\"enabled\": false}}}",
"",
}, },
{ {
controls.Groups[0].Checks[20], controls.Groups[0].Checks[20],
"", "",
"readOnlyPort: 15000", "readOnlyPort: 15000",
"",
}, },
{ {
controls.Groups[0].Checks[21], controls.Groups[0].Checks[21],
"", "",
"readOnlyPort: 15000", "readOnlyPort: 15000",
"",
}, },
{ {
controls.Groups[0].Checks[22], controls.Groups[0].Checks[22],
"", "",
"authentication:\n anonymous:\n enabled: false", "authentication:\n anonymous:\n enabled: false",
"",
}, },
{ {
controls.Groups[0].Checks[26], controls.Groups[0].Checks[26],
"", "",
"currentMasterVersion: 1.12.7", "currentMasterVersion: 1.12.7",
"",
}, },
{ {
controls.Groups[0].Checks[27], controls.Groups[0].Checks[27],
"--peer-client-cert-auth", "--peer-client-cert-auth",
"", "",
"",
}, },
{ {
controls.Groups[0].Checks[27], controls.Groups[0].Checks[27],
"--abc=true --peer-client-cert-auth --efg=false", "--abc=true --peer-client-cert-auth --efg=false",
"", "",
"",
}, },
{ {
controls.Groups[0].Checks[27], controls.Groups[0].Checks[27],
"--abc --peer-client-cert-auth --efg", "--abc --peer-client-cert-auth --efg",
"", "",
"",
}, },
{ {
controls.Groups[0].Checks[27], controls.Groups[0].Checks[27],
"--peer-client-cert-auth=true", "--peer-client-cert-auth=true",
"", "",
"",
}, },
{ {
controls.Groups[0].Checks[27], controls.Groups[0].Checks[27],
"--abc --peer-client-cert-auth=true --efg", "--abc --peer-client-cert-auth=true --efg",
"", "",
"",
}, },
{ {
controls.Groups[0].Checks[28], controls.Groups[0].Checks[28],
"--abc --peer-client-cert-auth=false --efg", "--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) { t.Run(c.Text, func(t *testing.T) {
c.Check.AuditOutput = c.str c.Check.AuditOutput = c.str
c.Check.AuditConfigOutput = c.strConfig c.Check.AuditConfigOutput = c.strConfig
c.Check.AuditEnvOutput = c.strEnv
res, err := c.Check.execute() res, err := c.Check.execute()
if err != nil { if err != nil {
t.Errorf(err.Error()) t.Errorf(err.Error())

@ -100,11 +100,11 @@ func runChecks(nodetype check.NodeType, testYamlFile string) {
// Variable substitutions. Replace all occurrences of variables in controls files. // Variable substitutions. Replace all occurrences of variables in controls files.
s := string(in) s := string(in)
s = makeSubstitutions(s, "bin", binmap) s, binSubs := makeSubstitutions(s, "bin", binmap)
s = makeSubstitutions(s, "conf", confmap) s, _ = makeSubstitutions(s, "conf", confmap)
s = makeSubstitutions(s, "svc", svcmap) s, _ = makeSubstitutions(s, "svc", svcmap)
s = makeSubstitutions(s, "kubeconfig", kubeconfmap) s, _ = makeSubstitutions(s, "kubeconfig", kubeconfmap)
s = makeSubstitutions(s, "cafile", cafilemap) s, _ = makeSubstitutions(s, "cafile", cafilemap)
controls, err := check.NewControls(nodetype, []byte(s)) controls, err := check.NewControls(nodetype, []byte(s))
if err != nil { if err != nil {
@ -117,10 +117,36 @@ func runChecks(nodetype check.NodeType, testYamlFile string) {
exitWithError(fmt.Errorf("error setting up run filter: %v", err)) exitWithError(fmt.Errorf("error setting up run filter: %v", err))
} }
generateDefaultEnvAudit(controls, binSubs)
controls.RunChecks(runner, filter, parseSkipIds(skipIds)) controls.RunChecks(runner, filter, parseSkipIds(skipIds))
controlsCollection = append(controlsCollection, controls) 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 { func parseSkipIds(skipIds string) map[string]bool {
var skipIdMap = make(map[string]bool, 0) var skipIdMap = make(map[string]bool, 0)
if skipIds != "" { if skipIds != "" {

@ -559,6 +559,41 @@ func TestExitCodeSelection(t *testing.T){
assert.Equal(t, 10, exitCodeFailure) 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) { func parseControlsJsonFile(filepath string) ([]*check.Controls, error) {
var result []*check.Controls var result []*check.Controls

@ -353,7 +353,8 @@ func getVersionFromKubeletOutput(s string) string {
return subs[1] 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 { for k, v := range m {
subst := "$" + k + ext subst := "$" + k + ext
if v == "" { if v == "" {
@ -361,10 +362,14 @@ func makeSubstitutions(s string, ext string, m map[string]string) string {
continue continue
} }
glog.V(2).Info(fmt.Sprintf("Substituting %s with '%s'\n", subst, v)) glog.V(2).Info(fmt.Sprintf("Substituting %s with '%s'\n", subst, v))
beforeS := s
s = multiWordReplace(s, subst, v) s = multiWordReplace(s, subst, v)
if beforeS != s {
substitutions = append(substitutions, v)
}
} }
return s return s, substitutions
} }
func isEmpty(str string) bool { func isEmpty(str string) bool {

@ -15,6 +15,7 @@
package cmd package cmd
import ( import (
"github.com/magiconair/properties/assert"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
@ -387,17 +388,19 @@ func TestMakeSubsitutions(t *testing.T) {
input string input string
subst map[string]string subst map[string]string
exp 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"}, exp: "Replace that", expectedSubs: []string{"that"}},
{input: "Replace $thisbin", subst: map[string]string{"this": "that", "here": "there"}, exp: "Replace 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"}, {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 { for _, c := range cases {
t.Run(c.input, func(t *testing.T) { 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 { if s != c.exp {
t.Fatalf("Got %s expected %s", s, c.exp) t.Fatalf("Got %s expected %s", s, c.exp)
} }
assert.Equal(t, c.expectedSubs, subs)
}) })
} }
} }

@ -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 the `audit` command and operations that compare these keywords against
values expected by the CIS Kubernetes Benchmark. values expected by the CIS Kubernetes Benchmark.
There are two ways to extract keywords from the output of the `audit` command, There are three ways to extract keywords from the output of the `audit` command,
`flag` and `path`. `flag`, `path`, `env`.
`flag` is used when the keyword is a command-line flag. The associated `audit` `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 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 `test_item` compares the output of the audit command and keywords using the
`set` and `compare` fields. `set` and `compare` fields.

Loading…
Cancel
Save