Skip to content

Commit db9e57a

Browse files
afdesksimar7
andauthored
feat(k8s): improve artifact selections for specific namespaces (#8248)
Co-authored-by: simar7 <[email protected]>
1 parent da7bba9 commit db9e57a

File tree

9 files changed

+234
-29
lines changed

9 files changed

+234
-29
lines changed

docs/docs/target/kubernetes.md

+35-1
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,41 @@ You can also specify a `kubeconfig` using the `--kubeconfig` flag:
4949
trivy k8s --kubeconfig ~/.kube/config2
5050
```
5151
52-
By default, all cluster resource images will be downloaded and scanned.
52+
## Required roles
53+
To successfully scan a Kubernetes cluster, `trivy kubernetes` subcommand must be executed under a role or a cluster role that has some specific permissions.
54+
55+
The role must have `list` verb for all resources (`"*"`) inside the following API groups: core (`""`), `"apps"`, `"batch"`,`"networking.k8s.io"`, `"rbac.authorization.k8s.io"`:
56+
```yaml
57+
- apiGroups: [""]
58+
resources: ["*"]
59+
verbs: ["list"]
60+
- apiGroups: ["apps", "batch", "networking.k8s.io", "rbac.authorization.k8s.io"]
61+
resources: ["*"]
62+
verbs: ["list"]
63+
```
64+
If `node collector` is enabled (default: enabled), Trivy needs a cluster role with some additional permissions to run and track the jobs:
65+
```yaml
66+
- apiGroups: [""]
67+
resources: ["nodes/proxy", "pods/log"]
68+
verbs: ["get"]
69+
- apiGroups: [""]
70+
resources: ["events"]
71+
verbs: ["watch"]
72+
- apiGroups: ["batch"]
73+
resources: ["jobs", "cronjobs"]
74+
verbs: ["list", "get"]
75+
- apiGroups: ["batch"]
76+
resources: ["jobs"]
77+
verbs: ["create","delete", "watch"]
78+
- apiGroups: [""]
79+
resources: ["namespaces"]
80+
verbs: ["create"]
81+
```
5382
5483
### Skip-images
5584
85+
By default, all cluster resource images will be downloaded and scanned.
86+
5687
You can control whether Trivy will scan and download the cluster resource images. To disable this feature, add the --skip-images flag.
5788
5889
- `--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
91122
92123
By default, all namespaces will be included in cluster scanning.
93124
125+
!!! note "using `--exclude-namespaces`"
126+
Trivy requires a complete list of namespaces to exclude specific ones. Therefore, `--exclude-namespaces` option is only available for cluster roles now.
127+
94128
Example:
95129
96130
```sh

go.mod

+19-16
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,13 @@ require (
2727
github.com/aquasecurity/trivy-checks v1.4.0
2828
github.com/aquasecurity/trivy-db v0.0.0-20241209111357-8c398f13db0e
2929
github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48
30-
github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20241101182546-89bffc3932bc
30+
github.com/aquasecurity/trivy-kubernetes v0.7.0
3131
github.com/aws/aws-sdk-go-v2 v1.34.0
3232
github.com/aws/aws-sdk-go-v2/config v1.29.2
3333
github.com/aws/aws-sdk-go-v2/credentials v1.17.55
3434
github.com/aws/aws-sdk-go-v2/service/ec2 v1.201.1
3535
github.com/aws/aws-sdk-go-v2/service/ecr v1.38.7
3636
github.com/aws/aws-sdk-go-v2/service/s3 v1.74.1
37-
github.com/aws/aws-sdk-go-v2/service/sts v1.33.10 // indirect
3837
github.com/aws/smithy-go v1.22.2
3938
github.com/bitnami/go-version v0.0.0-20231130084017-bb00604d650c
4039
github.com/bmatcuk/doublestar/v4 v4.8.1
@@ -179,16 +178,6 @@ require (
179178
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect
180179
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
181180
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
182-
github.com/aws/aws-sdk-go v1.55.5 // indirect
183-
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.25 // indirect
184-
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.29 // indirect
185-
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.29 // indirect
186-
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 // indirect
187-
github.com/aws/aws-sdk-go-v2/service/ebs v1.22.1 // indirect
188-
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 // indirect
189-
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.10 // indirect
190-
github.com/aws/aws-sdk-go-v2/service/sso v1.24.12 // indirect
191-
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.11 // indirect
192181
github.com/beorn7/perks v1.0.1 // indirect
193182
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
194183
github.com/blang/semver v3.5.1+incompatible // indirect
@@ -416,12 +405,12 @@ require (
416405
k8s.io/apiextensions-apiserver v0.32.0 // indirect
417406
k8s.io/apimachinery v0.32.1 // indirect
418407
k8s.io/apiserver v0.32.0 // indirect
419-
k8s.io/cli-runtime v0.32.0 // indirect
420-
k8s.io/client-go v0.32.0 // indirect
421-
k8s.io/component-base v0.32.0 // indirect
408+
k8s.io/cli-runtime v0.32.1 // indirect
409+
k8s.io/client-go v0.32.1 // indirect
410+
k8s.io/component-base v0.32.1 // indirect
422411
k8s.io/klog/v2 v2.130.1 // indirect
423412
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect
424-
k8s.io/kubectl v0.32.0 // indirect
413+
k8s.io/kubectl v0.32.1 // indirect
425414
modernc.org/libc v1.55.3 // indirect
426415
modernc.org/mathutil v1.6.0 // indirect
427416
modernc.org/memory v1.8.0 // indirect
@@ -434,3 +423,17 @@ require (
434423
tags.cncf.io/container-device-interface v0.8.0 // indirect
435424
tags.cncf.io/container-device-interface/specs-go v0.8.0 // indirect
436425
)
426+
427+
require (
428+
github.com/aws/aws-sdk-go v1.55.6 // indirect
429+
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.25 // indirect
430+
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.29 // indirect
431+
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.29 // indirect
432+
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 // indirect
433+
github.com/aws/aws-sdk-go-v2/service/ebs v1.22.1 // indirect
434+
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 // indirect
435+
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.10 // indirect
436+
github.com/aws/aws-sdk-go-v2/service/sso v1.24.12 // indirect
437+
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.11 // indirect
438+
github.com/aws/aws-sdk-go-v2/service/sts v1.33.10 // indirect
439+
)

go.sum

+12-12
Original file line numberDiff line numberDiff line change
@@ -809,17 +809,17 @@ github.com/aquasecurity/trivy-db v0.0.0-20241209111357-8c398f13db0e h1:O5j5SeCNB
809809
github.com/aquasecurity/trivy-db v0.0.0-20241209111357-8c398f13db0e/go.mod h1:gS8VhlNxhraiq60BBnJw9kGtjeMspQ9E8pX24jCL4jg=
810810
github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 h1:JVgBIuIYbwG+ekC5lUHUpGJboPYiCcxiz06RCtz8neI=
811811
github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48/go.mod h1:Ldya37FLi0e/5Cjq2T5Bty7cFkzUDwTcPeQua+2M8i8=
812-
github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20241101182546-89bffc3932bc h1:/mFBYIK9RY+L8s1CIbQbJ5B3v0YmoDSu5eAzavvMa+Y=
813-
github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20241101182546-89bffc3932bc/go.mod h1:ctlibFXOQyjWybeVVQI6NLG6GJoPWZJ4cIirQ/wPCQs=
812+
github.com/aquasecurity/trivy-kubernetes v0.7.0 h1:0pRJFSslUYd9xzQIEw1c0mS7k1Vv489nH/LsxeU6yME=
813+
github.com/aquasecurity/trivy-kubernetes v0.7.0/go.mod h1:O6JZMicTmZrsjEpGzsnBMhPTHAfpnTMqXTAMidG6M+M=
814814
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
815815
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
816816
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
817817
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
818818
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
819819
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
820820
github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
821-
github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU=
822-
github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
821+
github.com/aws/aws-sdk-go v1.55.6 h1:cSg4pvZ3m8dgYcgqB97MrcdjUmZ1BeMYKUxMMB89IPk=
822+
github.com/aws/aws-sdk-go v1.55.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
823823
github.com/aws/aws-sdk-go-v2 v1.34.0 h1:9iyL+cjifckRGEVpRKZP3eIxVlL06Qk1Tk13vreaVQU=
824824
github.com/aws/aws-sdk-go-v2 v1.34.0/go.mod h1:JgstGg0JjWU1KpVJjD5H0y0yyAIpSdKEq556EI6yOOM=
825825
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=
27872787
k8s.io/apimachinery v0.32.1/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
27882788
k8s.io/apiserver v0.32.0 h1:VJ89ZvQZ8p1sLeiWdRJpRD6oLozNZD2+qVSLi+ft5Qs=
27892789
k8s.io/apiserver v0.32.0/go.mod h1:HFh+dM1/BE/Hm4bS4nTXHVfN6Z6tFIZPi649n83b4Ag=
2790-
k8s.io/cli-runtime v0.32.0 h1:dP+OZqs7zHPpGQMCGAhectbHU2SNCuZtIimRKTv2T1c=
2791-
k8s.io/cli-runtime v0.32.0/go.mod h1:Mai8ht2+esoDRK5hr861KRy6z0zHsSTYttNVJXgP3YQ=
2792-
k8s.io/client-go v0.32.0 h1:DimtMcnN/JIKZcrSrstiwvvZvLjG0aSxy8PxN8IChp8=
2793-
k8s.io/client-go v0.32.0/go.mod h1:boDWvdM1Drk4NJj/VddSLnx59X3OPgwrOo0vGbtq9+8=
2794-
k8s.io/component-base v0.32.0 h1:d6cWHZkCiiep41ObYQS6IcgzOUQUNpywm39KVYaUqzU=
2795-
k8s.io/component-base v0.32.0/go.mod h1:JLG2W5TUxUu5uDyKiH2R/7NnxJo1HlPoRIIbVLkK5eM=
2790+
k8s.io/cli-runtime v0.32.1 h1:19nwZPlYGJPUDbhAxDIS2/oydCikvKMHsxroKNGA2mM=
2791+
k8s.io/cli-runtime v0.32.1/go.mod h1:NJPbeadVFnV2E7B7vF+FvU09mpwYlZCu8PqjzfuOnkY=
2792+
k8s.io/client-go v0.32.1 h1:otM0AxdhdBIaQh7l1Q0jQpmo7WOFIk5FFa4bg6YMdUU=
2793+
k8s.io/client-go v0.32.1/go.mod h1:aTTKZY7MdxUaJ/KiUs8D+GssR9zJZi77ZqtzcGXIiDg=
2794+
k8s.io/component-base v0.32.1 h1:/5IfJ0dHIKBWysGV0yKTFfacZ5yNV1sulPh3ilJjRZk=
2795+
k8s.io/component-base v0.32.1/go.mod h1:j1iMMHi/sqAHeG5z+O9BFNCF698a1u0186zkjMZQ28w=
27962796
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
27972797
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
27982798
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y=
27992799
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4=
2800-
k8s.io/kubectl v0.32.0 h1:rpxl+ng9qeG79YA4Em9tLSfX0G8W0vfaiPVrc/WR7Xw=
2801-
k8s.io/kubectl v0.32.0/go.mod h1:qIjSX+QgPQUgdy8ps6eKsYNF+YmFOAO3WygfucIqFiE=
2800+
k8s.io/kubectl v0.32.1 h1:/btLtXLQUU1rWx8AEvX9jrb9LaI6yeezt3sFALhB8M8=
2801+
k8s.io/kubectl v0.32.1/go.mod h1:sezNuyWi1STk4ZNPVRIFfgjqMI6XMf+oCVLjZen/pFQ=
28022802
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
28032803
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
28042804
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=

integration/k8s_test.go

+51
Original file line numberDiff line numberDiff line change
@@ -106,4 +106,55 @@ func TestK8s(t *testing.T) {
106106
return len(*r.Dependencies) > 0
107107
}))
108108
})
109+
110+
t.Run("limited user test", func(t *testing.T) {
111+
// Set up the output file
112+
outputFile := filepath.Join(t.TempDir(), "output.json")
113+
114+
osArgs := []string{
115+
"--cache-dir",
116+
cacheDir,
117+
"k8s",
118+
"limitedcontext",
119+
"--kubeconfig", "limitedconfig",
120+
"--report",
121+
"summary",
122+
"-q",
123+
"--timeout",
124+
"5m0s",
125+
"--include-namespaces", "limitedns",
126+
"--format",
127+
"json",
128+
"--output",
129+
outputFile,
130+
}
131+
132+
// Run Trivy
133+
err := execute(osArgs)
134+
require.NoError(t, err)
135+
136+
var got report.ConsolidatedReport
137+
f, err := os.Open(outputFile)
138+
require.NoError(t, err)
139+
defer f.Close()
140+
141+
err = json.NewDecoder(f).Decode(&got)
142+
require.NoError(t, err)
143+
144+
// Flatten findings
145+
results := lo.FlatMap(got.Findings, func(resource report.Resource, _ int) []types.Result {
146+
return resource.Results
147+
})
148+
149+
// Has vulnerabilities
150+
assert.True(t, lo.SomeBy(results, func(r types.Result) bool {
151+
return len(r.Vulnerabilities) > 0
152+
}))
153+
154+
// Has misconfigurations
155+
assert.True(t, lo.SomeBy(results, func(r types.Result) bool {
156+
return len(r.Misconfigurations) > 0
157+
}))
158+
159+
})
109160
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
apiVersion: v1
2+
clusters:
3+
- cluster:
4+
certificate-authority-data: {{CA}}
5+
server: {{URL}}
6+
name: kind-kind-test
7+
contexts:
8+
- context:
9+
cluster: kind-kind-test
10+
namespace: limitedns
11+
user: limiteduser
12+
name: limitedcontext
13+
kind: Config
14+
preferences: {}
15+
users:
16+
- name: limiteduser
17+
user:
18+
token: {{TOKEN}}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
apiVersion: rbac.authorization.k8s.io/v1
2+
kind: RoleBinding
3+
metadata:
4+
name: limited-binding
5+
namespace: limitedns
6+
7+
subjects:
8+
- kind: ServiceAccount
9+
name: limiteduser
10+
namespace: default
11+
12+
roleRef:
13+
kind: Role
14+
name: limited-role
15+
apiGroup: rbac.authorization.k8s.io
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
apiVersion: v1
2+
kind: Pod
3+
metadata:
4+
name: limited-pod
5+
namespace: limitedns
6+
spec:
7+
containers:
8+
- name: nginx
9+
image: nginx:1.14.2
10+
ports:
11+
- containerPort: 80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
apiVersion: rbac.authorization.k8s.io/v1
2+
kind: Role
3+
metadata:
4+
name: limited-role
5+
namespace: limitedns
6+
rules:
7+
- apiGroups: [""]
8+
resources: ["*"]
9+
verbs: ["list"]
10+
- apiGroups: ["apps", "batch", "networking.k8s.io","rbac.authorization.k8s.io"]
11+
resources: ["*"]
12+
verbs: ["list"]

magefiles/magefile.go

+61
Original file line numberDiff line numberDiff line change
@@ -309,13 +309,74 @@ func (t Test) K8s() error {
309309
defer func() {
310310
_ = sh.RunWithV(ENV, "kind", "delete", "cluster", "--name", "kind-test")
311311
}()
312+
// wait for the kind cluster is running correctly
313+
err = sh.RunWithV(ENV, "kubectl", "wait", "--for=condition=Ready", "nodes", "--all", "--timeout=300s")
314+
if err != nil {
315+
return fmt.Errorf("can't wait for the kind cluster: %w", err)
316+
}
317+
312318
err = sh.RunWithV(ENV, "kubectl", "apply", "-f", "./integration/testdata/fixtures/k8s/test_nginx.yaml")
319+
if err != nil {
320+
return fmt.Errorf("can't create a test deployment: %w", err)
321+
}
322+
323+
// create an environment for limited user test
324+
err = initk8sLimitedUserEnv()
325+
if err != nil {
326+
return fmt.Errorf("can't create environment for limited user: %w", err)
327+
}
328+
329+
// print all resources for info
330+
err = sh.RunWithV(ENV, "kubectl", "get", "all", "-A")
313331
if err != nil {
314332
return err
315333
}
334+
316335
return sh.RunWithV(ENV, "go", "test", "-v", "-tags=k8s_integration", "./integration/...")
317336
}
318337

338+
func initk8sLimitedUserEnv() error {
339+
commands := [][]string{
340+
{"kubectl", "create", "namespace", "limitedns"},
341+
{"kubectl", "create", "-f", "./integration/testdata/fixtures/k8s/limited-pod.yaml"},
342+
{"kubectl", "create", "serviceaccount", "limiteduser"},
343+
{"kubectl", "create", "-f", "./integration/testdata/fixtures/k8s/limited-role.yaml"},
344+
{"kubectl", "create", "-f", "./integration/testdata/fixtures/k8s/limited-binding.yaml"},
345+
{"cp", "./integration/testdata/fixtures/k8s/kube-config-template", "./integration/limitedconfig"},
346+
}
347+
348+
for _, cmd := range commands {
349+
if err := sh.RunV(cmd[0], cmd[1:]...); err != nil {
350+
return err
351+
}
352+
}
353+
envs := make(map[string]string)
354+
var err error
355+
envs["CA"], err = sh.Output("kubectl", "config", "view", "-o", "jsonpath=\"{.clusters[?(@.name == 'kind-kind-test')].cluster.certificate-authority-data}\"", "--flatten")
356+
if err != nil {
357+
return err
358+
}
359+
envs["URL"], err = sh.Output("kubectl", "config", "view", "-o", "jsonpath=\"{.clusters[?(@.name == 'kind-kind-test')].cluster.server}\"")
360+
if err != nil {
361+
return err
362+
}
363+
envs["TOKEN"], err = sh.Output("kubectl", "create", "token", "limiteduser", "--duration=8760h")
364+
if err != nil {
365+
return err
366+
}
367+
commandsWith := [][]string{
368+
{"sed", "-i", "-e", "s|{{CA}}|$CA|g", "./integration/limitedconfig"},
369+
{"sed", "-i", "-e", "s|{{URL}}|$URL|g", "./integration/limitedconfig"},
370+
{"sed", "-i", "-e", "s|{{TOKEN}}|$TOKEN|g", "./integration/limitedconfig"},
371+
}
372+
for _, cmd := range commandsWith {
373+
if err := sh.RunWithV(envs, cmd[0], cmd[1:]...); err != nil {
374+
return err
375+
}
376+
}
377+
return nil
378+
}
379+
319380
// Module runs Wasm integration tests
320381
func (t Test) Module() error {
321382
mg.Deps(t.FixtureContainerImages, t.GenerateExampleModules)

0 commit comments

Comments
 (0)