From 60c846a98e465fe51a1403360c8260ba793f6161 Mon Sep 17 00:00:00 2001 From: "Shane P. Lawrence" Date: Thu, 4 Oct 2018 13:52:27 -0400 Subject: [PATCH] Automatically detect local/cluster mode (#113) * Autodetect local/cluster mode. * Initialize logging level at startup. --- README.md | 56 +++++++++++++++++++++++------------------- cmd/kubernetes.go | 49 +++++++++++++++++++++++------------- cmd/networkPolicies.go | 2 +- cmd/root.go | 37 +++++++++++++++++++++++----- cmd/util.go | 2 +- cmd/version.go | 14 ++++------- 6 files changed, 101 insertions(+), 59 deletions(-) diff --git a/README.md b/README.md index 61ff6aa1..7a8e1efd 100644 --- a/README.md +++ b/README.md @@ -46,17 +46,24 @@ Now you can just call `kubeaudit` with one of commands from [here](#audits) ## General instructions `kubeaudit` has three different modes for its audits: -1. `kubeaudit cmd` will attempt to create an in-cluster client and audit. -1. `kubeaudit -l/--local cmd` will use your kubeconfig (`~/.kube/config` or if - you need different path use `-c /config/path` -1. `kubeaudit -f/--manifest /path/to/manifest.yml` will audit the manifest - -`kubeaudit` supports to different output types: +1. Cluster mode + If kubeaudit detects that it's running in a container, `kubeaudit cmd` will + attempt to audit the cluster it's running in. +1. Local config mode + If kubeaudit is not running in a container, `kubeaudit cmd` will audit the + resources specified by your local kubeconfig (`$HOME/.kube/config`) file. + You can force kubeaudit to use a specific local config file with the switch + `-c/--kubeconfig /config/path` +1. Manifest mode + If you wish to audit a manifest file, use the command + `kubeaudit -f/--manifest /path/to/manifest.yml` + +`kubeaudit` supports two different output types: 1. just running `kubeaudit` will log human readable output 1. if run with `-j/--json` it will log output json formatted so that its output can be used by other programs easily -`kubeaudit` has 4 different log levels `INFO, WARN, ERROR` controlled by +`kubeaudit` has four different log levels `INFO, WARN, ERROR` controlled by `-v/--verbose LEVEL` and for those who counted and want to work on `kubeaudit` `DEBUG` 1. by default the debug level is set to `ERROR` and will log `INFO`, `WARN` and @@ -105,7 +112,7 @@ The manifest might end up a little too secure for the work it is supposed to do. Runs all the above checks. ```sh -kubeaudit -l all +kubeaudit all ERRO[0000] RunAsNonRoot is not set, which results in root user being allowed! ERRO[0000] Default serviceAccount with token mounted. Please set automountServiceAccountToken to false WARN[0000] Privileged defaults to false, which results in non privileged, which is okay. @@ -120,7 +127,7 @@ The security context holds a couple of different security related configurations. For convenience, `kubeaudit` will always log the following information when it creates a log: ```sh -kubeaudit -l command +kubeaudit command LOG[0000] KubeType=deployment Name=THEdeployment Namespace=deploymentNS ``` And for brevity, the information will not be shown in the commands below. @@ -134,7 +141,7 @@ Currently, `kubeaudit` is able to check for the following fields in the security `kubeaudit` will detect whether `readOnlyRootFilesystem` is either not set `nil` or explicitly set to `false` ```sh -kubeaudit -l rootfs +kubeaudit rootfs ERRO[0000] ReadOnlyRootFilesystem not set which results in a writable rootFS, please set to true ERRO[0000] ReadOnlyRootFilesystem set to false, please set to true ``` @@ -146,7 +153,7 @@ ERRO[0000] ReadOnlyRootFilesystem set to false, please set to true `kubeaudit` will detect whether the container is to be run as root: ```sh -kubeaudit -l nonroot +kubeaudit nonroot ERRO[0000] RunAsNonRoot is set to false (root user allowed), please set to true! ERRO[0000] RunAsNonRoot is not set, which results in root user being allowed! ``` @@ -158,7 +165,7 @@ ERRO[0000] RunAsNonRoot is not set, which results in root user being allowed! `kubeaudit` will detect whether `allowPrivilegeEscalation` is either set to `nil` or explicitly set to `false` ```sh -kubeaudit -l allowpe +kubeaudit allowpe ERRO[0000] AllowPrivilegeEscalation set to true, please set to false ERRO[0000] AllowPrivilegeEscalation not set which allows privilege escalation, please set to false ``` @@ -170,14 +177,14 @@ ERRO[0000] AllowPrivilegeEscalation not set which allows privilege escalation, p `kubeaudit` will detect whether the container is to be run privileged: ```sh -kubeaudit -l priv +kubeaudit priv ERRO[0000] Privileged set to true! Please change it to false! ``` Since we want to make sure everything is intentionally configured correctly `kubeaudit` warns about `privileged` not being set: ```sh -kubeaudit -l priv +kubeaudit priv WARN[0000] Privileged defaults to false, which results in non privileged, which is okay. ``` @@ -191,14 +198,14 @@ therefore should be dropped. `kubeaudit` will also complain about added capabili If the capabilities field doesn't exist within the security context: ```sh -kubeaudiit -l caps +kubeaudit caps ERRO[0000] Capabilities field not defined! ``` When capabilities were added: ```sh -kubeaudiit -l caps +kubeaudit caps ERRO[0000] Capability added CapName=NET_ADMIN ``` @@ -208,7 +215,7 @@ want to keep some of the capabilities otherwise `kubeaudit` will complain about them not being dropped: ```sh -kubeaudiit -l caps +kubeaudit caps ERRO[0000] Capability not dropped CapName=AUDIT_WRITE ``` @@ -220,20 +227,20 @@ ERRO[0000] Capability not dropped CapName=AUDIT_WRITE 1. If the image tag is incorrect an ERROR will issued ```sh - kubeaudit -l image -i gcr.io/google_containers/echoserver:1.7 + kubeaudit image -i gcr.io/google_containers/echoserver:1.7 ERRO[0000] Image tag was incorrect ``` 1. If the image doesn't have a tag but an image of the name was found a WARNING will be created: ```sh - kubeaudit -l image -i gcr.io/google_containers/echoserver:1.7 + kubeaudit image -i gcr.io/google_containers/echoserver:1.7 WARN[0000] Image tag was missing ``` 1. If the image was found with correct tag `kubeaudit` notifies with an INFO message: ```sh - kubeaudit -l image -i gcr.io/google_containers/echoserver:1.7 + kubeaudit image -i gcr.io/google_containers/echoserver:1.7 INFO[0000] Image tag was correct ``` @@ -245,13 +252,13 @@ It audits against the following scenarios: 1. A default serviceAccount mounted with a token: ```sh - kubeaudit -l sat + kubeaudit sat ERRO[0000] Default serviceAccount with token mounted. Please set AutomountServiceAccountToken to false ``` 1. A deprecated service account: ```sh - kubeaudit -l sat + kubeaudit sat WARN[0000] serviceAccount is a deprecated alias for ServiceAccountName, use that one instead DSA=DeprecatedServiceAccount ``` @@ -264,7 +271,6 @@ installed. See [Kubernetes Network Policies](https://Kubernetes.io/docs/concepts for more information: ```sh -# don't specify -l or -c to run inside the cluster kubeaudit np WARN[0000] Default allow mode on test/testing ``` @@ -277,7 +283,7 @@ It checks that every resource has a CPU and memory limit. See [Kubernetes Resour for more information: ```sh -kubeaudit -l limits +kubeaudit limits WARN[0000] CPU limit not set, please set it! WARN[0000] Memory limit not set, please set it! ``` @@ -285,7 +291,7 @@ WARN[0000] Memory limit not set, please set it! With the `--cpu` and `--memory` parameters, `kubeaudit` can check the limits not to be exceeded. ```sh -kubeaudit -l limits --cpu 500m --memory 125Mi +kubeaudit limits --cpu 500m --memory 125Mi WARN[0000] CPU limit exceeded, it is set to 1 but it must not exceed 500m. Please adjust it! ! WARN[0000] Memory limit exceeded, it is set to 512Mi but it must not exceed 125Mi. Please adjust it! ``` diff --git a/cmd/kubernetes.go b/cmd/kubernetes.go index c1a537fa..cde40672 100644 --- a/cmd/kubernetes.go +++ b/cmd/kubernetes.go @@ -1,8 +1,9 @@ package cmd import ( - "fmt" + "errors" "os" + "path/filepath" log "github.com/sirupsen/logrus" networking "k8s.io/api/networking/v1" @@ -14,28 +15,42 @@ import ( "k8s.io/client-go/tools/clientcmd" ) -func kubeClientConfig(kubeconfig string) (*rest.Config, error) { - if kubeconfig == "" && !rootConfig.localMode { - fmt.Println("Running inside cluster, using the cluster config") - return rest.InClusterConfig() +// ErrNoReadableKubeConfig represents any error that prevents the client from opening a kubeconfig file. +var ErrNoReadableKubeConfig = errors.New("unable to open kubeconfig file") + +func kubeClientConfig() (*rest.Config, error) { + if rootConfig.kubeConfig != "" { + return kubeClientConfigLocal() } - return clientcmd.BuildConfigFromFlags("", kubeconfig) -} -func kubeClient(kubeconfig string) (*kubernetes.Clientset, error) { - if rootConfig.localMode { - kubeconfig = os.Getenv("HOME") + "/.kube/config" + if config, err := rest.InClusterConfig(); err == nil { + log.Info("Running inside cluster, using the cluster config") + return config, nil } - if rootConfig.verbose == "DEBUG" { - log.SetLevel(log.DebugLevel) - log.AddHook(NewDebugHook()) + + log.Info("Not running inside cluster, using local config") + home, ok := os.LookupEnv("HOME") + if !ok { + log.Error("Unable to load kubeconfig. No config file specified and $HOME not found.") + return nil, ErrNoReadableKubeConfig } - if rootConfig.json { - log.SetFormatter(&log.JSONFormatter{}) + + rootConfig.kubeConfig = filepath.Join(home, ".kube", "config") + return kubeClientConfigLocal() +} + +func kubeClientConfigLocal() (*rest.Config, error) { + if _, err := os.Stat(rootConfig.kubeConfig); err != nil { + log.Errorf("Unable to load kubeconfig. Could not open file %s.", rootConfig.kubeConfig) + return nil, ErrNoReadableKubeConfig } - config, err := kubeClientConfig(kubeconfig) + return clientcmd.BuildConfigFromFlags("", rootConfig.kubeConfig) +} + +func kubeClient() (*kubernetes.Clientset, error) { + config, err := kubeClientConfig() if err != nil { - log.Error(err) + return nil, err } kube, err := kubernetes.NewForConfig(config) return kube, err diff --git a/cmd/networkPolicies.go b/cmd/networkPolicies.go index 6592b0f7..e3a9436f 100644 --- a/cmd/networkPolicies.go +++ b/cmd/networkPolicies.go @@ -36,7 +36,7 @@ An ERROR log is given when a namespace does not have NetworkPolicy isolation Example usage: kubeaudit np`, Run: func(cmd *cobra.Command, args []string) { - kube, err := kubeClient(rootConfig.kubeConfig) + kube, err := kubeClient() if err != nil { log.Error(err) } diff --git a/cmd/root.go b/cmd/root.go index 6a7b57dc..459af732 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -3,7 +3,9 @@ package cmd import ( "fmt" "os" + "path/filepath" + log "github.com/sirupsen/logrus" "github.com/spf13/cobra" apiv1 "k8s.io/api/core/v1" ) @@ -25,10 +27,7 @@ type rootFlags struct { var RootCmd = &cobra.Command{ Use: "kubeaudit", Short: "A Kubernetes security auditor", - Long: `kubeaudit is a program that will help you audit -your Kubernetes clusters. Specify -l to run kubeaudit using ~/.kube/config -otherwise it will attempt to create an in-cluster client. - + Long: `kubeaudit is a program that checks security settings on your Kubernetes clusters. #patcheswelcome`, } @@ -41,12 +40,38 @@ func Execute() { } func init() { - RootCmd.PersistentFlags().StringVarP(&rootConfig.kubeConfig, "kubeconfig", "c", "", "config file (default is $HOME/.kube/config") + cobra.OnInitialize(processFlags) + RootCmd.PersistentFlags().BoolVarP(&rootConfig.localMode, "local", "l", false, "[DEPRECATED] Local mode, uses $HOME/.kube/config as configuration") + RootCmd.Flags().MarkHidden("local") + RootCmd.PersistentFlags().StringVarP(&rootConfig.kubeConfig, "kubeconfig", "c", "", "Specify local config file (default is $HOME/.kube/config") RootCmd.PersistentFlags().StringVarP(&rootConfig.verbose, "verbose", "v", "INFO", "Set the debug level") - RootCmd.PersistentFlags().BoolVarP(&rootConfig.localMode, "local", "l", false, "Local mode, uses ~/.kube/config as configuration") RootCmd.PersistentFlags().BoolVarP(&rootConfig.json, "json", "j", false, "Enable json logging") RootCmd.PersistentFlags().BoolVarP(&rootConfig.allPods, "allPods", "a", false, "Audit againsts pods in all the phases (default Running Phase)") RootCmd.PersistentFlags().StringVarP(&rootConfig.namespace, "namespace", "n", apiv1.NamespaceAll, "Specify the namespace scope to audit") RootCmd.PersistentFlags().StringVarP(&rootConfig.manifest, "manifest", "f", "", "yaml configuration to audit") RootCmd.PersistentFlags().StringVarP(&rootConfig.dropCapConfig, "dropCapConfig", "d", "", "yaml configuration to audit") } + +func processFlags() { + if rootConfig.verbose == "DEBUG" { + log.SetLevel(log.DebugLevel) + log.AddHook(NewDebugHook()) + } + if rootConfig.json { + log.SetFormatter(&log.JSONFormatter{}) + } + + if rootConfig.localMode == true { + log.Warn("-l/-local is deprecated! kubeaudit will default to local mode if it's not running in a cluster. ") + if rootConfig.kubeConfig != "" { + return + } + + log.Warn("To use a local kubeconfig file from inside a cluster specify '-c $HOME/.kube/config'.") + home, ok := os.LookupEnv("HOME") + if !ok { + log.Fatal("Local mode selected but $HOME not set.") + } + rootConfig.kubeConfig = filepath.Join(home, ".kube", "config") + } +} diff --git a/cmd/util.go b/cmd/util.go index 3d001918..8b661bfa 100644 --- a/cmd/util.go +++ b/cmd/util.go @@ -223,7 +223,7 @@ func getResources() (resources []k8sRuntime.Object, err error) { if rootConfig.manifest != "" { resources, err = getKubeResourcesManifest(rootConfig.manifest) } else { - if kube, err := kubeClient(rootConfig.kubeConfig); err == nil { + if kube, err := kubeClient(); err == nil { resources = getKubeResources(kube) } } diff --git a/cmd/version.go b/cmd/version.go index eb3a5119..a74402e5 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -1,10 +1,11 @@ package cmd import ( + "os" + "github.com/hashicorp/go-version" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" - "os" ) func init() { @@ -28,15 +29,10 @@ var versionCmd = &cobra.Command{ "Version": ver, }).Info("Kubeaudit") - kubeconfig := rootConfig.kubeConfig - if rootConfig.localMode { - kubeconfig = os.Getenv("HOME") + "/.kube/config" - } - - kube, err := kubeClient(kubeconfig) + kube, err := kubeClient() if err != nil { - log.Error(err) - os.Exit(1) + log.Warn("Could not get kubernetes server version.") + return } printKubernetesVersion(kube) },