mirror of
https://github.com/aquasecurity/kube-bench.git
synced 2024-11-22 08:08:07 +00:00
Add run subcommand (#529)
* test: fix TestGetConfigFilePath This test wasn't correctly creating the test file due to the wrong directory permissions on the temp file. This wasn't detected due to a lack of error checking. Also, the code was only checking for file not exist rather than lack of permission to read file (or any other error). The combination of these two things means the test wasn't checking what it thought it was checking, and passed more by luck than judgment. * add getYamlFilesFromDir * add getTestYamlFiles and test * docs: Update master / node help text * return path + filename from getYamlFilesFromDir * subcommand run to run specific section files
This commit is contained in:
parent
8780e5cb59
commit
f2caa1f0ec
@ -62,7 +62,7 @@ func NewRunFilter(opts FilterOpts) (check.Predicate, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runChecks(nodetype check.NodeType) {
|
func runChecks(nodetype check.NodeType, testYamlFile string) {
|
||||||
var summary check.Summary
|
var summary check.Summary
|
||||||
|
|
||||||
// Verify config file was loaded into Viper during Cobra sub-command initialization.
|
// Verify config file was loaded into Viper during Cobra sub-command initialization.
|
||||||
@ -71,19 +71,24 @@ func runChecks(nodetype check.NodeType) {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
def := loadConfig(nodetype)
|
in, err := ioutil.ReadFile(testYamlFile)
|
||||||
in, err := ioutil.ReadFile(def)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
exitWithError(fmt.Errorf("error opening %s controls file: %v", nodetype, err))
|
exitWithError(fmt.Errorf("error opening %s test file: %v", testYamlFile, err))
|
||||||
}
|
}
|
||||||
|
|
||||||
glog.V(1).Info(fmt.Sprintf("Using benchmark file: %s\n", def))
|
glog.V(1).Info(fmt.Sprintf("Using test file: %s\n", testYamlFile))
|
||||||
|
|
||||||
// Get the set of executables and config files we care about on this type of node.
|
// Get the viper config for this section of tests
|
||||||
typeConf := viper.Sub(string(nodetype))
|
typeConf := viper.Sub(string(nodetype))
|
||||||
|
if typeConf == nil {
|
||||||
|
colorPrint(check.FAIL, fmt.Sprintf("No config settings for %s\n", string(nodetype)))
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the set of executables we need for this section of the tests
|
||||||
binmap, err := getBinaries(typeConf, nodetype)
|
binmap, err := getBinaries(typeConf, nodetype)
|
||||||
|
|
||||||
// Checks that the executables we need for the node type are running.
|
// Checks that the executables we need for the section are running.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
exitWithError(err)
|
exitWithError(err)
|
||||||
}
|
}
|
||||||
@ -226,19 +231,26 @@ func loadConfig(nodetype check.NodeType) string {
|
|||||||
exitWithError(fmt.Errorf("can't find %s controls file in %s: %v", nodetype, cfgDir, err))
|
exitWithError(fmt.Errorf("can't find %s controls file in %s: %v", nodetype, cfgDir, err))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge kubernetes version specific config if any.
|
// Merge version-specific config if any.
|
||||||
|
mergeConfig(path)
|
||||||
|
|
||||||
|
return filepath.Join(path, file)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeConfig(path string) error {
|
||||||
viper.SetConfigFile(path + "/config.yaml")
|
viper.SetConfigFile(path + "/config.yaml")
|
||||||
err = viper.MergeInConfig()
|
err := viper.MergeInConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
glog.V(2).Info(fmt.Sprintf("No version-specific config.yaml file in %s", path))
|
glog.V(2).Info(fmt.Sprintf("No version-specific config.yaml file in %s", path))
|
||||||
} else {
|
} else {
|
||||||
exitWithError(fmt.Errorf("couldn't read config file %s: %v", path+"/config.yaml", err))
|
return fmt.Errorf("couldn't read config file %s: %v", path+"/config.yaml", err)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
glog.V(1).Info(fmt.Sprintf("Using config file: %s\n", viper.ConfigFileUsed()))
|
|
||||||
}
|
}
|
||||||
return filepath.Join(path, file)
|
|
||||||
|
glog.V(1).Info(fmt.Sprintf("Using config file: %s\n", viper.ConfigFileUsed()))
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapToBenchmarkVersion(kubeToBenchmarkMap map[string]string, kv string) (string, error) {
|
func mapToBenchmarkVersion(kubeToBenchmarkMap map[string]string, kv string) (string, error) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright © 2017 Aqua Security Software Ltd. <info@aquasec.com>
|
// Copyright © 2017-2019 Aqua Security Software Ltd. <info@aquasec.com>
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@ -22,10 +22,11 @@ import (
|
|||||||
// masterCmd represents the master command
|
// masterCmd represents the master command
|
||||||
var masterCmd = &cobra.Command{
|
var masterCmd = &cobra.Command{
|
||||||
Use: "master",
|
Use: "master",
|
||||||
Short: "Run benchmark checks for a Kubernetes master node.",
|
Short: "Run Kubernetes benchmark checks from the master.yaml file.",
|
||||||
Long: `Run benchmark checks for a Kubernetes master node.`,
|
Long: `Run Kubernetes benchmark checks from the master.yaml file in cfg/<version>.`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
runChecks(check.MASTER)
|
filename := loadConfig(check.MASTER)
|
||||||
|
runChecks(check.MASTER, filename)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright © 2017 Aqua Security Software Ltd. <info@aquasec.com>
|
// Copyright © 2017-2019 Aqua Security Software Ltd. <info@aquasec.com>
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@ -22,10 +22,11 @@ import (
|
|||||||
// nodeCmd represents the node command
|
// nodeCmd represents the node command
|
||||||
var nodeCmd = &cobra.Command{
|
var nodeCmd = &cobra.Command{
|
||||||
Use: "node",
|
Use: "node",
|
||||||
Short: "Run benchmark checks for a Kubernetes node.",
|
Short: "Run Kubernetes benchmark checks from the node.yaml file.",
|
||||||
Long: `Run benchmark checks for a Kubernetes node.`,
|
Long: `Run Kubernetes benchmark checks from the node.yaml file in cfg/<version>.`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
runChecks(check.NODE)
|
filename := loadConfig(check.NODE)
|
||||||
|
runChecks(check.NODE, filename)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,10 +61,12 @@ var RootCmd = &cobra.Command{
|
|||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
if isMaster() {
|
if isMaster() {
|
||||||
glog.V(1).Info("== Running master checks ==\n")
|
glog.V(1).Info("== Running master checks ==\n")
|
||||||
runChecks(check.MASTER)
|
filename := loadConfig(check.MASTER)
|
||||||
|
runChecks(check.MASTER, filename)
|
||||||
}
|
}
|
||||||
glog.V(1).Info("== Running node checks ==\n")
|
glog.V(1).Info("== Running node checks ==\n")
|
||||||
runChecks(check.NODE)
|
filename := loadConfig(check.NODE)
|
||||||
|
runChecks(check.NODE, filename)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
91
cmd/run.go
Normal file
91
cmd/run.go
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/aquasecurity/kube-bench/check"
|
||||||
|
"github.com/golang/glog"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RootCmd.AddCommand(runCmd)
|
||||||
|
runCmd.Flags().StringSliceP("sections", "s", []string{},
|
||||||
|
`Specify sections of the benchmark to run. These names need to match the filenames in the cfg/<version> directory.
|
||||||
|
For example, to run the tests specified in master.yaml and etcd.yaml, specify --sections=master,etcd
|
||||||
|
If no sections are specified, run tests from all files in the cfg/<version> directory.
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// runCmd represents the run command
|
||||||
|
var runCmd = &cobra.Command{
|
||||||
|
Use: "run",
|
||||||
|
Short: "Run tests",
|
||||||
|
Long: `Run tests. If no arguments are specified, runs tests from all files`,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
sections, err := cmd.Flags().GetStringSlice("sections")
|
||||||
|
if err != nil {
|
||||||
|
exitWithError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
benchmarkVersion, err := getBenchmarkVersion(kubeVersion, benchmarkVersion, viper.GetViper())
|
||||||
|
if err != nil {
|
||||||
|
exitWithError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge version-specific config if any.
|
||||||
|
path := filepath.Join(cfgDir, benchmarkVersion)
|
||||||
|
mergeConfig(path)
|
||||||
|
|
||||||
|
err = run(sections, benchmarkVersion)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error in run: %v\n", err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func run(sections []string, benchmarkVersion string) (err error) {
|
||||||
|
|
||||||
|
yamlFiles, err := getTestYamlFiles(sections, benchmarkVersion)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
glog.V(3).Infof("Running tests from files %v\n", yamlFiles)
|
||||||
|
|
||||||
|
for _, yamlFile := range yamlFiles {
|
||||||
|
_, name := filepath.Split(yamlFile)
|
||||||
|
testType := check.NodeType(strings.Split(name, ".")[0])
|
||||||
|
runChecks(testType, yamlFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTestYamlFiles(sections []string, benchmarkVersion string) (yamlFiles []string, err error) {
|
||||||
|
|
||||||
|
// Check that the specified sections have corresponding YAML files in the config directory
|
||||||
|
configFileDirectory := filepath.Join(cfgDir, benchmarkVersion)
|
||||||
|
for _, section := range sections {
|
||||||
|
filename := section + ".yaml"
|
||||||
|
file := filepath.Join(configFileDirectory, filename)
|
||||||
|
if _, err := os.Stat(file); err != nil {
|
||||||
|
return nil, fmt.Errorf("file %s not found for version %s", filename, benchmarkVersion)
|
||||||
|
}
|
||||||
|
yamlFiles = append(yamlFiles, file)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no sections were specified, we will run tests from all the files in the directory
|
||||||
|
if len(yamlFiles) == 0 {
|
||||||
|
yamlFiles, err = getYamlFilesFromDir(configFileDirectory)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return yamlFiles, err
|
||||||
|
}
|
84
cmd/run_test.go
Normal file
84
cmd/run_test.go
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetTestYamlFiles(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
sections []string
|
||||||
|
benchmark string
|
||||||
|
succeed bool
|
||||||
|
expCount int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Specify two sections",
|
||||||
|
sections: []string{"one", "two"},
|
||||||
|
benchmark: "benchmark",
|
||||||
|
succeed: true,
|
||||||
|
expCount: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Specify a section that doesn't exist",
|
||||||
|
sections: []string{"one", "missing"},
|
||||||
|
benchmark: "benchmark",
|
||||||
|
succeed: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "No sections specified - should return everything except config.yaml",
|
||||||
|
sections: []string{},
|
||||||
|
benchmark: "benchmark",
|
||||||
|
succeed: true,
|
||||||
|
expCount: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Specify benchmark that doesn't exist",
|
||||||
|
sections: []string{"one"},
|
||||||
|
benchmark: "missing",
|
||||||
|
succeed: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up temp config directory
|
||||||
|
var err error
|
||||||
|
cfgDir, err = ioutil.TempDir("", "kube-bench-test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create temp directory")
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(cfgDir)
|
||||||
|
|
||||||
|
d := filepath.Join(cfgDir, "benchmark")
|
||||||
|
err = os.Mkdir(d, 0766)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create temp dir")
|
||||||
|
}
|
||||||
|
|
||||||
|
// We never expect config.yaml to be returned
|
||||||
|
for _, filename := range []string{"one.yaml", "two.yaml", "three.yaml", "config.yaml"} {
|
||||||
|
err = ioutil.WriteFile(filepath.Join(d, filename), []byte("hello world"), 0666)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error writing temp file %s: %v", filename, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range cases {
|
||||||
|
t.Run(c.name, func(t *testing.T) {
|
||||||
|
yamlFiles, err := getTestYamlFiles(c.sections, c.benchmark)
|
||||||
|
if err != nil && c.succeed {
|
||||||
|
t.Fatalf("Error %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil && !c.succeed {
|
||||||
|
t.Fatalf("Expected failure")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(yamlFiles) != c.expCount {
|
||||||
|
t.Fatalf("Expected %d, got %d", c.expCount, len(yamlFiles))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
24
cmd/util.go
24
cmd/util.go
@ -123,21 +123,39 @@ func getBinaries(v *viper.Viper, nodetype check.NodeType) (map[string]string, er
|
|||||||
return binmap, nil
|
return binmap, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getConfigFilePath locates the config files we should be using CIS version
|
// getConfigFilePath locates the config files we should be using for CIS version
|
||||||
func getConfigFilePath(benchmarkVersion string, filename string) (path string, err error) {
|
func getConfigFilePath(benchmarkVersion string, filename string) (path string, err error) {
|
||||||
glog.V(2).Info(fmt.Sprintf("Looking for config specific CIS version %q", benchmarkVersion))
|
glog.V(2).Info(fmt.Sprintf("Looking for config specific CIS version %q", benchmarkVersion))
|
||||||
|
|
||||||
path = filepath.Join(cfgDir, benchmarkVersion)
|
path = filepath.Join(cfgDir, benchmarkVersion)
|
||||||
file := filepath.Join(path, string(filename))
|
file := filepath.Join(path, string(filename))
|
||||||
glog.V(2).Info(fmt.Sprintf("Looking for config file: %s", file))
|
glog.V(2).Info(fmt.Sprintf("Looking for file: %s", file))
|
||||||
|
|
||||||
if _, err = os.Stat(file); os.IsNotExist(err) {
|
if _, err := os.Stat(file); err != nil {
|
||||||
glog.V(2).Infof("error accessing config file: %q error: %v\n", file, err)
|
glog.V(2).Infof("error accessing config file: %q error: %v\n", file, err)
|
||||||
return "", fmt.Errorf("no test files found <= benchmark version: %s", benchmarkVersion)
|
return "", fmt.Errorf("no test files found <= benchmark version: %s", benchmarkVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
return path, nil
|
return path, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getYamlFilesFromDir returns a list of yaml files in the specified directory, ignoring config.yaml
|
||||||
|
func getYamlFilesFromDir(path string) (names []string, err error) {
|
||||||
|
err = filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, name := filepath.Split(path)
|
||||||
|
if name != "" && name != "config.yaml" && filepath.Ext(name) == ".yaml" {
|
||||||
|
names = append(names, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return names, err
|
||||||
|
}
|
||||||
|
|
||||||
// decrementVersion decrements the version number
|
// decrementVersion decrements the version number
|
||||||
// We want to decrement individually even through versions where we don't supply test files
|
// We want to decrement individually even through versions where we don't supply test files
|
||||||
// just in case someone wants to specify their own test files for that version
|
// just in case someone wants to specify their own test files for that version
|
||||||
|
@ -410,11 +410,14 @@ func TestGetConfigFilePath(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer os.RemoveAll(cfgDir)
|
defer os.RemoveAll(cfgDir)
|
||||||
d := filepath.Join(cfgDir, "cis-1.4")
|
d := filepath.Join(cfgDir, "cis-1.4")
|
||||||
err = os.Mkdir(d, 0666)
|
err = os.Mkdir(d, 0766)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create temp file")
|
t.Fatalf("Failed to create temp dir")
|
||||||
|
}
|
||||||
|
err = ioutil.WriteFile(filepath.Join(d, "master.yaml"), []byte("hello world"), 0666)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("Failed to create temp file")
|
||||||
}
|
}
|
||||||
ioutil.WriteFile(filepath.Join(d, "master.yaml"), []byte("hello world"), 0666)
|
|
||||||
|
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
benchmarkVersion string
|
benchmarkVersion string
|
||||||
@ -471,3 +474,38 @@ func TestDecrementVersion(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetYamlFilesFromDir(t *testing.T) {
|
||||||
|
cfgDir, err := ioutil.TempDir("", "kube-bench-test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create temp directory")
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(cfgDir)
|
||||||
|
|
||||||
|
d := filepath.Join(cfgDir, "cis-1.4")
|
||||||
|
err = os.Mkdir(d, 0766)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create temp dir")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ioutil.WriteFile(filepath.Join(d, "something.yaml"), []byte("hello world"), 0666)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error writing file %v", err)
|
||||||
|
}
|
||||||
|
err = ioutil.WriteFile(filepath.Join(d, "config.yaml"), []byte("hello world"), 0666)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error writing file %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
files, err := getYamlFilesFromDir(d)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if len(files) != 1 {
|
||||||
|
t.Fatalf("Expected to find one file, found %d", len(files))
|
||||||
|
}
|
||||||
|
|
||||||
|
if files[0] != filepath.Join(d, "something.yaml") {
|
||||||
|
t.Fatalf("Expected to find something.yaml, found %s", files[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user