Continue scanning even when some hosts have tech issues

see #264
This commit is contained in:
Kota Kanbe
2017-01-31 14:35:16 +09:00
parent 00660485b7
commit 386b97d2be
19 changed files with 447 additions and 311 deletions

View File

@@ -42,11 +42,14 @@ func (w EMailWriter) Write(rs ...models.ScanResult) (err error) {
}
for _, r := range rs {
subject := fmt.Sprintf("%s%s %s",
conf.EMail.SubjectPrefix,
r.ServerInfo(),
r.CveSummary(),
)
var subject string
if len(r.Errors) != 0 {
subject = fmt.Sprintf("%s%s An error occurred while scanning",
conf.EMail.SubjectPrefix, r.ServerInfo())
} else {
subject = fmt.Sprintf("%s%s %s",
conf.EMail.SubjectPrefix, r.ServerInfo(), r.CveSummary())
}
headers := make(map[string]string)
headers["From"] = conf.EMail.From

View File

@@ -20,6 +20,7 @@ package report
import (
"encoding/json"
"fmt"
"sort"
"strings"
"time"
@@ -59,48 +60,90 @@ type SlackWriter struct{}
func (w SlackWriter) Write(rs ...models.ScanResult) error {
conf := config.Conf.Slack
channel := conf.Channel
count := 0
retryMax := 10
for _, r := range rs {
if channel == "${servername}" {
channel = fmt.Sprintf("#%s", r.ServerName)
}
msg := message{
Text: msgText(r),
Username: conf.AuthUser,
IconEmoji: conf.IconEmoji,
Channel: channel,
Attachments: toSlackAttachments(r),
}
bytes, _ := json.Marshal(msg)
jsonBody := string(bytes)
f := func() (err error) {
resp, body, errs := gorequest.New().Proxy(config.Conf.HTTPProxy).Post(conf.HookURL).Send(string(jsonBody)).End()
if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
count++
if count == retryMax {
return nil
}
return fmt.Errorf(
"HTTP POST error: %v, url: %s, resp: %v, body: %s",
errs, conf.HookURL, resp, body)
if 0 < len(r.Errors) {
serverInfo := fmt.Sprintf("*%s*", r.ServerInfo())
notifyUsers := getNotifyUsers(config.Conf.Slack.NotifyUsers)
txt := fmt.Sprintf("%s\n%s\nError: %s", notifyUsers, serverInfo, r.Errors)
msg := message{
Text: txt,
Username: conf.AuthUser,
IconEmoji: conf.IconEmoji,
Channel: channel,
}
return nil
if err := send(msg); err != nil {
return err
}
continue
}
notify := func(err error, t time.Duration) {
log.Warnf("Error %s", err)
log.Warn("Retrying in ", t)
// A maximum of 100 attachments are allowed on a message.
// Split into chunks with 100 elements
// https://api.slack.com/methods/chat.postMessage
maxAttachments := 100
m := map[int][]*attachment{}
for i, a := range toSlackAttachments(r) {
m[i/maxAttachments] = append(m[i/maxAttachments], a)
}
boff := backoff.NewExponentialBackOff()
if err := backoff.RetryNotify(f, boff, notify); err != nil {
return fmt.Errorf("HTTP error: %s", err)
chunkKeys := []int{}
for k := range m {
chunkKeys = append(chunkKeys, k)
}
sort.Ints(chunkKeys)
for i, k := range chunkKeys {
txt := ""
if i == 0 {
txt = msgText(r)
}
msg := message{
Text: txt,
Username: conf.AuthUser,
IconEmoji: conf.IconEmoji,
Channel: channel,
Attachments: m[k],
}
if err := send(msg); err != nil {
return err
}
}
}
return nil
}
func send(msg message) error {
conf := config.Conf.Slack
count, retryMax := 0, 10
bytes, _ := json.Marshal(msg)
jsonBody := string(bytes)
f := func() (err error) {
resp, body, errs := gorequest.New().Proxy(config.Conf.HTTPProxy).Post(conf.HookURL).Send(string(jsonBody)).End()
if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
count++
if count == retryMax {
return nil
}
return fmt.Errorf(
"HTTP POST error: %v, url: %s, resp: %v, body: %s",
errs, conf.HookURL, resp, body)
}
return nil
}
notify := func(err error, t time.Duration) {
log.Warnf("Error %s", err)
log.Warn("Retrying in ", t)
}
boff := backoff.NewExponentialBackOff()
if err := backoff.RetryNotify(f, boff, notify); err != nil {
return fmt.Errorf("HTTP error: %s", err)
}
if count == retryMax {
return fmt.Errorf("Retry count exceeded")
}
@@ -112,7 +155,6 @@ func msgText(r models.ScanResult) string {
if 0 < len(r.KnownCves) || 0 < len(r.UnknownCves) {
notifyUsers = getNotifyUsers(config.Conf.Slack.NotifyUsers)
}
serverInfo := fmt.Sprintf("*%s*", r.ServerInfo())
return fmt.Sprintf("%s\n%s\n>%s", notifyUsers, serverInfo, r.CveSummary())
}

View File

@@ -545,6 +545,10 @@ func summaryLines() string {
stable.MaxColWidth = 1000
stable.Wrap = false
if len(currentScanResult.Errors) != 0 {
return "Error: Scan with --debug to view the details"
}
indexFormat := ""
if len(currentScanResult.AllCves()) < 10 {
indexFormat = "[%1d]"
@@ -650,6 +654,10 @@ type dataForTmpl struct {
}
func detailLines() (string, error) {
if len(currentScanResult.Errors) != 0 {
return "", nil
}
if len(currentScanResult.AllCves()) == 0 {
return "No vulnerable packages", nil
}
@@ -730,8 +738,6 @@ func detailLines() (string, error) {
return string(buf.Bytes()), nil
}
// * {{.Name}}-{{.Version}}-{{.Release}}
func detailTemplate() string {
return `
{{.CveID}}

View File

@@ -34,11 +34,21 @@ func toScanSummary(rs ...models.ScanResult) string {
table.MaxColWidth = maxColWidth
table.Wrap = true
for _, r := range rs {
cols := []interface{}{
r.FormatServerName(),
fmt.Sprintf("%s%s", r.Family, r.Release),
fmt.Sprintf("%d CVEs", len(r.ScannedCves)),
r.Packages.ToUpdatablePacksSummary(),
var cols []interface{}
if len(r.Errors) == 0 {
cols = []interface{}{
r.FormatServerName(),
fmt.Sprintf("%s%s", r.Family, r.Release),
fmt.Sprintf("%d CVEs", len(r.ScannedCves)),
r.Packages.ToUpdatablePacksSummary(),
}
} else {
cols = []interface{}{
r.FormatServerName(),
"Error",
"",
"Run with --debug to view the details",
}
}
table.AddRow(cols...)
}
@@ -50,10 +60,19 @@ func toOneLineSummary(rs ...models.ScanResult) string {
table.MaxColWidth = maxColWidth
table.Wrap = true
for _, r := range rs {
cols := []interface{}{
r.FormatServerName(),
r.CveSummary(),
r.Packages.ToUpdatablePacksSummary(),
var cols []interface{}
if len(r.Errors) == 0 {
cols = []interface{}{
r.FormatServerName(),
r.CveSummary(),
r.Packages.ToUpdatablePacksSummary(),
}
} else {
cols = []interface{}{
r.FormatServerName(),
"Error: Scan with --debug to view the details",
"",
}
}
table.AddRow(cols...)
}
@@ -81,6 +100,12 @@ func toShortPlainText(r models.ScanResult) string {
r.Packages.ToUpdatablePacksSummary(),
)
if len(r.Errors) != 0 {
return fmt.Sprintf(
"%s\nError: Scan with --debug to view the details\n%s\n\n",
header, r.Errors)
}
if len(cves) == 0 {
return fmt.Sprintf(`
%s
@@ -168,6 +193,12 @@ func toFullPlainText(r models.ScanResult) string {
r.Packages.ToUpdatablePacksSummary(),
)
if len(r.Errors) != 0 {
return fmt.Sprintf(
"%s\nError: Scan with --debug to view the details\n%s\n\n",
header, r.Errors)
}
if len(r.KnownCves) == 0 && len(r.UnknownCves) == 0 {
return fmt.Sprintf(`
%s