From 135d2e54cd9bbdf4682be358c6943b5d4d126b6a Mon Sep 17 00:00:00 2001 From: Tomoya Amachi Date: Sun, 25 Aug 2019 14:56:14 +0900 Subject: [PATCH] add --exit-level option (#49) * add exit-level parameter * sort Levels by risk * rename file * create config pkg and global Conf * change abend algorithm * update readme --- README.md | 16 +++++- cmd/dockle/main.go | 9 ++- config/config.go | 77 ++++++++++++++++++++++++++ pkg/report/json.go | 14 ++--- pkg/report/list.go | 29 +++++----- pkg/report/writer.go | 45 ++++++++------- pkg/run.go | 46 +++------------ pkg/types/{result.go => assessment.go} | 0 pkg/types/checkpoint.go | 8 +-- 9 files changed, 156 insertions(+), 88 deletions(-) create mode 100644 config/config.go rename pkg/types/{result.go => assessment.go} (100%) diff --git a/README.md b/README.md index 578c382..fae7f5f 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ See [Installation](#installation) and [Common Examples](#common-examples) - [Scan an image file](#scan-an-image-file) - [Get or Save the results as JSON](#get-or-save-the-results-as-json) - [Specify exit code](#specify-exit-code) + - [Specify exit level](#specify-exit-level) - [Ignore the specified checkpoints](#ignore-the-specified-checkpoints) - [Continuous Integration](#continuous-integration-ci) - [Travis CI](#travis-ci) @@ -430,10 +431,21 @@ $ dockle -f json -o results.json goodwithtech/test-image:v1 By default, `Dockle` exits with code `0` even if there are some problems. -Use the `--exit-code` option to exit with a non-zero exit code if any alert were found. +Use the `--exit-code, -c` option to exit with a non-zero exit code if `WARN` or `FATAL` alert were found. ```bash -$ dockle --exit-code 1 [IMAGE_NAME] +$ dockle --exit-code 1 [IMAGE_NAME] +``` + +### Specify exit level + +By default, `--exit-code` run when there are `WARN` or `FATAL` level alerts. + +Use the `--exit-level, -l` option to change alert level. You can set `info`, `warn` or `fatal`. + +```bash +$ dockle --exit-code 1 --exit-level info [IMAGE_NAME] +$ dockle --exit-code 1 --exit-level fatal [IMAGE_NAME] ``` ### Ignore the specified checkpoints diff --git a/cmd/dockle/main.go b/cmd/dockle/main.go index d72255e..4c13464 100644 --- a/cmd/dockle/main.go +++ b/cmd/dockle/main.go @@ -34,7 +34,7 @@ OPTIONS: app.Version = version app.ArgsUsage = "image_name" - app.Usage = "A Simple Security Checker for Container Image, Suitable for CI" + app.Usage = "Container Image Linter for Security, Helping build the Best-Practice Docker Image, Easy to start" app.Flags = []cli.Flag{ cli.StringFlag{ @@ -56,10 +56,15 @@ OPTIONS: Usage: "output file name", }, cli.IntFlag{ - Name: "exit-code", + Name: "exit-code, c", Usage: "Exit code when alert were found", Value: 0, }, + cli.StringFlag{ + Name: "exit-level, l", + Usage: "change ABEND level when use exit-code=1", + Value: "WARN", + }, cli.BoolFlag{ Name: "debug, d", Usage: "debug mode", diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..82683d4 --- /dev/null +++ b/config/config.go @@ -0,0 +1,77 @@ +package config + +import ( + "bufio" + "os" + "strings" + + "github.com/goodwithtech/dockle/pkg/types" + + "github.com/goodwithtech/dockle/pkg/log" + "github.com/urfave/cli" +) + +const ( + dockleIgnore = ".dockleignore" +) + +var exitLevelMap = map[string]int{ + "info": types.InfoLevel, + "INFO": types.InfoLevel, + "warn": types.WarnLevel, + "WARN": types.WarnLevel, + "fatal": types.FatalLevel, + "FATAL": types.FatalLevel, +} + +type Config struct { + IgnoreMap map[string]struct{} + ExitCode int + ExitLevel int +} + +var Conf Config + +func CreateFromCli(c *cli.Context) { + ignoreRules := c.StringSlice("ignore") + Conf = Config{ + IgnoreMap: getIgnoreCheckpointMap(ignoreRules), + ExitCode: c.Int("exit-code"), + ExitLevel: getExitLevel(c.String("exit-level")), + } +} + +func getExitLevel(param string) (exitLevel int) { + exitLevel, ok := exitLevelMap[param] + if !ok { + return types.WarnLevel + } + return exitLevel +} + +func getIgnoreCheckpointMap(ignoreRules []string) map[string]struct{} { + ignoreCheckpointMap := map[string]struct{}{} + // from cli command + for _, rule := range ignoreRules { + ignoreCheckpointMap[rule] = struct{}{} + } + + // from ignore file + f, err := os.Open(dockleIgnore) + if err != nil { + log.Logger.Debug("There is no .dockleignore file") + // dockle must work even if there isn't ignore file + return ignoreCheckpointMap + } + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := scanner.Text() + line = strings.TrimSpace(line) + if strings.HasPrefix(line, "#") || line == "" { + continue + } + log.Logger.Debugf("Add new ignore code: %s", line) + ignoreCheckpointMap[line] = struct{}{} + } + return ignoreCheckpointMap +} diff --git a/pkg/report/json.go b/pkg/report/json.go index c229482..1f2fd95 100644 --- a/pkg/report/json.go +++ b/pkg/report/json.go @@ -11,8 +11,7 @@ import ( ) type JsonWriter struct { - Output io.Writer - IgnoreMap map[string]struct{} + Output io.Writer } type JsonOutputFormat struct { @@ -32,13 +31,14 @@ type JsonDetail struct { Alerts []string `json:"alerts"` } -func (jw JsonWriter) Write(assessments []*types.Assessment) (bool, error) { - var abendAssessments []*types.Assessment +func (jw JsonWriter) Write(assessments AssessmentSlice) (bool, error) { + abend := AssessmentSlice{} + abendAssessments := &abend jsonSummary := JsonSummary{} jsonDetails := []*JsonDetail{} targetType := types.MinTypeNumber for targetType <= types.MaxTypeNumber { - filtered := filteredAssessments(jw.IgnoreMap, targetType, assessments) + filtered := assessments.FilteredByTargetCode(targetType) level, detail := jsonDetail(targetType, filtered) if detail != nil { jsonDetails = append(jsonDetails, detail) @@ -57,7 +57,7 @@ func (jw JsonWriter) Write(assessments []*types.Assessment) (bool, error) { } for _, assessment := range filtered { - abendAssessments = filterAbendAssessments(jw.IgnoreMap, abendAssessments, assessment) + abendAssessments.AddAbend(assessment) } targetType++ } @@ -73,7 +73,7 @@ func (jw JsonWriter) Write(assessments []*types.Assessment) (bool, error) { if _, err = fmt.Fprint(jw.Output, string(output)); err != nil { return false, xerrors.Errorf("failed to write json: %w", err) } - return len(abendAssessments) > 0, nil + return len(*abendAssessments) > 0, nil } func jsonDetail(assessmentType int, assessments []*types.Assessment) (level int, jsonInfo *JsonDetail) { if len(assessments) == 0 { diff --git a/pkg/report/list.go b/pkg/report/list.go index 9f25566..19b80e4 100644 --- a/pkg/report/list.go +++ b/pkg/report/list.go @@ -16,34 +16,33 @@ const ( NEWLINE = "\n" ) -var AlertLevelColors = []color.Color{ - color.Magenta, - color.Yellow, - color.Red, - color.Green, - color.Blue, - color.Blue, +var AlertLevelColors = map[int]color.Color{ + types.InfoLevel: color.Magenta, + types.WarnLevel: color.Yellow, + types.FatalLevel: color.Red, + types.PassLevel: color.Green, + types.SkipLevel: color.Blue, + types.IgnoreLevel: color.Blue, } type ListWriter struct { - Output io.Writer - IgnoreMap map[string]struct{} + Output io.Writer } -func (lw ListWriter) Write(assessments []*types.Assessment) (bool, error) { - var abendAssessments []*types.Assessment - +func (lw ListWriter) Write(assessments AssessmentSlice) (bool, error) { + abend := AssessmentSlice{} + abendAssessments := &abend targetType := types.MinTypeNumber for targetType <= types.MaxTypeNumber { - filtered := filteredAssessments(lw.IgnoreMap, targetType, assessments) + filtered := assessments.FilteredByTargetCode(targetType) showTargetResult(targetType, filtered) for _, assessment := range filtered { - abendAssessments = filterAbendAssessments(lw.IgnoreMap, abendAssessments, assessment) + abendAssessments.AddAbend(assessment) } targetType++ } - return len(abendAssessments) > 0, nil + return len(*abendAssessments) > 0, nil } func showTargetResult(assessmentType int, assessments []*types.Assessment) { diff --git a/pkg/report/writer.go b/pkg/report/writer.go index b84e7d4..fa1496c 100644 --- a/pkg/report/writer.go +++ b/pkg/report/writer.go @@ -1,27 +1,30 @@ package report import ( + "github.com/goodwithtech/dockle/config" "github.com/goodwithtech/dockle/pkg/types" ) -var AlertLabels = []string{ - "INFO", - "WARN", - "FATAL", - "PASS", - "SKIP", - "IGNORE", +var AlertLabels = map[int]string{ + types.InfoLevel: "INFO", + types.WarnLevel: "WARN", + types.FatalLevel: "FATAL", + types.PassLevel: "PASS", + types.SkipLevel: "SKIP", + types.IgnoreLevel: "IGNORE", } +type AssessmentSlice []*types.Assessment type Writer interface { - Write(assessments []*types.Assessment) (bool, error) + Write(assessments AssessmentSlice) (bool, error) } -func filteredAssessments(ignoreCheckpointMap map[string]struct{}, target int, assessments []*types.Assessment) (filtered []*types.Assessment) { +// FilteredByTargetCode returns only target type assessments from all assessments slice +func (as *AssessmentSlice) FilteredByTargetCode(target int) (filtered AssessmentSlice) { detail := types.AlertDetails[target] - for _, assessment := range assessments { + for _, assessment := range *as { if assessment.Type == target { - if _, ok := ignoreCheckpointMap[detail.Code]; ok { + if _, ok := config.Conf.IgnoreMap[detail.Code]; ok { assessment.Level = types.IgnoreLevel } filtered = append(filtered, assessment) @@ -30,14 +33,18 @@ func filteredAssessments(ignoreCheckpointMap map[string]struct{}, target int, as return filtered } -func filterAbendAssessments(ignoreCheckpointMap map[string]struct{}, abendAssessments []*types.Assessment, assessment *types.Assessment) []*types.Assessment { - if assessment.Level == types.SkipLevel { - return abendAssessments - } - +// AddAbend add assessment to AssessmentSlice pointer if abend level +func (as *AssessmentSlice) AddAbend(assessment *types.Assessment) { + level := assessment.Level detail := types.AlertDetails[assessment.Type] - if _, ok := ignoreCheckpointMap[detail.Code]; ok { - return abendAssessments + if level == 0 { + level = detail.DefaultLevel + } + if level < config.Conf.ExitLevel { + return + } + if _, ok := config.Conf.IgnoreMap[detail.Code]; ok { + return } - return append(abendAssessments, assessment) + *as = append(*as, assessment) } diff --git a/pkg/run.go b/pkg/run.go index 7e345c5..d967f0f 100644 --- a/pkg/run.go +++ b/pkg/run.go @@ -1,11 +1,10 @@ package pkg import ( - "bufio" l "log" "os" - "strings" + "github.com/goodwithtech/dockle/config" "github.com/goodwithtech/dockle/pkg/utils" "github.com/goodwithtech/dockle/pkg/report" @@ -24,15 +23,12 @@ var ( ignoreCheckpointMap map[string]struct{} ) -const ( - dockleIgnore = ".dockleignore" -) - func Run(c *cli.Context) (err error) { debug := c.Bool("debug") if err = log.InitLogger(debug); err != nil { l.Fatal(err) } + config.CreateFromCli(c) cliVersion := "v" + c.App.Version latestVersion, err := utils.FetchLatestVersion() @@ -86,11 +82,7 @@ func Run(c *cli.Context) (err error) { log.Logger.Debug("End assessments...") - exitCode := c.Int("exit-code") - // Store ignore checkpoint code - ignoreRules := c.StringSlice("ignore") - getIgnoreCheckpointMap(ignoreRules) o := c.String("output") output := os.Stdout if o != "" { @@ -102,43 +94,19 @@ func Run(c *cli.Context) (err error) { var writer report.Writer switch format := c.String("format"); format { case "json": - writer = &report.JsonWriter{Output: output, IgnoreMap: ignoreCheckpointMap} + writer = &report.JsonWriter{Output: output} default: - writer = &report.ListWriter{Output: output, IgnoreMap: ignoreCheckpointMap} + writer = &report.ListWriter{Output: output} } abend, err := writer.Write(assessments) if err != nil { return xerrors.Errorf("failed to write results: %w", err) } - if exitCode != 0 && abend { - os.Exit(exitCode) - } - - return nil -} - -func getIgnoreCheckpointMap(ignoreRules []string) { - ignoreCheckpointMap = map[string]struct{}{} - for _, rule := range ignoreRules { - ignoreCheckpointMap[rule] = struct{}{} - } - f, err := os.Open(dockleIgnore) - if err != nil { - log.Logger.Debug("There is no .dockleignore file") - // dockle must work even if there isn't ignore file - return + if config.Conf.ExitCode != 0 && abend { + os.Exit(config.Conf.ExitCode) } - scanner := bufio.NewScanner(f) - for scanner.Scan() { - line := scanner.Text() - line = strings.TrimSpace(line) - if strings.HasPrefix(line, "#") || line == "" { - continue - } - log.Logger.Debugf("Add new ignore code: %s", line) - ignoreCheckpointMap[line] = struct{}{} - } + return nil } diff --git a/pkg/types/result.go b/pkg/types/assessment.go similarity index 100% rename from pkg/types/result.go rename to pkg/types/assessment.go diff --git a/pkg/types/checkpoint.go b/pkg/types/checkpoint.go index 363c0a5..6adc32a 100644 --- a/pkg/types/checkpoint.go +++ b/pkg/types/checkpoint.go @@ -32,12 +32,12 @@ const ( ) const ( - InfoLevel = iota + PassLevel = iota + 1 + IgnoreLevel + SkipLevel + InfoLevel WarnLevel FatalLevel - PassLevel - SkipLevel - IgnoreLevel ) type AlertDetail struct {