diff --git a/.gitignore b/.gitignore index 4e258cb..4bfdd88 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ kube-bench *.swp vendor dist +.vscode/ +hack/kind.test.yaml diff --git a/Gopkg.lock b/Gopkg.lock index f06a5d0..4f431e3 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -2,23 +2,30 @@ [[projects]] + digest = "1:938a2672d6ebbb7f7bc63eee3e4b9464c16ffcf77ec8913d3edbf32b4e3984dd" name = "github.com/fatih/color" packages = ["."] + pruneopts = "UT" revision = "570b54cabe6b8eb0bc2dfce68d964677d63b5260" version = "v1.5.0" [[projects]] + digest = "1:38c783cf85b9454cc02a1a8319239800ed0af6c1c864adf19cea0539e134adad" name = "github.com/fsnotify/fsnotify" packages = ["."] + pruneopts = "UT" revision = "4da3e2cfbabc9f751898f250b49f2439785783a1" [[projects]] branch = "master" + digest = "1:1ba1d79f2810270045c328ae5d674321db34e3aae468eb4233883b473c5c0467" name = "github.com/golang/glog" packages = ["."] + pruneopts = "UT" revision = "23def4e6c14b4da8ac2ed8007337bc5eb5007998" [[projects]] + digest = "1:145eff5207977eb95c3649a27cdc2e467d8a1c104810bc12b6a53745a91d823a" name = "github.com/hashicorp/hcl" packages = [ ".", @@ -29,106 +36,140 @@ "hcl/token", "json/parser", "json/scanner", - "json/token" + "json/token", ] + pruneopts = "UT" revision = "23c074d0eceb2b8a5bfdbb271ab780cde70f05a8" [[projects]] + digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be" name = "github.com/inconshreveable/mousetrap" packages = ["."] + pruneopts = "UT" revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75" version = "v1.0" [[projects]] + digest = "1:510a2b056950ed12b4f3ac704ee1b34fd1920cb5b26f60f98d069de89c60adb7" name = "github.com/jinzhu/gorm" packages = [ ".", - "dialects/postgres" + "dialects/postgres", ] + pruneopts = "UT" revision = "5174cc5c242a728b435ea2be8a2f7f998e15429b" version = "v1.0" [[projects]] + digest = "1:2cf1e05311b662e68d938f6655c6c5a44ae0e6b3995e390c75fcd8224afa48df" name = "github.com/jinzhu/inflection" packages = ["."] + pruneopts = "UT" revision = "1c35d901db3da928c72a72d8458480cc9ade058f" [[projects]] + digest = "1:38dd5cee5306173bfe78f8c401edec1e08f772551bee95a85617b4e39347e366" name = "github.com/lib/pq" packages = [ ".", "hstore", - "oid" + "oid", ] + pruneopts = "UT" revision = "83612a56d3dd153a94a629cd64925371c9adad78" [[projects]] + digest = "1:9cbfcfd1ec741817698b844d17b523f54682a5f7c8683b1ec62b6b6542143240" name = "github.com/magiconair/properties" packages = ["."] + pruneopts = "UT" revision = "49d762b9817ba1c2e9d0c69183c2b4a8b8f1d934" [[projects]] + digest = "1:31fcf8f5f8f6cc36ef11182c0a0f63ba464e1a7ad9f0e92a7cf46f38bfc0adfd" name = "github.com/mattn/go-colorable" packages = ["."] + pruneopts = "UT" revision = "5411d3eea5978e6cdc258b30de592b60df6aba96" [[projects]] + digest = "1:fa610f9fe6a93f4a75e64c83673dfff9bf1a34bbb21e6102021b6bc7850834a3" name = "github.com/mattn/go-isatty" packages = ["."] + pruneopts = "UT" revision = "57fdcb988a5c543893cc61bce354a6e24ab70022" [[projects]] + digest = "1:56a0bd1a1f3809171d1abe0bfd389558be0cd672e858e1f831b88f806aa8764f" name = "github.com/mitchellh/mapstructure" packages = ["."] + pruneopts = "UT" revision = "06020f85339e21b2478f756a78e295255ffa4d6a" [[projects]] + digest = "1:128af710970a788bb2348dbcdde6b86fa41f92086297f909d659b6d3affc2902" name = "github.com/pelletier/go-toml" packages = ["."] + pruneopts = "UT" revision = "0131db6d737cfbbfb678f8b7d92e55e27ce46224" [[projects]] + digest = "1:1fccaaeae58b2a2f1af4dbf7eee92ff14f222e161d143bfd20082ef664f91216" name = "github.com/spf13/afero" packages = [ ".", - "mem" + "mem", ] + pruneopts = "UT" revision = "57afd63c68602b63ed976de00dd066ccb3c319db" [[projects]] + digest = "1:9b28ee2984c69d78afe2ce52b1650ba91a6381f355ff08c1d0e53d9e66bd62fe" name = "github.com/spf13/cast" packages = ["."] + pruneopts = "UT" revision = "acbeb36b902d72a7a4c18e8f3241075e7ab763e4" version = "v1.1.0" [[projects]] + digest = "1:7ffc0983035bc7e297da3688d9fe19d60a420e9c38bef23f845c53788ed6a05e" name = "github.com/spf13/cobra" packages = ["."] + pruneopts = "UT" revision = "7b2c5ac9fc04fc5efafb60700713d4fa609b777b" version = "v0.0.1" [[projects]] + digest = "1:9f28b7e326b8cd1db556678b7ce679cf9bf888647e40922f91d9d40f451f942b" name = "github.com/spf13/jwalterweatherman" packages = ["."] + pruneopts = "UT" revision = "12bd96e66386c1960ab0f74ced1362f66f552f7b" [[projects]] + digest = "1:772bf4d1907ccb275aaa532ec6cb0d85fc61bd05648af28551590af3ea4e2d53" name = "github.com/spf13/pflag" packages = ["."] + pruneopts = "UT" revision = "4c012f6dcd9546820e378d0bdda4d8fc772cdfea" [[projects]] + digest = "1:f8e1a678a2571e265f4bf91a3e5e32aa6b1474a55cb0ea849750cc177b664d96" name = "github.com/spf13/viper" packages = ["."] + pruneopts = "UT" revision = "25b30aa063fc18e48662b86996252eabdcf2f0c7" version = "v1.0.0" [[projects]] + digest = "1:c9c0ba9ea00233c41b91e441cfd490f34b129bbfebcb1858979623bd8de07f72" name = "golang.org/x/sys" packages = ["unix"] + pruneopts = "UT" revision = "e24f485414aeafb646f6fca458b0bf869c0880a1" [[projects]] + digest = "1:8029e9743749d4be5bc9f7d42ea1659471767860f0cdc34d37c3111bd308a295" name = "golang.org/x/text" packages = [ "internal/gen", @@ -136,18 +177,29 @@ "internal/ucd", "transform", "unicode/cldr", - "unicode/norm" + "unicode/norm", ] + pruneopts = "UT" revision = "e19ae1496984b1c655b8044a65c0300a3c878dd3" [[projects]] + digest = "1:cb130999076da4717267c51235d62b63228c330ad86160255cbe3276de4c9892" name = "gopkg.in/yaml.v2" packages = ["."] + pruneopts = "UT" revision = "c95af922eae69f190717a0b7148960af8c55a072" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "8d9a1b665b338530deef434f168913ba1184f835aa5bfed3a213a14c613bc17e" + input-imports = [ + "github.com/fatih/color", + "github.com/golang/glog", + "github.com/jinzhu/gorm", + "github.com/jinzhu/gorm/dialects/postgres", + "github.com/spf13/cobra", + "github.com/spf13/viper", + "gopkg.in/yaml.v2", + ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/README.md b/README.md index 08da010..1bfe745 100644 --- a/README.md +++ b/README.md @@ -28,45 +28,50 @@ You can choose to You can avoid installing kube-bench on the host by running it inside a container using the host PID namespace and mounting the `/etc` and `/var` directories where the configuration and other files are located on the host, so that kube-bench can check their existence and permissions. ``` -docker run --pid=host -v /etc:/etc:ro -v /var:/var:ro -t aquasec/kube-bench:latest +docker run --pid=host -v /etc:/etc:ro -v /var:/var:ro -t aquasec/kube-bench:latest [master|node] ``` You can even use your own configs by mounting them over the default ones in `/opt/kube-bench/cfg/` ``` -docker run --pid=host -v /etc:/etc:ro -v /var:/var:ro -t -v path/to/my-config.yaml:/opt/kube-bench/cfg/config.yaml aquasec/kube-bench:latest +docker run --pid=host -v /etc:/etc:ro -v /var:/var:ro -t -v path/to/my-config.yaml:/opt/kube-bench/cfg/config.yaml aquasec/kube-bench:latest [master|node] ``` > Note: the tests require either the kubelet or kubectl binary in the path in order to know the Kubernetes version. You can pass `-v $(which kubectl):/usr/bin/kubectl` to the above invocations to resolve this. ### Running in a kubernetes cluster -You can run kube-bench inside a pod, but it will need access to the host's PID namespace in order to check the running processes, as well as access to some directories on the host where config files and other files are stored. +You can run kube-bench inside a pod, but it will need access to the host's PID namespace in order to check the running processes, as well as access to some directories on the host where config files and other files are stored. -To run the tests on the master node, the pod needs to be scheduled on that node. This involves setting a nodeSelector and tolerations in the pod spec. +Master nodes are automatically detected by kube-bench and will run master checks when possible. +The detection is done by verifying that mandatory components for master are running. (see [config file](#configuration). -The supplied `job-node.yaml` and `job-master.yaml` files can be applied to run the tests as a job. For example: +The supplied `job.yaml` file can be applied to run the tests as a job. For example: ```bash -$ kubectl apply -f job-master.yaml -job.batch/kube-bench-master created +$ kubectl apply -f job.yaml +job.batch/kube-bench created $ kubectl get pods NAME READY STATUS RESTARTS AGE -kube-bench-master-j76s9 0/1 ContainerCreating 0 3s +kube-bench-j76s9 0/1 ContainerCreating 0 3s # Wait for a few seconds for the job to complete $ kubectl get pods NAME READY STATUS RESTARTS AGE -kube-bench-master-j76s9 0/1 Completed 0 11s +kube-bench-j76s9 0/1 Completed 0 11s # The results are held in the pod's logs -k logs kube-bench-master-j76s9 +k logs kube-bench-j76s9 [INFO] 1 Master Node Security Configuration [INFO] 1.1 API Server ... ``` +You can still force to run specific master or node checks using respectively `job-master.yaml` and `job-node.yaml`. + +To run the tests on the master node, the pod needs to be scheduled on that node. This involves setting a nodeSelector and tolerations in the pod spec. + The default labels applied to master nodes has changed since Kubernetes 1.11, so if you are using an older version you may need to modify the nodeSelector and tolerations to run the job on the master node. ### Installing from a container @@ -77,7 +82,7 @@ This command copies the kube-bench binary and configuration files to your host f docker run --rm -v `pwd`:/host aquasec/kube-bench:latest install ``` -You can then run `./kube-bench `. +You can then run `./kube-bench [master|node]`. ### Installing from sources @@ -93,8 +98,8 @@ go build -o kube-bench . # See all supported options ./kube-bench --help -# Run the all checks on a master node -./kube-bench master +# Run the all checks +./kube-bench ``` @@ -170,3 +175,17 @@ These operations are: Going forward we plan to release updates to kube-bench to add support for new releases of the Benchmark, which in turn we can anticipate being made for each new Kubernetes release. We welcome PRs and issue reports. + +# Testing locally with kind + +Our makefile contains targets to test your current version of kube-bench inside a [Kind](https://kind.sigs.k8s.io/) cluster. This can be very handy if you don't want to run a real kubernetes cluster for development purpose. + +First you'll need to create the cluster using `make kind-test-cluster` this will create a new cluster if it cannot be found on your machine. By default the cluster is named `kube-bench` but you can change the name by using the environment variable `KIND_PROFILE`. + +*If kind cannot be found on your system the target will try to install it using `go get`* + +Next you'll have to build the kube-bench docker image using `make build-docker`, then we will be able to push the docker image to the cluster using `make kind-push`. + +Finally we can use the `make kind-run` target to run the current version of kube-bench in the cluster and follow the logs of pods created. (Ctrl+C to exit) + +Everytime you want to test a change, you'll need to rebuild the docker image and push it to cluster before running it again. ( `make build-docker kind-push kind-run` ) \ No newline at end of file diff --git a/cmd/common.go b/cmd/common.go index 2cfa310..b7f7793 100644 --- a/cmd/common.go +++ b/cmd/common.go @@ -31,32 +31,8 @@ var ( func runChecks(nodetype check.NodeType) { var summary check.Summary - var file string - var err error - var typeConf *viper.Viper - - switch nodetype { - case check.MASTER: - file = masterFile - case check.NODE: - file = nodeFile - case check.FEDERATED: - file = federatedFile - } - - runningVersion := "" - if kubeVersion == "" { - runningVersion, err = getKubeVersion() - if err != nil { - exitWithError(fmt.Errorf("Version check failed: %s\nAlternatively, you can specify the version with --version", err)) - } - } - path, err := getConfigFilePath(kubeVersion, runningVersion, file) - if err != nil { - exitWithError(fmt.Errorf("can't find %s controls file in %s: %v", nodetype, cfgDir, err)) - } - def := filepath.Join(path, file) + def := loadConfig(nodetype) in, err := ioutil.ReadFile(def) if err != nil { exitWithError(fmt.Errorf("error opening %s controls file: %v", nodetype, err)) @@ -64,23 +40,15 @@ func runChecks(nodetype check.NodeType) { glog.V(1).Info(fmt.Sprintf("Using benchmark file: %s\n", def)) - // Merge kubernetes version specific config if any. - viper.SetConfigFile(path + "/config.yaml") - err = viper.MergeInConfig() + // Get the set of exectuables and config files we care about on this type of node. + typeConf := viper.Sub(string(nodetype)) + binmap, err := getBinaries(typeConf) + + // Checks that the executables we need for the node type are running. if err != nil { - if os.IsNotExist(err) { - glog.V(2).Info(fmt.Sprintf("No version-specific config.yaml file in %s", path)) - } else { - exitWithError(fmt.Errorf("couldn't read config file %s: %v", path+"/config.yaml", err)) - } - } else { - glog.V(1).Info(fmt.Sprintf("Using config file: %s\n", viper.ConfigFileUsed())) + exitWithError(err) } - // Get the set of exectuables and config files we care about on this type of node. This also - // checks that the executables we need for the node type are running. - typeConf = viper.Sub(string(nodetype)) - binmap := getBinaries(typeConf) confmap := getConfigFiles(typeConf) svcmap := getServiceFiles(typeConf) @@ -183,3 +151,58 @@ func prettyPrint(r *check.Controls, summary check.Summary) { ) } } + +// loadConfig finds the correct config dir based on the kubernetes version, +// merges any specific config.yaml file found with the main config +// and returns the benchmark file to use. +func loadConfig(nodetype check.NodeType) string { + var file string + var err error + + switch nodetype { + case check.MASTER: + file = masterFile + case check.NODE: + file = nodeFile + case check.FEDERATED: + file = federatedFile + } + + runningVersion := "" + if kubeVersion == "" { + runningVersion, err = getKubeVersion() + if err != nil { + exitWithError(fmt.Errorf("Version check failed: %s\nAlternatively, you can specify the version with --version", err)) + } + } + path, err := getConfigFilePath(kubeVersion, runningVersion, file) + if err != nil { + exitWithError(fmt.Errorf("can't find %s controls file in %s: %v", nodetype, cfgDir, err)) + } + + // Merge kubernetes version specific config if any. + viper.SetConfigFile(path + "/config.yaml") + err = viper.MergeInConfig() + if err != nil { + if os.IsNotExist(err) { + glog.V(2).Info(fmt.Sprintf("No version-specific config.yaml file in %s", path)) + } else { + exitWithError(fmt.Errorf("couldn't read config file %s: %v", path+"/config.yaml", err)) + } + } else { + glog.V(1).Info(fmt.Sprintf("Using config file: %s\n", viper.ConfigFileUsed())) + } + return filepath.Join(path, file) +} + +// isMaster verify if master components are running on the node. +func isMaster() bool { + _ = loadConfig(check.MASTER) + glog.V(2).Info("Checking if the current node is running master components") + masterConf := viper.Sub(string(check.MASTER)) + if _, err := getBinaries(masterConf); err != nil { + glog.V(2).Info(err) + return false + } + return true +} diff --git a/cmd/root.go b/cmd/root.go index 5f525a2..eab044d 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -20,6 +20,7 @@ import ( "os" "github.com/aquasecurity/kube-bench/check" + "github.com/golang/glog" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -34,8 +35,8 @@ var ( pgSQL bool checkList string groupList string - masterFile string - nodeFile string + masterFile = "master.yaml" + nodeFile = "node.yaml" federatedFile string noResults bool noSummary bool @@ -47,6 +48,14 @@ var RootCmd = &cobra.Command{ Use: os.Args[0], Short: "Run CIS Benchmarks checks against a Kubernetes deployment", Long: `This tool runs the CIS Kubernetes Benchmark (https://www.cisecurity.org/benchmark/kubernetes/)`, + Run: func(cmd *cobra.Command, args []string) { + if isMaster() { + glog.V(1).Info("== Running master checks ==\n") + runChecks(check.MASTER) + } + glog.V(1).Info("== Running node checks ==\n") + runChecks(check.NODE) + }, } // Execute adds all child commands to the root command sets flags appropriately. diff --git a/cmd/util.go b/cmd/util.go index 97cd94a..9988cb1 100644 --- a/cmd/util.go +++ b/cmd/util.go @@ -72,8 +72,9 @@ func ps(proc string) string { return string(out) } -// getBinaries finds which of the set of candidate executables are running -func getBinaries(v *viper.Viper) map[string]string { +// getBinaries finds which of the set of candidate executables are running. +// It returns an error if one mandatory executable is not running. +func getBinaries(v *viper.Viper) (map[string]string, error) { binmap := make(map[string]string) for _, component := range v.GetStringSlice("components") { @@ -87,7 +88,7 @@ func getBinaries(v *viper.Viper) map[string]string { if len(bins) > 0 { bin, err := findExecutable(bins) if err != nil && !optional { - exitWithError(fmt.Errorf("need %s executable but none of the candidates are running", component)) + return nil, fmt.Errorf("need %s executable but none of the candidates are running", component) } // Default the executable name that we'll substitute to the name of the component @@ -101,7 +102,7 @@ func getBinaries(v *viper.Viper) map[string]string { } } - return binmap + return binmap, nil } // getConfigFilePath locates the config files we should be using based on either the specified diff --git a/cmd/util_test.go b/cmd/util_test.go index cf8cb0d..1dea45d 100644 --- a/cmd/util_test.go +++ b/cmd/util_test.go @@ -109,38 +109,51 @@ func TestFindExecutable(t *testing.T) { func TestGetBinaries(t *testing.T) { cases := []struct { - config map[string]interface{} - psOut string - exp map[string]string + config map[string]interface{} + psOut string + exp map[string]string + expectErr bool }{ { - config: map[string]interface{}{"components": []string{"apiserver"}, "apiserver": map[string]interface{}{"bins": []string{"apiserver", "kube-apiserver"}}}, - psOut: "kube-apiserver", - exp: map[string]string{"apiserver": "kube-apiserver"}, + config: map[string]interface{}{"components": []string{"apiserver"}, "apiserver": map[string]interface{}{"bins": []string{"apiserver", "kube-apiserver"}}}, + psOut: "kube-apiserver", + exp: map[string]string{"apiserver": "kube-apiserver"}, + expectErr: false, }, { // "thing" is not in the list of components - config: map[string]interface{}{"components": []string{"apiserver"}, "apiserver": map[string]interface{}{"bins": []string{"apiserver", "kube-apiserver"}}, "thing": map[string]interface{}{"bins": []string{"something else", "thing"}}}, - psOut: "kube-apiserver thing", - exp: map[string]string{"apiserver": "kube-apiserver"}, + config: map[string]interface{}{"components": []string{"apiserver"}, "apiserver": map[string]interface{}{"bins": []string{"apiserver", "kube-apiserver"}}, "thing": map[string]interface{}{"bins": []string{"something else", "thing"}}}, + psOut: "kube-apiserver thing", + exp: map[string]string{"apiserver": "kube-apiserver"}, + expectErr: false, }, { // "anotherthing" in list of components but doesn't have a defintion - config: map[string]interface{}{"components": []string{"apiserver", "anotherthing"}, "apiserver": map[string]interface{}{"bins": []string{"apiserver", "kube-apiserver"}}, "thing": map[string]interface{}{"bins": []string{"something else", "thing"}}}, - psOut: "kube-apiserver thing", - exp: map[string]string{"apiserver": "kube-apiserver"}, + config: map[string]interface{}{"components": []string{"apiserver", "anotherthing"}, "apiserver": map[string]interface{}{"bins": []string{"apiserver", "kube-apiserver"}}, "thing": map[string]interface{}{"bins": []string{"something else", "thing"}}}, + psOut: "kube-apiserver thing", + exp: map[string]string{"apiserver": "kube-apiserver"}, + expectErr: false, }, { // more than one component - config: map[string]interface{}{"components": []string{"apiserver", "thing"}, "apiserver": map[string]interface{}{"bins": []string{"apiserver", "kube-apiserver"}}, "thing": map[string]interface{}{"bins": []string{"something else", "thing"}}}, - psOut: "kube-apiserver \nthing", - exp: map[string]string{"apiserver": "kube-apiserver", "thing": "thing"}, + config: map[string]interface{}{"components": []string{"apiserver", "thing"}, "apiserver": map[string]interface{}{"bins": []string{"apiserver", "kube-apiserver"}}, "thing": map[string]interface{}{"bins": []string{"something else", "thing"}}}, + psOut: "kube-apiserver \nthing", + exp: map[string]string{"apiserver": "kube-apiserver", "thing": "thing"}, + expectErr: false, }, { // default binary to component name - config: map[string]interface{}{"components": []string{"apiserver", "thing"}, "apiserver": map[string]interface{}{"bins": []string{"apiserver", "kube-apiserver"}}, "thing": map[string]interface{}{"bins": []string{"something else", "thing"}, "optional": true}}, - psOut: "kube-apiserver \notherthing some params", - exp: map[string]string{"apiserver": "kube-apiserver", "thing": "thing"}, + config: map[string]interface{}{"components": []string{"apiserver", "thing"}, "apiserver": map[string]interface{}{"bins": []string{"apiserver", "kube-apiserver"}}, "thing": map[string]interface{}{"bins": []string{"something else", "thing"}, "optional": true}}, + psOut: "kube-apiserver \notherthing some params", + exp: map[string]string{"apiserver": "kube-apiserver", "thing": "thing"}, + expectErr: false, + }, + { + // missing mandatory component + config: map[string]interface{}{"components": []string{"apiserver", "thing"}, "apiserver": map[string]interface{}{"bins": []string{"apiserver", "kube-apiserver"}}, "thing": map[string]interface{}{"bins": []string{"something else", "thing"}, "optional": true}}, + psOut: "otherthing some params", + exp: map[string]string{"apiserver": "kube-apiserver", "thing": "thing"}, + expectErr: true, }, } @@ -153,8 +166,12 @@ func TestGetBinaries(t *testing.T) { for k, val := range c.config { v.Set(k, val) } - m := getBinaries(v) - if !reflect.DeepEqual(m, c.exp) { + m, err := getBinaries(v) + if c.expectErr { + if err == nil { + t.Fatal("Got nil Expected error") + } + } else if !reflect.DeepEqual(m, c.exp) { t.Fatalf("Got %v\nExpected %v", m, c.exp) } }) diff --git a/hack/debug.yaml b/hack/debug.yaml new file mode 100644 index 0000000..e26e447 --- /dev/null +++ b/hack/debug.yaml @@ -0,0 +1,46 @@ +# use this pod with: kubectl run ubuntu -it --pid=host -- /bin/bash +# this allows you to debug what is running on the host. +apiVersion: v1 +kind: Pod +metadata: + name: ubuntu +spec: + hostPID: true + containers: + - name: ubuntu + image: ubuntu + command: [ "/bin/bash", "-c", "--" ] + args: [ "while true; do sleep 30; done;" ] + volumeMounts: + - name: var-lib-kubelet + mountPath: /var/lib/kubelet + - name: etc-systemd + mountPath: /etc/systemd + - name: etc-kubernetes + mountPath: /etc/kubernetes + # /usr/bin is mounted to access kubectl / kubelet, for auto-detecting the Kubernetes version. + # You can omit this mount if you specify --version as part of the command. + - name: usr-bin + mountPath: /usr/bin + - name: kind-bin + mountPath: /kind/bin + resources: + limits: + memory: "128Mi" + cpu: "500m" + volumes: + - name: var-lib-kubelet + hostPath: + path: "/var/lib/kubelet" + - name: etc-systemd + hostPath: + path: "/etc/systemd" + - name: etc-kubernetes + hostPath: + path: "/etc/kubernetes" + - name: usr-bin + hostPath: + path: "/usr/bin" + - name: kind-bin + hostPath: + path: "/kind/bin" diff --git a/hack/kind.yaml b/hack/kind.yaml new file mode 100644 index 0000000..b58811a --- /dev/null +++ b/hack/kind.yaml @@ -0,0 +1,50 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: kube-bench +spec: + template: + metadata: + labels: + app: kube-bench + spec: + hostPID: true + containers: + - name: kube-bench + image: aquasec/kube-bench:${VERSION} + command: ["kube-bench"] + volumeMounts: + - name: var-lib-etcd + mountPath: /var/lib/etcd + - name: var-lib-kubelet + mountPath: /var/lib/kubelet + - name: etc-systemd + mountPath: /etc/systemd + - name: etc-kubernetes + mountPath: /etc/kubernetes + # /usr/bin is mounted to access kubectl / kubelet, for auto-detecting the Kubernetes version. + # You can omit this mount if you specify --version as part of the command. + - name: usr-bin + mountPath: /usr/bin + - name: kind-bin + mountPath: /kind/bin + restartPolicy: Never + volumes: + - name: var-lib-etcd + hostPath: + path: "/var/lib/etcd" + - name: var-lib-kubelet + hostPath: + path: "/var/lib/kubelet" + - name: etc-systemd + hostPath: + path: "/etc/systemd" + - name: etc-kubernetes + hostPath: + path: "/etc/kubernetes" + - name: usr-bin + hostPath: + path: "/usr/bin" + - name: kind-bin + hostPath: + path: "/kind/bin" diff --git a/job.yaml b/job.yaml new file mode 100644 index 0000000..3fae9e1 --- /dev/null +++ b/job.yaml @@ -0,0 +1,45 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: kube-bench +spec: + template: + metadata: + labels: + app: kube-bench + spec: + hostPID: true + containers: + - name: kube-bench + image: aquasec/kube-bench:latest + command: ["kube-bench"] + volumeMounts: + - name: var-lib-etcd + mountPath: /var/lib/etcd + - name: var-lib-kubelet + mountPath: /var/lib/kubelet + - name: etc-systemd + mountPath: /etc/systemd + - name: etc-kubernetes + mountPath: /etc/kubernetes + # /usr/bin is mounted to access kubectl / kubelet, for auto-detecting the Kubernetes version. + # You can omit this mount if you specify --version as part of the command. + - name: usr-bin + mountPath: /usr/bin + restartPolicy: Never + volumes: + - name: var-lib-etcd + hostPath: + path: "/var/lib/etcd" + - name: var-lib-kubelet + hostPath: + path: "/var/lib/kubelet" + - name: etc-systemd + hostPath: + path: "/etc/systemd" + - name: etc-kubernetes + hostPath: + path: "/etc/kubernetes" + - name: usr-bin + hostPath: + path: "/usr/bin" diff --git a/makefile b/makefile index c0a68dd..2e1bc29 100644 --- a/makefile +++ b/makefile @@ -1,7 +1,72 @@ SOURCES := $(shell find . -name '*.go') -TARGET_OS := linux BINARY := kube-bench +DOCKER_REGISTRY ?= aquasec +VERSION ?= $(shell git rev-parse --short=7 HEAD) +IMAGE_NAME ?= $(DOCKER_REGISTRY)/$(BINARY):$(VERSION) +TARGET_OS := linux +BUILD_OS := linux +uname := $(shell uname -s) + +ifneq ($(findstring Microsoft,$(shell uname -r)),) + BUILD_OS := windows +else ifeq ($(uname),Linux) + BUILD_OS := linux +else ifeq ($(uname),Darwin) + BUILD_OS := darwin +endif + +# kind cluster name to use +KIND_PROFILE ?= kube-bench +KIND_CONTAINER_NAME=$(KIND_PROFILE)-control-plane + +build: kube-bench $(BINARY): $(SOURCES) GOOS=$(TARGET_OS) go build -o $(BINARY) . +# builds the current dev docker version +build-docker: + docker build --build-arg BUILD_DATE=$(shell date -u +"%Y-%m-%dT%H:%M:%SZ") \ + --build-arg VCS_REF=$(shell git rev-parse --short HEAD) \ + -t $(IMAGE_NAME) . + +tests: + go test -race -timeout 30s -cover ./cmd ./check + +# creates a kind cluster to be used for development. +HAS_KIND := $(shell command -v kind;) +kind-test-cluster: +ifndef HAS_KIND + go get -u sigs.k8s.io/kind +endif + @if [ -z $$(kind get clusters | grep $(KIND_PROFILE)) ]; then\ + echo "Could not find $(KIND_PROFILE) cluster. Creating...";\ + kind create cluster --name $(KIND_PROFILE) --image kindest/node:v1.11.3 --wait 5m;\ + fi + +# pushses the current dev version to the kind cluster. +kind-push: + docker save $(IMAGE_NAME) -o kube-bench.tar.gz; \ + docker cp kube-bench.tar.gz $(KIND_CONTAINER_NAME):/kube-bench.tar.gz; \ + docker exec $(KIND_CONTAINER_NAME) docker load -i /kube-bench.tar.gz; + -rm -f kube-bench.tar.gz + +# runs the current version on kind using a job and follow logs +kind-run: KUBECONFIG = "$(shell kind get kubeconfig-path --name="$(KIND_PROFILE)")" +kind-run: ensure-stern + sed "s/\$${VERSION}/$(VERSION)/" ./hack/kind.yaml > ./hack/kind.test.yaml + -KUBECONFIG=$(KUBECONFIG) \ + kubectl delete job kube-bench + KUBECONFIG=$(KUBECONFIG) \ + kubectl apply -f ./hack/kind.test.yaml + KUBECONFIG=$(KUBECONFIG) \ + stern -l app=kube-bench --container kube-bench + +# ensures that stern is installed +HAS_STERN := $(shell command -v stern;) +ensure-stern: +ifndef HAS_STERN + curl -LO https://github.com/wercker/stern/releases/download/1.10.0/stern_$(BUILD_OS)_amd64 && \ + chmod +rx ./stern_$(BUILD_OS)_amd64 && \ + mv ./stern_$(BUILD_OS)_amd64 /usr/local/bin/stern +endif