diff --git a/commands/report.go b/commands/report.go index f2eb14e9..6398234e 100644 --- a/commands/report.go +++ b/commands/report.go @@ -57,6 +57,7 @@ type ReportCmd struct { ovalDBURL string toSlack bool + toStride bool toHipChat bool toEMail bool toSyslog bool @@ -114,6 +115,7 @@ func (*ReportCmd) Usage() string { [-ignore-unfixed] [-to-email] [-to-slack] + [-to-stride] [-to-hipchat] [-to-localfile] [-to-s3] @@ -265,6 +267,7 @@ func (p *ReportCmd) SetFlags(f *flag.FlagSet) { f.BoolVar(&p.gzip, "gzip", false, "gzip compression") f.BoolVar(&p.toSlack, "to-slack", false, "Send report via Slack") + f.BoolVar(&p.toStride, "to-stride", false, "Send report via Stride") f.BoolVar(&p.toHipChat, "to-hipchat", false, "Send report via hipchat") f.BoolVar(&p.toEMail, "to-email", false, "Send report via Email") f.BoolVar(&p.toSyslog, "to-syslog", false, "Send report via Syslog") @@ -331,6 +334,7 @@ func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{} c.Conf.HTTPProxy = p.httpProxy c.Conf.ToSlack = p.toSlack + c.Conf.ToStride = p.toStride c.Conf.ToHipChat = p.toHipChat c.Conf.ToEmail = p.toEMail c.Conf.ToSyslog = p.toSyslog @@ -370,6 +374,10 @@ func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{} reports = append(reports, report.SlackWriter{}) } + if p.toStride { + reports = append(reports, report.StrideWriter{}) + } + if p.toHipChat { reports = append(reports, report.HipChatWriter{}) } diff --git a/config/config.go b/config/config.go index addfd920..ed16e84d 100644 --- a/config/config.go +++ b/config/config.go @@ -96,6 +96,7 @@ type Config struct { EMail SMTPConf Slack SlackConf + Stride StrideConf HipChat HipChatConf Syslog SyslogConf Default ServerInfo @@ -129,6 +130,7 @@ type Config struct { RefreshCve bool ToSlack bool + ToStride bool ToHipChat bool ToEmail bool ToSyslog bool @@ -277,6 +279,10 @@ func (c Config) ValidateOnReport() bool { errs = append(errs, hipchaterrs...) } + if strideerrs := c.Stride.Validate(); 0 < len(strideerrs) { + errs = append(errs, strideerrs...) + } + if syslogerrs := c.Syslog.Validate(); 0 < len(syslogerrs) { errs = append(errs, syslogerrs...) } @@ -416,6 +422,33 @@ func (c *SMTPConf) Validate() (errs []error) { return } +// StrideConf is stride config +type StrideConf struct { + HookURL string `json:"hook_url"` + AuthToken string `json:"AuthToken"` +} + +// Validate validates configuration +func (c *StrideConf) Validate() (errs []error) { + if !Conf.ToStride { + return + } + + if len(c.HookURL) == 0 { + errs = append(errs, fmt.Errorf("stride.HookURL must not be empty")) + } + + if len(c.AuthToken) == 0 { + errs = append(errs, fmt.Errorf("stride.AuthToken must not be empty")) + } + + _, err := valid.ValidateStruct(c) + if err != nil { + errs = append(errs, err) + } + return +} + // SlackConf is slack config type SlackConf struct { HookURL string `valid:"url" json:"-"` diff --git a/config/tomlloader.go b/config/tomlloader.go index 890a730e..4bb9e043 100644 --- a/config/tomlloader.go +++ b/config/tomlloader.go @@ -44,6 +44,7 @@ func (c TOMLLoader) Load(pathToToml, keyPass string) error { Conf.EMail = conf.EMail Conf.Slack = conf.Slack + Conf.Stride = conf.Stride Conf.HipChat = conf.HipChat Conf.Syslog = conf.Syslog diff --git a/report/stride.go b/report/stride.go new file mode 100644 index 00000000..7afcaf92 --- /dev/null +++ b/report/stride.go @@ -0,0 +1,75 @@ +package report + +import ( + "bytes" + "net/http" + + "github.com/future-architect/vuls/config" + "github.com/future-architect/vuls/models" + "strconv" + "strings" +) + +// StrideWriter send report to Stride +type StrideWriter struct{} +type StrideSender struct{} + +func (w StrideWriter) Write(rs ...models.ScanResult) (err error) { + conf := config.Conf.Stride + + for _, r := range rs { + w := StrideSender{} + jsonStr := `{"body":{"version":1,"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"` + r.ServerName + `"}]}]}}` + err = w.sendMessage(conf.HookURL, conf.AuthToken, jsonStr) + if err != nil { + return err + } + + for _, vinfo := range r.ScannedCves { + maxCvss := vinfo.MaxCvssScore() + severity := strings.ToUpper(maxCvss.Value.Severity) + if severity == "" { + severity = "?" + } + + jsonStr = `{"body":{"version":1,"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"` + vinfo.CveID + `","marks": [ { "type": "link", "attrs": { "href": "https://nvd.nist.gov/vuln/detail/` + vinfo.CveID + `", "title": "cve" } } ]}]}]}}` + w.sendMessage(conf.HookURL, conf.AuthToken, jsonStr) + if err != nil { + return err + } + jsonStr = `{"body":{"version":1,"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"` + strconv.FormatFloat(maxCvss.Value.Score, 'f', 1, 64) + "(" + severity + ")" + `"}]}]}}` + w.sendMessage(conf.HookURL, conf.AuthToken, jsonStr) + if err != nil { + return err + } + + jsonStr = `{"body":{"version":1,"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"` + vinfo.Summaries(config.Conf.Lang, r.Family)[0].Value + `"}]}]}}` + w.sendMessage(conf.HookURL, conf.AuthToken, jsonStr) + if err != nil { + return err + } + } + } + return nil +} + +func (w StrideSender) sendMessage(uri, token, jsonStr string) error { + + reqs, err := http.NewRequest("POST", uri, bytes.NewBuffer([]byte(jsonStr))) + + reqs.Header.Add("Content-Type", "application/json") + reqs.Header.Add("Authorization", "Bearer "+token) + + if err != nil { + return err + } + client := &http.Client{} + + resp, err := client.Do(reqs) + if err != nil { + return err + } + defer resp.Body.Close() + + return nil +} diff --git a/report/stride_test.go b/report/stride_test.go new file mode 100644 index 00000000..80c499fb --- /dev/null +++ b/report/stride_test.go @@ -0,0 +1 @@ +package report