diff --git a/.travis.yml b/.travis.yml index 9528ceb..16d33a5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,11 @@ --- language: go +sudo: required + +services: + - docker + notifications: email: false @@ -16,6 +21,10 @@ install: script: - go test ./... + - docker build --tag kube-bench . + - docker run -v `pwd`:/host kube-bench install + - test -d cfg + - test -f kube-bench after_success: - test -n "$TRAVIS_TAG" && curl -sL https://git.io/goreleaser | bash diff --git a/Dockerfile b/Dockerfile index 10f1676..0a0fbad 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,22 @@ -FROM golang:1.9 -WORKDIR /kube-bench -RUN go get github.com/aquasecurity/kube-bench +FROM golang:1.9 AS build +WORKDIR /go/src/github.com/aquasecurity/kube-bench/ +ADD glide.lock glide.yaml ./ +RUN go get github.com/Masterminds/glide && glide install +ADD main.go . +ADD check/ check/ +ADD cmd/ cmd/ +RUN CGO_ENABLED=0 go install -a -ldflags '-w' -FROM alpine:latest -WORKDIR / -COPY --from=0 /go/bin/kube-bench /kube-bench -COPY --from=0 /go/src/github.com/aquasecurity/kube-bench/cfg /cfg -COPY --from=0 /go/src/github.com/aquasecurity/kube-bench/entrypoint.sh /entrypoint.sh -ENTRYPOINT /entrypoint.sh +FROM alpine:3.7 AS run +WORKDIR /opt/kube-bench/ +# add GNU ps for -C, -o cmd, and --no-headers support +# https://github.com/aquasecurity/kube-bench/issues/109 +RUN apk --no-cache add procps +COPY --from=build /go/bin/kube-bench /usr/local/bin/kube-bench +ADD entrypoint.sh . +ADD cfg/ cfg/ +ENTRYPOINT ["./entrypoint.sh"] +CMD ["install"] # Build-time metadata as defined at http://label-schema.org ARG BUILD_DATE diff --git a/README.md b/README.md index 1c1369d..ab62ef9 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,9 @@ [![Docker image](https://images.microbadger.com/badges/image/aquasec/kube-bench.svg)](https://microbadger.com/images/aquasec/kube-bench "Get your own image badge on microbadger.com") [![Source commit](https://images.microbadger.com/badges/commit/aquasec/kube-bench.svg)](https://microbadger.com/images/aquasec/kube-bench) -# kube-bench +kube-bench logo -The Kubernetes Bench for Security is a Go application that checks whether Kubernetes is deployed securely by running the checks documented in the CIS Kubernetes Benchmark. +kube-bench is a Go application that checks whether Kubernetes is deployed securely by running the checks documented in the CIS Kubernetes Benchmark. Tests are configured with YAML files, making this tool easy to update as test specifications evolve. @@ -17,12 +17,37 @@ kube-bench supports the tests for multiple versions of Kubernetes (1.6, 1.7 and ## Installation -You can either install kube-bench through a dedicated container, or compile it from source: +You can choose to +* run kube-bench from inside a container (sharing PID namespace with the host) +* run a container that installs kube-bench on the host, and then run kube-bench directly on the host +* install the latest binaries from the [Releases page](https://github.com/aquasecurity/kube-bench/releases), +* compile it from source. -1. Container installation: -Run ```docker run --rm -v `pwd`:/host aquasec/kube-bench:latest```. This will copy the kube-bench binary and configuration to you host. You can then run ```./kube-bench ```. +### Running inside a container + +You can avoid installing kube-bench on the host by running it inside a container using the host PID namespace. + +``` +docker run --pid=host aquasec/kube-bench:latest +``` + +You can even use your own configs by mounting them over the default ones in `/opt/kube-bench/cfg/` + +``` +docker run --pid=host -v path/to/my-config.yaml:/opt/kube-bench/cfg/config.yaml aquasec/kube-bench:latest +``` + +### Installing from a container + +This command copies the kube-bench binary and configuration files to your host from the Docker container: +``` +docker run --rm -v `pwd`:/host aquasec/kube-bench:latest install +``` + +You can then run `./kube-bench `. + +### Installing from sources -2. Install from sources: If Go is installed on the target machines, you can simply clone this repository and run as follows (assuming your [$GOPATH is set](https://github.com/golang/go/wiki/GOPATH)): ```go get github.com/aquasecurity/kube-bench diff --git a/cfg/1.8/master.yaml b/cfg/1.8/master.yaml index bd97599..7fb9dfa 100644 --- a/cfg/1.8/master.yaml +++ b/cfg/1.8/master.yaml @@ -610,7 +610,7 @@ groups: remediation: | Edit the API server pod specification file $apiserverconf and set the below parameter as appropriate and if needed. For example, - --request-timeout=300 + --request-timeout=300s scored: true - id: 1.2 diff --git a/cmd/common.go b/cmd/common.go index 752a337..9e4cfef 100644 --- a/cmd/common.go +++ b/cmd/common.go @@ -47,7 +47,13 @@ func runChecks(t check.NodeType) { nodetype = "federated" } - ver := getKubeVersion() + var ver string + if kubeVersion != "" { + ver = kubeVersion + } else { + ver = getKubeVersion() + } + switch ver { case "1.9", "1.10": continueWithError(nil, fmt.Sprintf("No CIS spec for %s - using tests from CIS 1.2.0 spec for Kubernetes 1.8\n", ver)) @@ -131,41 +137,48 @@ func colorPrint(state check.State, s string) { // prettyPrint outputs the results to stdout in human-readable format func prettyPrint(r *check.Controls, summary check.Summary) { - 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)) - for _, c := range g.Checks { - colorPrint(c.State, fmt.Sprintf("%s %s\n", c.ID, c.Text)) + // Print check results. + if !noResults { + 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)) + for _, c := range g.Checks { + colorPrint(c.State, fmt.Sprintf("%s %s\n", c.ID, c.Text)) + } } - } - fmt.Println() + fmt.Println() + } // Print remediations. - if summary.Fail > 0 || summary.Warn > 0 { - colors[check.WARN].Printf("== Remediations ==\n") - for _, g := range r.Groups { - for _, c := range g.Checks { - if c.State != check.PASS { - fmt.Printf("%s %s\n", c.ID, c.Remediation) + if !noRemediations { + if summary.Fail > 0 || summary.Warn > 0 { + colors[check.WARN].Printf("== Remediations ==\n") + for _, g := range r.Groups { + for _, c := range g.Checks { + if c.State != check.PASS { + fmt.Printf("%s %s\n", c.ID, c.Remediation) + } } } + fmt.Println() } - fmt.Println() } // Print summary setting output color to highest severity. - var res check.State - if summary.Fail > 0 { - res = check.FAIL - } else if summary.Warn > 0 { - res = check.WARN - } else { - res = check.PASS - } + if !noSummary { + var res check.State + if summary.Fail > 0 { + res = check.FAIL + } else if summary.Warn > 0 { + res = check.WARN + } else { + res = check.PASS + } - colors[res].Printf("== Summary ==\n") - fmt.Printf("%d checks PASS\n%d checks FAIL\n%d checks WARN\n", - summary.Pass, summary.Fail, summary.Warn, - ) + colors[res].Printf("== Summary ==\n") + fmt.Printf("%d checks PASS\n%d checks FAIL\n%d checks WARN\n", + summary.Pass, summary.Fail, summary.Warn, + ) + } } diff --git a/cmd/root.go b/cmd/root.go index 76d871a..9f8aa4d 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -26,9 +26,10 @@ import ( var ( envVarsPrefix = "KUBE_BENCH" - cfgDir = "./cfg" defaultKubeVersion = "1.6" + kubeVersion string cfgFile string + cfgDir string jsonFmt bool pgSQL bool checkList string @@ -36,6 +37,9 @@ var ( masterFile string nodeFile string federatedFile string + noResults bool + noSummary bool + noRemediations bool ) // RootCmd represents the base command when called without any subcommands @@ -60,8 +64,13 @@ func Execute() { func init() { cobra.OnInitialize(initConfig) + // Output control + RootCmd.PersistentFlags().BoolVar(&noResults, "noresults", false, "Disable prints of results section") + RootCmd.PersistentFlags().BoolVar(&noSummary, "nosummary", false, "Disable printing of summary section") + RootCmd.PersistentFlags().BoolVar(&noRemediations, "noremediations", false, "Disable printing of remediations section") RootCmd.PersistentFlags().BoolVar(&jsonFmt, "json", false, "Prints the results as JSON") RootCmd.PersistentFlags().BoolVar(&pgSQL, "pgsql", false, "Save the results to PostgreSQL") + RootCmd.PersistentFlags().StringVarP( &checkList, "check", @@ -77,6 +86,8 @@ 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().StringVarP(&cfgDir, "config-dir", "D", "./cfg/", "config directory") + RootCmd.PersistentFlags().StringVar(&kubeVersion, "version", "", "Manually specify Kubernetes version, automatically detected if unset") goflag.CommandLine.VisitAll(func(goflag *goflag.Flag) { RootCmd.PersistentFlags().AddGoFlag(goflag) diff --git a/cmd/util.go b/cmd/util.go index 7b8e9de..ab78945 100644 --- a/cmd/util.go +++ b/cmd/util.go @@ -219,7 +219,7 @@ func getKubeVersion() string { if err != nil { _, err = exec.LookPath("kubelet") if err != nil { - exitWithError(fmt.Errorf("Version check failed: need kubectl or kubelet binaries to get kubernetes version")) + exitWithError(fmt.Errorf("Version check failed: need kubectl or kubelet binaries to get kubernetes version.\nAlternately, you can specify the version with --version")) } return getKubeVersionFromKubelet() } @@ -240,7 +240,7 @@ func getKubeVersionFromKubectl() string { func getKubeVersionFromKubelet() string { cmd := exec.Command("kubelet", "--version") out, err := cmd.CombinedOutput() - + if err != nil { continueWithError(fmt.Errorf("%s", out), "") } diff --git a/entrypoint.sh b/entrypoint.sh index ad28fbf..b06f083 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,14 +1,19 @@ -#!/bin/sh -if [ -d /host ]; then - mkdir -p /host/cfg/ - yes | cp -rf /cfg/* /host/cfg/ - yes | cp -rf /kube-bench /host/ - echo "===============================================" - echo "kube-bench is now installed on your host " - echo "Run ./kube-bench to perform a security check " - echo "===============================================" +#!/bin/sh -e +if [ "$1" == "install" ]; then + if [ -d /host ]; then + mkdir -p /host/cfg/ + yes | cp -rf cfg/* /host/cfg/ + yes | cp -rf /usr/local/bin/kube-bench /host/ + echo "===============================================" + echo "kube-bench is now installed on your host " + echo "Run ./kube-bench to perform a security check " + echo "===============================================" + else + echo "Usage:" + echo " install: docker run --rm -v \`pwd\`:/host aquasec/kube-bench install" + echo " run: docker run --rm --pid=host aquasec/kube-bench [command]" + exit + fi else - echo "Usage:" - echo " docker run --rm -v \`pwd\`:/host aquasec/kube-bench" - exit + exec kube-bench "$@" fi diff --git a/hooks/build b/hooks/build old mode 100644 new mode 100755 diff --git a/images/kube-bench.png b/images/kube-bench.png new file mode 100644 index 0000000..c135396 Binary files /dev/null and b/images/kube-bench.png differ diff --git a/images/kube-bench.svg b/images/kube-bench.svg new file mode 100644 index 0000000..ba64a9e --- /dev/null +++ b/images/kube-bench.svg @@ -0,0 +1,121 @@ + +image/svg+xml \ No newline at end of file