2017-08-11 16:59:57 +00:00
// 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 (
2018-06-29 11:19:34 +00:00
"io/ioutil"
2017-08-30 17:01:53 +00:00
"os"
2018-06-29 11:19:34 +00:00
"path/filepath"
2017-08-30 17:01:53 +00:00
"reflect"
2017-08-15 15:44:40 +00:00
"strconv"
2017-08-11 16:59:57 +00:00
"testing"
2017-08-30 17:01:53 +00:00
2019-11-05 15:44:57 +00:00
"github.com/aquasecurity/kube-bench/check"
2017-08-30 17:01:53 +00:00
"github.com/spf13/viper"
2017-08-11 16:59:57 +00:00
)
2017-08-15 15:44:40 +00:00
var g string
2017-08-30 17:01:53 +00:00
var e [ ] error
var eIndex int
2017-08-15 15:44:40 +00:00
func fakeps ( proc string ) string {
return g
}
2017-08-30 17:01:53 +00:00
func fakestat ( file string ) ( os . FileInfo , error ) {
err := e [ eIndex ]
eIndex ++
return nil , err
}
2017-08-15 15:44:40 +00:00
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 } ,
2017-08-31 15:01:31 +00:00
{ 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 } ,
2017-08-15 15:44:40 +00:00
}
2017-08-30 16:51:28 +00:00
psFunc = fakeps
2017-08-15 15:44:40 +00:00
for id , c := range cases {
t . Run ( strconv . Itoa ( id ) , func ( t * testing . T ) {
g = c . psOut
2017-08-30 16:51:28 +00:00
v := verifyBin ( c . proc )
2017-08-15 15:44:40 +00:00
if v != c . exp {
t . Fatalf ( "Expected %v got %v" , c . exp , v )
}
} )
}
}
2017-08-15 16:00:35 +00:00
2017-08-30 11:07:46 +00:00
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 } ,
2017-08-30 16:51:28 +00:00
{ candidates : [ ] string { "apiserver" , "kube-apiserver" } , psOut : "kube-apiserver" , exp : "kube-apiserver" } ,
{ candidates : [ ] string { "apiserver" , "kube-apiserver" , "hyperkube-apiserver" } , psOut : "kube-apiserver" , exp : "kube-apiserver" } ,
2017-08-30 11:07:46 +00:00
}
2017-08-30 16:51:28 +00:00
psFunc = fakeps
2017-08-30 11:07:46 +00:00
for id , c := range cases {
t . Run ( strconv . Itoa ( id ) , func ( t * testing . T ) {
g = c . psOut
2017-08-30 16:51:28 +00:00
e , err := findExecutable ( c . candidates )
2017-08-30 11:07:46 +00:00
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 )
}
} )
}
}
2017-08-30 17:01:53 +00:00
func TestGetBinaries ( t * testing . T ) {
cases := [ ] struct {
2019-03-07 17:02:43 +00:00
config map [ string ] interface { }
psOut string
exp map [ string ] string
expectErr bool
2017-08-30 17:01:53 +00:00
} {
{
2019-03-07 17:02:43 +00:00
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 ,
2017-08-30 17:01:53 +00:00
} ,
{
2017-08-31 14:22:30 +00:00
// "thing" is not in the list of components
2019-03-07 17:02:43 +00:00
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 ,
2017-08-31 14:22:30 +00:00
} ,
{
// "anotherthing" in list of components but doesn't have a defintion
2019-03-07 17:02:43 +00:00
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 ,
2017-08-31 14:22:30 +00:00
} ,
{
// more than one component
2019-03-07 17:02:43 +00:00
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 ,
2017-08-30 17:01:53 +00:00
} ,
2017-08-31 14:22:30 +00:00
{
// default binary to component name
2019-03-07 17:02:43 +00:00
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 ,
2017-08-31 14:22:30 +00:00
} ,
2017-08-30 17:01:53 +00:00
}
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 )
}
2019-11-05 15:44:57 +00:00
m , err := getBinaries ( v , check . MASTER )
2019-03-07 17:02:43 +00:00
if c . expectErr {
if err == nil {
t . Fatal ( "Got nil Expected error" )
}
} else if ! reflect . DeepEqual ( m , c . exp ) {
2017-08-30 17:01:53 +00:00
t . Fatalf ( "Got %v\nExpected %v" , m , c . exp )
}
} )
}
}
2017-08-15 16:00:35 +00:00
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 )
}
} )
}
}
2017-09-17 14:35:25 +00:00
2017-11-21 13:19:09 +00:00
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 )
}
2017-09-20 00:39:30 +00:00
2017-11-21 13:19:09 +00:00
ver = getVersionFromKubectlOutput ( "Something completely different" )
2019-10-14 14:52:43 +00:00
if ver != defaultKubeVersion {
t . Fatalf ( "Expected %s got %s" , defaultKubeVersion , ver )
2017-09-20 00:39:30 +00:00
}
}
2017-08-30 17:01:53 +00:00
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
} {
{
2017-08-31 14:22:30 +00:00
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" } } } ,
2017-08-30 17:01:53 +00:00
statResults : [ ] error { os . ErrNotExist , nil } ,
exp : map [ string ] string { "apiserver" : "kube-apiserver" } ,
} ,
{
2017-08-31 14:22:30 +00:00
// 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" } } } ,
2017-08-30 17:01:53 +00:00
statResults : [ ] error { os . ErrNotExist , nil , nil } ,
exp : map [ string ] string { "apiserver" : "kube-apiserver" , "thing" : "/my/file/thing" } ,
} ,
2017-08-31 14:22:30 +00:00
{
// 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" } ,
} ,
2017-08-30 17:01:53 +00:00
}
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
2019-07-13 06:48:24 +00:00
m := getFiles ( v , "config" )
2017-08-30 17:01:53 +00:00
if ! reflect . DeepEqual ( m , c . exp ) {
t . Fatalf ( "Got %v\nExpected %v" , m , c . exp )
}
} )
}
}
2017-08-30 17:37:01 +00:00
2018-10-23 02:26:38 +00:00
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
2019-07-13 06:48:24 +00:00
m := getFiles ( v , "service" )
2018-10-23 02:26:38 +00:00
if ! reflect . DeepEqual ( m , c . exp ) {
t . Fatalf ( "Got %v\nExpected %v" , m , c . exp )
}
} )
}
}
2017-08-30 17:37:01 +00:00
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 )
}
} )
2017-09-17 14:35:25 +00:00
}
}
2018-06-29 11:19:34 +00:00
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 , "1.8" )
err = os . Mkdir ( d , 0666 )
if err != nil {
t . Fatalf ( "Failed to create temp file" )
}
ioutil . WriteFile ( filepath . Join ( d , "master.yaml" ) , [ ] byte ( "hello world" ) , 0666 )
cases := [ ] struct {
specifiedVersion string
runningVersion string
succeed bool
exp string
} {
{ runningVersion : "1.8" , succeed : true , exp : d } ,
{ runningVersion : "1.9" , succeed : true , exp : d } ,
{ runningVersion : "1.10" , succeed : true , exp : d } ,
{ runningVersion : "1.1" , succeed : false } ,
{ specifiedVersion : "1.8" , succeed : true , exp : d } ,
{ specifiedVersion : "1.9" , succeed : false } ,
{ specifiedVersion : "1.10" , succeed : false } ,
}
for _ , c := range cases {
t . Run ( c . specifiedVersion + "-" + c . runningVersion , func ( t * testing . T ) {
path , err := getConfigFilePath ( c . specifiedVersion , c . runningVersion , "/master.yaml" )
if err != nil && c . succeed {
t . Fatalf ( "Error %v" , err )
}
if path != c . exp {
t . Fatalf ( "Got %s expected %s" , path , c . exp )
}
} )
}
}