Skip to content

Commit

Permalink
Automatically detect local/cluster mode (#113)
Browse files Browse the repository at this point in the history
* Autodetect local/cluster mode.

* Initialize logging level at startup.
  • Loading branch information
Shane P. Lawrence committed Jan 8, 2019
1 parent 3013754 commit 60c846a
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 59 deletions.
56 changes: 31 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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
```
Expand All @@ -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!
```
Expand All @@ -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
```
Expand All @@ -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.
```

Expand All @@ -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
```

Expand All @@ -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
```

Expand All @@ -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
```
Expand All @@ -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
```
Expand All @@ -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
```
Expand All @@ -277,15 +283,15 @@ 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!
```
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!
```
Expand Down
49 changes: 32 additions & 17 deletions cmd/kubernetes.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package cmd

import (
"fmt"
"errors"
"os"
"path/filepath"

log "github.com/sirupsen/logrus"
networking "k8s.io/api/networking/v1"
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion cmd/networkPolicies.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
37 changes: 31 additions & 6 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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`,
}

Expand All @@ -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")
}
}
2 changes: 1 addition & 1 deletion cmd/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Expand Down
14 changes: 5 additions & 9 deletions cmd/version.go
Original file line number Diff line number Diff line change
@@ -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() {
Expand All @@ -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)
},
Expand Down

0 comments on commit 60c846a

Please sign in to comment.