1
0
mirror of https://github.com/aquasecurity/kube-bench.git synced 2024-11-22 08:08:07 +00:00

Merge branch 'master' into issue-19

This commit is contained in:
Liz Rice 2017-08-07 16:23:59 +01:00 committed by GitHub
commit cf5f025593
7 changed files with 131 additions and 103 deletions

View File

@ -30,18 +30,25 @@ If Go is installed on the target machines, you can simply clone this repository
```
Available Commands:
master Checks for Kubernetes master node
node Checks for Kubernetes node
federated Checks for Kubernetes federated deployment
help Help information
federated Run benchmark checks for a Kubernetes federated deployment.
help Help about any command
master Run benchmark checks for a Kubernetes master node.
node Run benchmark checks for a Kubernetes node.
Flags:
-c, --check string A comma-delimited list of checks to run as specified in CIS document. Example --check="1.1.1,1.1.2"
-g, --group string Run all the checks under this comma-delimited list of groups. Example --group="1.1"
-h, --help help for kube-bench
--json Output results as JSON
-c, --check string A comma-delimited list of checks to run as specified in CIS document. Example --check="1.1.1,1.1.2"
--config string config file (default is ./cfg/config.yaml)
-g, --group string Run all the checks under this comma-delimited list of groups. Example --group="1.1"
--installation string Specify how kubernetes cluster was installed. Possible values are default,hyperkube,kops,kubeadm (default "default")
--json Prints the results as JSON
-v, --verbose verbose output (default false)
```
## Configuration
Kubernetes config and binary file locations and names can vary from installation to installation, so these are configurable in the `cfg/config.yaml` file.
They also tend to vary according to which tool was used to install Kubernetes. You can use the `--installation` flag to pick up a different default set of file names and locations. Again these defaults are configurable through `cfg/config.yaml` (and pull requests to correct or add default file locations are especially welcome).
## Test config YAML representation
The tests are represented as YAML documents (installed by default into ./cfg).

View File

@ -29,7 +29,7 @@ installation:
conf:
apiserver: /etc/kubernetes/apiserver
scheduler: /etc/kubernetes/scheduler
controller-manager: /etc/kubernetes/apiserver
controller-manager: /etc/kubernetes/controller-manager
node:
bin:
kubelet: kubelet

View File

@ -18,9 +18,10 @@ import (
"bytes"
"fmt"
"io"
"os"
"os/exec"
"strings"
"github.com/golang/glog"
)
// NodeType indicates the type of node (master, node, federated).
@ -155,9 +156,7 @@ func (c *Check) Run(verbose bool) {
i++
}
if verbose && errmsgs != "" {
fmt.Fprintf(os.Stderr, "%s\n", errmsgs)
}
glog.V(2).Info("%s\n", errmsgs)
res := c.Tests.execute(out.String())
if res {

View File

@ -68,7 +68,7 @@ func NewControls(t NodeType, in []byte) (*Controls, error) {
}
// RunGroup runs all checks in a group.
func (controls *Controls) RunGroup(verbose bool, gids ...string) Summary {
func (controls *Controls) RunGroup(gids ...string) Summary {
g := []*Group{}
controls.Summary.Pass, controls.Summary.Fail, controls.Summary.Warn = 0, 0, 0
@ -82,7 +82,7 @@ func (controls *Controls) RunGroup(verbose bool, gids ...string) Summary {
for _, gid := range gids {
if gid == group.ID {
for _, check := range group.Checks {
check.Run(verbose)
check.Run()
summarize(controls, check)
}
@ -96,7 +96,7 @@ func (controls *Controls) RunGroup(verbose bool, gids ...string) Summary {
}
// RunChecks runs the checks with the supplied IDs.
func (controls *Controls) RunChecks(verbose bool, ids ...string) Summary {
func (controls *Controls) RunChecks(ids ...string) Summary {
g := []*Group{}
m := make(map[string]*Group)
controls.Summary.Pass, controls.Summary.Fail, controls.Summary.Warn = 0, 0, 0
@ -110,7 +110,7 @@ func (controls *Controls) RunChecks(verbose bool, ids ...string) Summary {
for _, check := range group.Checks {
for _, id := range ids {
if id == check.ID {
check.Run(verbose)
check.Run()
summarize(controls, check)
// Check if we have already added this checks group.

View File

@ -17,7 +17,6 @@ package cmd
import (
"fmt"
"io/ioutil"
"os"
"strings"
"github.com/aquasecurity/kube-bench/check"
@ -59,7 +58,7 @@ func runChecks(t check.NodeType) {
schedulerBin = viper.GetString("installation." + installation + ".master.bin.scheduler")
schedulerConf = viper.GetString("installation." + installation + ".master.conf.scheduler")
controllerManagerBin = viper.GetString("installation." + installation + ".master.bin.controller-manager")
controllerManagerConf = viper.GetString("installation." + installation + ".master.conf.controler-manager")
controllerManagerConf = viper.GetString("installation." + installation + ".master.conf.controller-manager")
config = viper.GetString("installation." + installation + ".config")
etcdBin = viper.GetString("etcd.bin")
@ -78,7 +77,7 @@ func runChecks(t check.NodeType) {
fedControllerManagerBin = viper.GetString("installation." + installation + ".federated.bin.controller-manager")
// Run kubernetes installation validation checks.
warns := verifyNodeType(t)
verifyNodeType(t)
switch t {
case check.MASTER:
@ -91,8 +90,7 @@ func runChecks(t check.NodeType) {
in, err := ioutil.ReadFile(file)
if err != nil {
fmt.Fprintf(os.Stderr, "error opening %s controls file: %v\n", t, err)
os.Exit(1)
exitWithError(fmt.Errorf("error opening %s controls file: %v", t, err))
}
// Variable substitutions. Replace all occurrences of variables in controls files.
@ -102,7 +100,6 @@ func runChecks(t check.NodeType) {
s = strings.Replace(s, "$schedulerconf", schedulerConf, -1)
s = strings.Replace(s, "$controllermanagerbin", controllerManagerBin, -1)
s = strings.Replace(s, "$controllermanagerconf", controllerManagerConf, -1)
s = strings.Replace(s, "$controllermanagerconf", controllerManagerConf, -1)
s = strings.Replace(s, "$config", config, -1)
s = strings.Replace(s, "$etcdbin", etcdBin, -1)
@ -120,63 +117,50 @@ func runChecks(t check.NodeType) {
controls, err := check.NewControls(t, []byte(s))
if err != nil {
fmt.Fprintf(os.Stderr, "error setting up %s controls: %v\n", t, err)
os.Exit(1)
exitWithError(fmt.Errorf("error setting up %s controls: %v", t, err))
}
if groupList != "" && checkList == "" {
ids := cleanIDs(groupList)
summary = controls.RunGroup(verbose, ids...)
summary = controls.RunGroup(ids...)
} else if checkList != "" && groupList == "" {
ids := cleanIDs(checkList)
summary = controls.RunChecks(verbose, ids...)
summary = controls.RunChecks(ids...)
} else if checkList != "" && groupList != "" {
fmt.Fprintf(os.Stderr, "group option and check option can't be used together\n")
os.Exit(1)
exitWithError(fmt.Errorf("group option and check option can't be used together"))
} else {
summary = controls.RunGroup(verbose)
summary = controls.RunGroup()
}
// if we successfully ran some tests and it's json format, ignore the warnings
if (summary.Fail > 0 || summary.Warn > 0 || summary.Pass > 0) && jsonFmt {
out, err := controls.JSON()
if err != nil {
fmt.Fprintf(os.Stderr, "failed to output in JSON format: %v\n", err)
os.Exit(1)
exitWithError(fmt.Errorf("failed to output in JSON format: %v", err))
}
fmt.Println(string(out))
} else {
prettyPrint(warns, controls, summary)
prettyPrint(controls, summary)
}
}
// verifyNodeType checks the executables and config files are as expected
// for the specified tests (master, node or federated).
func verifyNodeType(t check.NodeType) []string {
var w []string
// Always clear out error messages.
errmsgs = ""
func verifyNodeType(t check.NodeType) {
switch t {
case check.MASTER:
w = append(w, verifyBin(apiserverBin, schedulerBin, controllerManagerBin)...)
w = append(w, verifyConf(apiserverConf, schedulerConf, controllerManagerConf)...)
w = append(w, verifyKubeVersion(apiserverBin)...)
verifyKubeVersion(apiserverBin)
verifyBin(apiserverBin, schedulerBin, controllerManagerBin)
verifyConf(apiserverConf, schedulerConf, controllerManagerConf)
case check.NODE:
w = append(w, verifyBin(kubeletBin, proxyBin)...)
w = append(w, verifyConf(kubeletConf, proxyConf)...)
w = append(w, verifyKubeVersion(kubeletBin)...)
verifyKubeVersion(kubeletBin)
verifyBin(kubeletBin, proxyBin)
verifyConf(kubeletConf, proxyConf)
case check.FEDERATED:
w = append(w, verifyBin(fedApiserverBin, fedControllerManagerBin)...)
w = append(w, verifyKubeVersion(fedApiserverBin)...)
verifyKubeVersion(fedApiserverBin)
verifyBin(fedApiserverBin, fedControllerManagerBin)
}
if verbose {
fmt.Fprintf(os.Stderr, "%s\n", errmsgs)
}
return w
}
// colorPrint outputs the state in a specific colour, along with a message string
@ -186,13 +170,9 @@ func colorPrint(state check.State, s string) {
}
// prettyPrint outputs the results to stdout in human-readable format
func prettyPrint(warnings []string, r *check.Controls, summary check.Summary) {
func prettyPrint(r *check.Controls, summary check.Summary) {
colorPrint(check.INFO, fmt.Sprintf("Using config file: %s\n", viper.ConfigFileUsed()))
for _, w := range warnings {
colorPrint(check.WARN, w)
}
colorPrint(check.INFO, fmt.Sprintf("%s %s\n", r.ID, r.Text))
for _, g := range r.Groups {
colorPrint(check.INFO, fmt.Sprintf("%s %s\n", g.ID, g.Text))

View File

@ -15,6 +15,7 @@
package cmd
import (
goflag "flag"
"fmt"
"os"
@ -34,11 +35,12 @@ var (
nodeFile string
federatedFile string
loud bool
kubeConfDir string
etcdConfDir string
flanneldConfDir string
verbose bool
installation string
)
@ -52,6 +54,9 @@ var RootCmd = &cobra.Command{
// Execute adds all child commands to the root command sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
goflag.Set("logtostderr", "true")
goflag.CommandLine.Parse([]string{})
if err := RootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(-1)
@ -83,7 +88,11 @@ func init() {
`Run all the checks under this comma-delimited list of groups. Example --group="1.1"`,
)
RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is ./cfg/config.yaml)")
RootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output (default false)")
goflag.CommandLine.VisitAll(func(goflag *goflag.Flag) {
RootCmd.PersistentFlags().AddGoFlag(goflag)
})
}
// initConfig reads in config file and ENV variables if set.
@ -103,5 +112,4 @@ func initConfig() {
colorPrint(check.FAIL, fmt.Sprintf("Failed to read config file: %v\n", err))
os.Exit(1)
}
}

View File

@ -8,6 +8,7 @@ import (
"github.com/aquasecurity/kube-bench/check"
"github.com/fatih/color"
"github.com/golang/glog"
)
var (
@ -20,11 +21,35 @@ var (
}
)
func handleError(err error, context string) (errmsg string) {
func printlnWarn(msg string) {
fmt.Fprintf(os.Stderr, "[%s] %s\n",
colors[check.WARN].Sprintf("%s", check.WARN),
msg,
)
}
func sprintlnWarn(msg string) string {
return fmt.Sprintf("[%s] %s",
colors[check.WARN].Sprintf("%s", check.WARN),
msg,
)
}
func exitWithError(err error) {
fmt.Fprintf(os.Stderr, "\n%v\n", err)
os.Exit(1)
}
func continueWithError(err error, msg string) string {
if err != nil {
errmsg = fmt.Sprintf("%s, error: %s\n", context, err)
glog.V(1).Info(err)
}
return
if msg != "" {
fmt.Fprintf(os.Stderr, "%s\n", msg)
}
return ""
}
func cleanIDs(list string) []string {
@ -38,76 +63,85 @@ func cleanIDs(list string) []string {
return ids
}
func verifyConf(confPath ...string) []string {
var w []string
func verifyConf(confPath ...string) {
var missing string
for _, c := range confPath {
if _, err := os.Stat(c); err != nil && os.IsNotExist(err) {
w = append(w, fmt.Sprintf("config file %s does not exist\n", c))
continueWithError(err, "")
missing += c + ", "
}
}
return w
if len(missing) > 0 {
missing = strings.Trim(missing, ", ")
printlnWarn(fmt.Sprintf("Missing kubernetes config files: %s", missing))
}
}
func verifyBin(binPath ...string) []string {
var w []string
var binList string
func verifyBin(binPath ...string) {
var binSlice []string
var bin string
var missing string
var notRunning string
// Construct proc name for ps(1)
for _, b := range binPath {
binList += b + ","
_, err := exec.LookPath(b)
errmsgs += handleError(
err,
fmt.Sprintf("%s: command not found in path", b),
)
bin = bin + "," + b
binSlice = append(binSlice, b)
if err != nil {
missing += b + ", "
continueWithError(err, "")
}
}
bin = strings.Trim(bin, ",")
cmd := exec.Command("ps", "-C", bin, "-o", "cmd", "--no-headers")
out, err := cmd.Output()
if err != nil {
continueWithError(fmt.Errorf("%s: %s", cmd.Args, err), "")
}
binList = strings.Trim(binList, ",")
// Run ps command
cmd := exec.Command("ps", "-C", binList, "-o", "cmd", "--no-headers")
out, err := cmd.Output()
errmsgs += handleError(
err,
fmt.Sprintf("failed to run: %s", cmd.Args),
)
// Actual verification
for _, b := range binPath {
for _, b := range binSlice {
matched := strings.Contains(string(out), b)
if !matched {
w = append(w, fmt.Sprintf("%s is not running\n", b))
notRunning += b + ", "
}
}
return w
if len(missing) > 0 {
missing = strings.Trim(missing, ", ")
printlnWarn(fmt.Sprintf("Missing kubernetes binaries: %s", missing))
}
if len(notRunning) > 0 {
notRunning = strings.Trim(notRunning, ", ")
printlnWarn(fmt.Sprintf("Kubernetes binaries not running: %s", notRunning))
}
}
func verifyKubeVersion(b string) []string {
func verifyKubeVersion(b string) {
// These executables might not be on the user's path.
// TODO! Check the version number using kubectl, which is more likely to be on the path.
var w []string
_, err := exec.LookPath(b)
errmsgs += handleError(
err,
fmt.Sprintf("%s: command not found on path - version check skipped", b),
)
if err != nil {
continueWithError(err, sprintlnWarn("Kubernetes version check skipped"))
return
}
// Check version
cmd := exec.Command(b, "--version")
out, err := cmd.Output()
errmsgs += handleError(
err,
fmt.Sprintf("failed to run:%s", cmd.Args),
)
if err != nil {
continueWithError(err, sprintlnWarn("Kubernetes version check skipped"))
return
}
matched := strings.Contains(string(out), kubeVersion)
if !matched {
w = append(w, fmt.Sprintf("%s unsupported version\n", b))
printlnWarn(fmt.Sprintf("Unsupported kubernetes version: %s", out))
}
return w
}