github.com/khulnasoft-lab/kube-bench@v0.2.1-0.20240330183753-9df52345ae58/cmd/common_test.go (about)

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