diff --git a/docs/docs/target/kubernetes.md b/docs/docs/target/kubernetes.md index 9dfe52ee89ff..0c6fa2dabed8 100644 --- a/docs/docs/target/kubernetes.md +++ b/docs/docs/target/kubernetes.md @@ -49,10 +49,41 @@ You can also specify a `kubeconfig` using the `--kubeconfig` flag: trivy k8s --kubeconfig ~/.kube/config2 ``` -By default, all cluster resource images will be downloaded and scanned. +## Required roles +To successfully scan a Kubernetes cluster, `trivy kubernetes` subcommand must be executed under a role or a cluster role that has some specific permissions. + +The role must have `list` verb for all resources (`"*"`) inside next API groups: core (`""`), `"apps"`, `"batch"`,`"networking.k8s.io"`, `"rbac.authorization.k8s.io"`: +```yaml +- apiGroups: [""] + resources: ["*"] + verbs: ["list"] +- apiGroups: ["apps", "batch", "networking.k8s.io", "rbac.authorization.k8s.io"] + resources: ["*"] + verbs: ["list"] +``` +If `node collector` is enabled (by default), Trivy needs a cluster role with some additional permissions to run and track the jobs: +```yaml +- apiGroups: [""] + resources: ["nodes/proxy", "pods/log"] + verbs: ["get"] +- apiGroups: [""] + resources: ["events"] + verbs: ["watch"] +- apiGroups: ["batch"] + resources: ["jobs", "cronjobs"] + verbs: ["list", "get"] +- apiGroups: ["batch"] + resources: ["jobs"] + verbs: ["create","delete", "watch"] +- apiGroups: [""] + resources: ["namespaces"] + verbs: ["create"] +``` ### Skip-images +By default, all cluster resource images will be downloaded and scanned. + You can control whether Trivy will scan and download the cluster resource images. To disable this feature, add the --skip-images flag. - `--skip-images` flag will prevent the downloading and scanning of images (including vulnerabilities and secrets) in the cluster resources. @@ -91,6 +122,9 @@ You can control which namespaces will be discovered using the `--include-namespa By default, all namespaces will be included in cluster scanning. +!!! note "using `--exclude-namespaces`" + Trivy requires a complete list of namespaces to exclude specific ones. Therefore, `--exclude-namespaces` option is only available for cluster roles now. + Example: ```sh diff --git a/go.mod b/go.mod index d2e5c33270ec..cbed7d2bd4a9 100644 --- a/go.mod +++ b/go.mod @@ -27,14 +27,13 @@ require ( github.com/aquasecurity/trivy-checks v1.4.0 github.com/aquasecurity/trivy-db v0.0.0-20241209111357-8c398f13db0e github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 - github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20241101182546-89bffc3932bc + github.com/aquasecurity/trivy-kubernetes v0.7.0 github.com/aws/aws-sdk-go-v2 v1.34.0 github.com/aws/aws-sdk-go-v2/config v1.29.2 github.com/aws/aws-sdk-go-v2/credentials v1.17.55 github.com/aws/aws-sdk-go-v2/service/ec2 v1.201.1 github.com/aws/aws-sdk-go-v2/service/ecr v1.38.7 github.com/aws/aws-sdk-go-v2/service/s3 v1.74.1 - github.com/aws/aws-sdk-go-v2/service/sts v1.33.10 // indirect github.com/aws/smithy-go v1.22.2 github.com/bitnami/go-version v0.0.0-20231130084017-bb00604d650c github.com/bmatcuk/doublestar/v4 v4.8.1 @@ -179,16 +178,6 @@ require ( github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect - github.com/aws/aws-sdk-go v1.55.5 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.25 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.29 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.29 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 // indirect - github.com/aws/aws-sdk-go-v2/service/ebs v1.22.1 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.10 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.24.12 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.11 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/blang/semver v3.5.1+incompatible // indirect @@ -416,12 +405,12 @@ require ( k8s.io/apiextensions-apiserver v0.32.0 // indirect k8s.io/apimachinery v0.32.1 // indirect k8s.io/apiserver v0.32.0 // indirect - k8s.io/cli-runtime v0.32.0 // indirect - k8s.io/client-go v0.32.0 // indirect - k8s.io/component-base v0.32.0 // indirect + k8s.io/cli-runtime v0.32.1 // indirect + k8s.io/client-go v0.32.1 // indirect + k8s.io/component-base v0.32.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect - k8s.io/kubectl v0.32.0 // indirect + k8s.io/kubectl v0.32.1 // indirect modernc.org/libc v1.55.3 // indirect modernc.org/mathutil v1.6.0 // indirect modernc.org/memory v1.8.0 // indirect @@ -434,3 +423,17 @@ require ( tags.cncf.io/container-device-interface v0.8.0 // indirect tags.cncf.io/container-device-interface/specs-go v0.8.0 // indirect ) + +require ( + github.com/aws/aws-sdk-go v1.55.6 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.25 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.29 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.29 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 // indirect + github.com/aws/aws-sdk-go-v2/service/ebs v1.22.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.10 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.24.12 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.11 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.33.10 // indirect +) diff --git a/go.sum b/go.sum index cae9d2fa163d..0a997b782160 100644 --- a/go.sum +++ b/go.sum @@ -809,8 +809,8 @@ github.com/aquasecurity/trivy-db v0.0.0-20241209111357-8c398f13db0e h1:O5j5SeCNB github.com/aquasecurity/trivy-db v0.0.0-20241209111357-8c398f13db0e/go.mod h1:gS8VhlNxhraiq60BBnJw9kGtjeMspQ9E8pX24jCL4jg= github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 h1:JVgBIuIYbwG+ekC5lUHUpGJboPYiCcxiz06RCtz8neI= github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48/go.mod h1:Ldya37FLi0e/5Cjq2T5Bty7cFkzUDwTcPeQua+2M8i8= -github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20241101182546-89bffc3932bc h1:/mFBYIK9RY+L8s1CIbQbJ5B3v0YmoDSu5eAzavvMa+Y= -github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20241101182546-89bffc3932bc/go.mod h1:ctlibFXOQyjWybeVVQI6NLG6GJoPWZJ4cIirQ/wPCQs= +github.com/aquasecurity/trivy-kubernetes v0.7.0 h1:0pRJFSslUYd9xzQIEw1c0mS7k1Vv489nH/LsxeU6yME= +github.com/aquasecurity/trivy-kubernetes v0.7.0/go.mod h1:O6JZMicTmZrsjEpGzsnBMhPTHAfpnTMqXTAMidG6M+M= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= @@ -818,8 +818,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkY github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= -github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU= -github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= +github.com/aws/aws-sdk-go v1.55.6 h1:cSg4pvZ3m8dgYcgqB97MrcdjUmZ1BeMYKUxMMB89IPk= +github.com/aws/aws-sdk-go v1.55.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/aws/aws-sdk-go-v2 v1.34.0 h1:9iyL+cjifckRGEVpRKZP3eIxVlL06Qk1Tk13vreaVQU= github.com/aws/aws-sdk-go-v2 v1.34.0/go.mod h1:JgstGg0JjWU1KpVJjD5H0y0yyAIpSdKEq556EI6yOOM= github.com/aws/aws-sdk-go-v2/config v1.29.2 h1:JuIxOEPcSKpMB0J+khMjznG9LIhIBdmqNiEcPclnwqc= @@ -2787,18 +2787,18 @@ k8s.io/apimachinery v0.32.1 h1:683ENpaCBjma4CYqsmZyhEzrGz6cjn1MY/X2jB2hkZs= k8s.io/apimachinery v0.32.1/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= k8s.io/apiserver v0.32.0 h1:VJ89ZvQZ8p1sLeiWdRJpRD6oLozNZD2+qVSLi+ft5Qs= k8s.io/apiserver v0.32.0/go.mod h1:HFh+dM1/BE/Hm4bS4nTXHVfN6Z6tFIZPi649n83b4Ag= -k8s.io/cli-runtime v0.32.0 h1:dP+OZqs7zHPpGQMCGAhectbHU2SNCuZtIimRKTv2T1c= -k8s.io/cli-runtime v0.32.0/go.mod h1:Mai8ht2+esoDRK5hr861KRy6z0zHsSTYttNVJXgP3YQ= -k8s.io/client-go v0.32.0 h1:DimtMcnN/JIKZcrSrstiwvvZvLjG0aSxy8PxN8IChp8= -k8s.io/client-go v0.32.0/go.mod h1:boDWvdM1Drk4NJj/VddSLnx59X3OPgwrOo0vGbtq9+8= -k8s.io/component-base v0.32.0 h1:d6cWHZkCiiep41ObYQS6IcgzOUQUNpywm39KVYaUqzU= -k8s.io/component-base v0.32.0/go.mod h1:JLG2W5TUxUu5uDyKiH2R/7NnxJo1HlPoRIIbVLkK5eM= +k8s.io/cli-runtime v0.32.1 h1:19nwZPlYGJPUDbhAxDIS2/oydCikvKMHsxroKNGA2mM= +k8s.io/cli-runtime v0.32.1/go.mod h1:NJPbeadVFnV2E7B7vF+FvU09mpwYlZCu8PqjzfuOnkY= +k8s.io/client-go v0.32.1 h1:otM0AxdhdBIaQh7l1Q0jQpmo7WOFIk5FFa4bg6YMdUU= +k8s.io/client-go v0.32.1/go.mod h1:aTTKZY7MdxUaJ/KiUs8D+GssR9zJZi77ZqtzcGXIiDg= +k8s.io/component-base v0.32.1 h1:/5IfJ0dHIKBWysGV0yKTFfacZ5yNV1sulPh3ilJjRZk= +k8s.io/component-base v0.32.1/go.mod h1:j1iMMHi/sqAHeG5z+O9BFNCF698a1u0186zkjMZQ28w= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y= k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4= -k8s.io/kubectl v0.32.0 h1:rpxl+ng9qeG79YA4Em9tLSfX0G8W0vfaiPVrc/WR7Xw= -k8s.io/kubectl v0.32.0/go.mod h1:qIjSX+QgPQUgdy8ps6eKsYNF+YmFOAO3WygfucIqFiE= +k8s.io/kubectl v0.32.1 h1:/btLtXLQUU1rWx8AEvX9jrb9LaI6yeezt3sFALhB8M8= +k8s.io/kubectl v0.32.1/go.mod h1:sezNuyWi1STk4ZNPVRIFfgjqMI6XMf+oCVLjZen/pFQ= k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= diff --git a/integration/k8s_test.go b/integration/k8s_test.go index 498f667932c4..109c4b7b2a82 100644 --- a/integration/k8s_test.go +++ b/integration/k8s_test.go @@ -106,4 +106,55 @@ func TestK8s(t *testing.T) { return len(*r.Dependencies) > 0 })) }) + + t.Run("limited user test", func(t *testing.T) { + // Set up the output file + outputFile := filepath.Join(t.TempDir(), "output.json") + + osArgs := []string{ + "--cache-dir", + cacheDir, + "k8s", + "limitedcontext", + "--kubeconfig", "limitedconfig", + "--report", + "summary", + "-q", + "--timeout", + "5m0s", + "--include-namespaces", "limitedns", + "--format", + "json", + "--output", + outputFile, + } + + // Run Trivy + err := execute(osArgs) + require.NoError(t, err) + + var got report.ConsolidatedReport + f, err := os.Open(outputFile) + require.NoError(t, err) + defer f.Close() + + err = json.NewDecoder(f).Decode(&got) + require.NoError(t, err) + + // Flatten findings + results := lo.FlatMap(got.Findings, func(resource report.Resource, _ int) []types.Result { + return resource.Results + }) + + // Has vulnerabilities + assert.True(t, lo.SomeBy(results, func(r types.Result) bool { + return len(r.Vulnerabilities) > 0 + })) + + // Has misconfigurations + assert.True(t, lo.SomeBy(results, func(r types.Result) bool { + return len(r.Misconfigurations) > 0 + })) + + }) } diff --git a/integration/testdata/fixtures/k8s/kube-config-template b/integration/testdata/fixtures/k8s/kube-config-template new file mode 100644 index 000000000000..90b8b082e579 --- /dev/null +++ b/integration/testdata/fixtures/k8s/kube-config-template @@ -0,0 +1,18 @@ +apiVersion: v1 +clusters: +- cluster: + certificate-authority-data: {{CA}} + server: {{URL}} + name: kind-kind-test +contexts: +- context: + cluster: kind-kind-test + namespace: limitedns + user: limiteduser + name: limitedcontext +kind: Config +preferences: {} +users: +- name: limiteduser + user: + token: {{TOKEN}} \ No newline at end of file diff --git a/integration/testdata/fixtures/k8s/limited-binding.yaml b/integration/testdata/fixtures/k8s/limited-binding.yaml new file mode 100644 index 000000000000..6844922417be --- /dev/null +++ b/integration/testdata/fixtures/k8s/limited-binding.yaml @@ -0,0 +1,15 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: limited-binding + namespace: limitedns + +subjects: +- kind: ServiceAccount + name: limiteduser + namespace: default + +roleRef: + kind: Role + name: limited-role + apiGroup: rbac.authorization.k8s.io \ No newline at end of file diff --git a/integration/testdata/fixtures/k8s/limited-pod.yaml b/integration/testdata/fixtures/k8s/limited-pod.yaml new file mode 100644 index 000000000000..a4968e7fae84 --- /dev/null +++ b/integration/testdata/fixtures/k8s/limited-pod.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Pod +metadata: + name: limited-pod + namespace: limitedns +spec: + containers: + - name: nginx + image: nginx:1.14.2 + ports: + - containerPort: 80 diff --git a/integration/testdata/fixtures/k8s/limited-role.yaml b/integration/testdata/fixtures/k8s/limited-role.yaml new file mode 100644 index 000000000000..31dff1fb1f26 --- /dev/null +++ b/integration/testdata/fixtures/k8s/limited-role.yaml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: limited-role + namespace: limitedns +rules: +- apiGroups: [""] + resources: ["*"] + verbs: ["list"] +- apiGroups: ["apps", "batch", "networking.k8s.io","rbac.authorization.k8s.io"] + resources: ["*"] + verbs: ["list"] diff --git a/magefiles/magefile.go b/magefiles/magefile.go index 9e8ffba1365a..de3e36df08ea 100644 --- a/magefiles/magefile.go +++ b/magefiles/magefile.go @@ -309,13 +309,74 @@ func (t Test) K8s() error { defer func() { _ = sh.RunWithV(ENV, "kind", "delete", "cluster", "--name", "kind-test") }() + // wait for the kind cluster is running correctly + err = sh.RunWithV(ENV, "kubectl", "wait", "--for=condition=Ready", "nodes", "--all", "--timeout=300s") + if err != nil { + return fmt.Errorf("can't wait for the kind cluster: %w", err) + } + err = sh.RunWithV(ENV, "kubectl", "apply", "-f", "./integration/testdata/fixtures/k8s/test_nginx.yaml") + if err != nil { + return fmt.Errorf("can't create a test deployment: %w", err) + } + + // create an environment for limited user test + err = initk8sLimitedUserEnv() + if err != nil { + return fmt.Errorf("can't create environment for limited user: %w", err) + } + + // print all resources for info + err = sh.RunWithV(ENV, "kubectl", "get", "all", "-A") if err != nil { return err } + return sh.RunWithV(ENV, "go", "test", "-v", "-tags=k8s_integration", "./integration/...") } +func initk8sLimitedUserEnv() error { + commands := [][]string{ + {"kubectl", "create", "namespace", "limitedns"}, + {"kubectl", "create", "-f", "./integration/testdata/fixtures/k8s/limited-pod.yaml"}, + {"kubectl", "create", "serviceaccount", "limiteduser"}, + {"kubectl", "create", "-f", "./integration/testdata/fixtures/k8s/limited-role.yaml"}, + {"kubectl", "create", "-f", "./integration/testdata/fixtures/k8s/limited-binding.yaml"}, + {"cp", "./integration/testdata/fixtures/k8s/kube-config-template", "./integration/limitedconfig"}, + } + + for _, cmd := range commands { + if err := sh.RunV(cmd[0], cmd[1:]...); err != nil { + return err + } + } + envs := make(map[string]string) + var err error + envs["CA"], err = sh.Output("kubectl", "config", "view", "-o", "jsonpath=\"{.clusters[?(@.name == 'kind-kind-test')].cluster.certificate-authority-data}\"", "--flatten") + if err != nil { + return err + } + envs["URL"], err = sh.Output("kubectl", "config", "view", "-o", "jsonpath=\"{.clusters[?(@.name == 'kind-kind-test')].cluster.server}\"") + if err != nil { + return err + } + envs["TOKEN"], err = sh.Output("kubectl", "create", "token", "limiteduser", "--duration=8760h") + if err != nil { + return err + } + commandsWith := [][]string{ + {"sed", "-i", "-e", "s|{{CA}}|$CA|g", "./integration/limitedconfig"}, + {"sed", "-i", "-e", "s|{{URL}}|$URL|g", "./integration/limitedconfig"}, + {"sed", "-i", "-e", "s|{{TOKEN}}|$TOKEN|g", "./integration/limitedconfig"}, + } + for _, cmd := range commandsWith { + if err := sh.RunWithV(envs, cmd[0], cmd[1:]...); err != nil { + return err + } + } + return nil +} + // Module runs Wasm integration tests func (t Test) Module() error { mg.Deps(t.FixtureContainerImages, t.GenerateExampleModules)