1
0
mirror of https://github.com/aquasecurity/kube-bench.git synced 2024-11-25 01:18:12 +00:00

Adds master node detection and a root command that automatically detect checks to run.

The root command will run node checks and if possible master checks.
I've also added some Makefile targets to improve local testing and improve the documentation.
This commit is contained in:
Cyril Tovena 2019-03-07 12:02:43 -05:00
parent 0a58805cdb
commit 5baf81a70a
11 changed files with 414 additions and 85 deletions

2
.gitignore vendored
View File

@ -2,3 +2,5 @@ kube-bench
*.swp *.swp
vendor vendor
dist dist
.vscode/
hack/kind.test.yaml

64
Gopkg.lock generated
View File

@ -2,23 +2,30 @@
[[projects]] [[projects]]
digest = "1:938a2672d6ebbb7f7bc63eee3e4b9464c16ffcf77ec8913d3edbf32b4e3984dd"
name = "github.com/fatih/color" name = "github.com/fatih/color"
packages = ["."] packages = ["."]
pruneopts = "UT"
revision = "570b54cabe6b8eb0bc2dfce68d964677d63b5260" revision = "570b54cabe6b8eb0bc2dfce68d964677d63b5260"
version = "v1.5.0" version = "v1.5.0"
[[projects]] [[projects]]
digest = "1:38c783cf85b9454cc02a1a8319239800ed0af6c1c864adf19cea0539e134adad"
name = "github.com/fsnotify/fsnotify" name = "github.com/fsnotify/fsnotify"
packages = ["."] packages = ["."]
pruneopts = "UT"
revision = "4da3e2cfbabc9f751898f250b49f2439785783a1" revision = "4da3e2cfbabc9f751898f250b49f2439785783a1"
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:1ba1d79f2810270045c328ae5d674321db34e3aae468eb4233883b473c5c0467"
name = "github.com/golang/glog" name = "github.com/golang/glog"
packages = ["."] packages = ["."]
pruneopts = "UT"
revision = "23def4e6c14b4da8ac2ed8007337bc5eb5007998" revision = "23def4e6c14b4da8ac2ed8007337bc5eb5007998"
[[projects]] [[projects]]
digest = "1:145eff5207977eb95c3649a27cdc2e467d8a1c104810bc12b6a53745a91d823a"
name = "github.com/hashicorp/hcl" name = "github.com/hashicorp/hcl"
packages = [ packages = [
".", ".",
@ -29,106 +36,140 @@
"hcl/token", "hcl/token",
"json/parser", "json/parser",
"json/scanner", "json/scanner",
"json/token" "json/token",
] ]
pruneopts = "UT"
revision = "23c074d0eceb2b8a5bfdbb271ab780cde70f05a8" revision = "23c074d0eceb2b8a5bfdbb271ab780cde70f05a8"
[[projects]] [[projects]]
digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be"
name = "github.com/inconshreveable/mousetrap" name = "github.com/inconshreveable/mousetrap"
packages = ["."] packages = ["."]
pruneopts = "UT"
revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75" revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
version = "v1.0" version = "v1.0"
[[projects]] [[projects]]
digest = "1:510a2b056950ed12b4f3ac704ee1b34fd1920cb5b26f60f98d069de89c60adb7"
name = "github.com/jinzhu/gorm" name = "github.com/jinzhu/gorm"
packages = [ packages = [
".", ".",
"dialects/postgres" "dialects/postgres",
] ]
pruneopts = "UT"
revision = "5174cc5c242a728b435ea2be8a2f7f998e15429b" revision = "5174cc5c242a728b435ea2be8a2f7f998e15429b"
version = "v1.0" version = "v1.0"
[[projects]] [[projects]]
digest = "1:2cf1e05311b662e68d938f6655c6c5a44ae0e6b3995e390c75fcd8224afa48df"
name = "github.com/jinzhu/inflection" name = "github.com/jinzhu/inflection"
packages = ["."] packages = ["."]
pruneopts = "UT"
revision = "1c35d901db3da928c72a72d8458480cc9ade058f" revision = "1c35d901db3da928c72a72d8458480cc9ade058f"
[[projects]] [[projects]]
digest = "1:38dd5cee5306173bfe78f8c401edec1e08f772551bee95a85617b4e39347e366"
name = "github.com/lib/pq" name = "github.com/lib/pq"
packages = [ packages = [
".", ".",
"hstore", "hstore",
"oid" "oid",
] ]
pruneopts = "UT"
revision = "83612a56d3dd153a94a629cd64925371c9adad78" revision = "83612a56d3dd153a94a629cd64925371c9adad78"
[[projects]] [[projects]]
digest = "1:9cbfcfd1ec741817698b844d17b523f54682a5f7c8683b1ec62b6b6542143240"
name = "github.com/magiconair/properties" name = "github.com/magiconair/properties"
packages = ["."] packages = ["."]
pruneopts = "UT"
revision = "49d762b9817ba1c2e9d0c69183c2b4a8b8f1d934" revision = "49d762b9817ba1c2e9d0c69183c2b4a8b8f1d934"
[[projects]] [[projects]]
digest = "1:31fcf8f5f8f6cc36ef11182c0a0f63ba464e1a7ad9f0e92a7cf46f38bfc0adfd"
name = "github.com/mattn/go-colorable" name = "github.com/mattn/go-colorable"
packages = ["."] packages = ["."]
pruneopts = "UT"
revision = "5411d3eea5978e6cdc258b30de592b60df6aba96" revision = "5411d3eea5978e6cdc258b30de592b60df6aba96"
[[projects]] [[projects]]
digest = "1:fa610f9fe6a93f4a75e64c83673dfff9bf1a34bbb21e6102021b6bc7850834a3"
name = "github.com/mattn/go-isatty" name = "github.com/mattn/go-isatty"
packages = ["."] packages = ["."]
pruneopts = "UT"
revision = "57fdcb988a5c543893cc61bce354a6e24ab70022" revision = "57fdcb988a5c543893cc61bce354a6e24ab70022"
[[projects]] [[projects]]
digest = "1:56a0bd1a1f3809171d1abe0bfd389558be0cd672e858e1f831b88f806aa8764f"
name = "github.com/mitchellh/mapstructure" name = "github.com/mitchellh/mapstructure"
packages = ["."] packages = ["."]
pruneopts = "UT"
revision = "06020f85339e21b2478f756a78e295255ffa4d6a" revision = "06020f85339e21b2478f756a78e295255ffa4d6a"
[[projects]] [[projects]]
digest = "1:128af710970a788bb2348dbcdde6b86fa41f92086297f909d659b6d3affc2902"
name = "github.com/pelletier/go-toml" name = "github.com/pelletier/go-toml"
packages = ["."] packages = ["."]
pruneopts = "UT"
revision = "0131db6d737cfbbfb678f8b7d92e55e27ce46224" revision = "0131db6d737cfbbfb678f8b7d92e55e27ce46224"
[[projects]] [[projects]]
digest = "1:1fccaaeae58b2a2f1af4dbf7eee92ff14f222e161d143bfd20082ef664f91216"
name = "github.com/spf13/afero" name = "github.com/spf13/afero"
packages = [ packages = [
".", ".",
"mem" "mem",
] ]
pruneopts = "UT"
revision = "57afd63c68602b63ed976de00dd066ccb3c319db" revision = "57afd63c68602b63ed976de00dd066ccb3c319db"
[[projects]] [[projects]]
digest = "1:9b28ee2984c69d78afe2ce52b1650ba91a6381f355ff08c1d0e53d9e66bd62fe"
name = "github.com/spf13/cast" name = "github.com/spf13/cast"
packages = ["."] packages = ["."]
pruneopts = "UT"
revision = "acbeb36b902d72a7a4c18e8f3241075e7ab763e4" revision = "acbeb36b902d72a7a4c18e8f3241075e7ab763e4"
version = "v1.1.0" version = "v1.1.0"
[[projects]] [[projects]]
digest = "1:7ffc0983035bc7e297da3688d9fe19d60a420e9c38bef23f845c53788ed6a05e"
name = "github.com/spf13/cobra" name = "github.com/spf13/cobra"
packages = ["."] packages = ["."]
pruneopts = "UT"
revision = "7b2c5ac9fc04fc5efafb60700713d4fa609b777b" revision = "7b2c5ac9fc04fc5efafb60700713d4fa609b777b"
version = "v0.0.1" version = "v0.0.1"
[[projects]] [[projects]]
digest = "1:9f28b7e326b8cd1db556678b7ce679cf9bf888647e40922f91d9d40f451f942b"
name = "github.com/spf13/jwalterweatherman" name = "github.com/spf13/jwalterweatherman"
packages = ["."] packages = ["."]
pruneopts = "UT"
revision = "12bd96e66386c1960ab0f74ced1362f66f552f7b" revision = "12bd96e66386c1960ab0f74ced1362f66f552f7b"
[[projects]] [[projects]]
digest = "1:772bf4d1907ccb275aaa532ec6cb0d85fc61bd05648af28551590af3ea4e2d53"
name = "github.com/spf13/pflag" name = "github.com/spf13/pflag"
packages = ["."] packages = ["."]
pruneopts = "UT"
revision = "4c012f6dcd9546820e378d0bdda4d8fc772cdfea" revision = "4c012f6dcd9546820e378d0bdda4d8fc772cdfea"
[[projects]] [[projects]]
digest = "1:f8e1a678a2571e265f4bf91a3e5e32aa6b1474a55cb0ea849750cc177b664d96"
name = "github.com/spf13/viper" name = "github.com/spf13/viper"
packages = ["."] packages = ["."]
pruneopts = "UT"
revision = "25b30aa063fc18e48662b86996252eabdcf2f0c7" revision = "25b30aa063fc18e48662b86996252eabdcf2f0c7"
version = "v1.0.0" version = "v1.0.0"
[[projects]] [[projects]]
digest = "1:c9c0ba9ea00233c41b91e441cfd490f34b129bbfebcb1858979623bd8de07f72"
name = "golang.org/x/sys" name = "golang.org/x/sys"
packages = ["unix"] packages = ["unix"]
pruneopts = "UT"
revision = "e24f485414aeafb646f6fca458b0bf869c0880a1" revision = "e24f485414aeafb646f6fca458b0bf869c0880a1"
[[projects]] [[projects]]
digest = "1:8029e9743749d4be5bc9f7d42ea1659471767860f0cdc34d37c3111bd308a295"
name = "golang.org/x/text" name = "golang.org/x/text"
packages = [ packages = [
"internal/gen", "internal/gen",
@ -136,18 +177,29 @@
"internal/ucd", "internal/ucd",
"transform", "transform",
"unicode/cldr", "unicode/cldr",
"unicode/norm" "unicode/norm",
] ]
pruneopts = "UT"
revision = "e19ae1496984b1c655b8044a65c0300a3c878dd3" revision = "e19ae1496984b1c655b8044a65c0300a3c878dd3"
[[projects]] [[projects]]
digest = "1:cb130999076da4717267c51235d62b63228c330ad86160255cbe3276de4c9892"
name = "gopkg.in/yaml.v2" name = "gopkg.in/yaml.v2"
packages = ["."] packages = ["."]
pruneopts = "UT"
revision = "c95af922eae69f190717a0b7148960af8c55a072" revision = "c95af922eae69f190717a0b7148960af8c55a072"
[solve-meta] [solve-meta]
analyzer-name = "dep" analyzer-name = "dep"
analyzer-version = 1 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-name = "gps-cdcl"
solver-version = 1 solver-version = 1

View File

@ -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. 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 <master|node> 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/` 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 <master|node> 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. > 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 ### 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 ```bash
$ kubectl apply -f job-master.yaml $ kubectl apply -f job.yaml
job.batch/kube-bench-master created job.batch/kube-bench created
$ kubectl get pods $ kubectl get pods
NAME READY STATUS RESTARTS AGE 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 # Wait for a few seconds for the job to complete
$ kubectl get pods $ kubectl get pods
NAME READY STATUS RESTARTS AGE 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 # 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 Master Node Security Configuration
[INFO] 1.1 API Server [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. 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 ### 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 docker run --rm -v `pwd`:/host aquasec/kube-bench:latest install
``` ```
You can then run `./kube-bench <master|node>`. You can then run `./kube-bench [master|node]`.
### Installing from sources ### Installing from sources
@ -93,8 +98,8 @@ go build -o kube-bench .
# See all supported options # See all supported options
./kube-bench --help ./kube-bench --help
# Run the all checks on a master node # Run the all checks
./kube-bench master ./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. 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. 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` )

View File

@ -31,32 +31,8 @@ var (
func runChecks(nodetype check.NodeType) { func runChecks(nodetype check.NodeType) {
var summary check.Summary var summary check.Summary
var file string
var err error
var typeConf *viper.Viper
switch nodetype { def := loadConfig(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)
in, err := ioutil.ReadFile(def) in, err := ioutil.ReadFile(def)
if err != nil { if err != nil {
exitWithError(fmt.Errorf("error opening %s controls file: %v", nodetype, err)) 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)) glog.V(1).Info(fmt.Sprintf("Using benchmark file: %s\n", def))
// Merge kubernetes version specific config if any. // Get the set of exectuables and config files we care about on this type of node.
viper.SetConfigFile(path + "/config.yaml") typeConf := viper.Sub(string(nodetype))
err = viper.MergeInConfig() binmap, err := getBinaries(typeConf)
// Checks that the executables we need for the node type are running.
if err != nil { if err != nil {
if os.IsNotExist(err) { exitWithError(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()))
} }
// 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) confmap := getConfigFiles(typeConf)
svcmap := getServiceFiles(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
}

View File

@ -20,6 +20,7 @@ import (
"os" "os"
"github.com/aquasecurity/kube-bench/check" "github.com/aquasecurity/kube-bench/check"
"github.com/golang/glog"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
@ -34,8 +35,8 @@ var (
pgSQL bool pgSQL bool
checkList string checkList string
groupList string groupList string
masterFile string masterFile = "master.yaml"
nodeFile string nodeFile = "node.yaml"
federatedFile string federatedFile string
noResults bool noResults bool
noSummary bool noSummary bool
@ -47,6 +48,14 @@ var RootCmd = &cobra.Command{
Use: os.Args[0], Use: os.Args[0],
Short: "Run CIS Benchmarks checks against a Kubernetes deployment", Short: "Run CIS Benchmarks checks against a Kubernetes deployment",
Long: `This tool runs the CIS Kubernetes Benchmark (https://www.cisecurity.org/benchmark/kubernetes/)`, 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. // Execute adds all child commands to the root command sets flags appropriately.

View File

@ -72,8 +72,9 @@ func ps(proc string) string {
return string(out) return string(out)
} }
// getBinaries finds which of the set of candidate executables are running // getBinaries finds which of the set of candidate executables are running.
func getBinaries(v *viper.Viper) map[string]string { // 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) binmap := make(map[string]string)
for _, component := range v.GetStringSlice("components") { for _, component := range v.GetStringSlice("components") {
@ -87,7 +88,7 @@ func getBinaries(v *viper.Viper) map[string]string {
if len(bins) > 0 { if len(bins) > 0 {
bin, err := findExecutable(bins) bin, err := findExecutable(bins)
if err != nil && !optional { 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 // 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 // getConfigFilePath locates the config files we should be using based on either the specified

View File

@ -109,38 +109,51 @@ func TestFindExecutable(t *testing.T) {
func TestGetBinaries(t *testing.T) { func TestGetBinaries(t *testing.T) {
cases := []struct { cases := []struct {
config map[string]interface{} config map[string]interface{}
psOut string psOut string
exp map[string]string exp map[string]string
expectErr bool
}{ }{
{ {
config: map[string]interface{}{"components": []string{"apiserver"}, "apiserver": map[string]interface{}{"bins": []string{"apiserver", "kube-apiserver"}}}, config: map[string]interface{}{"components": []string{"apiserver"}, "apiserver": map[string]interface{}{"bins": []string{"apiserver", "kube-apiserver"}}},
psOut: "kube-apiserver", psOut: "kube-apiserver",
exp: map[string]string{"apiserver": "kube-apiserver"}, exp: map[string]string{"apiserver": "kube-apiserver"},
expectErr: false,
}, },
{ {
// "thing" is not in the list of components // "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"}}}, 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", psOut: "kube-apiserver thing",
exp: map[string]string{"apiserver": "kube-apiserver"}, exp: map[string]string{"apiserver": "kube-apiserver"},
expectErr: false,
}, },
{ {
// "anotherthing" in list of components but doesn't have a defintion // "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"}}}, 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", psOut: "kube-apiserver thing",
exp: map[string]string{"apiserver": "kube-apiserver"}, exp: map[string]string{"apiserver": "kube-apiserver"},
expectErr: false,
}, },
{ {
// more than one component // 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"}}}, 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", psOut: "kube-apiserver \nthing",
exp: map[string]string{"apiserver": "kube-apiserver", "thing": "thing"}, exp: map[string]string{"apiserver": "kube-apiserver", "thing": "thing"},
expectErr: false,
}, },
{ {
// default binary to component name // 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}}, 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", psOut: "kube-apiserver \notherthing some params",
exp: map[string]string{"apiserver": "kube-apiserver", "thing": "thing"}, 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 { for k, val := range c.config {
v.Set(k, val) v.Set(k, val)
} }
m := getBinaries(v) m, err := getBinaries(v)
if !reflect.DeepEqual(m, c.exp) { 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) t.Fatalf("Got %v\nExpected %v", m, c.exp)
} }
}) })

46
hack/debug.yaml Normal file
View File

@ -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"

50
hack/kind.yaml Normal file
View File

@ -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"

45
job.yaml Normal file
View File

@ -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"

View File

@ -1,7 +1,72 @@
SOURCES := $(shell find . -name '*.go') SOURCES := $(shell find . -name '*.go')
TARGET_OS := linux
BINARY := kube-bench 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) $(BINARY): $(SOURCES)
GOOS=$(TARGET_OS) go build -o $(BINARY) . 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