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 }