github.com/castai/kvisor@v1.7.1-0.20240516114728-b3572a2607b5/cmd/linter/kubebench/kubebench.go (about)

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