From 5782c2124de984ec1adb7c40d12e9eacb184bbc7 Mon Sep 17 00:00:00 2001 From: Abubakr-Sadik Nii Nai Davis Date: Wed, 8 Aug 2018 16:54:39 +0000 Subject: [PATCH] Remove kube-bench internal check code. --- check/check.go | 242 ----------------------------------------- check/controls.go | 197 --------------------------------- check/controls_test.go | 41 ------- check/data | 160 --------------------------- check/test.go | 194 --------------------------------- check/test_test.go | 121 --------------------- 6 files changed, 955 deletions(-) delete mode 100644 check/check.go delete mode 100644 check/controls.go delete mode 100644 check/controls_test.go delete mode 100644 check/data delete mode 100644 check/test.go delete mode 100644 check/test_test.go diff --git a/check/check.go b/check/check.go deleted file mode 100644 index a1b67f8..0000000 --- a/check/check.go +++ /dev/null @@ -1,242 +0,0 @@ -// Copyright © 2017 Aqua Security Software Ltd. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package check - -import ( - "bytes" - "fmt" - "io" - "os" - "os/exec" - "regexp" - "strings" - - "github.com/golang/glog" -) - -// NodeType indicates the type of node (master, node, federated). -type NodeType string - -// State is the state of a control check. -type State string - -const ( - // PASS check passed. - PASS State = "PASS" - // FAIL check failed. - FAIL = "FAIL" - // WARN could not carry out check. - WARN = "WARN" - // INFO informational message - INFO = "INFO" - - // MASTER a master node - MASTER NodeType = "master" - // NODE a node - NODE NodeType = "node" - // FEDERATED a federated deployment. - FEDERATED NodeType = "federated" -) - -func handleError(err error, context string) (errmsg string) { - if err != nil { - errmsg = fmt.Sprintf("%s, error: %s\n", context, err) - } - return -} - -// Check contains information about a recommendation in the -// CIS Kubernetes 1.6+ document. -type Check struct { - ID string `yaml:"id" json:"test_number"` - Text string `json:"test_desc"` - Audit string `json:"omit"` - Type string `json:"type"` - Commands []*exec.Cmd `json:"omit"` - Tests *tests `json:"omit"` - Set bool `json:"omit"` - Remediation string `json:"-"` - TestInfo []string `json:"test_info"` - State `json:"status"` - ActualValue string `json:"actual_value"` -} - -// Run executes the audit commands specified in a check and outputs -// the results. -func (c *Check) Run() { - // If check type is manual, force result to WARN. - if c.Type == "manual" { - c.State = WARN - return - } - - var out bytes.Buffer - var errmsgs string - - // Check if command exists or exit with WARN. - for _, cmd := range c.Commands { - if !isShellCommand(cmd.Path) { - c.State = WARN - return - } - } - - // Run commands. - n := len(c.Commands) - if n == 0 { - // Likely a warning message. - c.State = WARN - return - } - - // Each command runs, - // cmd0 out -> cmd1 in, cmd1 out -> cmd2 in ... cmdn out -> os.stdout - // cmd0 err should terminate chain - cs := c.Commands - - // Initialize command pipeline - cs[n-1].Stdout = &out - i := 1 - - var err error - errmsgs = "" - - for i < n { - cs[i-1].Stdout, err = cs[i].StdinPipe() - errmsgs += handleError( - err, - fmt.Sprintf("failed to run: %s\nfailed command: %s", - c.Audit, - cs[i].Args, - ), - ) - i++ - } - - // Start command pipeline - i = 0 - for i < n { - err := cs[i].Start() - errmsgs += handleError( - err, - fmt.Sprintf("failed to run: %s\nfailed command: %s", - c.Audit, - cs[i].Args, - ), - ) - i++ - } - - // Complete command pipeline - i = 0 - for i < n { - err := cs[i].Wait() - errmsgs += handleError( - err, - fmt.Sprintf("failed to run: %s\nfailed command:%s", - c.Audit, - cs[i].Args, - ), - ) - - if i < n-1 { - cs[i].Stdout.(io.Closer).Close() - } - - i++ - } - - finalOutput := c.Tests.execute(out.String()) - if finalOutput != nil { - c.ActualValue = finalOutput.actualResult - if finalOutput.testResult { - c.State = PASS - } else { - c.State = FAIL - } - } else { - errmsgs += handleError( - fmt.Errorf("final output is nil"), - fmt.Sprintf("failed to run: %s\n", - c.Audit, - ), - ) - } - - if errmsgs != "" { - glog.V(2).Info(errmsgs) - } -} - -// textToCommand transforms an input text representation of commands to be -// run into a slice of commands. -// TODO: Make this more robust. -func textToCommand(s string) []*exec.Cmd { - cmds := []*exec.Cmd{} - - cp := strings.Split(s, "|") - - for _, v := range cp { - v = strings.Trim(v, " ") - - // TODO: - // GOAL: To split input text into arguments for exec.Cmd. - // - // CHALLENGE: The input text may contain quoted strings that - // must be passed as a unit to exec.Cmd. - // eg. bash -c 'foo bar' - // 'foo bar' must be passed as unit to exec.Cmd if not the command - // will fail when it is executed. - // eg. exec.Cmd("bash", "-c", "foo bar") - // - // PROBLEM: Current solution assumes the grouped string will always - // be at the end of the input text. - re := regexp.MustCompile(`^(.*)(['"].*['"])$`) - grps := re.FindStringSubmatch(v) - - var cs []string - if len(grps) > 0 { - s := strings.Trim(grps[1], " ") - cs = strings.Split(s, " ") - - s1 := grps[len(grps)-1] - s1 = strings.Trim(s1, "'\"") - - cs = append(cs, s1) - } else { - cs = strings.Split(v, " ") - } - - cmd := exec.Command(cs[0], cs[1:]...) - cmds = append(cmds, cmd) - } - - return cmds -} - -func isShellCommand(s string) bool { - cmd := exec.Command("/bin/sh", "-c", "command -v "+s) - - out, err := cmd.Output() - if err != nil { - fmt.Fprintf(os.Stderr, "%s\n", err) - os.Exit(1) - } - - if strings.Contains(string(out), s) { - return true - } - return false -} diff --git a/check/controls.go b/check/controls.go deleted file mode 100644 index 640278f..0000000 --- a/check/controls.go +++ /dev/null @@ -1,197 +0,0 @@ -// Copyright © 2017 Aqua Security Software Ltd. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package check - -import ( - "encoding/json" - "fmt" - - yaml "gopkg.in/yaml.v2" -) - -// Controls holds all controls to check for master nodes. -type Controls struct { - ID string `yaml:"id" json:"id"` - Version string `json:"version"` - Text string `json:"text"` - Type NodeType `json:"node_type"` - Groups []*Group `json:"tests"` - Summary -} - -// Group is a collection of similar checks. -type Group struct { - ID string `yaml:"id" json:"section"` - Pass int `json:"pass"` - Fail int `json:"fail"` - Warn int `json:"warn"` - Text string `json:"desc"` - Checks []*Check `json:"results"` -} - -// Summary is a summary of the results of control checks run. -type Summary struct { - Pass int `json:"total_pass"` - Fail int `json:"total_fail"` - Warn int `json:"total_warn"` -} - -// NewControls instantiates a new master Controls object. -func NewControls(t NodeType, in []byte) (*Controls, error) { - c := new(Controls) - - err := yaml.Unmarshal(in, c) - if err != nil { - return nil, fmt.Errorf("failed to unmarshal YAML: %s", err) - } - - if t != c.Type { - return nil, fmt.Errorf("non-%s controls file specified", t) - } - - // Prepare audit commands - for _, group := range c.Groups { - for _, check := range group.Checks { - check.Commands = textToCommand(check.Audit) - } - } - - return c, nil -} - -// RunGroup runs all checks in a group. -func (controls *Controls) RunGroup(gids ...string) Summary { - g := []*Group{} - controls.Summary.Pass, controls.Summary.Fail, controls.Summary.Warn = 0, 0, 0 - - // If no groupid is passed run all group checks. - if len(gids) == 0 { - gids = controls.getAllGroupIDs() - } - - for _, group := range controls.Groups { - - for _, gid := range gids { - if gid == group.ID { - for _, check := range group.Checks { - check.Run() - check.TestInfo = append(check.TestInfo, check.Remediation) - summarize(controls, check) - summarizeGroup(group, check) - } - - g = append(g, group) - } - } - } - - controls.Groups = g - return controls.Summary -} - -// RunChecks runs the checks with the supplied IDs. -func (controls *Controls) RunChecks(ids ...string) Summary { - g := []*Group{} - m := make(map[string]*Group) - controls.Summary.Pass, controls.Summary.Fail, controls.Summary.Warn = 0, 0, 0 - - // If no groupid is passed run all group checks. - if len(ids) == 0 { - ids = controls.getAllCheckIDs() - } - - for _, group := range controls.Groups { - for _, check := range group.Checks { - for _, id := range ids { - if id == check.ID { - check.Run() - check.TestInfo = append(check.TestInfo, check.Remediation) - summarize(controls, check) - - // Check if we have already added this checks group. - if v, ok := m[group.ID]; !ok { - // Create a group with same info - w := &Group{ - ID: group.ID, - Text: group.Text, - Checks: []*Check{}, - } - - // Add this check to the new group - w.Checks = append(w.Checks, check) - - // Add to groups we have visited. - m[w.ID] = w - g = append(g, w) - } else { - v.Checks = append(v.Checks, check) - } - - } - } - } - } - - controls.Groups = g - return controls.Summary -} - -// JSON encodes the results of last run to JSON. -func (controls *Controls) JSON() ([]byte, error) { - return json.Marshal(controls) -} - -func (controls *Controls) getAllGroupIDs() []string { - var ids []string - - for _, group := range controls.Groups { - ids = append(ids, group.ID) - } - return ids -} - -func (controls *Controls) getAllCheckIDs() []string { - var ids []string - - for _, group := range controls.Groups { - for _, check := range group.Checks { - ids = append(ids, check.ID) - } - } - return ids - -} - -func summarize(controls *Controls, check *Check) { - switch check.State { - case PASS: - controls.Summary.Pass++ - case FAIL: - controls.Summary.Fail++ - case WARN: - controls.Summary.Warn++ - } -} - -func summarizeGroup(group *Group, check *Check) { - switch check.State { - case PASS: - group.Pass++ - case FAIL: - group.Fail++ - case WARN: - group.Warn++ - } -} diff --git a/check/controls_test.go b/check/controls_test.go deleted file mode 100644 index 3cf9b60..0000000 --- a/check/controls_test.go +++ /dev/null @@ -1,41 +0,0 @@ -package check - -import ( - "io/ioutil" - "testing" - - yaml "gopkg.in/yaml.v2" -) - -const cfgDir = "../cfg/" - -// validate that the files we're shipping are valid YAML -func TestYamlFiles(t *testing.T) { - // TODO: make this list dynamic - dirs := []string{"1.6/", "1.7/"} - - for _, dir := range dirs { - dir = cfgDir + dir - - files, err := ioutil.ReadDir(dir) - if err != nil { - t.Fatalf("error reading %s directory: %v", dir, err) - } - - for _, file := range files { - - fileName := file.Name() - in, err := ioutil.ReadFile(dir + fileName) - if err != nil { - t.Fatalf("error opening file %s: %v", fileName, err) - } - - c := new(Controls) - - err = yaml.Unmarshal(in, c) - if err != nil { - t.Fatalf("failed to load YAML from %s: %v", fileName, err) - } - } - } -} diff --git a/check/data b/check/data deleted file mode 100644 index 88bdc85..0000000 --- a/check/data +++ /dev/null @@ -1,160 +0,0 @@ ---- -controls: -id: 1 -text: "Master Checks" -type: "master" -groups: -- id: 1.1 - text: "Kube-apiserver" - checks: - - id: 0 - text: "flag is set" - tests: - test_items: - - flag: "--allow-privileged" - set: true - - - id: 1 - text: "flag is not set" - tests: - test_items: - - flag: "--basic-auth" - set: false - - - id: 2 - text: "flag value is set to some value" - tests: - test_items: - - flag: "--insecure-port" - compare: - op: eq - value: 0 - set: true - - - id: 3 - text: "flag value is greater than or equal some number" - tests: - test_items: - - flag: "--audit-log-maxage" - compare: - op: gte - value: 30 - set: true - - - id: 4 - text: "flag value is less than some number" - tests: - test_items: - - flag: "--max-backlog" - compare: - op: lt - value: 30 - set: true - - - id: 5 - text: "flag value does not have some value" - tests: - test_items: - - flag: "--admission-control" - compare: - op: nothave - value: AlwaysAdmit - set: true - - - id: 6 - text: "test AND binary operation" - tests: - bin_op: and - test_items: - - flag: "--kubelet-client-certificate" - set: true - - flag: "--kubelet-clientkey" - set: true - - - id: 7 - text: "test OR binary operation" - tests: - bin_op: or - test_items: - - flag: "--secure-port" - compare: - op: eq - value: 0 - set: true - - - flag: "--secure-port" - set: false - - - id: 8 - text: "test flag with arbitrary text" - tests: - test_items: - - flag: "644" - compare: - op: eq - value: "644" - set: true - - - id: 9 - text: "test permissions" - audit: "/bin/sh -c 'if test -e $config; then stat -c %a $config; fi'" - tests: - bin_op: or - test_items: - - flag: "644" - compare: - op: eq - value: "644" - set: true - - flag: "640" - compare: - op: eq - value: "640" - set: true - - flag: "600" - compare: - op: eq - value: "600" - set: true - - - id: 10 - text: "flag value includes some value in a comma-separated list, value is last in list" - tests: - test_items: - - flag: "--admission-control" - compare: - op: has - value: RBAC - set: true - - - id: 11 - text: "flag value includes some value in a comma-separated list, value is first in list" - tests: - test_items: - - flag: "--admission-control" - compare: - op: has - value: WebHook - set: true - - - id: 12 - text: "flag value includes some value in a comma-separated list, value middle of list" - tests: - test_items: - - flag: "--admission-control" - compare: - op: has - value: Something - set: true - - - id: 13 - text: "flag value includes some value in a comma-separated list, value only one in list" - tests: - test_items: - - flag: "--admission-control" - compare: - op: has - value: Something - set: true - - diff --git a/check/test.go b/check/test.go deleted file mode 100644 index ca762bb..0000000 --- a/check/test.go +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright © 2017 Aqua Security Software Ltd. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package check - -import ( - "fmt" - "os" - "regexp" - "strconv" - "strings" -) - -// test: -// flag: OPTION -// set: (true|false) -// compare: -// op: (eq|gt|gte|lt|lte|has) -// value: val - -type binOp string - -const ( - and binOp = "and" - or = "or" -) - -type testItem struct { - Flag string - Output string - Value string - Set bool - Compare compare -} - -type compare struct { - Op string - Value string -} - -type testOutput struct { - testResult bool - actualResult string -} - -func (t *testItem) execute(s string) *testOutput { - result := &testOutput{} - match := strings.Contains(s, t.Flag) - - if t.Set { - var flagVal string - isset := match - - if isset && t.Compare.Op != "" { - // Expects flags in the form; - // --flag=somevalue - // --flag - // somevalue - //pttn := `(` + t.Flag + `)(=)*([^\s,]*) *` - pttn := `(` + t.Flag + `)(=)*([^\s]*) *` - flagRe := regexp.MustCompile(pttn) - vals := flagRe.FindStringSubmatch(s) - - if len(vals) > 0 { - if vals[3] != "" { - flagVal = vals[3] - } else { - flagVal = vals[1] - } - } else { - fmt.Fprintf(os.Stderr, "invalid flag in testitem definition") - os.Exit(1) - } - - result.actualResult = strings.ToLower(flagVal) - switch t.Compare.Op { - case "eq": - value := strings.ToLower(flagVal) - // Do case insensitive comparaison for booleans ... - if value == "false" || value == "true" { - result.testResult = value == t.Compare.Value - } else { - result.testResult = flagVal == t.Compare.Value - } - - case "noteq": - value := strings.ToLower(flagVal) - // Do case insensitive comparaison for booleans ... - if value == "false" || value == "true" { - result.testResult = !(value == t.Compare.Value) - } else { - result.testResult = !(flagVal == t.Compare.Value) - } - - case "gt": - a, b := toNumeric(flagVal, t.Compare.Value) - result.testResult = a > b - - case "gte": - a, b := toNumeric(flagVal, t.Compare.Value) - result.testResult = a >= b - - case "lt": - a, b := toNumeric(flagVal, t.Compare.Value) - result.testResult = a < b - - case "lte": - a, b := toNumeric(flagVal, t.Compare.Value) - result.testResult = a <= b - - case "has": - result.testResult = strings.Contains(flagVal, t.Compare.Value) - - case "nothave": - result.testResult = !strings.Contains(flagVal, t.Compare.Value) - } - } else { - result.testResult = isset - } - - } else { - notset := !match - result.testResult = notset - } - return result -} - -type tests struct { - TestItems []*testItem `yaml:"test_items"` - BinOp binOp `yaml:"bin_op"` -} - -func (ts *tests) execute(s string) *testOutput { - finalOutput := &testOutput{} - - res := make([]testOutput, len(ts.TestItems)) - if len(res) == 0 { - return finalOutput - } - - for i, t := range ts.TestItems { - res[i] = *(t.execute(s)) - } - - var result bool - // If no binary operation is specified, default to AND - switch ts.BinOp { - default: - fmt.Fprintf(os.Stderr, "unknown binary operator for tests %s\n", ts.BinOp) - os.Exit(1) - case and, "": - result = true - for i := range res { - result = result && res[i].testResult - } - case or: - result = false - for i := range res { - result = result || res[i].testResult - } - } - - finalOutput.testResult = result - finalOutput.actualResult = res[0].actualResult - - return finalOutput -} - -func toNumeric(a, b string) (c, d int) { - var err error - c, err = strconv.Atoi(a) - if err != nil { - fmt.Fprintf(os.Stderr, "error converting %s: %s\n", a, err) - os.Exit(1) - } - d, err = strconv.Atoi(b) - if err != nil { - fmt.Fprintf(os.Stderr, "error converting %s: %s\n", b, err) - os.Exit(1) - } - - return c, d -} diff --git a/check/test_test.go b/check/test_test.go deleted file mode 100644 index 4b96e07..0000000 --- a/check/test_test.go +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright © 2017 Aqua Security Software Ltd. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package check - -import ( - "io/ioutil" - "os" - "strings" - "testing" -) - -var ( - in []byte - controls *Controls -) - -func init() { - var err error - in, err = ioutil.ReadFile("data") - if err != nil { - panic("Failed reading test data: " + err.Error()) - } - - // substitute variables in data file - user := os.Getenv("USER") - s := strings.Replace(string(in), "$user", user, -1) - - controls, err = NewControls(MASTER, []byte(s)) - // controls, err = NewControls(MASTER, in) - if err != nil { - panic("Failed creating test controls: " + err.Error()) - } -} - -func TestTestExecute(t *testing.T) { - - cases := []struct { - *Check - str 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", - }, - } - - for _, c := range cases { - res := c.Tests.execute(c.str).testResult - if !res { - t.Errorf("%s, expected:%v, got:%v\n", c.Text, true, res) - } - } -}