1
0
mirror of https://github.com/aquasecurity/kube-bench.git synced 2024-11-17 21:58:42 +00:00
kube-bench/cmd/util_test.go
Liz Rice f2caa1f0ec
Add run subcommand (#529)
* test: fix TestGetConfigFilePath

This test wasn't correctly creating the test file due to the wrong directory permissions on the temp file. This wasn't detected due to a lack of error checking.

Also, the code was only checking for file not exist rather than lack of permission to read file (or any other error).

The combination of these two things means the test wasn't checking what it thought it was checking, and passed more by luck than judgment.

* add getYamlFilesFromDir

* add getTestYamlFiles and test

* docs: Update master / node help text

* return path + filename from getYamlFilesFromDir

* subcommand run to run specific section files
2019-12-02 15:40:44 +00:00

512 lines
17 KiB
Go

// Copyright © 2017 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 (
"io/ioutil"
"os"
"path/filepath"
"reflect"
"strconv"
"testing"
"github.com/aquasecurity/kube-bench/check"
"github.com/spf13/viper"
)
var g string
var e []error
var eIndex int
func fakeps(proc string) string {
return g
}
func fakestat(file string) (os.FileInfo, error) {
err := e[eIndex]
eIndex++
return nil, err
}
func TestVerifyBin(t *testing.T) {
cases := []struct {
proc string
psOut string
exp bool
}{
{proc: "single", psOut: "single", exp: true},
{proc: "single", psOut: "", exp: false},
{proc: "two words", psOut: "two words", exp: true},
{proc: "two words", psOut: "", exp: false},
{proc: "cmd", psOut: "cmd param1 param2", exp: true},
{proc: "cmd param", psOut: "cmd param1 param2", exp: true},
{proc: "cmd param", psOut: "cmd", exp: false},
{proc: "cmd", psOut: "cmd x \ncmd y", exp: true},
{proc: "cmd y", psOut: "cmd x \ncmd y", exp: true},
{proc: "cmd", psOut: "/usr/bin/cmd", exp: true},
{proc: "cmd", psOut: "kube-cmd", exp: false},
{proc: "cmd", psOut: "/usr/bin/kube-cmd", exp: false},
}
psFunc = fakeps
for id, c := range cases {
t.Run(strconv.Itoa(id), func(t *testing.T) {
g = c.psOut
v := verifyBin(c.proc)
if v != c.exp {
t.Fatalf("Expected %v got %v", c.exp, v)
}
})
}
}
func TestFindExecutable(t *testing.T) {
cases := []struct {
candidates []string // list of executables we'd consider
psOut string // fake output from ps
exp string // the one we expect to find in the (fake) ps output
expErr bool
}{
{candidates: []string{"one", "two", "three"}, psOut: "two", exp: "two"},
{candidates: []string{"one", "two", "three"}, psOut: "two three", exp: "two"},
{candidates: []string{"one double", "two double", "three double"}, psOut: "two double is running", exp: "two double"},
{candidates: []string{"one", "two", "three"}, psOut: "blah", expErr: true},
{candidates: []string{"one double", "two double", "three double"}, psOut: "two", expErr: true},
{candidates: []string{"apiserver", "kube-apiserver"}, psOut: "kube-apiserver", exp: "kube-apiserver"},
{candidates: []string{"apiserver", "kube-apiserver", "hyperkube-apiserver"}, psOut: "kube-apiserver", exp: "kube-apiserver"},
}
psFunc = fakeps
for id, c := range cases {
t.Run(strconv.Itoa(id), func(t *testing.T) {
g = c.psOut
e, err := findExecutable(c.candidates)
if e != c.exp {
t.Fatalf("Expected %v got %v", c.exp, e)
}
if err == nil && c.expErr {
t.Fatalf("Expected error")
}
if err != nil && !c.expErr {
t.Fatalf("Didn't expect error: %v", err)
}
})
}
}
func TestGetBinaries(t *testing.T) {
cases := []struct {
config map[string]interface{}
psOut string
exp map[string]string
expectErr bool
}{
{
config: map[string]interface{}{"components": []string{"apiserver"}, "apiserver": map[string]interface{}{"bins": []string{"apiserver", "kube-apiserver"}}},
psOut: "kube-apiserver",
exp: map[string]string{"apiserver": "kube-apiserver"},
expectErr: false,
},
{
// "thing" is not in the list of components
config: map[string]interface{}{"components": []string{"apiserver"}, "apiserver": map[string]interface{}{"bins": []string{"apiserver", "kube-apiserver"}}, "thing": map[string]interface{}{"bins": []string{"something else", "thing"}}},
psOut: "kube-apiserver thing",
exp: map[string]string{"apiserver": "kube-apiserver"},
expectErr: false,
},
{
// "anotherthing" in list of components but doesn't have a defintion
config: map[string]interface{}{"components": []string{"apiserver", "anotherthing"}, "apiserver": map[string]interface{}{"bins": []string{"apiserver", "kube-apiserver"}}, "thing": map[string]interface{}{"bins": []string{"something else", "thing"}}},
psOut: "kube-apiserver thing",
exp: map[string]string{"apiserver": "kube-apiserver"},
expectErr: false,
},
{
// more than one component
config: map[string]interface{}{"components": []string{"apiserver", "thing"}, "apiserver": map[string]interface{}{"bins": []string{"apiserver", "kube-apiserver"}}, "thing": map[string]interface{}{"bins": []string{"something else", "thing"}}},
psOut: "kube-apiserver \nthing",
exp: map[string]string{"apiserver": "kube-apiserver", "thing": "thing"},
expectErr: false,
},
{
// default binary to component name
config: map[string]interface{}{"components": []string{"apiserver", "thing"}, "apiserver": map[string]interface{}{"bins": []string{"apiserver", "kube-apiserver"}}, "thing": map[string]interface{}{"bins": []string{"something else", "thing"}, "optional": true}},
psOut: "kube-apiserver \notherthing some params",
exp: map[string]string{"apiserver": "kube-apiserver", "thing": "thing"},
expectErr: false,
},
{
// missing mandatory component
config: map[string]interface{}{"components": []string{"apiserver", "thing"}, "apiserver": map[string]interface{}{"bins": []string{"apiserver", "kube-apiserver"}}, "thing": map[string]interface{}{"bins": []string{"something else", "thing"}, "optional": true}},
psOut: "otherthing some params",
exp: map[string]string{"apiserver": "kube-apiserver", "thing": "thing"},
expectErr: true,
},
}
v := viper.New()
psFunc = fakeps
for id, c := range cases {
t.Run(strconv.Itoa(id), func(t *testing.T) {
g = c.psOut
for k, val := range c.config {
v.Set(k, val)
}
m, err := getBinaries(v, check.MASTER)
if c.expectErr {
if err == nil {
t.Fatal("Got nil Expected error")
}
} else if !reflect.DeepEqual(m, c.exp) {
t.Fatalf("Got %v\nExpected %v", m, c.exp)
}
})
}
}
func TestMultiWordReplace(t *testing.T) {
cases := []struct {
input string
sub string
subname string
output string
}{
{input: "Here's a file with no substitutions", sub: "blah", subname: "blah", output: "Here's a file with no substitutions"},
{input: "Here's a file with a substitution", sub: "blah", subname: "substitution", output: "Here's a file with a blah"},
{input: "Here's a file with multi-word substitutions", sub: "multi word", subname: "multi-word", output: "Here's a file with 'multi word' substitutions"},
{input: "Here's a file with several several substitutions several", sub: "blah", subname: "several", output: "Here's a file with blah blah substitutions blah"},
}
for id, c := range cases {
t.Run(strconv.Itoa(id), func(t *testing.T) {
s := multiWordReplace(c.input, c.subname, c.sub)
if s != c.output {
t.Fatalf("Expected %s got %s", c.output, s)
}
})
}
}
func TestKubeVersionRegex(t *testing.T) {
ver := getVersionFromKubectlOutput(`Client Version: v1.8.0
Server Version: v1.8.12
`)
if ver != "1.8" {
t.Fatalf("Expected 1.8 got %s", ver)
}
ver = getVersionFromKubectlOutput("Something completely different")
if ver != defaultKubeVersion {
t.Fatalf("Expected %s got %s", defaultKubeVersion, ver)
}
}
func TestFindConfigFile(t *testing.T) {
cases := []struct {
input []string
statResults []error
exp string
}{
{input: []string{"myfile"}, statResults: []error{nil}, exp: "myfile"},
{input: []string{"thisfile", "thatfile"}, statResults: []error{os.ErrNotExist, nil}, exp: "thatfile"},
{input: []string{"thisfile", "thatfile"}, statResults: []error{os.ErrNotExist, os.ErrNotExist}, exp: ""},
}
statFunc = fakestat
for id, c := range cases {
t.Run(strconv.Itoa(id), func(t *testing.T) {
e = c.statResults
eIndex = 0
conf := findConfigFile(c.input)
if conf != c.exp {
t.Fatalf("Got %s expected %s", conf, c.exp)
}
})
}
}
func TestGetConfigFiles(t *testing.T) {
cases := []struct {
config map[string]interface{}
exp map[string]string
statResults []error
}{
{
config: map[string]interface{}{"components": []string{"apiserver"}, "apiserver": map[string]interface{}{"confs": []string{"apiserver", "kube-apiserver"}}},
statResults: []error{os.ErrNotExist, nil},
exp: map[string]string{"apiserver": "kube-apiserver"},
},
{
// Component "thing" isn't included in the list of components
config: map[string]interface{}{
"components": []string{"apiserver"},
"apiserver": map[string]interface{}{"confs": []string{"apiserver", "kube-apiserver"}},
"thing": map[string]interface{}{"confs": []string{"/my/file/thing"}}},
statResults: []error{os.ErrNotExist, nil},
exp: map[string]string{"apiserver": "kube-apiserver"},
},
{
// More than one component
config: map[string]interface{}{
"components": []string{"apiserver", "thing"},
"apiserver": map[string]interface{}{"confs": []string{"apiserver", "kube-apiserver"}},
"thing": map[string]interface{}{"confs": []string{"/my/file/thing"}}},
statResults: []error{os.ErrNotExist, nil, nil},
exp: map[string]string{"apiserver": "kube-apiserver", "thing": "/my/file/thing"},
},
{
// Default thing to specified default config
config: map[string]interface{}{
"components": []string{"apiserver", "thing"},
"apiserver": map[string]interface{}{"confs": []string{"apiserver", "kube-apiserver"}},
"thing": map[string]interface{}{"confs": []string{"/my/file/thing"}, "defaultconf": "another/thing"}},
statResults: []error{os.ErrNotExist, nil, os.ErrNotExist},
exp: map[string]string{"apiserver": "kube-apiserver", "thing": "another/thing"},
},
{
// Default thing to component name
config: map[string]interface{}{
"components": []string{"apiserver", "thing"},
"apiserver": map[string]interface{}{"confs": []string{"apiserver", "kube-apiserver"}},
"thing": map[string]interface{}{"confs": []string{"/my/file/thing"}}},
statResults: []error{os.ErrNotExist, nil, os.ErrNotExist},
exp: map[string]string{"apiserver": "kube-apiserver", "thing": "thing"},
},
}
v := viper.New()
statFunc = fakestat
for id, c := range cases {
t.Run(strconv.Itoa(id), func(t *testing.T) {
for k, val := range c.config {
v.Set(k, val)
}
e = c.statResults
eIndex = 0
m := getFiles(v, "config")
if !reflect.DeepEqual(m, c.exp) {
t.Fatalf("Got %v\nExpected %v", m, c.exp)
}
})
}
}
func TestGetServiceFiles(t *testing.T) {
cases := []struct {
config map[string]interface{}
exp map[string]string
statResults []error
}{
{
config: map[string]interface{}{
"components": []string{"kubelet"},
"kubelet": map[string]interface{}{"svc": []string{"kubelet", "10-kubeadm.conf"}},
},
statResults: []error{os.ErrNotExist, nil},
exp: map[string]string{"kubelet": "10-kubeadm.conf"},
},
{
// Component "thing" isn't included in the list of components
config: map[string]interface{}{
"components": []string{"kubelet"},
"kubelet": map[string]interface{}{"svc": []string{"kubelet", "10-kubeadm.conf"}},
"thing": map[string]interface{}{"svc": []string{"/my/file/thing"}},
},
statResults: []error{os.ErrNotExist, nil},
exp: map[string]string{"kubelet": "10-kubeadm.conf"},
},
{
// More than one component
config: map[string]interface{}{
"components": []string{"kubelet", "thing"},
"kubelet": map[string]interface{}{"svc": []string{"kubelet", "10-kubeadm.conf"}},
"thing": map[string]interface{}{"svc": []string{"/my/file/thing"}},
},
statResults: []error{os.ErrNotExist, nil, nil},
exp: map[string]string{"kubelet": "10-kubeadm.conf", "thing": "/my/file/thing"},
},
{
// Default thing to specified default service
config: map[string]interface{}{
"components": []string{"kubelet", "thing"},
"kubelet": map[string]interface{}{"svc": []string{"kubelet", "10-kubeadm.conf"}},
"thing": map[string]interface{}{"svc": []string{"/my/file/thing"}, "defaultsvc": "another/thing"},
},
statResults: []error{os.ErrNotExist, nil, os.ErrNotExist},
exp: map[string]string{"kubelet": "10-kubeadm.conf", "thing": "another/thing"},
},
{
// Default thing to component name
config: map[string]interface{}{
"components": []string{"kubelet", "thing"},
"kubelet": map[string]interface{}{"svc": []string{"kubelet", "10-kubeadm.conf"}},
"thing": map[string]interface{}{"svc": []string{"/my/file/thing"}},
},
statResults: []error{os.ErrNotExist, nil, os.ErrNotExist},
exp: map[string]string{"kubelet": "10-kubeadm.conf", "thing": "thing"},
},
}
v := viper.New()
statFunc = fakestat
for id, c := range cases {
t.Run(strconv.Itoa(id), func(t *testing.T) {
for k, val := range c.config {
v.Set(k, val)
}
e = c.statResults
eIndex = 0
m := getFiles(v, "service")
if !reflect.DeepEqual(m, c.exp) {
t.Fatalf("Got %v\nExpected %v", m, c.exp)
}
})
}
}
func TestMakeSubsitutions(t *testing.T) {
cases := []struct {
input string
subst map[string]string
exp 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"},
}
for _, c := range cases {
t.Run(c.input, func(t *testing.T) {
s := makeSubstitutions(c.input, "bin", c.subst)
if s != c.exp {
t.Fatalf("Got %s expected %s", s, c.exp)
}
})
}
}
func TestGetConfigFilePath(t *testing.T) {
var err error
cfgDir, err = ioutil.TempDir("", "kube-bench-test")
if err != nil {
t.Fatalf("Failed to create temp directory")
}
defer os.RemoveAll(cfgDir)
d := filepath.Join(cfgDir, "cis-1.4")
err = os.Mkdir(d, 0766)
if err != nil {
t.Fatalf("Failed to create temp dir")
}
err = ioutil.WriteFile(filepath.Join(d, "master.yaml"), []byte("hello world"), 0666)
if err != nil {
t.Logf("Failed to create temp file")
}
cases := []struct {
benchmarkVersion string
succeed bool
exp string
}{
{benchmarkVersion: "cis-1.4", succeed: true, exp: d},
{benchmarkVersion: "cis-1.5", succeed: false, exp: ""},
{benchmarkVersion: "1.1", succeed: false, exp: ""},
}
for _, c := range cases {
t.Run(c.benchmarkVersion, func(t *testing.T) {
path, err := getConfigFilePath(c.benchmarkVersion, "/master.yaml")
if c.succeed {
if err != nil {
t.Fatalf("Error %v", err)
}
if path != c.exp {
t.Fatalf("Got %s expected %s", path, c.exp)
}
} else {
if err == nil {
t.Fatalf("Expected Error, but none")
}
}
})
}
}
func TestDecrementVersion(t *testing.T) {
cases := []struct {
kubeVersion string
succeed bool
exp string
}{
{kubeVersion: "1.13", succeed: true, exp: "1.12"},
{kubeVersion: "1.15", succeed: true, exp: "1.14"},
{kubeVersion: "1.11", succeed: true, exp: "1.10"},
{kubeVersion: "1.1", succeed: true, exp: ""},
{kubeVersion: "invalid", succeed: false, exp: ""},
}
for _, c := range cases {
rv := decrementVersion(c.kubeVersion)
if c.succeed {
if c.exp != rv {
t.Fatalf("decrementVersion(%q) - Got %q expected %s", c.kubeVersion, rv, c.exp)
}
} else {
if len(rv) > 0 {
t.Fatalf("decrementVersion(%q) - Expected empty string but Got %s", c.kubeVersion, rv)
}
}
}
}
func TestGetYamlFilesFromDir(t *testing.T) {
cfgDir, err := ioutil.TempDir("", "kube-bench-test")
if err != nil {
t.Fatalf("Failed to create temp directory")
}
defer os.RemoveAll(cfgDir)
d := filepath.Join(cfgDir, "cis-1.4")
err = os.Mkdir(d, 0766)
if err != nil {
t.Fatalf("Failed to create temp dir")
}
err = ioutil.WriteFile(filepath.Join(d, "something.yaml"), []byte("hello world"), 0666)
if err != nil {
t.Fatalf("error writing file %v", err)
}
err = ioutil.WriteFile(filepath.Join(d, "config.yaml"), []byte("hello world"), 0666)
if err != nil {
t.Fatalf("error writing file %v", err)
}
files, err := getYamlFilesFromDir(d)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if len(files) != 1 {
t.Fatalf("Expected to find one file, found %d", len(files))
}
if files[0] != filepath.Join(d, "something.yaml") {
t.Fatalf("Expected to find something.yaml, found %s", files[0])
}
}