From a2b725ad3289e55c31c2e4aa3fa75e205a1e816a Mon Sep 17 00:00:00 2001 From: chenk Date: Fri, 20 Oct 2023 20:17:23 +0300 Subject: [PATCH] feat: scan vulns on k8s core component apps Signed-off-by: chenk --- go.mod | 16 +-- go.sum | 32 +++--- magefiles/magefile.go | 2 +- pkg/k8s/inject.go | 15 +++ pkg/k8s/k8s.go | 39 +++++++ pkg/k8s/report/report.go | 29 +++-- pkg/k8s/scanner/scanner.go | 193 ++++++++++++++++++++++++++++---- pkg/k8s/scanner/scanner_test.go | 156 +++++++++++++++++++++++++- pkg/k8s/wire_gen.go | 30 +++++ 9 files changed, 453 insertions(+), 59 deletions(-) create mode 100644 pkg/k8s/inject.go create mode 100644 pkg/k8s/k8s.go create mode 100644 pkg/k8s/wire_gen.go diff --git a/go.mod b/go.mod index 5d496371fa04..5b3ac8dabc6e 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/aquasecurity/tml v0.6.1 github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d github.com/aquasecurity/trivy-java-db v0.0.0-20230209231723-7cddb1406728 - github.com/aquasecurity/trivy-kubernetes v0.5.8-0.20230928134646-b414e546fe6d + github.com/aquasecurity/trivy-kubernetes v0.5.9-0.20231019164303-dcdfdc50763f github.com/aws/aws-sdk-go-v2 v1.21.2 github.com/aws/aws-sdk-go-v2/config v1.18.45 github.com/aws/aws-sdk-go-v2/credentials v1.13.43 @@ -107,7 +107,7 @@ require ( golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 google.golang.org/protobuf v1.31.0 gopkg.in/yaml.v3 v3.0.1 - k8s.io/api v0.28.1 + k8s.io/api v0.28.2 k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 modernc.org/sqlite v1.23.1 ) @@ -149,7 +149,7 @@ require ( github.com/apparentlymart/go-cidr v1.1.0 // indirect github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect - github.com/aws/aws-sdk-go v1.45.19 // indirect + github.com/aws/aws-sdk-go v1.46.1 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.14 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.13 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.43 // indirect @@ -377,14 +377,14 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect helm.sh/helm/v3 v3.12.3 // indirect k8s.io/apiextensions-apiserver v0.27.3 // indirect - k8s.io/apimachinery v0.28.1 // indirect + k8s.io/apimachinery v0.28.2 // indirect k8s.io/apiserver v0.27.3 // indirect - k8s.io/cli-runtime v0.28.1 // indirect - k8s.io/client-go v0.28.1 // indirect - k8s.io/component-base v0.28.1 // indirect + k8s.io/cli-runtime v0.28.2 // indirect + k8s.io/client-go v0.28.2 // indirect + k8s.io/component-base v0.28.2 // indirect k8s.io/klog/v2 v2.100.1 // indirect k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect - k8s.io/kubectl v0.28.1 // indirect + k8s.io/kubectl v0.28.2 // indirect lukechampine.com/uint128 v1.2.0 // indirect modernc.org/cc/v3 v3.40.0 // indirect modernc.org/ccgo/v3 v3.16.13 // indirect diff --git a/go.sum b/go.sum index 1fdfe563f603..5bf5f10cede5 100644 --- a/go.sum +++ b/go.sum @@ -347,8 +347,8 @@ github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d h1:fjI9mkoTU github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d/go.mod h1:cj9/QmD9N3OZnKQMp+/DvdV+ym3HyIkd4e+F0ZM3ZGs= github.com/aquasecurity/trivy-java-db v0.0.0-20230209231723-7cddb1406728 h1:0eS+V7SXHgqoT99tV1mtMW6HL4HdoB9qGLMCb1fZp8A= github.com/aquasecurity/trivy-java-db v0.0.0-20230209231723-7cddb1406728/go.mod h1:Ldya37FLi0e/5Cjq2T5Bty7cFkzUDwTcPeQua+2M8i8= -github.com/aquasecurity/trivy-kubernetes v0.5.8-0.20230928134646-b414e546fe6d h1:5urHj0NMGflp/M9Ll5QlKfo0Kf6nJ01RED1HRgl0CeE= -github.com/aquasecurity/trivy-kubernetes v0.5.8-0.20230928134646-b414e546fe6d/go.mod h1:e1RaMcs2R/C+eP1Pi7JyhDB7Qn1PNRg5rTVwuJL7AiE= +github.com/aquasecurity/trivy-kubernetes v0.5.9-0.20231019164303-dcdfdc50763f h1:HDWxGTNMAeX8LFUDQKME+JwE2sPkFEFLso1OicnoXgw= +github.com/aquasecurity/trivy-kubernetes v0.5.9-0.20231019164303-dcdfdc50763f/go.mod h1:k2Nf7s+Gx88BZE/yjBv7Kqdng/quv/hwaYI2bjSWFqY= 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/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= @@ -363,8 +363,8 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= -github.com/aws/aws-sdk-go v1.45.19 h1:+4yXWhldhCVXWFOQRF99ZTJ92t4DtoHROZIbN7Ujk/U= -github.com/aws/aws-sdk-go v1.45.19/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.46.1 h1:U26quvBWFZMQuultLw5tloW4GnmWaChEwMZNq8uYatw= +github.com/aws/aws-sdk-go v1.46.1/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/aws/aws-sdk-go-v2 v1.16.16/go.mod h1:SwiyXi/1zTUZ6KIAmLK5V5ll8SiURNUYOqTerZPaF9k= github.com/aws/aws-sdk-go-v2 v1.17.1/go.mod h1:JLnGeGONAyi2lWXI1p0PCIOIy333JMVK1U7Hf0aRFLw= github.com/aws/aws-sdk-go-v2 v1.17.5/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= @@ -2524,32 +2524,32 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= -k8s.io/api v0.28.1 h1:i+0O8k2NPBCPYaMB+uCkseEbawEt/eFaiRqUx8aB108= -k8s.io/api v0.28.1/go.mod h1:uBYwID+66wiL28Kn2tBjBYQdEU0Xk0z5qF8bIBqk/Dg= +k8s.io/api v0.28.2 h1:9mpl5mOb6vXZvqbQmankOfPIGiudghwCoLl1EYfUZbw= +k8s.io/api v0.28.2/go.mod h1:RVnJBsjU8tcMq7C3iaRSGMeaKt2TWEUXcpIt/90fjEg= k8s.io/apiextensions-apiserver v0.27.3 h1:xAwC1iYabi+TDfpRhxh4Eapl14Hs2OftM2DN5MpgKX4= k8s.io/apiextensions-apiserver v0.27.3/go.mod h1:BH3wJ5NsB9XE1w+R6SSVpKmYNyIiyIz9xAmBl8Mb+84= k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc= -k8s.io/apimachinery v0.28.1 h1:EJD40og3GizBSV3mkIoXQBsws32okPOy+MkRyzh6nPY= -k8s.io/apimachinery v0.28.1/go.mod h1:X0xh/chESs2hP9koe+SdIAcXWcQ+RM5hy0ZynB+yEvw= +k8s.io/apimachinery v0.28.2 h1:KCOJLrc6gu+wV1BYgwik4AF4vXOlVJPdiqn0yAWWwXQ= +k8s.io/apimachinery v0.28.2/go.mod h1:RdzF87y/ngqk9H4z3EL2Rppv5jj95vGS/HaFXrLDApU= k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= k8s.io/apiserver v0.27.3 h1:AxLvq9JYtveYWK+D/Dz/uoPCfz8JC9asR5z7+I/bbQ4= k8s.io/apiserver v0.27.3/go.mod h1:Y61+EaBMVWUBJtxD5//cZ48cHZbQD+yIyV/4iEBhhNA= -k8s.io/cli-runtime v0.28.1 h1:7Njc4eD5kaO4tYdSYVJJEs54koYD/vT6gxOq8dEVf9g= -k8s.io/cli-runtime v0.28.1/go.mod h1:yIThSWkAVLqeRs74CMkq6lNFW42GyJmvMtcNn01SZho= +k8s.io/cli-runtime v0.28.2 h1:64meB2fDj10/ThIMEJLO29a1oujSm0GQmKzh1RtA/uk= +k8s.io/cli-runtime v0.28.2/go.mod h1:bTpGOvpdsPtDKoyfG4EG041WIyFZLV9qq4rPlkyYfDA= k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k= k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0= -k8s.io/client-go v0.28.1 h1:pRhMzB8HyLfVwpngWKE8hDcXRqifh1ga2Z/PU9SXVK8= -k8s.io/client-go v0.28.1/go.mod h1:pEZA3FqOsVkCc07pFVzK076R+P/eXqsgx5zuuRWukNE= +k8s.io/client-go v0.28.2 h1:DNoYI1vGq0slMBN/SWKMZMw0Rq+0EQW6/AK4v9+3VeY= +k8s.io/client-go v0.28.2/go.mod h1:sMkApowspLuc7omj1FOSUxSoqjr+d5Q0Yc0LOFnYFJY= k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI= k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM= -k8s.io/component-base v0.28.1 h1:LA4AujMlK2mr0tZbQDZkjWbdhTV5bRyEyAFe0TJxlWg= -k8s.io/component-base v0.28.1/go.mod h1:jI11OyhbX21Qtbav7JkhehyBsIRfnO8oEgoAR12ArIU= +k8s.io/component-base v0.28.2 h1:Yc1yU+6AQSlpJZyvehm/NkJBII72rzlEsd6MkBQ+G0E= +k8s.io/component-base v0.28.2/go.mod h1:4IuQPQviQCg3du4si8GpMrhAIegxpsgPngPRR/zWpzc= k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM= k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= @@ -2562,8 +2562,8 @@ k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 h1:LyMgNKD2P8Wn1iAwQU5OhxCKlKJy0sHc+PcDwFB24dQ= k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM= -k8s.io/kubectl v0.28.1 h1:jAq4yKEqQL+fwkWcEsUWxhJ7uIRcOYQraJxx4SyAMTY= -k8s.io/kubectl v0.28.1/go.mod h1:a0nk/lMMeKBulp0lMTJAKbkjZg1ykqfLfz/d6dnv1ak= +k8s.io/kubectl v0.28.2 h1:fOWOtU6S0smdNjG1PB9WFbqEIMlkzU5ahyHkc7ESHgM= +k8s.io/kubectl v0.28.2/go.mod h1:6EQWTPySF1fn7yKoQZHYf9TPwIl2AygHEcJoxFekr64= k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk= diff --git a/magefiles/magefile.go b/magefiles/magefile.go index bc43487bca01..424c447ac945 100644 --- a/magefiles/magefile.go +++ b/magefiles/magefile.go @@ -108,7 +108,7 @@ func (Tool) Mockery() error { // Wire generates the wire_gen.go file for each package func Wire() error { mg.Deps(Tool{}.Wire) - return sh.RunV("wire", "gen", "./pkg/commands/...", "./pkg/rpc/...") + return sh.RunV("wire", "gen", "./pkg/commands/...", "./pkg/rpc/...", "./pkg/k8s/...") } // Mock generates mocks diff --git a/pkg/k8s/inject.go b/pkg/k8s/inject.go new file mode 100644 index 000000000000..31ffd2afffa7 --- /dev/null +++ b/pkg/k8s/inject.go @@ -0,0 +1,15 @@ +//go:build wireinject +// +build wireinject + +package k8s + +import ( + "github.com/google/wire" + + "github.com/aquasecurity/trivy/pkg/fanal/cache" +) + +func initializeScanK8s(localArtifactCache cache.LocalArtifactCache) *ScanKubernetes { + wire.Build(ScanSuperSet) + return &ScanKubernetes{} +} diff --git a/pkg/k8s/k8s.go b/pkg/k8s/k8s.go new file mode 100644 index 000000000000..faca5c25e924 --- /dev/null +++ b/pkg/k8s/k8s.go @@ -0,0 +1,39 @@ +package k8s + +import ( + "context" + + "github.com/google/wire" + + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/scanner" + "github.com/aquasecurity/trivy/pkg/scanner/local" + "github.com/aquasecurity/trivy/pkg/types" +) + +// ScanSuperSet binds the dependencies for k8s +var ScanSuperSet = wire.NewSet( + local.SuperSet, + wire.Bind(new(scanner.Driver), new(local.Scanner)), + NewScanKubernetes, +) + +// ScanKubernetes implements the scanner +type ScanKubernetes struct { + localScanner local.Scanner +} + +// NewScanKubernetes is the factory method for scanner +func NewScanKubernetes(s local.Scanner) *ScanKubernetes { + return &ScanKubernetes{localScanner: s} +} + +// NewKubenetesScanner is the factory method for scanner +func NewKubenetesScanner() *ScanKubernetes { + return initializeScanK8s(nil) +} + +// // Scan scans k8s core components and return it findings +func (sk ScanKubernetes) Scan(ctx context.Context, target types.ScanTarget, options types.ScanOptions) (types.Results, ftypes.OS, error) { + return sk.localScanner.ScanTarget(ctx, target, options) +} diff --git a/pkg/k8s/report/report.go b/pkg/k8s/report/report.go index ca2e33cb39d8..bf0d0aaf8626 100644 --- a/pkg/k8s/report/report.go +++ b/pkg/k8s/report/report.go @@ -222,9 +222,22 @@ func infraResource(misConfig Resource) bool { } func CreateResource(artifact *artifacts.Artifact, report types.Report, err error) Resource { - results := make([]types.Result, 0, len(report.Results)) + r := CreateK8sResource(artifact, report.Results) + + r.Metadata = report.Metadata + r.Report = report + // if there was any error during the scan + if err != nil { + r.Error = err.Error() + } + + return r +} + +func CreateK8sResource(artifact *artifacts.Artifact, scanResults types.Results) Resource { + results := make([]types.Result, 0, len(scanResults)) // fix target name - for _, result := range report.Results { + for _, result := range scanResults { // if resource is a kubernetes file fix the target name, // to avoid showing the temp file that was removed. if result.Type == ftypes.Kubernetes { @@ -237,14 +250,12 @@ func CreateResource(artifact *artifacts.Artifact, report types.Report, err error Namespace: artifact.Namespace, Kind: artifact.Kind, Name: artifact.Name, - Metadata: report.Metadata, + Metadata: types.Metadata{}, Results: results, - Report: report, - } - - // if there was any error during the scan - if err != nil { - r.Error = err.Error() + Report: types.Report{ + Results: results, + ArtifactName: artifact.Name, + }, } return r diff --git a/pkg/k8s/scanner/scanner.go b/pkg/k8s/scanner/scanner.go index e4676665aeaf..f260e8b088e5 100644 --- a/pkg/k8s/scanner/scanner.go +++ b/pkg/k8s/scanner/scanner.go @@ -20,6 +20,7 @@ import ( "github.com/aquasecurity/trivy/pkg/digest" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/flag" + "github.com/aquasecurity/trivy/pkg/k8s" "github.com/aquasecurity/trivy/pkg/k8s/report" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/parallel" @@ -52,7 +53,6 @@ func NewScanner(cluster string, runner cmd.Runner, opts flag.Options) *Scanner { } func (s *Scanner) Scan(ctx context.Context, artifactsData []*artifacts.Artifact) (report.Report, error) { - // disable logs before scanning err := log.InitLogger(s.opts.Debug, true) if err != nil { @@ -80,6 +80,16 @@ func (s *Scanner) Scan(ctx context.Context, artifactsData []*artifacts.Artifact) RootComponent: rootComponent, }, nil } + var resourceArtifacts []*artifacts.Artifact + var k8sCoreArtifacts []*artifacts.Artifact + for _, artifact := range artifactsData { + if strings.HasSuffix(artifact.Kind, "Components") || strings.HasSuffix(artifact.Kind, "Cluster") { + k8sCoreArtifacts = append(k8sCoreArtifacts, artifact) + continue + } + resourceArtifacts = append(resourceArtifacts, artifact) + } + var resources []report.Resource type scanResult struct { @@ -129,11 +139,18 @@ func (s *Scanner) Scan(ctx context.Context, artifactsData []*artifacts.Artifact) return nil } - p := parallel.NewPipeline(s.opts.Parallel, !s.opts.Quiet, artifactsData, onItem, onResult) + p := parallel.NewPipeline(s.opts.Parallel, !s.opts.Quiet, resourceArtifacts, onItem, onResult) err = p.Do(ctx) if err != nil { return report.Report{}, err } + if s.opts.Scanners.AnyEnabled(types.VulnerabilityScanner) { + k8sResource, err := s.scanK8sVulns(ctx, k8sCoreArtifacts) + if err != nil { + return report.Report{}, err + } + resources = append(resources, k8sResource...) + } return report.Report{ SchemaVersion: 0, ClusterName: s.cluster, @@ -196,32 +213,152 @@ func (s *Scanner) filter(ctx context.Context, r types.Report, artifact *artifact } const ( - golang = "golang" - oci = "oci" - kubelet = "k8s.io/kubelet" - pod = "PodInfo" - clusterInfo = "ClusterInfo" - nodeInfo = "NodeInfo" - nodeCoreComponents = "node-core-components" + golang = "golang" + oci = "oci" + kubelet = "k8s.io/kubelet" + controlPlaneComponents = "ControlPlaneComponents" + clusterInfo = "Cluster" + nodeComponents = "NodeComponents" + nodeCoreComponents = "node-core-components" ) +func (s *Scanner) scanK8sVulns(ctx context.Context, artifactsData []*artifacts.Artifact) ([]report.Resource, error) { + var resources []report.Resource + var nodeName string + if nodeName = findNodeName(artifactsData); nodeName == "" { + return nil, fmt.Errorf("failed to find node name") + } + + k8sScanner := k8s.NewKubenetesScanner() + scanOptions := types.ScanOptions{ + Scanners: s.opts.Scanners, + VulnType: s.opts.VulnType, + } + for _, artifact := range artifactsData { + switch artifact.Kind { + case controlPlaneComponents: + var comp bom.Component + err := ms.Decode(artifact.RawResource, &comp) + if err != nil { + return nil, err + } + + lang := k8sNamespace(comp.Version, nodeName) + results, _, err := k8sScanner.Scan(ctx, types.ScanTarget{ + Applications: []ftypes.Application{ + { + Type: ftypes.LangType(lang), + FilePath: artifact.Name, + Libraries: []ftypes.Package{ + { + Name: comp.Name, + Version: comp.Version, + }, + }, + }, + }, + }, scanOptions) + if err != nil { + return nil, err + } + if results != nil { + resources = append(resources, report.CreateK8sResource(artifact, results)) + } + case nodeComponents: + var nf bom.NodeInfo + err := ms.Decode(artifact.RawResource, &nf) + if err != nil { + return nil, err + } + kubeletVersion := sanitizedVersion(nf.KubeletVersion) + lang := k8sNamespace(kubeletVersion, nodeName) + runtimeName, runtimeVersion := runtimeNameVersion(nf.ContainerRuntimeVersion) + results, _, err := k8sScanner.Scan(ctx, types.ScanTarget{ + Applications: []ftypes.Application{ + { + Type: ftypes.LangType(lang), + FilePath: artifact.Name, + Libraries: []ftypes.Package{ + { + Name: kubelet, + Version: kubeletVersion, + }, + }, + }, + { + Type: ftypes.GoBinary, + FilePath: artifact.Name, + Libraries: []ftypes.Package{ + { + Name: runtimeName, + Version: runtimeVersion, + }, + }, + }, + }, + }, scanOptions) + if err != nil { + return nil, err + } + if results != nil { + resources = append(resources, report.CreateK8sResource(artifact, results)) + } + case clusterInfo: + var cf bom.ClusterInfo + err := ms.Decode(artifact.RawResource, &cf) + if err != nil { + return nil, err + } + lang := k8sNamespace(cf.Version, nodeName) + + results, _, err := k8sScanner.Scan(ctx, types.ScanTarget{ + Applications: []ftypes.Application{ + { + Type: ftypes.LangType(lang), + FilePath: artifact.Name, + Libraries: []ftypes.Package{ + { + Name: cf.Name, + Version: cf.Version, + }, + }, + }, + }, + }, scanOptions) + if err != nil { + return nil, err + } + if results != nil { + resources = append(resources, report.CreateK8sResource(artifact, results)) + } + } + } + return resources, nil +} + +func findNodeName(allArtifact []*artifacts.Artifact) string { + for _, artifact := range allArtifact { + if artifact.Kind != nodeComponents { + continue + } + return artifact.Name + } + return "" +} + func clusterInfoToReportResources(allArtifact []*artifacts.Artifact) (*core.Component, error) { var coreComponents []*core.Component var cInfo *core.Component // Find the first node name to identify AKS cluster var nodeName string - for _, artifact := range allArtifact { - if artifact.Kind != nodeInfo { - continue - } - nodeName = artifact.Name - break + if nodeName = findNodeName(allArtifact); nodeName == "" { + return nil, fmt.Errorf("failed to find node name") } for _, artifact := range allArtifact { switch artifact.Kind { - case pod: + case controlPlaneComponents: var comp bom.Component err := ms.Decode(artifact.RawResource, &comp) if err != nil { @@ -271,7 +408,7 @@ func clusterInfoToReportResources(allArtifact []*artifacts.Artifact) (*core.Comp PackageURL: generatePURL(comp.Name, comp.Version, nodeName), } coreComponents = append(coreComponents, rootComponent) - case nodeInfo: + case nodeComponents: var nf bom.NodeInfo err := ms.Decode(artifact.RawResource, &nf) if err != nil { @@ -444,8 +581,22 @@ func toProperties(props map[string]string, namespace string) []core.Property { } func generatePURL(name, ver, nodeName string) *purl.PackageURL { - // Identify k8s distribution. An empty namespace means upstream. + var namespace string + // Identify k8s distribution. An empty namespace means upstream. + if namespace = k8sNamespace(ver, nodeName); namespace == "" { + return nil + } else if namespace == "kubernetes" { + namespace = "" + } + + return &purl.PackageURL{ + PackageURL: *packageurl.NewPackageURL(purl.TypeK8s, namespace, name, ver, nil, ""), + } +} + +func k8sNamespace(ver, nodeName string) string { + namespace := "kubernetes" switch { case strings.Contains(ver, "eks"): namespace = purl.NamespaceEKS @@ -456,13 +607,11 @@ func generatePURL(name, ver, nodeName string) *purl.PackageURL { case strings.Contains(ver, "hotfix"): if !strings.Contains(nodeName, "aks") { // Unknown k8s distribution - return nil + return "" } namespace = purl.NamespaceAKS case strings.Contains(nodeName, "ocp"): namespace = purl.NamespaceOCP } - return &purl.PackageURL{ - PackageURL: *packageurl.NewPackageURL(purl.TypeK8s, namespace, name, ver, nil, ""), - } + return namespace } diff --git a/pkg/k8s/scanner/scanner_test.go b/pkg/k8s/scanner/scanner_test.go index 7a7253a3c24b..ec6d78f73c05 100644 --- a/pkg/k8s/scanner/scanner_test.go +++ b/pkg/k8s/scanner/scanner_test.go @@ -31,7 +31,7 @@ func TestScanner_Scan(t *testing.T) { artifacts: []*artifacts.Artifact{ { Namespace: "kube-system", - Kind: "ClusterInfo", + Kind: "Cluster", Name: "k8s.io/kubernetes", RawResource: map[string]interface{}{ "name": "k8s.io/kubernetes", @@ -45,7 +45,7 @@ func TestScanner_Scan(t *testing.T) { }, { Namespace: "kube-system", - Kind: "PodInfo", + Kind: "ControlPlaneComponents", Name: "k8s.io/apiserver", RawResource: map[string]interface{}{ "Containers": []interface{}{ @@ -62,7 +62,7 @@ func TestScanner_Scan(t *testing.T) { }, }, { - Kind: "NodeInfo", + Kind: "NodeComponents", Name: "kind-control-plane", RawResource: map[string]interface{}{ "ContainerRuntimeVersion": "containerd://1.5.2", @@ -401,3 +401,153 @@ func TestGeneratePURL(t *testing.T) { }) } } + +func TestK8sNamespace(t *testing.T) { + tests := []struct { + name string + compVersion string + nodeName string + want string + }{ + { + name: "native k8s component", + compVersion: "1.24.10", + nodeName: "kind-kind", + want: "kubernetes", + }, + + { + name: "GKE", + compVersion: "1.24.10-gke.2300", + nodeName: "gke-gke1796-default-pool-768cb718-sk1d", + want: "gke", + }, + { + name: "AKS", + compVersion: "1.24.10-hotfix.20221110", + nodeName: "aks-default-23814474-vmss000000", + want: "aks", + }, + { + name: "EKS", + compVersion: "1.23.17-eks-8ccc7ba", + nodeName: "eks-vmss000000", + want: "eks", + }, + { + name: "Rancher", + compVersion: "1.24.11+rke2r1", + nodeName: "ip-10-0-5-23", + want: "rke", + }, + { + name: "OCP", + compVersion: "1.26.7+c7ee51f", + nodeName: "ocp413vpool14000-p8vnm-master-2", + want: "ocp", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := k8sNamespace(tt.compVersion, tt.nodeName) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestRuntimeVersion(t *testing.T) { + tests := []struct { + name string + runtimeVersion string + wantName string + wantVersion string + }{ + { + name: "containerd", + runtimeVersion: "containerd://1.5.2", + wantName: "github.com/containerd/containerd", + wantVersion: "1.5.2", + }, + { + name: "cri-o", + runtimeVersion: "cri-o://1.5.2", + wantName: "github.com/cri-o/cri-o", + wantVersion: "1.5.2", + }, + { + name: "cri-dockerd", + runtimeVersion: "cri-dockerd://1.5.2", + wantName: "github.com/Mirantis/cri-dockerd", + wantVersion: "1.5.2", + }, + { + name: "na runtime", + runtimeVersion: "cri:1.5.2", + wantName: "", + wantVersion: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotName, gotVersion := runtimeNameVersion(tt.runtimeVersion) + assert.Equal(t, tt.wantName, gotName) + assert.Equal(t, tt.wantVersion, gotVersion) + }) + } +} + +func TestFindNodeName(t *testing.T) { + tests := []struct { + name string + artifacts []*artifacts.Artifact + want string + }{ + { + name: "find node name", + artifacts: []*artifacts.Artifact{ + { + Namespace: "kube-system", + Kind: "Cluster", + Name: "k8s.io/kubernetes", + RawResource: map[string]interface{}{}, + }, + { + Namespace: "kube-system", + Kind: "ControlPlaneComponents", + Name: "k8s.io/apiserver", + RawResource: map[string]interface{}{}, + }, + { + Kind: "NodeComponents", + Name: "kind-control-plane", + RawResource: map[string]interface{}{}, + }, + }, + want: "kind-control-plane", + }, + { + name: "didn't find node name", + artifacts: []*artifacts.Artifact{ + { + Namespace: "kube-system", + Kind: "Cluster", + Name: "k8s.io/kubernetes", + RawResource: map[string]interface{}{}, + }, + { + Namespace: "kube-system", + Kind: "ControlPlaneComponents", + Name: "k8s.io/apiserver", + RawResource: map[string]interface{}{}, + }, + }, + want: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := findNodeName(tt.artifacts) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/k8s/wire_gen.go b/pkg/k8s/wire_gen.go new file mode 100644 index 000000000000..2b2343a654b7 --- /dev/null +++ b/pkg/k8s/wire_gen.go @@ -0,0 +1,30 @@ +// Code generated by Wire. DO NOT EDIT. + +//go:generate go run github.com/google/wire/cmd/wire +//go:build !wireinject +// +build !wireinject + +package k8s + +import ( + "github.com/aquasecurity/trivy-db/pkg/db" + "github.com/aquasecurity/trivy/pkg/fanal/applier" + "github.com/aquasecurity/trivy/pkg/fanal/cache" + "github.com/aquasecurity/trivy/pkg/scanner/langpkg" + "github.com/aquasecurity/trivy/pkg/scanner/local" + "github.com/aquasecurity/trivy/pkg/scanner/ospkg" + "github.com/aquasecurity/trivy/pkg/vulnerability" +) + +// Injectors from inject.go: + +func initializeScanK8s(localArtifactCache cache.LocalArtifactCache) *ScanKubernetes { + applierApplier := applier.NewApplier(localArtifactCache) + scanner := ospkg.NewScanner() + langpkgScanner := langpkg.NewScanner() + config := db.Config{} + client := vulnerability.NewClient(config) + localScanner := local.NewScanner(applierApplier, scanner, langpkgScanner, client) + scanKubernetes := NewScanKubernetes(localScanner) + return scanKubernetes +}