From 17bb575002971f657587d951b7b8dfd201c9d886 Mon Sep 17 00:00:00 2001 From: Kota Kanbe Date: Wed, 12 Jun 2019 21:35:21 +0900 Subject: [PATCH] fix(scan): enable to report if some warnings occured on scanning (#805) * fix(scan): enable to report if some warnings occured on scanning * alpine, debian, freebsd, suse * -format-full-text, -format-list, -format-one-line-text * implement slack.go * implement tui.go * go fmt --- commands/tui.go | 8 +++++ models/scanresults.go | 4 +++ report/slack.go | 80 ++++++++++++++++--------------------------- report/util.go | 39 +++++++++++++++++---- scan/alpine.go | 9 +++-- scan/base.go | 14 +++++--- scan/debian.go | 12 +++++-- scan/freebsd.go | 9 ++--- scan/redhatbase.go | 44 ++++++++++++++++-------- scan/serverapi.go | 16 +++++++++ scan/suse.go | 21 +++++++----- 11 files changed, 163 insertions(+), 93 deletions(-) diff --git a/commands/tui.go b/commands/tui.go index fe3936de..48b1e4da 100644 --- a/commands/tui.go +++ b/commands/tui.go @@ -245,5 +245,13 @@ func (p *TuiCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) s util.Log.Error(err) return subcommands.ExitFailure } + + for _, r := range res { + if len(r.Warnings) != 0 { + util.Log.Warnf("Warning: Some warnings occurred while scanning on %s: %s", + r.FormatServerName(), r.Warnings) + } + } + return report.RunTui(res) } diff --git a/models/scanresults.go b/models/scanresults.go index ea08e86d..050f672e 100644 --- a/models/scanresults.go +++ b/models/scanresults.go @@ -60,6 +60,7 @@ type ScanResult struct { ReportedRevision string `json:"reportedRevision"` ReportedBy string `json:"reportedBy"` Errors []string `json:"errors"` + Warnings []string `json:"warnings"` ScannedCves VulnInfos `json:"scannedCves"` RunningKernel Kernel `json:"runningKernel"` @@ -319,6 +320,9 @@ func (r ScanResult) ServerInfoTui() string { if len(r.Container.ContainerID) == 0 { line := fmt.Sprintf("%s (%s%s)", r.ServerName, r.Family, r.Release) + if len(r.Warnings) != 0 { + line = "[Warn] " + line + } if r.RunningKernel.RebootRequired { return "[Reboot] " + line } diff --git a/report/slack.go b/report/slack.go index e12b65ed..832999c6 100644 --- a/report/slack.go +++ b/report/slack.go @@ -74,58 +74,60 @@ func (w SlackWriter) Write(rs ...models.ScanResult) (err error) { } sort.Ints(chunkKeys) + summary := fmt.Sprintf("%s\n%s", + getNotifyUsers(config.Conf.Slack.NotifyUsers), + formatOneLineSummary(r)) + // Send slack by API if 0 < len(token) { api := slack.New(token) - ParentMsg := slack.PostMessageParameters{ - // Text: msgText(r), + msgPrms := slack.PostMessageParameters{ Username: conf.AuthUser, IconEmoji: conf.IconEmoji, } - if config.Conf.FormatOneLineText { - if _, _, err = api.PostMessage(channel, formatOneLineSummary(r), ParentMsg); err != nil { - return err - } - continue + var ts string + if _, ts, err = api.PostMessage(channel, + summary, msgPrms); err != nil { + return err } - var ts string - if _, ts, err = api.PostMessage(channel, msgText(r), ParentMsg); err != nil { - return err + if config.Conf.FormatOneLineText || 0 < len(r.Errors) { + continue } for _, k := range chunkKeys { params := slack.PostMessageParameters{ - // Text: msgText(r), Username: conf.AuthUser, IconEmoji: conf.IconEmoji, Attachments: m[k], ThreadTimestamp: ts, } - if _, _, err = api.PostMessage(channel, msgText(r), params); err != nil { + if _, _, err = api.PostMessage(channel, "", params); err != nil { return err } } } else { - if config.Conf.FormatOneLineText { - msg := message{ - Text: formatOneLineSummary(r), - Username: conf.AuthUser, - IconEmoji: conf.IconEmoji, - Channel: channel, - } - if err := send(msg); err != nil { - return err - } + msg := message{ + Text: summary, + Username: conf.AuthUser, + IconEmoji: conf.IconEmoji, + Channel: channel, + } + if err := send(msg); err != nil { + return err + } + + if config.Conf.FormatOneLineText || 0 < len(r.Errors) { continue } - for i, k := range chunkKeys { - txt := "" - if i == 0 { - txt = msgText(r) - } + for _, k := range chunkKeys { + txt := fmt.Sprintf("%d/%d for %s", + k+1, + len(chunkKeys), + r.FormatServerName()) + msg := message{ Text: txt, Username: conf.AuthUser, @@ -176,30 +178,6 @@ func send(msg message) error { return nil } -func msgText(r models.ScanResult) string { - notifyUsers := "" - if 0 < len(r.ScannedCves) { - notifyUsers = getNotifyUsers(config.Conf.Slack.NotifyUsers) - } - serverInfo := fmt.Sprintf("*%s*", r.ServerInfo()) - - if 0 < len(r.Errors) { - return fmt.Sprintf("%s\n%s\n%s\n%s\n%s\nError: %s", - notifyUsers, - serverInfo, - r.ScannedCves.FormatCveSummary(), - r.ScannedCves.FormatFixedStatus(r.Packages), - r.FormatUpdatablePacksSummary(), - r.Errors) - } - return fmt.Sprintf("%s\n%s\n%s\n%s\n%s", - notifyUsers, - serverInfo, - r.ScannedCves.FormatCveSummary(), - r.ScannedCves.FormatFixedStatus(r.Packages), - r.FormatUpdatablePacksSummary()) -} - func toSlackAttachments(r models.ScanResult) (attaches []slack.Attachment) { vinfos := r.ScannedCves.ToSortedSlice() for _, vinfo := range vinfos { diff --git a/report/util.go b/report/util.go index bcccbedd..e02265ae 100644 --- a/report/util.go +++ b/report/util.go @@ -44,6 +44,8 @@ func formatScanSummary(rs ...models.ScanResult) string { table := uitable.New() table.MaxColWidth = maxColWidth table.Wrap = true + + warnMsgs := []string{} for _, r := range rs { var cols []interface{} if len(r.Errors) == 0 { @@ -57,18 +59,26 @@ func formatScanSummary(rs ...models.ScanResult) string { r.FormatServerName(), "Error", "", - "Run with --debug to view the details", + "Use configtest subcommand or scan with --debug to view the details", } } table.AddRow(cols...) + + if len(r.Warnings) != 0 { + warnMsgs = append(warnMsgs, fmt.Sprintf("Warning for %s: %s", + r.FormatServerName(), r.Warnings)) + } } - return fmt.Sprintf("%s\n", table) + return fmt.Sprintf("%s\n\n%s", table, strings.Join( + warnMsgs, "\n\n")) } func formatOneLineSummary(rs ...models.ScanResult) string { table := uitable.New() table.MaxColWidth = maxColWidth table.Wrap = true + + warnMsgs := []string{} for _, r := range rs { var cols []interface{} if len(r.Errors) == 0 { @@ -83,22 +93,33 @@ func formatOneLineSummary(rs ...models.ScanResult) string { } else { cols = []interface{}{ r.FormatServerName(), - "Error: Scan with --debug to view the details", + "Use configtest subcommand or scan with --debug to view the details", "", } } table.AddRow(cols...) + + if len(r.Warnings) != 0 { + warnMsgs = append(warnMsgs, fmt.Sprintf("Warning for %s: %s", + r.FormatServerName(), r.Warnings)) + } } - return fmt.Sprintf("%s\n", table) + return fmt.Sprintf("%s\n\n%s", table, strings.Join( + warnMsgs, "\n\n")) } func formatList(r models.ScanResult) string { header := r.FormatTextReportHeadedr() if len(r.Errors) != 0 { return fmt.Sprintf( - "%s\nError: Scan with --debug to view the details\n%s\n\n", + "%s\nError: Use configtest subcommand or scan with --debug to view the details\n%s\n\n", header, r.Errors) } + if len(r.Warnings) != 0 { + header += fmt.Sprintf( + "\nWarning: Some warnings occurred.\n%s\n\n", + r.Warnings) + } if len(r.ScannedCves) == 0 { return fmt.Sprintf(` @@ -165,10 +186,16 @@ func formatFullPlainText(r models.ScanResult) (lines string) { header := r.FormatTextReportHeadedr() if len(r.Errors) != 0 { return fmt.Sprintf( - "%s\nError: Scan with --debug to view the details\n%s\n\n", + "%s\nError: Use configtest subcommand or scan with --debug to view the details\n%s\n\n", header, r.Errors) } + if len(r.Warnings) != 0 { + header += fmt.Sprintf( + "\nWarning: Some warnings occurred.\n%s\n\n", + r.Warnings) + } + if len(r.ScannedCves) == 0 { return fmt.Sprintf(` %s diff --git a/scan/alpine.go b/scan/alpine.go index 30ce051f..98e5e2fb 100644 --- a/scan/alpine.go +++ b/scan/alpine.go @@ -130,11 +130,14 @@ func (o *alpine) scanPackages() error { updatable, err := o.scanUpdatablePackages() if err != nil { - o.log.Errorf("Failed to scan installed packages: %s", err) - return err + err = xerrors.Errorf("Failed to scan updatable packages: %w", err) + o.log.Warnf("err: %+v", err) + o.warns = append(o.warns, err) + // Only warning this error + } else { + installed.MergeNewVersion(updatable) } - installed.MergeNewVersion(updatable) o.Packages = installed return nil } diff --git a/scan/base.go b/scan/base.go index baca5038..cd32dd68 100644 --- a/scan/base.go +++ b/scan/base.go @@ -52,8 +52,10 @@ type base struct { osPackages LibraryScanners []models.LibraryScanner WordPress *models.WordPressPackages - log *logrus.Entry - errs []error + + log *logrus.Entry + errs []error + warns []error } func (l *base) exec(cmd string, sudo bool) execResult { @@ -403,9 +405,12 @@ func (l *base) convertToModel() models.ScanResult { Tag: l.ServerInfo.Image.Tag, } - errs := []string{} + errs, warns := []string{}, []string{} for _, e := range l.errs { - errs = append(errs, fmt.Sprintf("%s", e)) + errs = append(errs, fmt.Sprintf("%+v", e)) + } + for _, w := range l.warns { + warns = append(warns, fmt.Sprintf("%+v", w)) } scannedVia := scannedViaRemote @@ -436,6 +441,7 @@ func (l *base) convertToModel() models.ScanResult { LibraryScanners: l.LibraryScanners, Optional: l.ServerInfo.Optional, Errors: errs, + Warnings: warns, } } diff --git a/scan/debian.go b/scan/debian.go index 61812609..bb2fe5b2 100644 --- a/scan/debian.go +++ b/scan/debian.go @@ -263,7 +263,12 @@ func (o *debian) preCure() error { func (o *debian) postScan() error { if o.getServerInfo().Mode.IsDeep() || o.getServerInfo().Mode.IsFastRoot() { - return o.checkrestart() + if err := o.checkrestart(); err != nil { + err = xerrors.Errorf("Failed to scan need-restarting processes: %w", err) + o.log.Warnf("err: %+v", err) + o.warns = append(o.warns, err) + // Only warning this error + } } return nil } @@ -282,8 +287,9 @@ func (o *debian) scanPackages() error { } rebootRequired, err := o.rebootRequired() if err != nil { - o.log.Errorf("Failed to detect the kernel reboot required: %s", err) - return err + o.log.Warnf("Failed to detect the kernel reboot required: %s", err) + o.warns = append(o.warns, err) + // Only warning this error } o.Kernel = models.Kernel{ Version: version, diff --git a/scan/freebsd.go b/scan/freebsd.go index a055a6ff..f8c3994c 100644 --- a/scan/freebsd.go +++ b/scan/freebsd.go @@ -143,12 +143,13 @@ func (o *bsd) scanPackages() error { Version: version, } - rebootRequired, err := o.rebootRequired() + o.Kernel.RebootRequired, err = o.rebootRequired() if err != nil { - o.log.Errorf("Failed to detect the kernel reboot required: %s", err) - return err + err = xerrors.Errorf("Failed to detect the kernel reboot required: %w", err) + o.log.Warnf("err: %+v", err) + o.warns = append(o.warns, err) + // Only warning this error } - o.Kernel.RebootRequired = rebootRequired packs, err := o.scanInstalledPackages() if err != nil { diff --git a/scan/redhatbase.go b/scan/redhatbase.go index 687ba919..ec8ba1c2 100644 --- a/scan/redhatbase.go +++ b/scan/redhatbase.go @@ -184,7 +184,8 @@ func (o *redhatBase) execCheckDeps(packNames []string) error { func (o *redhatBase) preCure() error { if err := o.detectIPAddr(); err != nil { - o.log.Debugf("Failed to detect IP addresses: %s", err) + o.log.Warnf("Failed to detect IP addresses: %s", err) + o.warns = append(o.warns, err) } // Ignore this error as it just failed to detect the IP addresses return nil @@ -193,12 +194,19 @@ func (o *redhatBase) preCure() error { func (o *redhatBase) postScan() error { if o.isExecYumPS() { if err := o.yumPS(); err != nil { - return xerrors.Errorf("Failed to execute yum-ps. err: %w", err) + err = xerrors.Errorf("Failed to execute yum-ps: %w", err) + o.log.Warnf("err: %+v", err) + o.warns = append(o.warns, err) + // Only warning this error } } + if o.isExecNeedsRestarting() { if err := o.needsRestarting(); err != nil { - return xerrors.Errorf("Failed to execute need-restarting: %w", err) + err = xerrors.Errorf("Failed to execute need-restarting: %w", err) + o.log.Warnf("err: %+v", err) + o.warns = append(o.warns, err) + // Only warning this error } } return nil @@ -216,36 +224,41 @@ func (o *redhatBase) scanPackages() error { o.log.Errorf("Failed to scan installed packages: %s", err) return err } + o.Packages = installed rebootRequired, err := o.rebootRequired() if err != nil { - o.log.Errorf("Failed to detect the kernel reboot required: %s", err) - return err + err = xerrors.Errorf("Failed to detect the kernel reboot required: %w", err) + o.log.Warnf("err: %+v", err) + o.warns = append(o.warns, err) + // Only warning this error + } else { + o.Kernel.RebootRequired = rebootRequired } - o.Kernel.RebootRequired = rebootRequired if o.getServerInfo().Mode.IsOffline() { switch o.Distro.Family { case config.Amazon: // nop default: - o.Packages = installed return nil } } else if o.Distro.Family == config.RedHat { if o.getServerInfo().Mode.IsFast() { - o.Packages = installed return nil } } updatable, err := o.scanUpdatablePackages() if err != nil { - o.log.Errorf("Failed to scan installed packages: %s", err) - return err + err = xerrors.Errorf("Failed to scan updatable packages: %w", err) + o.log.Warnf("err: %+v", err) + o.warns = append(o.warns, err) + // Only warning this error + } else { + installed.MergeNewVersion(updatable) + o.Packages = installed } - installed.MergeNewVersion(updatable) - o.Packages = installed var unsecures models.VulnInfos if unsecures, err = o.scanUnsecurePackages(updatable); err != nil { @@ -516,7 +529,10 @@ func (o *redhatBase) isExecNeedsRestarting() bool { func (o *redhatBase) scanUnsecurePackages(updatable models.Packages) (models.VulnInfos, error) { if o.isExecFillChangelogs() { if err := o.fillChangelogs(updatable); err != nil { - return nil, err + err = xerrors.Errorf("Failed to fetch changelogs: %w", err) + o.log.Warnf("err: %+v", err) + o.warns = append(o.warns, err) + // Only warning this error } } @@ -1275,7 +1291,7 @@ func (o *redhatBase) needsRestarting() error { cmd := "LANGUAGE=en_US.UTF-8 needs-restarting" r := o.exec(cmd, sudo) if !r.isSuccess() { - return xerrors.Errorf("Failed to SSH: %s", r) + return xerrors.Errorf("Failed to SSH: %w", r) } procs := o.parseNeedsRestarting(r.Stdout) for _, proc := range procs { diff --git a/scan/serverapi.go b/scan/serverapi.go index fe111fda..1c3d14b1 100644 --- a/scan/serverapi.go +++ b/scan/serverapi.go @@ -746,6 +746,11 @@ func scanVulns(jsonDir string, scannedAt time.Time, timeoutSec int) error { r.ScannedIPv6Addrs = ipv6s r.Config.Scan = config.Conf results = append(results, r) + + if 0 < len(r.Warnings) { + util.Log.Warnf("Some warnings occurred during scanning on %s. Please fix the warnings to get a useful information. Execute configtest subcommand before scanning to know the cause of the warnings. warnings: %v", + r.ServerName, r.Warnings) + } } config.Conf.FormatJSON = true @@ -759,6 +764,17 @@ func scanVulns(jsonDir string, scannedAt time.Time, timeoutSec int) error { } report.StdoutWriter{}.WriteScanSummary(results...) + + errServerNames := []string{} + for _, r := range results { + if 0 < len(r.Errors) { + errServerNames = append(errServerNames, r.ServerName) + } + } + if 0 < len(errServerNames) { + return fmt.Errorf("An error occurred on %s", errServerNames) + } + return nil } diff --git a/scan/suse.go b/scan/suse.go index 1ac735e6..6a56afd6 100644 --- a/scan/suse.go +++ b/scan/suse.go @@ -121,12 +121,14 @@ func (o *suse) scanPackages() error { return err } - rebootRequired, err := o.rebootRequired() + o.Kernel.RebootRequired, err = o.rebootRequired() if err != nil { - o.log.Errorf("Failed to detect the kernel reboot required: %s", err) - return err + err = xerrors.Errorf("Failed to detect the kernel reboot required: %w", err) + o.log.Warnf("err: %+v", err) + o.warns = append(o.warns, err) + // Only warning this error } - o.Kernel.RebootRequired = rebootRequired + if o.getServerInfo().Mode.IsOffline() { o.Packages = installed return nil @@ -134,12 +136,15 @@ func (o *suse) scanPackages() error { updatable, err := o.scanUpdatablePackages() if err != nil { - o.log.Errorf("Failed to scan updatable packages: %s", err) - return err + err = xerrors.Errorf("Failed to scan updatable packages: %w", err) + o.log.Warnf("err: %+v", err) + o.warns = append(o.warns, err) + // Only warning this error + } else { + installed.MergeNewVersion(updatable) } - installed.MergeNewVersion(updatable) - o.Packages = installed + o.Packages = installed return nil }