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 }