mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-21 23:00:42 -08:00
refactor: k8s (#2116)
* refactor: add pkg/k8s Signed-off-by: Jose Donizetti <jdbjunior@gmail.com> * refactor: extract scanner Signed-off-by: Jose Donizetti <jdbjunior@gmail.com> * refactor: extract scanVulns Signed-off-by: Jose Donizetti <jdbjunior@gmail.com> * refactor: extract scanMisconfigs Signed-off-by: Jose Donizetti <jdbjunior@gmail.com> * refactor: extract filter Signed-off-by: Jose Donizetti <jdbjunior@gmail.com> * refactor: improve k8s/run.go Signed-off-by: Jose Donizetti <jdbjunior@gmail.com> * fix(k8s): code improvements Signed-off-by: Jose Donizetti <jdbjunior@gmail.com> * chore: go mod tidy Signed-off-by: Jose Donizetti <jdbjunior@gmail.com>
This commit is contained in:
2
go.mod
2
go.mod
@@ -46,7 +46,7 @@ require (
|
|||||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect
|
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
|
||||||
google.golang.org/protobuf v1.28.0
|
google.golang.org/protobuf v1.28.0
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||||
k8s.io/utils v0.0.0-20211116205334-6203023598ed
|
k8s.io/utils v0.0.0-20211116205334-6203023598ed
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import (
|
|||||||
"github.com/aquasecurity/trivy/pkg/commands/option"
|
"github.com/aquasecurity/trivy/pkg/commands/option"
|
||||||
"github.com/aquasecurity/trivy/pkg/commands/plugin"
|
"github.com/aquasecurity/trivy/pkg/commands/plugin"
|
||||||
"github.com/aquasecurity/trivy/pkg/commands/server"
|
"github.com/aquasecurity/trivy/pkg/commands/server"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/k8s"
|
||||||
"github.com/aquasecurity/trivy/pkg/result"
|
"github.com/aquasecurity/trivy/pkg/result"
|
||||||
"github.com/aquasecurity/trivy/pkg/types"
|
"github.com/aquasecurity/trivy/pkg/types"
|
||||||
"github.com/aquasecurity/trivy/pkg/utils"
|
"github.com/aquasecurity/trivy/pkg/utils"
|
||||||
@@ -793,7 +794,7 @@ func NewK8sCommand() *cli.Command {
|
|||||||
Name: "kubernetes",
|
Name: "kubernetes",
|
||||||
Aliases: []string{"k8s"},
|
Aliases: []string{"k8s"},
|
||||||
Usage: "scan kubernetes vulnerabilities and misconfigurations",
|
Usage: "scan kubernetes vulnerabilities and misconfigurations",
|
||||||
Action: artifact.K8sRun,
|
Action: k8s.Run,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&namespaceFlag,
|
&namespaceFlag,
|
||||||
&reportFlag,
|
&reportFlag,
|
||||||
|
|||||||
@@ -1,220 +0,0 @@
|
|||||||
package artifact
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/cheggaaa/pb/v3"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
"golang.org/x/xerrors"
|
|
||||||
"gopkg.in/yaml.v2"
|
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy/pkg/log"
|
|
||||||
pkgReport "github.com/aquasecurity/trivy/pkg/report"
|
|
||||||
k8sReport "github.com/aquasecurity/trivy/pkg/report/k8s"
|
|
||||||
"github.com/aquasecurity/trivy/pkg/types"
|
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy-kubernetes/pkg/artifacts"
|
|
||||||
"github.com/aquasecurity/trivy-kubernetes/pkg/k8s"
|
|
||||||
"github.com/aquasecurity/trivy-kubernetes/pkg/trivyk8s"
|
|
||||||
)
|
|
||||||
|
|
||||||
// K8sRun runs scan on kubernetes cluster
|
|
||||||
func K8sRun(cliCtx *cli.Context) error {
|
|
||||||
opt, err := InitOption(cliCtx)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Errorf("option error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(cliCtx.Context, opt.Timeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if xerrors.Is(err, context.DeadlineExceeded) {
|
|
||||||
log.Logger.Warn("Increase --timeout value")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
runner, err := NewRunner(opt)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, SkipScan) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return xerrors.Errorf("init error: %w", err)
|
|
||||||
}
|
|
||||||
defer runner.Close()
|
|
||||||
|
|
||||||
cluster, err := k8s.GetCluster()
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Errorf("get k8s cluster: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
trivyk8s := trivyk8s.New(cluster).Namespace(opt.KubernetesOption.Namespace)
|
|
||||||
|
|
||||||
// list all kubernetes scannable artifacts
|
|
||||||
k8sArtifacts, err := trivyk8s.ListArtifacts(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Errorf("get k8s artifacts error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
report, err := k8sRun(ctx, runner, opt, k8sArtifacts)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Errorf("k8s scan error: %w", err)
|
|
||||||
}
|
|
||||||
report.ClusterName = cluster.GetCurrentContext()
|
|
||||||
|
|
||||||
if err = k8sReport.Write(report, pkgReport.Option{
|
|
||||||
Format: opt.KubernetesOption.ReportFormat, // for now json is the default
|
|
||||||
Output: opt.Output,
|
|
||||||
}, opt.Severities); err != nil {
|
|
||||||
return xerrors.Errorf("unable to write results: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
exit(opt, report.Failed())
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func k8sRun(ctx context.Context, runner *Runner, opt Option, artifacts []*artifacts.Artifact) (k8sReport.Report, error) {
|
|
||||||
opt.SecurityChecks = []string{types.SecurityCheckVulnerability, types.SecurityCheckConfig}
|
|
||||||
|
|
||||||
// progress bar
|
|
||||||
bar := pb.StartNew(len(artifacts))
|
|
||||||
if opt.NoProgress {
|
|
||||||
bar.SetWriter(io.Discard)
|
|
||||||
}
|
|
||||||
defer bar.Finish()
|
|
||||||
|
|
||||||
vulns := make([]k8sReport.Resource, 0)
|
|
||||||
misconfigs := make([]k8sReport.Resource, 0)
|
|
||||||
|
|
||||||
// disable logs before scanning
|
|
||||||
err := log.InitLogger(opt.Debug, true)
|
|
||||||
if err != nil {
|
|
||||||
return k8sReport.Report{}, xerrors.Errorf("logger error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loops once over all artifacts, and execute scanners as necessary. Not every artifacts has an image,
|
|
||||||
// so image scanner is not always executed.
|
|
||||||
for _, artifact := range artifacts {
|
|
||||||
bar.Increment()
|
|
||||||
|
|
||||||
// scan images if present
|
|
||||||
for _, image := range artifact.Images {
|
|
||||||
opt.Target = image
|
|
||||||
imageReport, err := runner.ScanImage(ctx, opt)
|
|
||||||
if err != nil {
|
|
||||||
// add error to report
|
|
||||||
log.Logger.Debugf("failed to scan image %s: %s", image, err)
|
|
||||||
vulns = append(vulns, newK8sResource(artifact, imageReport, err))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
imageReport, err = runner.Filter(ctx, opt, imageReport)
|
|
||||||
if err != nil {
|
|
||||||
return k8sReport.Report{}, xerrors.Errorf("filter error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
vulns = append(vulns, newK8sResource(artifact, imageReport, nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
// scan configurations
|
|
||||||
configFile, err := createTempFile(artifact)
|
|
||||||
if err != nil {
|
|
||||||
return k8sReport.Report{}, xerrors.Errorf("scan error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
opt.Target = configFile
|
|
||||||
configReport, err := runner.ScanFilesystem(ctx, opt)
|
|
||||||
removeFile(configFile)
|
|
||||||
if err != nil {
|
|
||||||
// add error to report
|
|
||||||
log.Logger.Debugf("failed to scan config %s/%s: %s", artifact.Kind, artifact.Name, err)
|
|
||||||
misconfigs = append(misconfigs, newK8sResource(artifact, configReport, err))
|
|
||||||
}
|
|
||||||
|
|
||||||
configReport, err = runner.Filter(ctx, opt, configReport)
|
|
||||||
if err != nil {
|
|
||||||
return k8sReport.Report{}, xerrors.Errorf("filter error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
misconfigs = append(misconfigs, newK8sResource(artifact, configReport, nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
// enable logs after scanning
|
|
||||||
err = log.InitLogger(opt.Debug, opt.Quiet)
|
|
||||||
if err != nil {
|
|
||||||
return k8sReport.Report{}, xerrors.Errorf("logger error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return k8sReport.Report{
|
|
||||||
SchemaVersion: 0,
|
|
||||||
Vulnerabilities: vulns,
|
|
||||||
Misconfigurations: misconfigs,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func createTempFile(artifact *artifacts.Artifact) (string, error) {
|
|
||||||
filename := fmt.Sprintf("%s-%s-%s-*.yaml", artifact.Namespace, artifact.Kind, artifact.Name)
|
|
||||||
|
|
||||||
file, err := os.CreateTemp("", filename)
|
|
||||||
if err != nil {
|
|
||||||
return "", xerrors.Errorf("creating tmp file error: %w", err)
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if err := file.Close(); err != nil {
|
|
||||||
log.Logger.Errorf("failed to close temp file %s: %s:", file.Name(), err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// TODO(josedonizetti): marshal and return as byte slice should be on the trivy-kubernetes library?
|
|
||||||
data, err := yaml.Marshal(artifact.RawResource)
|
|
||||||
if err != nil {
|
|
||||||
removeFile(filename)
|
|
||||||
return "", xerrors.Errorf("marshaling resource error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = file.Write(data)
|
|
||||||
if err != nil {
|
|
||||||
removeFile(filename)
|
|
||||||
return "", xerrors.Errorf("writing tmp file error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return file.Name(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newK8sResource(artifact *artifacts.Artifact, report types.Report, err error) k8sReport.Resource {
|
|
||||||
results := make([]types.Result, 0, len(report.Results))
|
|
||||||
// fix target name
|
|
||||||
for _, result := range report.Results {
|
|
||||||
// if resource is a kubernetes file fix the target name,
|
|
||||||
// to avoid showing the temp file that was removed.
|
|
||||||
if result.Type == "kubernetes" {
|
|
||||||
result.Target = fmt.Sprintf("%s/%s", artifact.Kind, artifact.Name)
|
|
||||||
}
|
|
||||||
results = append(results, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
k8sreport := k8sReport.Resource{
|
|
||||||
Namespace: artifact.Namespace,
|
|
||||||
Kind: artifact.Kind,
|
|
||||||
Name: artifact.Name,
|
|
||||||
Results: results,
|
|
||||||
}
|
|
||||||
|
|
||||||
// if there was any error during the scan
|
|
||||||
if err != nil {
|
|
||||||
k8sreport.Error = err.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
return k8sreport
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeFile(filename string) {
|
|
||||||
if err := os.Remove(filename); err != nil {
|
|
||||||
log.Logger.Errorf("failed to remove temp file %s: %s:", filename, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -344,7 +344,7 @@ func run(ctx context.Context, opt Option, artifactType ArtifactType) (err error)
|
|||||||
return xerrors.Errorf("report error: %w", err)
|
return xerrors.Errorf("report error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
exit(opt, report.Results.Failed())
|
Exit(opt, report.Results.Failed())
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -466,7 +466,7 @@ func scan(ctx context.Context, opt Option, initializeScanner InitializeScanner,
|
|||||||
return report, nil
|
return report, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func exit(c Option, failedResults bool) {
|
func Exit(c Option, failedResults bool) {
|
||||||
if c.ExitCode != 0 && failedResults {
|
if c.ExitCode != 0 && failedResults {
|
||||||
os.Exit(c.ExitCode)
|
os.Exit(c.ExitCode)
|
||||||
}
|
}
|
||||||
|
|||||||
40
pkg/k8s/io.go
Normal file
40
pkg/k8s/io.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package k8s
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/xerrors"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
|
"github.com/aquasecurity/trivy/pkg/log"
|
||||||
|
|
||||||
|
"github.com/aquasecurity/trivy-kubernetes/pkg/artifacts"
|
||||||
|
)
|
||||||
|
|
||||||
|
func createTempFile(artifact *artifacts.Artifact) (string, error) {
|
||||||
|
filename := fmt.Sprintf("%s-%s-%s-*.yaml", artifact.Namespace, artifact.Kind, artifact.Name)
|
||||||
|
|
||||||
|
file, err := os.CreateTemp("", filename)
|
||||||
|
if err != nil {
|
||||||
|
return "", xerrors.Errorf("creating tmp file error: %w", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err := file.Close(); err != nil {
|
||||||
|
log.Logger.Errorf("failed to close temp file %s: %s:", file.Name(), err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := yaml.NewEncoder(file).Encode(artifact.RawResource); err != nil {
|
||||||
|
removeFile(filename)
|
||||||
|
return "", xerrors.Errorf("marshaling resource error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return file.Name(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeFile(filename string) {
|
||||||
|
if err := os.Remove(filename); err != nil {
|
||||||
|
log.Logger.Errorf("failed to remove temp file %s: %s:", filename, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,13 @@
|
|||||||
package k8s
|
package k8s
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
|
ftypes "github.com/aquasecurity/fanal/types"
|
||||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||||
|
"github.com/aquasecurity/trivy-kubernetes/pkg/artifacts"
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy/pkg/report"
|
"github.com/aquasecurity/trivy/pkg/report"
|
||||||
"github.com/aquasecurity/trivy/pkg/types"
|
"github.com/aquasecurity/trivy/pkg/types"
|
||||||
@@ -85,8 +89,8 @@ type Writer interface {
|
|||||||
Write(Report) error
|
Write(Report) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write writes the results in the give format
|
// write writes the results in the give format
|
||||||
func Write(report Report, option report.Option, severities []dbTypes.Severity) error {
|
func write(report Report, option report.Option, severities []dbTypes.Severity) error {
|
||||||
var writer Writer
|
var writer Writer
|
||||||
switch option.Format {
|
switch option.Format {
|
||||||
case "all":
|
case "all":
|
||||||
@@ -99,3 +103,30 @@ func Write(report Report, option report.Option, severities []dbTypes.Severity) e
|
|||||||
|
|
||||||
return writer.Write(report)
|
return writer.Write(report)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createResource(artifact *artifacts.Artifact, report types.Report, err error) Resource {
|
||||||
|
results := make([]types.Result, 0, len(report.Results))
|
||||||
|
// fix target name
|
||||||
|
for _, result := range report.Results {
|
||||||
|
// if resource is a kubernetes file fix the target name,
|
||||||
|
// to avoid showing the temp file that was removed.
|
||||||
|
if result.Type == ftypes.Kubernetes {
|
||||||
|
result.Target = fmt.Sprintf("%s/%s", artifact.Kind, artifact.Name)
|
||||||
|
}
|
||||||
|
results = append(results, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
r := Resource{
|
||||||
|
Namespace: artifact.Namespace,
|
||||||
|
Kind: artifact.Kind,
|
||||||
|
Name: artifact.Name,
|
||||||
|
Results: results,
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there was any error during the scan
|
||||||
|
if err != nil {
|
||||||
|
r.Error = err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
84
pkg/k8s/run.go
Normal file
84
pkg/k8s/run.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package k8s
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
|
cmd "github.com/aquasecurity/trivy/pkg/commands/artifact"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/log"
|
||||||
|
pkgReport "github.com/aquasecurity/trivy/pkg/report"
|
||||||
|
|
||||||
|
"github.com/aquasecurity/trivy-kubernetes/pkg/artifacts"
|
||||||
|
"github.com/aquasecurity/trivy-kubernetes/pkg/k8s"
|
||||||
|
"github.com/aquasecurity/trivy-kubernetes/pkg/trivyk8s"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Run runs scan on kubernetes cluster
|
||||||
|
func Run(cliCtx *cli.Context) error {
|
||||||
|
opt, err := cmd.InitOption(cliCtx)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("option error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(cliCtx.Context, opt.Timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if xerrors.Is(err, context.DeadlineExceeded) {
|
||||||
|
log.Logger.Warn("Increase --timeout value")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
runner, err := cmd.NewRunner(opt)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, cmd.SkipScan) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return xerrors.Errorf("init error: %w", err)
|
||||||
|
}
|
||||||
|
defer runner.Close()
|
||||||
|
|
||||||
|
cluster, err := k8s.GetCluster()
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("get k8s cluster: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// get kubernetes scannable artifacts
|
||||||
|
artifacts, err := getArtifacts(ctx, cluster, opt.KubernetesOption.Namespace)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("get k8s artifacts error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s := &scanner{
|
||||||
|
cluster: cluster.GetCurrentContext(),
|
||||||
|
runner: runner,
|
||||||
|
opt: opt,
|
||||||
|
}
|
||||||
|
|
||||||
|
return run(ctx, s, opt, artifacts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func run(ctx context.Context, s *scanner, opt cmd.Option, artifacts []*artifacts.Artifact) error {
|
||||||
|
report, err := s.run(ctx, artifacts)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("k8s scan error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = write(report, pkgReport.Option{
|
||||||
|
Format: opt.KubernetesOption.ReportFormat,
|
||||||
|
Output: opt.Output,
|
||||||
|
}, opt.Severities); err != nil {
|
||||||
|
return xerrors.Errorf("unable to write results: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Exit(opt, report.Failed())
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getArtifacts(ctx context.Context, cluster k8s.Cluster, namespace string) ([]*artifacts.Artifact, error) {
|
||||||
|
return trivyk8s.New(cluster).Namespace(namespace).ListArtifacts(ctx)
|
||||||
|
}
|
||||||
127
pkg/k8s/scanner.go
Normal file
127
pkg/k8s/scanner.go
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
package k8s
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/cheggaaa/pb/v3"
|
||||||
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
|
cmd "github.com/aquasecurity/trivy/pkg/commands/artifact"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/log"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/types"
|
||||||
|
|
||||||
|
"github.com/aquasecurity/trivy-kubernetes/pkg/artifacts"
|
||||||
|
)
|
||||||
|
|
||||||
|
type scanner struct {
|
||||||
|
cluster string
|
||||||
|
runner *cmd.Runner
|
||||||
|
opt cmd.Option
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *scanner) run(ctx context.Context, artifacts []*artifacts.Artifact) (Report, error) {
|
||||||
|
// Todo move to run.go
|
||||||
|
s.opt.SecurityChecks = []string{types.SecurityCheckVulnerability, types.SecurityCheckConfig}
|
||||||
|
|
||||||
|
// progress bar
|
||||||
|
bar := pb.StartNew(len(artifacts))
|
||||||
|
if s.opt.NoProgress {
|
||||||
|
bar.SetWriter(io.Discard)
|
||||||
|
}
|
||||||
|
defer bar.Finish()
|
||||||
|
|
||||||
|
var vulns, misconfigs []Resource
|
||||||
|
|
||||||
|
// disable logs before scanning
|
||||||
|
err := log.InitLogger(s.opt.Debug, true)
|
||||||
|
if err != nil {
|
||||||
|
return Report{}, xerrors.Errorf("logger error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loops once over all artifacts, and execute scanners as necessary. Not every artifacts has an image,
|
||||||
|
// so image scanner is not always executed.
|
||||||
|
for _, artifact := range artifacts {
|
||||||
|
bar.Increment()
|
||||||
|
|
||||||
|
resources, err := s.scanVulns(ctx, artifact)
|
||||||
|
if err != nil {
|
||||||
|
return Report{}, xerrors.Errorf("scanning vulnerabilities error: %w", err)
|
||||||
|
}
|
||||||
|
vulns = append(vulns, resources...)
|
||||||
|
|
||||||
|
resource, err := s.scanMisconfigs(ctx, artifact)
|
||||||
|
if err != nil {
|
||||||
|
return Report{}, xerrors.Errorf("scanning misconfigurations error: %w", err)
|
||||||
|
}
|
||||||
|
misconfigs = append(misconfigs, resource)
|
||||||
|
}
|
||||||
|
|
||||||
|
// enable logs after scanning
|
||||||
|
err = log.InitLogger(s.opt.Debug, s.opt.Quiet)
|
||||||
|
if err != nil {
|
||||||
|
return Report{}, xerrors.Errorf("logger error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Report{
|
||||||
|
SchemaVersion: 0,
|
||||||
|
ClusterName: s.cluster,
|
||||||
|
Vulnerabilities: vulns,
|
||||||
|
Misconfigurations: misconfigs,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *scanner) scanVulns(ctx context.Context, artifact *artifacts.Artifact) ([]Resource, error) {
|
||||||
|
resources := make([]Resource, 0, len(artifact.Images))
|
||||||
|
|
||||||
|
for _, image := range artifact.Images {
|
||||||
|
|
||||||
|
s.opt.Target = image
|
||||||
|
|
||||||
|
imageReport, err := s.runner.ScanImage(ctx, s.opt)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Logger.Debugf("failed to scan image %s: %s", image, err)
|
||||||
|
resources = append(resources, createResource(artifact, imageReport, err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
resource, err := s.filter(ctx, imageReport, artifact)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Errorf("filter error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resources = append(resources, resource)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resources, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *scanner) scanMisconfigs(ctx context.Context, artifact *artifacts.Artifact) (Resource, error) {
|
||||||
|
configFile, err := createTempFile(artifact)
|
||||||
|
if err != nil {
|
||||||
|
return Resource{}, xerrors.Errorf("scan error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.opt.Target = configFile
|
||||||
|
|
||||||
|
configReport, err := s.runner.ScanFilesystem(ctx, s.opt)
|
||||||
|
//remove config file after scanning
|
||||||
|
removeFile(configFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Logger.Debugf("failed to scan config %s/%s: %s", artifact.Kind, artifact.Name, err)
|
||||||
|
return createResource(artifact, configReport, err), err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.filter(ctx, configReport, artifact)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *scanner) filter(ctx context.Context, report types.Report, artifact *artifacts.Artifact) (Resource, error) {
|
||||||
|
report, err := s.runner.Filter(ctx, s.opt, report)
|
||||||
|
if err != nil {
|
||||||
|
return Resource{}, xerrors.Errorf("filter error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return createResource(artifact, report, nil), nil
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user