1
0
mirror of https://github.com/aquasecurity/kube-bench.git synced 2024-12-24 07:28:06 +00:00

Merge branch 'master' into bugfix-no-actual-result

This commit is contained in:
Liz Rice 2019-05-24 13:18:34 +02:00 committed by GitHub
commit 31019c44da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 457 additions and 151 deletions

2
.gitignore vendored
View File

@ -4,3 +4,5 @@ vendor
dist dist
.vscode/ .vscode/
hack/kind.test.yaml hack/kind.test.yaml
.idea/

38
Gopkg.lock generated
View File

@ -1,6 +1,14 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
digest = "1:ffe9824d294da03b391f44e1ae8281281b4afc1bdaa9588c9097785e3af10cec"
name = "github.com/davecgh/go-spew"
packages = ["spew"]
pruneopts = "UT"
revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73"
version = "v1.1.1"
[[projects]] [[projects]]
digest = "1:938a2672d6ebbb7f7bc63eee3e4b9464c16ffcf77ec8913d3edbf32b4e3984dd" digest = "1:938a2672d6ebbb7f7bc63eee3e4b9464c16ffcf77ec8913d3edbf32b4e3984dd"
name = "github.com/fatih/color" name = "github.com/fatih/color"
@ -113,6 +121,14 @@
pruneopts = "UT" pruneopts = "UT"
revision = "0131db6d737cfbbfb678f8b7d92e55e27ce46224" revision = "0131db6d737cfbbfb678f8b7d92e55e27ce46224"
[[projects]]
digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe"
name = "github.com/pmezard/go-difflib"
packages = ["difflib"]
pruneopts = "UT"
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
version = "v1.0.0"
[[projects]] [[projects]]
digest = "1:1fccaaeae58b2a2f1af4dbf7eee92ff14f222e161d143bfd20082ef664f91216" digest = "1:1fccaaeae58b2a2f1af4dbf7eee92ff14f222e161d143bfd20082ef664f91216"
name = "github.com/spf13/afero" name = "github.com/spf13/afero"
@ -161,6 +177,25 @@
revision = "25b30aa063fc18e48662b86996252eabdcf2f0c7" revision = "25b30aa063fc18e48662b86996252eabdcf2f0c7"
version = "v1.0.0" version = "v1.0.0"
[[projects]]
digest = "1:ac83cf90d08b63ad5f7e020ef480d319ae890c208f8524622a2f3136e2686b02"
name = "github.com/stretchr/objx"
packages = ["."]
pruneopts = "UT"
revision = "477a77ecc69700c7cdeb1fa9e129548e1c1c393c"
version = "v0.1.1"
[[projects]]
digest = "1:0bcc464dabcfad5393daf87c3f8142911d0f6c52569b837e91a1c15e890265f3"
name = "github.com/stretchr/testify"
packages = [
"assert",
"mock",
]
pruneopts = "UT"
revision = "ffdc059bfe9ce6a4e144ba849dbedead332c6053"
version = "v1.3.0"
[[projects]] [[projects]]
digest = "1:c9c0ba9ea00233c41b91e441cfd490f34b129bbfebcb1858979623bd8de07f72" digest = "1:c9c0ba9ea00233c41b91e441cfd490f34b129bbfebcb1858979623bd8de07f72"
name = "golang.org/x/sys" name = "golang.org/x/sys"
@ -210,7 +245,10 @@
"github.com/jinzhu/gorm/dialects/postgres", "github.com/jinzhu/gorm/dialects/postgres",
"github.com/spf13/cobra", "github.com/spf13/cobra",
"github.com/spf13/viper", "github.com/spf13/viper",
"github.com/stretchr/testify/assert",
"github.com/stretchr/testify/mock",
"gopkg.in/yaml.v2", "gopkg.in/yaml.v2",
"k8s.io/client-go/util/jsonpath",
] ]
solver-name = "gps-cdcl" solver-name = "gps-cdcl"
solver-version = 1 solver-version = 1

View File

@ -18,6 +18,10 @@
name = "github.com/spf13/viper" name = "github.com/spf13/viper"
version = "1.0.0" version = "1.0.0"
[[constraint]]
name = "github.com/stretchr/testify"
version = "1.3.0"
[prune] [prune]
go-tests = true go-tests = true
unused-packages = true unused-packages = true

View File

@ -9,10 +9,6 @@ node:
- "/var/lib/kubelet/kubeconfig" - "/var/lib/kubelet/kubeconfig"
kubelet: kubelet:
bins:
- "hyperkube kubelet"
- "kubelet"
defaultconf: "/etc/kubernetes/kubelet/kubelet-config.json"
defaultsvc: "/etc/systemd/system/kubelet.service" defaultsvc: "/etc/systemd/system/kubelet.service"
defaultkubeconfig: "/var/lib/kubelet/kubeconfig" defaultkubeconfig: "/var/lib/kubelet/kubeconfig"

View File

@ -9,21 +9,25 @@
master: master:
apiserver: apiserver:
confs:
- /etc/kubernetes/manifests/kube-apiserver.yaml
- /etc/kubernetes/manifests/kube-apiserver.manifest
defaultconf: /etc/kubernetes/manifests/kube-apiserver.yaml defaultconf: /etc/kubernetes/manifests/kube-apiserver.yaml
scheduler: scheduler:
confs:
- /etc/kubernetes/manifests/kube-scheduler.yaml
- /etc/kubernetes/manifests/kube-scheduler.manifest
defaultconf: /etc/kubernetes/manifests/kube-scheduler.yaml defaultconf: /etc/kubernetes/manifests/kube-scheduler.yaml
controllermanager: controllermanager:
confs:
- /etc/kubernetes/manifests/kube-controller-manager.yaml
- /etc/kubernetes/manifests/kube-controller-manager.manifest
defaultconf: /etc/kubernetes/manifests/kube-controller-manager.yaml defaultconf: /etc/kubernetes/manifests/kube-controller-manager.yaml
etcd: etcd:
confs:
- /etc/kubernetes/manifests/etcd.yaml
- /etc/kubernetes/manifests/etcd.manifest
defaultconf: /etc/kubernetes/manifests/etcd.yaml defaultconf: /etc/kubernetes/manifests/etcd.yaml
node:
kubelet:
defaultconf: /etc/kubernetes/kubelet.conf
defaultsvc: /etc/systemd/system/kubelet.service.d/10-kubeadm.conf
proxy:
defaultconf: /etc/kubernetes/addons/kube-proxy-daemonset.yaml

View File

@ -9,21 +9,25 @@
master: master:
apiserver: apiserver:
confs:
- /etc/kubernetes/manifests/kube-apiserver.yaml
- /etc/kubernetes/manifests/kube-apiserver.manifest
defaultconf: /etc/kubernetes/manifests/kube-apiserver.yaml defaultconf: /etc/kubernetes/manifests/kube-apiserver.yaml
scheduler: scheduler:
confs:
- /etc/kubernetes/manifests/kube-scheduler.yaml
- /etc/kubernetes/manifests/kube-scheduler.manifest
defaultconf: /etc/kubernetes/manifests/kube-scheduler.yaml defaultconf: /etc/kubernetes/manifests/kube-scheduler.yaml
controllermanager: controllermanager:
confs:
- /etc/kubernetes/manifests/kube-controller-manager.yaml
- /etc/kubernetes/manifests/kube-controller-manager.manifest
defaultconf: /etc/kubernetes/manifests/kube-controller-manager.yaml defaultconf: /etc/kubernetes/manifests/kube-controller-manager.yaml
etcd: etcd:
confs:
- /etc/kubernetes/manifests/etcd.yaml
- /etc/kubernetes/manifests/etcd.manifest
defaultconf: /etc/kubernetes/manifests/etcd.yaml defaultconf: /etc/kubernetes/manifests/etcd.yaml
node:
kubelet:
defaultconf: /etc/kubernetes/kubelet.conf
defaultsvc: /etc/systemd/system/kubelet.service.d/10-kubeadm.conf
proxy:
defaultconf: /etc/kubernetes/addons/kube-proxy-daemonset.yaml

View File

@ -220,12 +220,15 @@ groups:
text: "Ensure that the admission control plugin NamespaceLifecycle is set (Scored)" text: "Ensure that the admission control plugin NamespaceLifecycle is set (Scored)"
audit: "ps -ef | grep $apiserverbin | grep -v grep" audit: "ps -ef | grep $apiserverbin | grep -v grep"
tests: tests:
bin_op: or
test_items: test_items:
- flag: "--disable-admission-plugins" - flag: "--disable-admission-plugins"
compare: compare:
op: nothave op: nothave
value: "NamespaceLifecycle" value: "NamespaceLifecycle"
set: true set: true
- flag: "--disable-admission-plugins"
set: false
remediation: | remediation: |
Edit the API server pod specification file $apiserverconf Edit the API server pod specification file $apiserverconf
on the master node and set the --disable-admission-plugins parameter to on the master node and set the --disable-admission-plugins parameter to

View File

@ -31,12 +31,3 @@ master:
- /etc/kubernetes/manifests/etcd.yaml - /etc/kubernetes/manifests/etcd.yaml
- /etc/kubernetes/manifests/etcd.manifest - /etc/kubernetes/manifests/etcd.manifest
defaultconf: /etc/kubernetes/manifests/etcd.yaml defaultconf: /etc/kubernetes/manifests/etcd.yaml
node:
kubelet:
defaultconf: /var/lib/kubelet/config.yaml
defaultsvc: /etc/systemd/system/kubelet.service.d/10-kubeadm.conf
defaultkubeconfig: /etc/kubernetes/kubelet.conf
proxy:
defaultconf: /etc/kubernetes/addons/kube-proxy-daemonset.yaml

View File

@ -81,6 +81,9 @@ node:
bins: bins:
- "hyperkube kubelet" - "hyperkube kubelet"
- "kubelet" - "kubelet"
confs:
- "/var/lib/kubelet/config.yaml"
- "/etc/kubernetes/kubelet/kubelet-config.json"
defaultconf: "/var/lib/kubelet/config.yaml" defaultconf: "/var/lib/kubelet/config.yaml"
defaultsvc: "/etc/systemd/system/kubelet.service.d/10-kubeadm.conf" defaultsvc: "/etc/systemd/system/kubelet.service.d/10-kubeadm.conf"
defaultkubeconfig: "/etc/kubernetes/kubelet.conf" defaultkubeconfig: "/etc/kubernetes/kubelet.conf"
@ -93,6 +96,7 @@ node:
confs: confs:
- /etc/kubernetes/proxy - /etc/kubernetes/proxy
- /etc/kubernetes/addons/kube-proxy-daemonset.yaml - /etc/kubernetes/addons/kube-proxy-daemonset.yaml
defaultconf: /etc/kubernetes/addons/kube-proxy-daemonset.yaml
defaultkubeconfig: "/etc/kubernetes/proxy.conf" defaultkubeconfig: "/etc/kubernetes/proxy.conf"
federated: federated:

View File

@ -36,11 +36,11 @@ const (
// PASS check passed. // PASS check passed.
PASS State = "PASS" PASS State = "PASS"
// FAIL check failed. // FAIL check failed.
FAIL = "FAIL" FAIL State = "FAIL"
// WARN could not carry out check. // WARN could not carry out check.
WARN = "WARN" WARN State = "WARN"
// INFO informational message // INFO informational message
INFO = "INFO" INFO State = "INFO"
// MASTER a master node // MASTER a master node
MASTER NodeType = "master" MASTER NodeType = "master"
@ -62,32 +62,49 @@ func handleError(err error, context string) (errmsg string) {
type Check struct { 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:"omit"` Audit string `json:"audit"`
Type string `json:"type"` Type string `json:"type"`
Commands []*exec.Cmd `json:"omit"` Commands []*exec.Cmd `json:"omit"`
Tests *tests `json:"omit"` Tests *tests `json:"omit"`
Set bool `json:"omit"` Set bool `json:"omit"`
Remediation string `json:"-"` Remediation string `json:"remediation"`
TestInfo []string `json:"test_info"` TestInfo []string `json:"test_info"`
State `json:"status"` State `json:"status"`
ActualValue string `json:"actual_value"` ActualValue string `json:"actual_value"`
Scored bool `json:"scored"` Scored bool `json:"scored"`
} }
// Runner wraps the basic Run method.
type Runner interface {
// Run runs a given check and returns the execution state.
Run(c *Check) State
}
// NewRunner constructs a default Runner.
func NewRunner() Runner {
return &defaultRunner{}
}
type defaultRunner struct{}
func (r *defaultRunner) Run(c *Check) State {
return c.run()
}
// Run executes the audit commands specified in a check and outputs // Run executes the audit commands specified in a check and outputs
// the results. // the results.
func (c *Check) Run() { func (c *Check) run() State {
// If check type is skip, force result to INFO // If check type is skip, force result to INFO
if c.Type == "skip" { if c.Type == "skip" {
c.State = INFO c.State = INFO
return return c.State
} }
// If check type is manual or the check is not scored, force result to WARN // If check type is manual or the check is not scored, force result to WARN
if c.Type == "manual" || !c.Scored { if c.Type == "manual" || !c.Scored {
c.State = WARN c.State = WARN
return return c.State
} }
var out bytes.Buffer var out bytes.Buffer
@ -97,7 +114,7 @@ func (c *Check) Run() {
for _, cmd := range c.Commands { for _, cmd := range c.Commands {
if !isShellCommand(cmd.Path) { if !isShellCommand(cmd.Path) {
c.State = WARN c.State = WARN
return return c.State
} }
} }
@ -106,7 +123,7 @@ func (c *Check) Run() {
if n == 0 { if n == 0 {
// Likely a warning message. // Likely a warning message.
c.State = WARN c.State = WARN
return return c.State
} }
// Each command runs, // Each command runs,
@ -188,6 +205,7 @@ func (c *Check) Run() {
if errmsgs != "" { if errmsgs != "" {
glog.V(2).Info(errmsgs) glog.V(2).Info(errmsgs)
} }
return c.State
} }
// textToCommand transforms an input text representation of commands to be // textToCommand transforms an input text representation of commands to be

View File

@ -1,3 +1,17 @@
// Copyright © 2017-2019 Aqua Security Software Ltd. <info@aquasec.com>
//
// 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 package check
import ( import (
@ -21,7 +35,7 @@ func TestCheck_Run(t *testing.T) {
for _, testCase := range testCases { for _, testCase := range testCases {
testCase.check.Run() testCase.check.run()
if testCase.check.State != testCase.Expected { if testCase.check.State != testCase.Expected {
t.Errorf("test failed, expected %s, actual %s\n", testCase.Expected, testCase.check.State) t.Errorf("test failed, expected %s, actual %s\n", testCase.Expected, testCase.check.State)

View File

@ -17,8 +17,8 @@ package check
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/golang/glog"
yaml "gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
// Controls holds all controls to check for master nodes. // Controls holds all controls to check for master nodes.
@ -50,6 +50,9 @@ type Summary struct {
Info int `json:"total_info"` Info int `json:"total_info"`
} }
// Predicate a predicate on the given Group and Check arguments.
type Predicate func(group *Group, check *Check) bool
// NewControls instantiates a new master Controls object. // NewControls instantiates a new master Controls object.
func NewControls(t NodeType, in []byte) (*Controls, error) { func NewControls(t NodeType, in []byte) (*Controls, error) {
c := new(Controls) c := new(Controls)
@ -73,54 +76,21 @@ func NewControls(t NodeType, in []byte) (*Controls, error) {
return c, nil return c, nil
} }
// RunGroup runs all checks in a group. // RunChecks runs the checks with the given Runner. Only checks for which the filter Predicate returns `true` will run.
func (controls *Controls) RunGroup(gids ...string) Summary { func (controls *Controls) RunChecks(runner Runner, filter Predicate) Summary {
g := []*Group{} var g []*Group
controls.Summary.Pass, controls.Summary.Fail, controls.Summary.Warn, controls.Info = 0, 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) m := make(map[string]*Group)
controls.Summary.Pass, controls.Summary.Fail, controls.Summary.Warn, controls.Info = 0, 0, 0, 0 controls.Summary.Pass, controls.Summary.Fail, controls.Summary.Warn, controls.Info = 0, 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 _, group := range controls.Groups {
for _, check := range group.Checks { for _, check := range group.Checks {
for _, id := range ids {
if id == check.ID { if !filter(group, check) {
check.Run() continue
}
state := runner.Run(check)
check.TestInfo = append(check.TestInfo, check.Remediation) check.TestInfo = append(check.TestInfo, check.Remediation)
summarize(controls, check)
// Check if we have already added this checks group. // Check if we have already added this checks group.
if v, ok := m[group.ID]; !ok { if v, ok := m[group.ID]; !ok {
@ -133,16 +103,17 @@ func (controls *Controls) RunChecks(ids ...string) Summary {
// Add this check to the new group // Add this check to the new group
w.Checks = append(w.Checks, check) w.Checks = append(w.Checks, check)
summarizeGroup(w, state)
// Add to groups we have visited. // Add to groups we have visited.
m[w.ID] = w m[w.ID] = w
g = append(g, w) g = append(g, w)
} else { } else {
v.Checks = append(v.Checks, check) v.Checks = append(v.Checks, check)
summarizeGroup(v, state)
} }
} summarize(controls, state)
}
} }
} }
@ -155,29 +126,8 @@ func (controls *Controls) JSON() ([]byte, error) {
return json.Marshal(controls) return json.Marshal(controls)
} }
func (controls *Controls) getAllGroupIDs() []string { func summarize(controls *Controls, state State) {
var ids []string switch state {
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: case PASS:
controls.Summary.Pass++ controls.Summary.Pass++
case FAIL: case FAIL:
@ -186,11 +136,13 @@ func summarize(controls *Controls, check *Check) {
controls.Summary.Warn++ controls.Summary.Warn++
case INFO: case INFO:
controls.Summary.Info++ controls.Summary.Info++
default:
glog.Warningf("Unrecognized state %s", state)
} }
} }
func summarizeGroup(group *Group, check *Check) { func summarizeGroup(group *Group, state State) {
switch check.State { switch state {
case PASS: case PASS:
group.Pass++ group.Pass++
case FAIL: case FAIL:
@ -199,5 +151,7 @@ func summarizeGroup(group *Group, check *Check) {
group.Warn++ group.Warn++
case INFO: case INFO:
group.Info++ group.Info++
default:
glog.Warningf("Unrecognized state %s", state)
} }
} }

View File

@ -1,3 +1,17 @@
// Copyright © 2017-2019 Aqua Security Software Ltd. <info@aquasec.com>
//
// 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 package check
import ( import (
@ -6,11 +20,22 @@ import (
"path/filepath" "path/filepath"
"testing" "testing"
yaml "gopkg.in/yaml.v2" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"gopkg.in/yaml.v2"
) )
const cfgDir = "../cfg/" 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 // validate that the files we're shipping are valid YAML
func TestYamlFiles(t *testing.T) { func TestYamlFiles(t *testing.T) {
err := filepath.Walk(cfgDir, func(path string, info os.FileInfo, err error) error { err := filepath.Walk(cfgDir, func(path string, info os.FileInfo, err error) error {
@ -38,3 +63,89 @@ func TestYamlFiles(t *testing.T) {
t.Fatalf("failure walking cfg dir: %v\n", err) t.Fatalf("failure walking cfg dir: %v\n", err)
} }
} }
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(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
`)
// and
controls, _ := NewControls(MASTER, in)
// 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
}
// when
controls.RunChecks(runner, runAll)
// 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)
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)
})
}
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)
}

View File

@ -19,15 +19,47 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"github.com/aquasecurity/kube-bench/check" "github.com/aquasecurity/kube-bench/check"
"github.com/golang/glog" "github.com/golang/glog"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
var ( // NewRunFilter constructs a Predicate based on FilterOpts which determines whether tested Checks should be run or not.
errmsgs string func NewRunFilter(opts FilterOpts) (check.Predicate, error) {
)
if opts.CheckList != "" && opts.GroupList != "" {
return nil, fmt.Errorf("group option and check option can't be used together")
}
var groupIDs map[string]bool
if opts.GroupList != "" {
groupIDs = cleanIDs(opts.GroupList)
}
var checkIDs map[string]bool
if opts.CheckList != "" {
checkIDs = cleanIDs(opts.CheckList)
}
return func(g *check.Group, c *check.Check) bool {
var test = true
if len(groupIDs) > 0 {
_, ok := groupIDs[g.ID]
test = test && ok
}
if len(checkIDs) > 0 {
_, ok := checkIDs[c.ID]
test = test && ok
}
test = test && (opts.Scored && c.Scored || opts.Unscored && !c.Scored)
return test
}, nil
}
func runChecks(nodetype check.NodeType) { func runChecks(nodetype check.NodeType) {
var summary check.Summary var summary check.Summary
@ -40,7 +72,7 @@ func runChecks(nodetype check.NodeType) {
glog.V(1).Info(fmt.Sprintf("Using benchmark file: %s\n", def)) glog.V(1).Info(fmt.Sprintf("Using benchmark file: %s\n", def))
// Get the set of exectuables and config files we care about on this type of node. // Get the set of executables and config files we care about on this type of node.
typeConf := viper.Sub(string(nodetype)) typeConf := viper.Sub(string(nodetype))
binmap, err := getBinaries(typeConf) binmap, err := getBinaries(typeConf)
@ -65,18 +97,14 @@ func runChecks(nodetype check.NodeType) {
exitWithError(fmt.Errorf("error setting up %s controls: %v", nodetype, err)) exitWithError(fmt.Errorf("error setting up %s controls: %v", nodetype, err))
} }
if groupList != "" && checkList == "" { runner := check.NewRunner()
ids := cleanIDs(groupList) filter, err := NewRunFilter(filterOpts)
summary = controls.RunGroup(ids...) if err != nil {
} else if checkList != "" && groupList == "" { exitWithError(fmt.Errorf("error setting up run filter: %v", err))
ids := cleanIDs(checkList)
summary = controls.RunChecks(ids...)
} else if checkList != "" && groupList != "" {
exitWithError(fmt.Errorf("group option and check option can't be used together"))
} else {
summary = controls.RunGroup()
} }
summary = controls.RunChecks(runner, filter)
// if we successfully ran some tests and it's json format, ignore the warnings // if we successfully ran some tests and it's json format, ignore the warnings
if (summary.Fail > 0 || summary.Warn > 0 || summary.Pass > 0 || summary.Info > 0) && jsonFmt { if (summary.Fail > 0 || summary.Warn > 0 || summary.Pass > 0 || summary.Info > 0) && jsonFmt {
out, err := controls.JSON() out, err := controls.JSON()
@ -115,6 +143,10 @@ func prettyPrint(r *check.Controls, summary check.Summary) {
colorPrint(check.INFO, fmt.Sprintf("%s %s\n", g.ID, g.Text)) colorPrint(check.INFO, fmt.Sprintf("%s %s\n", g.ID, g.Text))
for _, c := range g.Checks { for _, c := range g.Checks {
colorPrint(c.State, fmt.Sprintf("%s %s\n", c.ID, c.Text)) colorPrint(c.State, fmt.Sprintf("%s %s\n", c.ID, c.Text))
if includeTestOutput && c.State == check.FAIL && len(c.ActualValue) > 0 {
printRawOutput(c.ActualValue)
}
} }
} }
@ -213,3 +245,9 @@ func isMaster() bool {
} }
return true return true
} }
func printRawOutput(output string) {
for _, row := range strings.Split(output, "\n") {
fmt.Println(fmt.Sprintf("\t %s", row))
}
}

112
cmd/common_test.go Normal file
View File

@ -0,0 +1,112 @@
// Copyright © 2017-2019 Aqua Security Software Ltd. <info@aquasec.com>
//
// 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 cmd
import (
"github.com/aquasecurity/kube-bench/check"
"github.com/stretchr/testify/assert"
"testing"
)
func TestNewRunFilter(t *testing.T) {
type TestCase struct {
Name string
FilterOpts FilterOpts
Group *check.Group
Check *check.Check
Expected bool
}
testCases := []TestCase{
{
Name: "Should return true when scored flag is enabled and check is scored",
FilterOpts: FilterOpts{Scored: true, Unscored: false},
Group: &check.Group{},
Check: &check.Check{Scored: true},
Expected: true,
},
{
Name: "Should return false when scored flag is enabled and check is not scored",
FilterOpts: FilterOpts{Scored: true, Unscored: false},
Group: &check.Group{},
Check: &check.Check{Scored: false},
Expected: false,
},
{
Name: "Should return true when unscored flag is enabled and check is not scored",
FilterOpts: FilterOpts{Scored: false, Unscored: true},
Group: &check.Group{},
Check: &check.Check{Scored: false},
Expected: true,
},
{
Name: "Should return false when unscored flag is enabled and check is scored",
FilterOpts: FilterOpts{Scored: false, Unscored: true},
Group: &check.Group{},
Check: &check.Check{Scored: true},
Expected: false,
},
{
Name: "Should return true when group flag contains group's ID",
FilterOpts: FilterOpts{Scored: true, Unscored: true, GroupList: "G1,G2,G3"},
Group: &check.Group{ID: "G2"},
Check: &check.Check{},
Expected: true,
},
{
Name: "Should return false when group flag doesn't contain group's ID",
FilterOpts: FilterOpts{GroupList: "G1,G3"},
Group: &check.Group{ID: "G2"},
Check: &check.Check{},
Expected: false,
},
{
Name: "Should return true when check flag contains check's ID",
FilterOpts: FilterOpts{Scored: true, Unscored: true, CheckList: "C1,C2,C3"},
Group: &check.Group{},
Check: &check.Check{ID: "C2"},
Expected: true,
},
{
Name: "Should return false when check flag doesn't contain check's ID",
FilterOpts: FilterOpts{CheckList: "C1,C3"},
Group: &check.Group{},
Check: &check.Check{ID: "C2"},
Expected: false,
},
}
for _, testCase := range testCases {
t.Run(testCase.Name, func(t *testing.T) {
filter, _ := NewRunFilter(testCase.FilterOpts)
assert.Equal(t, testCase.Expected, filter(testCase.Group, testCase.Check))
})
}
t.Run("Should return error when both group and check flags are used", func(t *testing.T) {
// given
opts := FilterOpts{GroupList: "G1", CheckList: "C1"}
// when
_, err := NewRunFilter(opts)
// then
assert.EqualError(t, err, "group option and check option can't be used together")
})
}

View File

@ -25,6 +25,13 @@ import (
"github.com/spf13/viper" "github.com/spf13/viper"
) )
type FilterOpts struct {
CheckList string
GroupList string
Scored bool
Unscored bool
}
var ( var (
envVarsPrefix = "KUBE_BENCH" envVarsPrefix = "KUBE_BENCH"
defaultKubeVersion = "1.6" defaultKubeVersion = "1.6"
@ -33,14 +40,14 @@ var (
cfgDir string cfgDir string
jsonFmt bool jsonFmt bool
pgSQL bool pgSQL bool
checkList string
groupList string
masterFile = "master.yaml" masterFile = "master.yaml"
nodeFile = "node.yaml" nodeFile = "node.yaml"
federatedFile string federatedFile string
noResults bool noResults bool
noSummary bool noSummary bool
noRemediations bool noRemediations bool
filterOpts FilterOpts
includeTestOutput bool
) )
// RootCmd represents the base command when called without any subcommands // RootCmd represents the base command when called without any subcommands
@ -79,16 +86,19 @@ func init() {
RootCmd.PersistentFlags().BoolVar(&noRemediations, "noremediations", false, "Disable printing of remediations section") RootCmd.PersistentFlags().BoolVar(&noRemediations, "noremediations", false, "Disable printing of remediations section")
RootCmd.PersistentFlags().BoolVar(&jsonFmt, "json", false, "Prints the results as JSON") RootCmd.PersistentFlags().BoolVar(&jsonFmt, "json", false, "Prints the results as JSON")
RootCmd.PersistentFlags().BoolVar(&pgSQL, "pgsql", false, "Save the results to PostgreSQL") RootCmd.PersistentFlags().BoolVar(&pgSQL, "pgsql", false, "Save the results to PostgreSQL")
RootCmd.PersistentFlags().BoolVar(&filterOpts.Scored, "scored", true, "Run the scored CIS checks")
RootCmd.PersistentFlags().BoolVar(&filterOpts.Unscored, "unscored", true, "Run the unscored CIS checks")
RootCmd.PersistentFlags().BoolVar(&includeTestOutput, "include-test-output", false, "Prints the actual result when test fails")
RootCmd.PersistentFlags().StringVarP( RootCmd.PersistentFlags().StringVarP(
&checkList, &filterOpts.CheckList,
"check", "check",
"c", "c",
"", "",
`A comma-delimited list of checks to run as specified in CIS document. Example --check="1.1.1,1.1.2"`, `A comma-delimited list of checks to run as specified in CIS document. Example --check="1.1.1,1.1.2"`,
) )
RootCmd.PersistentFlags().StringVarP( RootCmd.PersistentFlags().StringVarP(
&groupList, &filterOpts.GroupList,
"group", "group",
"g", "g",
"", "",

View File

@ -50,15 +50,18 @@ func continueWithError(err error, msg string) string {
return "" return ""
} }
func cleanIDs(list string) []string { func cleanIDs(list string) map[string]bool {
list = strings.Trim(list, ",") list = strings.Trim(list, ",")
ids := strings.Split(list, ",") ids := strings.Split(list, ",")
set := make(map[string]bool)
for _, id := range ids { for _, id := range ids {
id = strings.Trim(id, " ") id = strings.Trim(id, " ")
set[id] = true
} }
return ids return set
} }
// ps execs out to the ps command; it's separated into a function so we can write tests // ps execs out to the ps command; it's separated into a function so we can write tests