github.com/castai/kvisor@v1.7.1-0.20240516114728-b3572a2607b5/cmd/linter/kubebench/common_test.go (about)

     1  // Copyright © 2017-2019 Aqua Security Software Ltd. <info@aquasec.com>
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package kubebench
    16  
    17  import (
    18  	"encoding/json"
    19  	"errors"
    20  	"fmt"
    21  	"io/ioutil"
    22  	"os"
    23  	"path"
    24  	"path/filepath"
    25  	"testing"
    26  	"time"
    27  
    28  	check2 "github.com/castai/kvisor/cmd/linter/kubebench/check"
    29  	"github.com/spf13/viper"
    30  	"github.com/stretchr/testify/assert"
    31  )
    32  
    33  type JsonOutputFormat struct {
    34  	Controls     []*check2.Controls `json:"Controls"`
    35  	TotalSummary map[string]int     `json:"Totals"`
    36  }
    37  
    38  type JsonOutputFormatNoTotals struct {
    39  	Controls []*check2.Controls `json:"Controls"`
    40  }
    41  
    42  func TestParseSkipIds(t *testing.T) {
    43  	skipMap := parseSkipIds("4.12,4.13,5")
    44  	_, fourTwelveExists := skipMap["4.12"]
    45  	_, fourThirteenExists := skipMap["4.13"]
    46  	_, fiveExists := skipMap["5"]
    47  	_, other := skipMap["G1"]
    48  	assert.True(t, fourThirteenExists)
    49  	assert.True(t, fourTwelveExists)
    50  	assert.True(t, fiveExists)
    51  	assert.False(t, other)
    52  }
    53  
    54  func TestNewRunFilter(t *testing.T) {
    55  	type TestCase struct {
    56  		Name       string
    57  		FilterOpts FilterOpts
    58  		Group      *check2.Group
    59  		Check      *check2.Check
    60  
    61  		Expected bool
    62  	}
    63  
    64  	testCases := []TestCase{
    65  		{
    66  			Name:       "Should return true when scored flag is enabled and check is scored",
    67  			FilterOpts: FilterOpts{Scored: true, Unscored: false},
    68  			Group:      &check2.Group{},
    69  			Check:      &check2.Check{Scored: true},
    70  			Expected:   true,
    71  		},
    72  		{
    73  			Name:       "Should return false when scored flag is enabled and check is not scored",
    74  			FilterOpts: FilterOpts{Scored: true, Unscored: false},
    75  			Group:      &check2.Group{},
    76  			Check:      &check2.Check{Scored: false},
    77  			Expected:   false,
    78  		},
    79  
    80  		{
    81  			Name:       "Should return true when unscored flag is enabled and check is not scored",
    82  			FilterOpts: FilterOpts{Scored: false, Unscored: true},
    83  			Group:      &check2.Group{},
    84  			Check:      &check2.Check{Scored: false},
    85  			Expected:   true,
    86  		},
    87  		{
    88  			Name:       "Should return false when unscored flag is enabled and check is scored",
    89  			FilterOpts: FilterOpts{Scored: false, Unscored: true},
    90  			Group:      &check2.Group{},
    91  			Check:      &check2.Check{Scored: true},
    92  			Expected:   false,
    93  		},
    94  
    95  		{
    96  			Name:       "Should return true when group flag contains group's ID",
    97  			FilterOpts: FilterOpts{Scored: true, Unscored: true, GroupList: "G1,G2,G3"},
    98  			Group:      &check2.Group{ID: "G2"},
    99  			Check:      &check2.Check{},
   100  			Expected:   true,
   101  		},
   102  		{
   103  			Name:       "Should return false when group flag doesn't contain group's ID",
   104  			FilterOpts: FilterOpts{GroupList: "G1,G3"},
   105  			Group:      &check2.Group{ID: "G2"},
   106  			Check:      &check2.Check{},
   107  			Expected:   false,
   108  		},
   109  
   110  		{
   111  			Name:       "Should return true when check flag contains check's ID",
   112  			FilterOpts: FilterOpts{Scored: true, Unscored: true, CheckList: "C1,C2,C3"},
   113  			Group:      &check2.Group{},
   114  			Check:      &check2.Check{ID: "C2"},
   115  			Expected:   true,
   116  		},
   117  		{
   118  			Name:       "Should return false when check flag doesn't contain check's ID",
   119  			FilterOpts: FilterOpts{CheckList: "C1,C3"},
   120  			Group:      &check2.Group{},
   121  			Check:      &check2.Check{ID: "C2"},
   122  			Expected:   false,
   123  		},
   124  	}
   125  
   126  	for _, testCase := range testCases {
   127  		t.Run(testCase.Name, func(t *testing.T) {
   128  			filter, _ := NewRunFilter(testCase.FilterOpts)
   129  			assert.Equal(t, testCase.Expected, filter(testCase.Group, testCase.Check))
   130  		})
   131  	}
   132  
   133  	t.Run("Should return error when both group and check flags are used", func(t *testing.T) {
   134  		// given
   135  		opts := FilterOpts{GroupList: "G1", CheckList: "C1"}
   136  		// when
   137  		_, err := NewRunFilter(opts)
   138  		// then
   139  		assert.EqualError(t, err, "group option and check option can't be used together")
   140  	})
   141  }
   142  
   143  func TestIsMaster(t *testing.T) {
   144  	testCases := []struct {
   145  		name            string
   146  		cfgFile         string
   147  		getBinariesFunc func(*viper.Viper, check2.NodeType) (map[string]string, error)
   148  		isMaster        bool
   149  	}{
   150  		{
   151  			name:    "valid config, is master and all components are running",
   152  			cfgFile: "./kubebench-rules/config.yaml",
   153  			getBinariesFunc: func(viper *viper.Viper, nt check2.NodeType) (strings map[string]string, i error) {
   154  				return map[string]string{"apiserver": "kube-apiserver"}, nil
   155  			},
   156  			isMaster: true,
   157  		},
   158  		{
   159  			name:    "valid config, is master and but not all components are running",
   160  			cfgFile: "./kubebench-rules/config.yaml",
   161  			getBinariesFunc: func(viper *viper.Viper, nt check2.NodeType) (strings map[string]string, i error) {
   162  				return map[string]string{}, nil
   163  			},
   164  			isMaster: false,
   165  		},
   166  		{
   167  			name:    "valid config, is master, not all components are running and fails to find all binaries",
   168  			cfgFile: "./kubebench-rules/config.yaml",
   169  			getBinariesFunc: func(viper *viper.Viper, nt check2.NodeType) (strings map[string]string, i error) {
   170  				return map[string]string{}, errors.New("failed to find binaries")
   171  			},
   172  			isMaster: false,
   173  		},
   174  	}
   175  	cfgDirOld := cfgDir
   176  	cfgDir = "../cfg"
   177  	defer func() {
   178  		cfgDir = cfgDirOld
   179  	}()
   180  
   181  	execCode := `#!/bin/sh
   182  	echo "Server Version: v1.13.10"
   183  	`
   184  	restore, err := fakeExecutableInPath("kubectl", execCode)
   185  	if err != nil {
   186  		t.Fatal("Failed when calling fakeExecutableInPath ", err)
   187  	}
   188  	defer restore()
   189  
   190  	for _, tc := range testCases {
   191  		func() {
   192  			cfgFile = tc.cfgFile
   193  			initConfig()
   194  
   195  			oldGetBinariesFunc := getBinariesFunc
   196  			getBinariesFunc = tc.getBinariesFunc
   197  			defer func() {
   198  				getBinariesFunc = oldGetBinariesFunc
   199  				cfgFile = ""
   200  			}()
   201  
   202  			assert.Equal(t, tc.isMaster, isMaster(), tc.name)
   203  		}()
   204  	}
   205  }
   206  
   207  func TestMapToCISVersion(t *testing.T) {
   208  	viperWithData, err := loadConfigForTest()
   209  	if err != nil {
   210  		t.Fatalf("Unable to load config file %v", err)
   211  	}
   212  	kubeToBenchmarkMap, err := loadVersionMapping(viperWithData)
   213  	if err != nil {
   214  		t.Fatalf("Unable to load config file %v", err)
   215  	}
   216  
   217  	cases := []struct {
   218  		kubeVersion string
   219  		succeed     bool
   220  		exp         string
   221  		expErr      string
   222  	}{
   223  		{kubeVersion: "1.9", succeed: false, exp: "", expErr: "unable to find a matching Benchmark Version match for kubernetes version: 1.9"},
   224  		{kubeVersion: "1.11", succeed: false, exp: "", expErr: "unable to find a matching Benchmark Version match for kubernetes version: 1.11"},
   225  		{kubeVersion: "1.12", succeed: false, exp: "", expErr: "unable to find a matching Benchmark Version match for kubernetes version: 1.12"},
   226  		{kubeVersion: "1.13", succeed: false, exp: "", expErr: "unable to find a matching Benchmark Version match for kubernetes version: 1.13"},
   227  		{kubeVersion: "1.14", succeed: false, exp: "", expErr: "unable to find a matching Benchmark Version match for kubernetes version: 1.14"},
   228  		{kubeVersion: "1.15", succeed: true, exp: "cis-1.5"},
   229  		{kubeVersion: "1.16", succeed: true, exp: "cis-1.6"},
   230  		{kubeVersion: "1.17", succeed: true, exp: "cis-1.6"},
   231  		{kubeVersion: "1.18", succeed: true, exp: "cis-1.6"},
   232  		{kubeVersion: "1.19", succeed: true, exp: "cis-1.20"},
   233  		{kubeVersion: "1.20", succeed: true, exp: "cis-1.20"},
   234  		{kubeVersion: "1.21", succeed: true, exp: "cis-1.20"},
   235  		{kubeVersion: "1.22", succeed: true, exp: "cis-1.23"},
   236  		{kubeVersion: "1.23", succeed: true, exp: "cis-1.23"},
   237  		{kubeVersion: "1.24", succeed: true, exp: "cis-1.24"},
   238  		{kubeVersion: "1.25", succeed: true, exp: "cis-1.7"},
   239  		{kubeVersion: "gke-1.2.0", succeed: true, exp: "gke-1.2.0"},
   240  		{kubeVersion: "ocp-3.10", succeed: true, exp: "rh-0.7"},
   241  		{kubeVersion: "ocp-3.11", succeed: true, exp: "rh-0.7"},
   242  		{kubeVersion: "unknown", succeed: false, exp: "", expErr: "unable to find a matching Benchmark Version match for kubernetes version: unknown"},
   243  	}
   244  	for _, c := range cases {
   245  		rv, err := mapToBenchmarkVersion(kubeToBenchmarkMap, c.kubeVersion)
   246  		if c.succeed {
   247  			if err != nil {
   248  				t.Errorf("[%q]-Unexpected error: %v", c.kubeVersion, err)
   249  			}
   250  
   251  			if len(rv) == 0 {
   252  				t.Errorf("[%q]-missing return value", c.kubeVersion)
   253  			}
   254  
   255  			if c.exp != rv {
   256  				t.Errorf("[%q]- expected %q but Got %q", c.kubeVersion, c.exp, rv)
   257  			}
   258  
   259  		} else {
   260  			if c.exp != rv {
   261  				t.Errorf("[%q]-mapToBenchmarkVersion kubeversion: %q Got %q expected %s", c.kubeVersion, c.kubeVersion, rv, c.exp)
   262  			}
   263  
   264  			if c.expErr != err.Error() {
   265  				t.Errorf("[%q]-mapToBenchmarkVersion expected Error: %q instead Got %q", c.kubeVersion, c.expErr, err.Error())
   266  			}
   267  		}
   268  	}
   269  }
   270  
   271  func TestLoadVersionMapping(t *testing.T) {
   272  	setDefault := func(v *viper.Viper, key string, value interface{}) *viper.Viper {
   273  		v.SetDefault(key, value)
   274  		return v
   275  	}
   276  
   277  	viperWithData, err := loadConfigForTest()
   278  	if err != nil {
   279  		t.Fatalf("Unable to load config file %v", err)
   280  	}
   281  
   282  	cases := []struct {
   283  		n       string
   284  		v       *viper.Viper
   285  		succeed bool
   286  	}{
   287  		{n: "empty", v: viper.New(), succeed: false},
   288  		{
   289  			n:       "novals",
   290  			v:       setDefault(viper.New(), "version_mapping", "novals"),
   291  			succeed: false,
   292  		},
   293  		{
   294  			n:       "good",
   295  			v:       viperWithData,
   296  			succeed: true,
   297  		},
   298  	}
   299  	for _, c := range cases {
   300  		rv, err := loadVersionMapping(c.v)
   301  		if c.succeed {
   302  			if err != nil {
   303  				t.Errorf("[%q]-Unexpected error: %v", c.n, err)
   304  			}
   305  
   306  			if len(rv) == 0 {
   307  				t.Errorf("[%q]-missing mapping value", c.n)
   308  			}
   309  		} else {
   310  			if err == nil {
   311  				t.Errorf("[%q]-Expected error but got none", c.n)
   312  			}
   313  		}
   314  	}
   315  }
   316  
   317  func TestGetBenchmarkVersion(t *testing.T) {
   318  	viperWithData, err := loadConfigForTest()
   319  	if err != nil {
   320  		t.Fatalf("Unable to load config file %v", err)
   321  	}
   322  
   323  	type getBenchmarkVersionFnToTest func(kubeVersion, benchmarkVersion string, platform Platform, v *viper.Viper) (string, error)
   324  
   325  	withFakeKubectl := func(kubeVersion, benchmarkVersion string, platform Platform, v *viper.Viper, fn getBenchmarkVersionFnToTest) (string, error) {
   326  		execCode := `#!/bin/sh
   327  		echo '{"serverVersion": {"major": "1", "minor": "18", "gitVersion": "v1.18.10"}}'
   328  		`
   329  		restore, err := fakeExecutableInPath("kubectl", execCode)
   330  		if err != nil {
   331  			t.Fatal("Failed when calling fakeExecutableInPath ", err)
   332  		}
   333  		defer restore()
   334  
   335  		return fn(kubeVersion, benchmarkVersion, platform, v)
   336  	}
   337  
   338  	withNoPath := func(kubeVersion, benchmarkVersion string, platform Platform, v *viper.Viper, fn getBenchmarkVersionFnToTest) (string, error) {
   339  		restore, err := prunePath()
   340  		if err != nil {
   341  			t.Fatal("Failed when calling prunePath ", err)
   342  		}
   343  		defer restore()
   344  
   345  		return fn(kubeVersion, benchmarkVersion, platform, v)
   346  	}
   347  
   348  	type getBenchmarkVersionFn func(string, string, Platform, *viper.Viper, getBenchmarkVersionFnToTest) (string, error)
   349  	cases := []struct {
   350  		n                string
   351  		kubeVersion      string
   352  		benchmarkVersion string
   353  		platform         Platform
   354  		v                *viper.Viper
   355  		callFn           getBenchmarkVersionFn
   356  		exp              string
   357  		succeed          bool
   358  	}{
   359  		{n: "both versions", kubeVersion: "1.11", benchmarkVersion: "cis-1.3", platform: Platform{}, exp: "cis-1.3", callFn: withNoPath, v: viper.New(), succeed: false},
   360  		{n: "no version-missing-kubectl", kubeVersion: "", benchmarkVersion: "", platform: Platform{}, v: viperWithData, exp: "cis-1.6", callFn: withNoPath, succeed: true},
   361  		{n: "no version-fakeKubectl", kubeVersion: "", benchmarkVersion: "", platform: Platform{}, v: viperWithData, exp: "cis-1.6", callFn: withFakeKubectl, succeed: true},
   362  		{n: "kubeVersion", kubeVersion: "1.15", benchmarkVersion: "", platform: Platform{}, v: viperWithData, exp: "cis-1.5", callFn: withNoPath, succeed: true},
   363  		{n: "ocpVersion310", kubeVersion: "ocp-3.10", benchmarkVersion: "", platform: Platform{}, v: viperWithData, exp: "rh-0.7", callFn: withNoPath, succeed: true},
   364  		{n: "ocpVersion311", kubeVersion: "ocp-3.11", benchmarkVersion: "", platform: Platform{}, v: viperWithData, exp: "rh-0.7", callFn: withNoPath, succeed: true},
   365  		{n: "gke12", kubeVersion: "gke-1.2.0", benchmarkVersion: "", platform: Platform{}, v: viperWithData, exp: "gke-1.2.0", callFn: withNoPath, succeed: true},
   366  	}
   367  	for _, c := range cases {
   368  		rv, err := c.callFn(c.kubeVersion, c.benchmarkVersion, c.platform, c.v, getBenchmarkVersion)
   369  		if c.succeed {
   370  			if err != nil {
   371  				t.Errorf("[%q]-Unexpected error: %v", c.n, err)
   372  			}
   373  
   374  			if len(rv) == 0 {
   375  				t.Errorf("[%q]-missing return value", c.n)
   376  			}
   377  
   378  			if c.exp != rv {
   379  				t.Errorf("[%q]- expected %q but Got %q", c.n, c.exp, rv)
   380  			}
   381  		} else {
   382  			if err == nil {
   383  				t.Errorf("[%q]-Expected error but got none", c.n)
   384  			}
   385  		}
   386  	}
   387  }
   388  
   389  func TestValidTargets(t *testing.T) {
   390  	viperWithData, err := loadConfigForTest()
   391  	if err != nil {
   392  		t.Fatalf("Unable to load config file %v", err)
   393  	}
   394  	cases := []struct {
   395  		name      string
   396  		benchmark string
   397  		targets   []string
   398  		expected  bool
   399  	}{
   400  		{
   401  			name:      "cis-1.5 no dummy",
   402  			benchmark: "cis-1.5",
   403  			targets:   []string{"master", "node", "controlplane", "etcd", "dummy"},
   404  			expected:  false,
   405  		},
   406  		{
   407  			name:      "cis-1.5 valid",
   408  			benchmark: "cis-1.5",
   409  			targets:   []string{"master", "node", "controlplane", "etcd", "policies"},
   410  			expected:  true,
   411  		},
   412  		{
   413  			name:      "cis-1.6 no Pikachu",
   414  			benchmark: "cis-1.6",
   415  			targets:   []string{"master", "node", "controlplane", "etcd", "Pikachu"},
   416  			expected:  false,
   417  		},
   418  		{
   419  			name:      "cis-1.6 valid",
   420  			benchmark: "cis-1.6",
   421  			targets:   []string{"master", "node", "controlplane", "etcd", "policies"},
   422  			expected:  true,
   423  		},
   424  		{
   425  			name:      "gke-1.2.0 valid",
   426  			benchmark: "gke-1.2.0",
   427  			targets:   []string{"master", "node", "controlplane", "policies", "managedservices"},
   428  			expected:  true,
   429  		},
   430  		{
   431  			name:      "aks-1.0 valid",
   432  			benchmark: "aks-1.0",
   433  			targets:   []string{"node", "policies", "controlplane", "managedservices"},
   434  			expected:  true,
   435  		},
   436  		{
   437  			name:      "eks-1.0.1 valid",
   438  			benchmark: "eks-1.0.1",
   439  			targets:   []string{"node", "policies", "controlplane", "managedservices"},
   440  			expected:  true,
   441  		},
   442  		{
   443  			name:      "eks-1.1.0 valid",
   444  			benchmark: "eks-1.1.0",
   445  			targets:   []string{"node", "policies", "controlplane", "managedservices"},
   446  			expected:  true,
   447  		},
   448  		{
   449  			name:      "eks-1.2.0 valid",
   450  			benchmark: "eks-1.2.0",
   451  			targets:   []string{"node", "policies", "controlplane", "managedservices"},
   452  			expected:  true,
   453  		},
   454  	}
   455  
   456  	for _, c := range cases {
   457  		t.Run(c.name, func(t *testing.T) {
   458  			ret, err := validTargets(c.benchmark, c.targets, viperWithData)
   459  			if err != nil {
   460  				t.Fatalf("Expected nil error, got: %v", err)
   461  			}
   462  			if ret != c.expected {
   463  				t.Fatalf("Expected %t, got %t", c.expected, ret)
   464  			}
   465  		})
   466  	}
   467  }
   468  
   469  func TestIsEtcd(t *testing.T) {
   470  	testCases := []struct {
   471  		name            string
   472  		cfgFile         string
   473  		getBinariesFunc func(*viper.Viper, check2.NodeType) (map[string]string, error)
   474  		isEtcd          bool
   475  	}{
   476  		{
   477  			name:    "valid config, is etcd and all components are running",
   478  			cfgFile: "./kubebench-rules/config.yaml",
   479  			getBinariesFunc: func(viper *viper.Viper, nt check2.NodeType) (strings map[string]string, i error) {
   480  				return map[string]string{"etcd": "etcd"}, nil
   481  			},
   482  			isEtcd: true,
   483  		},
   484  		{
   485  			name:    "valid config, is etcd and but not all components are running",
   486  			cfgFile: "./kubebench-rules/config.yaml",
   487  			getBinariesFunc: func(viper *viper.Viper, nt check2.NodeType) (strings map[string]string, i error) {
   488  				return map[string]string{}, nil
   489  			},
   490  			isEtcd: false,
   491  		},
   492  		{
   493  			name:    "valid config, is etcd, not all components are running and fails to find all binaries",
   494  			cfgFile: "./kubebench-rules/config.yaml",
   495  			getBinariesFunc: func(viper *viper.Viper, nt check2.NodeType) (strings map[string]string, i error) {
   496  				return map[string]string{}, errors.New("failed to find binaries")
   497  			},
   498  			isEtcd: false,
   499  		},
   500  	}
   501  	cfgDirOld := cfgDir
   502  	cfgDir = "../cfg"
   503  	defer func() {
   504  		cfgDir = cfgDirOld
   505  	}()
   506  
   507  	execCode := `#!/bin/sh
   508  	echo "Server Version: v1.15.03"
   509  	`
   510  	restore, err := fakeExecutableInPath("kubectl", execCode)
   511  	if err != nil {
   512  		t.Fatal("Failed when calling fakeExecutableInPath ", err)
   513  	}
   514  	defer restore()
   515  
   516  	for _, tc := range testCases {
   517  		func() {
   518  			cfgFile = tc.cfgFile
   519  			initConfig()
   520  
   521  			oldGetBinariesFunc := getBinariesFunc
   522  			getBinariesFunc = tc.getBinariesFunc
   523  			defer func() {
   524  				getBinariesFunc = oldGetBinariesFunc
   525  				cfgFile = ""
   526  			}()
   527  
   528  			assert.Equal(t, tc.isEtcd, isEtcd(), tc.name)
   529  		}()
   530  	}
   531  }
   532  
   533  func TestWriteResultToJsonFile(t *testing.T) {
   534  	defer func() {
   535  		controlsCollection = []*check2.Controls{}
   536  		jsonFmt = false
   537  		outputFile = ""
   538  	}()
   539  	var err error
   540  	jsonFmt = true
   541  	outputFile = path.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().UnixNano()))
   542  
   543  	controlsCollection, err = parseControlsJsonFile("./testdata/controlsCollection.json")
   544  	if err != nil {
   545  		t.Error(err)
   546  	}
   547  	writeOutput(controlsCollection)
   548  
   549  	var expect JsonOutputFormat
   550  	var result JsonOutputFormat
   551  	result, err = parseResultJsonFile(outputFile)
   552  	if err != nil {
   553  		t.Error(err)
   554  	}
   555  	expect, err = parseResultJsonFile("./testdata/result.json")
   556  	if err != nil {
   557  		t.Error(err)
   558  	}
   559  
   560  	assert.Equal(t, expect, result)
   561  }
   562  
   563  func TestWriteResultNoTotalsToJsonFile(t *testing.T) {
   564  	defer func() {
   565  		controlsCollection = []*check2.Controls{}
   566  		jsonFmt = false
   567  		outputFile = ""
   568  	}()
   569  	var err error
   570  	jsonFmt = true
   571  	outputFile = path.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().UnixNano()))
   572  
   573  	noTotals = true
   574  
   575  	controlsCollection, err = parseControlsJsonFile("./testdata/controlsCollection.json")
   576  	if err != nil {
   577  		t.Error(err)
   578  	}
   579  	writeOutput(controlsCollection)
   580  
   581  	var expect []*check2.Controls
   582  	var result []*check2.Controls
   583  	result, err = parseResultNoTotalsJsonFile(outputFile)
   584  	if err != nil {
   585  		t.Error(err)
   586  	}
   587  	expect, err = parseResultNoTotalsJsonFile("./testdata/result_no_totals.json")
   588  	if err != nil {
   589  		t.Error(err)
   590  	}
   591  
   592  	assert.Equal(t, expect, result)
   593  }
   594  
   595  func TestExitCodeSelection(t *testing.T) {
   596  	exitCode = 10
   597  	controlsCollectionAllPassed, errPassed := parseControlsJsonFile("./testdata/passedControlsCollection.json")
   598  	if errPassed != nil {
   599  		t.Error(errPassed)
   600  	}
   601  	controlsCollectionWithFailures, errFailure := parseControlsJsonFile("./testdata/controlsCollection.json")
   602  	if errFailure != nil {
   603  		t.Error(errFailure)
   604  	}
   605  
   606  	exitCodePassed := exitCodeSelection(controlsCollectionAllPassed)
   607  	assert.Equal(t, 0, exitCodePassed)
   608  
   609  	exitCodeFailure := exitCodeSelection(controlsCollectionWithFailures)
   610  	assert.Equal(t, 10, exitCodeFailure)
   611  }
   612  
   613  func TestGenerationDefaultEnvAudit(t *testing.T) {
   614  	input := []byte(`
   615  ---
   616  type: "master"
   617  groups:
   618  - id: G1
   619    checks:
   620    - id: G1/C1
   621  - id: G2
   622    checks:
   623    - id: G2/C1
   624      text: "Verify that the SomeSampleFlag argument is set to true"
   625      audit: "grep -B1 SomeSampleFlag=true /this/is/a/file/path"
   626      tests:
   627        test_items:
   628        - flag: "SomeSampleFlag=true"
   629          env: "SOME_SAMPLE_FLAG"
   630          compare:
   631            op: has
   632            value: "true"
   633          set: true
   634      remediation: |
   635        Edit the config file /this/is/a/file/path and set SomeSampleFlag to true.
   636      scored: true
   637  `)
   638  	controls, err := check2.NewControls(check2.MASTER, input, "")
   639  	assert.NoError(t, err)
   640  
   641  	binSubs := []string{"TestBinPath"}
   642  	generateDefaultEnvAudit(controls, binSubs)
   643  
   644  	expectedAuditEnv := fmt.Sprintf("cat \"/proc/$(/bin/ps -C %s -o pid= | tr -d ' ')/environ\" | tr '\\0' '\\n'", binSubs[0])
   645  	assert.Equal(t, expectedAuditEnv, controls.Groups[1].Checks[0].AuditEnv)
   646  }
   647  
   648  func TestGetSummaryTotals(t *testing.T) {
   649  	controlsCollection, err := parseControlsJsonFile("./testdata/controlsCollection.json")
   650  	if err != nil {
   651  		t.Error(err)
   652  	}
   653  
   654  	resultTotals := getSummaryTotals(controlsCollection)
   655  	assert.Equal(t, 12, resultTotals.Fail)
   656  	assert.Equal(t, 14, resultTotals.Warn)
   657  	assert.Equal(t, 0, resultTotals.Info)
   658  	assert.Equal(t, 49, resultTotals.Pass)
   659  }
   660  
   661  func TestPrintSummary(t *testing.T) {
   662  	controlsCollection, err := parseControlsJsonFile("./testdata/controlsCollection.json")
   663  	if err != nil {
   664  		t.Error(err)
   665  	}
   666  
   667  	resultTotals := getSummaryTotals(controlsCollection)
   668  	rescueStdout := os.Stdout
   669  	r, w, _ := os.Pipe()
   670  	os.Stdout = w
   671  	printSummary(resultTotals, "totals")
   672  	w.Close()
   673  	out, _ := ioutil.ReadAll(r)
   674  	os.Stdout = rescueStdout
   675  
   676  	assert.Contains(t, string(out), "49 checks PASS\n12 checks FAIL\n14 checks WARN\n0 checks INFO\n\n")
   677  }
   678  
   679  func TestPrettyPrintNoSummary(t *testing.T) {
   680  	controlsCollection, err := parseControlsJsonFile("./testdata/controlsCollection.json")
   681  	if err != nil {
   682  		t.Error(err)
   683  	}
   684  
   685  	resultTotals := getSummaryTotals(controlsCollection)
   686  	rescueStdout := os.Stdout
   687  	r, w, _ := os.Pipe()
   688  	os.Stdout = w
   689  	noSummary = true
   690  	prettyPrint(controlsCollection[0], resultTotals)
   691  	w.Close()
   692  	out, _ := ioutil.ReadAll(r)
   693  	os.Stdout = rescueStdout
   694  
   695  	assert.NotContains(t, string(out), "49 checks PASS")
   696  }
   697  
   698  func TestPrettyPrintSummary(t *testing.T) {
   699  	controlsCollection, err := parseControlsJsonFile("./testdata/controlsCollection.json")
   700  	if err != nil {
   701  		t.Error(err)
   702  	}
   703  
   704  	resultTotals := getSummaryTotals(controlsCollection)
   705  	rescueStdout := os.Stdout
   706  	r, w, _ := os.Pipe()
   707  	os.Stdout = w
   708  	noSummary = false
   709  	prettyPrint(controlsCollection[0], resultTotals)
   710  	w.Close()
   711  	out, _ := ioutil.ReadAll(r)
   712  	os.Stdout = rescueStdout
   713  
   714  	assert.Contains(t, string(out), "49 checks PASS")
   715  }
   716  
   717  func TestWriteStdoutOutputNoTotal(t *testing.T) {
   718  	controlsCollection, err := parseControlsJsonFile("./testdata/controlsCollection.json")
   719  	if err != nil {
   720  		t.Error(err)
   721  	}
   722  
   723  	rescueStdout := os.Stdout
   724  	r, w, _ := os.Pipe()
   725  	os.Stdout = w
   726  	noTotals = true
   727  	writeStdoutOutput(controlsCollection)
   728  	w.Close()
   729  	out, _ := ioutil.ReadAll(r)
   730  	os.Stdout = rescueStdout
   731  
   732  	assert.NotContains(t, string(out), "49 checks PASS")
   733  }
   734  
   735  func TestWriteStdoutOutputTotal(t *testing.T) {
   736  	controlsCollection, err := parseControlsJsonFile("./testdata/controlsCollection.json")
   737  	if err != nil {
   738  		t.Error(err)
   739  	}
   740  
   741  	rescueStdout := os.Stdout
   742  
   743  	r, w, _ := os.Pipe()
   744  
   745  	os.Stdout = w
   746  	noTotals = false
   747  	writeStdoutOutput(controlsCollection)
   748  	w.Close()
   749  	out, _ := ioutil.ReadAll(r)
   750  
   751  	os.Stdout = rescueStdout
   752  
   753  	assert.Contains(t, string(out), "49 checks PASS")
   754  }
   755  
   756  func parseControlsJsonFile(filepath string) ([]*check2.Controls, error) {
   757  	var result []*check2.Controls
   758  
   759  	d, err := ioutil.ReadFile(filepath)
   760  	if err != nil {
   761  		return nil, err
   762  	}
   763  	err = json.Unmarshal(d, &result)
   764  	if err != nil {
   765  		return nil, err
   766  	}
   767  
   768  	return result, nil
   769  }
   770  
   771  func parseResultJsonFile(filepath string) (JsonOutputFormat, error) {
   772  	var result JsonOutputFormat
   773  
   774  	d, err := ioutil.ReadFile(filepath)
   775  	if err != nil {
   776  		return result, err
   777  	}
   778  	err = json.Unmarshal(d, &result)
   779  	if err != nil {
   780  		return result, err
   781  	}
   782  
   783  	return result, nil
   784  }
   785  
   786  func parseResultNoTotalsJsonFile(filepath string) ([]*check2.Controls, error) {
   787  	var result []*check2.Controls
   788  
   789  	d, err := ioutil.ReadFile(filepath)
   790  	if err != nil {
   791  		return nil, err
   792  	}
   793  	err = json.Unmarshal(d, &result)
   794  	if err != nil {
   795  		return nil, err
   796  	}
   797  
   798  	return result, nil
   799  }
   800  
   801  func loadConfigForTest() (*viper.Viper, error) {
   802  	viperWithData := viper.New()
   803  	viperWithData.SetConfigFile("./kubebench-rules/config.yaml")
   804  	if err := viperWithData.ReadInConfig(); err != nil {
   805  		return nil, err
   806  	}
   807  	return viperWithData, nil
   808  }
   809  
   810  type restoreFn func()
   811  
   812  func fakeExecutableInPath(execFile, execCode string) (restoreFn, error) {
   813  	pathenv := os.Getenv("PATH")
   814  	tmp, err := ioutil.TempDir("", "TestfakeExecutableInPath")
   815  	if err != nil {
   816  		return nil, err
   817  	}
   818  
   819  	wd, err := os.Getwd()
   820  	if err != nil {
   821  		return nil, err
   822  	}
   823  
   824  	if len(execCode) > 0 {
   825  		ioutil.WriteFile(filepath.Join(tmp, execFile), []byte(execCode), 0700)
   826  	} else {
   827  		f, err := os.OpenFile(execFile, os.O_CREATE|os.O_EXCL, 0700)
   828  		if err != nil {
   829  			return nil, err
   830  		}
   831  		err = f.Close()
   832  		if err != nil {
   833  			return nil, err
   834  		}
   835  	}
   836  
   837  	err = os.Setenv("PATH", fmt.Sprintf("%s:%s", tmp, pathenv))
   838  	if err != nil {
   839  		return nil, err
   840  	}
   841  
   842  	restorePath := func() {
   843  		os.RemoveAll(tmp)
   844  		os.Chdir(wd)
   845  		os.Setenv("PATH", pathenv)
   846  	}
   847  
   848  	return restorePath, nil
   849  }
   850  
   851  func prunePath() (restoreFn, error) {
   852  	pathenv := os.Getenv("PATH")
   853  	err := os.Setenv("PATH", "")
   854  	if err != nil {
   855  		return nil, err
   856  	}
   857  	restorePath := func() {
   858  		os.Setenv("PATH", pathenv)
   859  	}
   860  	return restorePath, nil
   861  }