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

     1  // Copyright © 2017 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  	goflag "flag"
    19  	"fmt"
    20  	"os"
    21  
    22  	"github.com/golang/glog"
    23  	"github.com/khulnasoft-lab/kube-bench/check"
    24  	"github.com/spf13/cobra"
    25  	"github.com/spf13/viper"
    26  )
    27  
    28  type FilterOpts struct {
    29  	CheckList string
    30  	GroupList string
    31  	Scored    bool
    32  	Unscored  bool
    33  }
    34  
    35  var (
    36  	envVarsPrefix        = "KUBE_BENCH"
    37  	defaultKubeVersion   = "1.18"
    38  	kubeVersion          string
    39  	detecetedKubeVersion string
    40  	benchmarkVersion     string
    41  	cfgFile              string
    42  	cfgDir               = "./cfg/"
    43  	jsonFmt              bool
    44  	junitFmt             bool
    45  	pgSQL                bool
    46  	aSFF                 bool
    47  	masterFile           = "master.yaml"
    48  	nodeFile             = "node.yaml"
    49  	etcdFile             = "etcd.yaml"
    50  	controlplaneFile     = "controlplane.yaml"
    51  	policiesFile         = "policies.yaml"
    52  	managedservicesFile  = "managedservices.yaml"
    53  	exitCode             int
    54  	noResults            bool
    55  	noSummary            bool
    56  	noRemediations       bool
    57  	skipIds              string
    58  	noTotals             bool
    59  	filterOpts           FilterOpts
    60  	includeTestOutput    bool
    61  	outputFile           string
    62  	configFileError      error
    63  	controlsCollection   []*check.Controls
    64  )
    65  
    66  // RootCmd represents the base command when called without any subcommands
    67  var RootCmd = &cobra.Command{
    68  	Use:   os.Args[0],
    69  	Short: "Run CIS Benchmarks checks against a Kubernetes deployment",
    70  	Long:  `This tool runs the CIS Kubernetes Benchmark (https://www.cisecurity.org/benchmark/kubernetes/)`,
    71  	Run: func(cmd *cobra.Command, args []string) {
    72  		bv, err := getBenchmarkVersion(kubeVersion, benchmarkVersion, getPlatformInfo(), viper.GetViper())
    73  		if err != nil {
    74  			exitWithError(fmt.Errorf("unable to determine benchmark version: %v", err))
    75  		}
    76  		glog.V(1).Infof("Running checks for benchmark %v", bv)
    77  
    78  		if isMaster() {
    79  			glog.V(1).Info("== Running master checks ==")
    80  			runChecks(check.MASTER, loadConfig(check.MASTER, bv), detecetedKubeVersion)
    81  
    82  			// Control Plane is only valid for CIS 1.5 and later,
    83  			// this a gatekeeper for previous versions
    84  			valid, err := validTargets(bv, []string{string(check.CONTROLPLANE)}, viper.GetViper())
    85  			if err != nil {
    86  				exitWithError(fmt.Errorf("error validating targets: %v", err))
    87  			}
    88  			if valid {
    89  				glog.V(1).Info("== Running control plane checks ==")
    90  				runChecks(check.CONTROLPLANE, loadConfig(check.CONTROLPLANE, bv), detecetedKubeVersion)
    91  			}
    92  		} else {
    93  			glog.V(1).Info("== Skipping master checks ==")
    94  		}
    95  
    96  		// Etcd is only valid for CIS 1.5 and later,
    97  		// this a gatekeeper for previous versions.
    98  		valid, err := validTargets(bv, []string{string(check.ETCD)}, viper.GetViper())
    99  		if err != nil {
   100  			exitWithError(fmt.Errorf("error validating targets: %v", err))
   101  		}
   102  		if valid && isEtcd() {
   103  			glog.V(1).Info("== Running etcd checks ==")
   104  			runChecks(check.ETCD, loadConfig(check.ETCD, bv), detecetedKubeVersion)
   105  		} else {
   106  			glog.V(1).Info("== Skipping etcd checks ==")
   107  		}
   108  
   109  		glog.V(1).Info("== Running node checks ==")
   110  		runChecks(check.NODE, loadConfig(check.NODE, bv), detecetedKubeVersion)
   111  
   112  		// Policies is only valid for CIS 1.5 and later,
   113  		// this a gatekeeper for previous versions.
   114  		valid, err = validTargets(bv, []string{string(check.POLICIES)}, viper.GetViper())
   115  		if err != nil {
   116  			exitWithError(fmt.Errorf("error validating targets: %v", err))
   117  		}
   118  		if valid {
   119  			glog.V(1).Info("== Running policies checks ==")
   120  			runChecks(check.POLICIES, loadConfig(check.POLICIES, bv), detecetedKubeVersion)
   121  		} else {
   122  			glog.V(1).Info("== Skipping policies checks ==")
   123  		}
   124  
   125  		// Managedservices is only valid for GKE 1.0 and later,
   126  		// this a gatekeeper for previous versions.
   127  		valid, err = validTargets(bv, []string{string(check.MANAGEDSERVICES)}, viper.GetViper())
   128  		if err != nil {
   129  			exitWithError(fmt.Errorf("error validating targets: %v", err))
   130  		}
   131  		if valid {
   132  			glog.V(1).Info("== Running managed services checks ==")
   133  			runChecks(check.MANAGEDSERVICES, loadConfig(check.MANAGEDSERVICES, bv), detecetedKubeVersion)
   134  		} else {
   135  			glog.V(1).Info("== Skipping managed services checks ==")
   136  		}
   137  
   138  		writeOutput(controlsCollection)
   139  		os.Exit(exitCodeSelection(controlsCollection))
   140  	},
   141  }
   142  
   143  // Execute adds all child commands to the root command sets flags appropriately.
   144  // This is called by main.main(). It only needs to happen once to the rootCmd.
   145  func Execute() {
   146  	goflag.CommandLine.Parse([]string{})
   147  
   148  	if err := RootCmd.Execute(); err != nil {
   149  		fmt.Println(err)
   150  		// flush before exit non-zero
   151  		glog.Flush()
   152  		os.Exit(-1)
   153  	}
   154  	// flush before exit
   155  	glog.Flush()
   156  }
   157  
   158  func init() {
   159  	cobra.OnInitialize(initConfig)
   160  
   161  	// Output control
   162  	RootCmd.PersistentFlags().IntVar(&exitCode, "exit-code", 0, "Specify the exit code for when checks fail")
   163  	RootCmd.PersistentFlags().BoolVar(&noResults, "noresults", false, "Disable printing of results section")
   164  	RootCmd.PersistentFlags().BoolVar(&noSummary, "nosummary", false, "Disable printing of summary section")
   165  	RootCmd.PersistentFlags().BoolVar(&noRemediations, "noremediations", false, "Disable printing of remediations section")
   166  	RootCmd.PersistentFlags().BoolVar(&noTotals, "nototals", false, "Disable printing of totals for failed, passed, ... checks across all sections")
   167  	RootCmd.PersistentFlags().BoolVar(&jsonFmt, "json", false, "Prints the results as JSON")
   168  	RootCmd.PersistentFlags().BoolVar(&junitFmt, "junit", false, "Prints the results as JUnit")
   169  	RootCmd.PersistentFlags().BoolVar(&pgSQL, "pgsql", false, "Save the results to PostgreSQL")
   170  	RootCmd.PersistentFlags().BoolVar(&aSFF, "asff", false, "Send the results to AWS Security Hub")
   171  	RootCmd.PersistentFlags().BoolVar(&filterOpts.Scored, "scored", true, "Run the scored CIS checks")
   172  	RootCmd.PersistentFlags().BoolVar(&filterOpts.Unscored, "unscored", true, "Run the unscored CIS checks")
   173  	RootCmd.PersistentFlags().StringVar(&skipIds, "skip", "", "List of comma separated values of checks to be skipped")
   174  	RootCmd.PersistentFlags().BoolVar(&includeTestOutput, "include-test-output", false, "Prints the actual result when test fails")
   175  	RootCmd.PersistentFlags().StringVar(&outputFile, "outputfile", "", "Writes the results to output file when run with --json or --junit")
   176  
   177  	RootCmd.PersistentFlags().StringVarP(
   178  		&filterOpts.CheckList,
   179  		"check",
   180  		"c",
   181  		"",
   182  		`A comma-delimited list of checks to run as specified in CIS document. Example --check="1.1.1,1.1.2"`,
   183  	)
   184  	RootCmd.PersistentFlags().StringVarP(
   185  		&filterOpts.GroupList,
   186  		"group",
   187  		"g",
   188  		"",
   189  		`Run all the checks under this comma-delimited list of groups. Example --group="1.1"`,
   190  	)
   191  	RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is ./cfg/config.yaml)")
   192  	RootCmd.PersistentFlags().StringVarP(&cfgDir, "config-dir", "D", cfgDir, "config directory")
   193  	RootCmd.PersistentFlags().StringVar(&kubeVersion, "version", "", "Manually specify Kubernetes version, automatically detected if unset")
   194  	RootCmd.PersistentFlags().StringVar(&benchmarkVersion, "benchmark", "", "Manually specify CIS benchmark version. It would be an error to specify both --version and --benchmark flags")
   195  
   196  	if err := goflag.Set("logtostderr", "true"); err != nil {
   197  		fmt.Printf("unable to set logtostderr: %+v\n", err)
   198  		os.Exit(-1)
   199  	}
   200  	goflag.CommandLine.VisitAll(func(goflag *goflag.Flag) {
   201  		RootCmd.PersistentFlags().AddGoFlag(goflag)
   202  	})
   203  }
   204  
   205  // initConfig reads in config file and ENV variables if set.
   206  func initConfig() {
   207  	if cfgFile != "" { // enable ability to specify config file via flag
   208  		viper.SetConfigFile(cfgFile)
   209  	} else {
   210  		viper.SetConfigName("config") // name of config file (without extension)
   211  		viper.AddConfigPath(cfgDir)   // adding ./cfg as first search path
   212  	}
   213  
   214  	// Read flag values from environment variables.
   215  	// Precedence: Command line flags take precedence over environment variables.
   216  	viper.SetEnvPrefix(envVarsPrefix)
   217  	viper.AutomaticEnv()
   218  
   219  	if kubeVersion == "" {
   220  		if env := viper.Get("version"); env != nil {
   221  			kubeVersion = env.(string)
   222  		}
   223  	}
   224  
   225  	// If a config file is found, read it in.
   226  	if err := viper.ReadInConfig(); err != nil {
   227  		if _, ok := err.(viper.ConfigFileNotFoundError); ok {
   228  			// Config file not found; ignore error for now to prevent commands
   229  			// which don't need the config file exiting.
   230  			configFileError = err
   231  		} else {
   232  			// Config file was found but another error was produced
   233  			colorPrint(check.FAIL, fmt.Sprintf("Failed to read config file: %v\n", err))
   234  			os.Exit(1)
   235  		}
   236  	}
   237  }