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 }