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.