diff --git a/.travis.yml b/.travis.yml index 1a5de1d..e1f5137 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ sudo: required language: go go: - - 1.11.x + - 1.12.x script: - make deploy diff --git a/Makefile b/Makefile index a545c78..db12df9 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,7 @@ V?=0 deploy: @mkdir -p ~/.kube/plugins/ + @rm -rf ~/.kube/plugins/kubectl-scan || true @go build -o ~/.kube/plugins/kubectl-scan diff --git a/README.md b/README.md index 9ea392c..7a540e6 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # kubectl-kubesec -[![Build Status](https://travis-ci.org/stefanprodan/kubectl-kubesec.svg?branch=master)](https://travis-ci.org/stefanprodan/kubectl-kubesec) +[![Build Status](https://travis-ci.org/controlplaneio/kubectl-kubesec.svg?branch=master)](https://travis-ci.org/controlplaneio/kubectl-kubesec) This is a kubectl plugin for scanning Kubernetes pods, deployments, daemonsets and statefulsets with [kubesec.io](https://kubesec.io) -For the admission controller see [kubesec-webhook](https://github.com/stefanprodan/kubesec-webhook) +For the admission controller see [kubesec-webhook](https://github.com/controlplaneio/kubesec-webhook) ### Install @@ -21,7 +21,7 @@ For Kubernetes 1.12 or newer: ```bash mkdir -p ~/.kube/plugins/scan && \ -curl -sL https://github.com/stefanprodan/kubectl-kubesec/releases/download/1.0.0/kubectl-kubesec_1.0.0_`uname -s`_amd64.tar.gz | tar xzvf - -C ~/.kube/plugins/scan +curl -sL https://github.com/controlplaneio/kubectl-kubesec/releases/download/1.0.0/kubectl-kubesec_1.0.0_`uname -s`_amd64.tar.gz | tar xzvf - -C ~/.kube/plugins/scan mv ~/.kube/plugins/scan/scan ~/.kube/plugins/scan/kubectl-scan export PATH=$PATH:~/.kube/plugins/scan ``` @@ -30,7 +30,7 @@ For Kubernetes older than 1.12: ```bash mkdir -p ~/.kube/plugins/scan && \ -curl -sL https://github.com/stefanprodan/kubectl-kubesec/releases/download/0.3.1/kubectl-kubesec_0.3.1_`uname -s`_amd64.tar.gz | tar xzvf - -C ~/.kube/plugins/scan +curl -sL https://github.com/controlplaneio/kubectl-kubesec/releases/download/0.3.1/kubectl-kubesec_0.3.1_`uname -s`_amd64.tar.gz | tar xzvf - -C ~/.kube/plugins/scan ``` ### Usage @@ -68,17 +68,33 @@ kubectl scan -n weave daemonset weave-scope-agent Result: ``` -daemonset/weave-scope-agent kubesec.io score -54 +kubesec.io score: -51 ----------------- Critical -1. containers[] .securityContext .privileged == true -Privileged containers can allow almost completely unrestricted host access -2. .spec .hostNetwork +1. .spec .hostNetwork == true Sharing the host's network namespace permits processes in the pod to communicate with processes bound to the host's loopback adapter -3. .spec .hostPID +2. .spec .hostPID == true Sharing the host's PID namespace allows visibility of processes on the host, potentially leaking information such as environment variables and configuration -4. .spec .volumes[] .hostPath .path == "/var/run/docker.sock" +3. containers[] .securityContext .privileged == true +Privileged containers can allow almost completely unrestricted host access +4. volumes[] .hostPath .path == /var/run/docker.sock Mounting the docker.socket leaks information about other containers and can allow container breakout +----------------- +Advise +1. containers[] .securityContext .runAsUser -gt 10000 +Run as a high-UID user to avoid conflicts with the host's user table +2. containers[] .securityContext .readOnlyRootFilesystem == true +An immutable root filesystem can prevent malicious binaries being added to PATH and increase attack cost +3. containers[] .securityContext .runAsNonRoot == true +Force the running image to run as a non-root user to ensure least privilege +4. containers[] .resources .limits .cpu +Enforcing CPU limits prevents DOS via resource exhaustion +5. containers[] .securityContext .capabilities .drop +Reducing kernel capabilities available to a container limits its attack surface +6. .metadata .annotations ."container.seccomp.security.alpha.kubernetes.io/pod" +Seccomp profiles set minimum privilege and secure against unknown threats +7. containers[] .securityContext .capabilities .drop | index("ALL") +Drop all capabilities and add only those required to reduce syscall attack surface ``` Scan a StatefulSet: @@ -127,3 +143,5 @@ Run as a high-UID user to avoid conflicts with the host's user table 5. containers[] .securityContext .capabilities .drop | index("ALL") Drop all capabilities and add only those required to reduce syscall attack surface ``` + +Note that you can change the kubesec API address with the `KUBESEC_URL` env var. \ No newline at end of file diff --git a/main.go b/main.go index af5e7a1..ea39101 100644 --- a/main.go +++ b/main.go @@ -3,8 +3,8 @@ package main import ( "flag" "fmt" + "github.com/controlplaneio/kubectl-kubesec/pkg/cmd" _ "github.com/golang/glog" - "github.com/stefanprodan/kubectl-kubesec/pkg/cmd" "os" "strings" ) diff --git a/pkg/cmd/daemonset.go b/pkg/cmd/daemonset.go index 562008f..3be08ae 100644 --- a/pkg/cmd/daemonset.go +++ b/pkg/cmd/daemonset.go @@ -5,8 +5,8 @@ import ( "bytes" "errors" "fmt" + "github.com/controlplaneio/kubectl-kubesec/pkg/kubesec" "github.com/spf13/cobra" - "github.com/stefanprodan/kubectl-kubesec/pkg/kubesec" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "os" ) diff --git a/pkg/cmd/deployment.go b/pkg/cmd/deployment.go index 30853a4..8278b9f 100644 --- a/pkg/cmd/deployment.go +++ b/pkg/cmd/deployment.go @@ -5,8 +5,8 @@ import ( "bytes" "errors" "fmt" + "github.com/controlplaneio/kubectl-kubesec/pkg/kubesec" "github.com/spf13/cobra" - "github.com/stefanprodan/kubectl-kubesec/pkg/kubesec" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "os" ) diff --git a/pkg/cmd/pod.go b/pkg/cmd/pod.go index a9f4425..df03b4c 100644 --- a/pkg/cmd/pod.go +++ b/pkg/cmd/pod.go @@ -5,8 +5,8 @@ import ( "bytes" "errors" "fmt" + "github.com/controlplaneio/kubectl-kubesec/pkg/kubesec" "github.com/spf13/cobra" - "github.com/stefanprodan/kubectl-kubesec/pkg/kubesec" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "os" ) diff --git a/pkg/cmd/statefulset.go b/pkg/cmd/statefulset.go index d6bddc5..11aba5c 100644 --- a/pkg/cmd/statefulset.go +++ b/pkg/cmd/statefulset.go @@ -5,8 +5,8 @@ import ( "bytes" "errors" "fmt" + "github.com/controlplaneio/kubectl-kubesec/pkg/kubesec" "github.com/spf13/cobra" - "github.com/stefanprodan/kubectl-kubesec/pkg/kubesec" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "os" ) diff --git a/pkg/cmd/version.go b/pkg/cmd/version.go index 02325fc..61d6989 100644 --- a/pkg/cmd/version.go +++ b/pkg/cmd/version.go @@ -2,8 +2,8 @@ package cmd import ( "fmt" + "github.com/controlplaneio/kubectl-kubesec/pkg/version" "github.com/spf13/cobra" - "github.com/stefanprodan/kubectl-kubesec/pkg/version" ) var versionCmd = &cobra.Command{ diff --git a/pkg/kubesec/kubesec.go b/pkg/kubesec/kubesec.go index de22caa..9e5899a 100644 --- a/pkg/kubesec/kubesec.go +++ b/pkg/kubesec/kubesec.go @@ -7,8 +7,8 @@ import ( "fmt" "io" "io/ioutil" - "mime/multipart" "net/http" + "os" ) // KubesecClient represent a client for kubesec.io. @@ -22,20 +22,12 @@ func NewClient() *KubesecClient { // ScanDefinition scans the provided resource definition. func (kc *KubesecClient) ScanDefinition(def bytes.Buffer) (*KubesecResult, error) { - bodyBuf := &bytes.Buffer{} - bodyWriter := multipart.NewWriter(bodyBuf) - fileWriter, err := bodyWriter.CreateFormFile("uploadfile", "object.yaml") - if err != nil { - return nil, err - } - _, err = io.Copy(fileWriter, &def) - if err != nil { - return nil, err + url := os.Getenv("KUBESEC_URL") + if url == "" { + url = "https://v2.kubesec.io/scan" } - contentType := bodyWriter.FormDataContentType() - bodyWriter.Close() - resp, err := http.Post("https://kubesec.io/", contentType, bodyBuf) + resp, err := http.Post(url, "application/yaml", bytes.NewBuffer(def.Bytes())) if err != nil { return nil, err } @@ -49,13 +41,14 @@ func (kc *KubesecClient) ScanDefinition(def bytes.Buffer) (*KubesecResult, error return nil, errors.New("failed to scan definition") } - var result KubesecResult + var result []KubesecResult err = json.Unmarshal(body, &result) if err != nil { - return nil, err + fmt.Println(string(body)) + return nil, fmt.Errorf("json unmarshal error: %s", err.Error()) } - return &result, nil + return &result[0], nil } // KubesecResult represents a result returned by kubesec.io. @@ -92,7 +85,7 @@ func (r *KubesecResult) Dump(w io.Writer) { fmt.Fprintln(w, "-----------------") } if len(r.Scoring.Advise) > 0 { - fmt.Fprintf(w, "Advise") + fmt.Fprintln(w, "Advise") for i, el := range r.Scoring.Advise { fmt.Fprintf(w, "%v. %v\n", i+1, el.Selector) if len(el.Reason) > 0 { diff --git a/pkg/version/version.go b/pkg/version/version.go index b0e0776..b48ef25 100644 --- a/pkg/version/version.go +++ b/pkg/version/version.go @@ -1,4 +1,4 @@ package version -var VERSION = "1.0.0" +var VERSION = "2.0.0" var REVISION = "unknown"