diff --git a/commands/report.go b/commands/report.go index 3a4b4a7e..a22b2e86 100644 --- a/commands/report.go +++ b/commands/report.go @@ -57,6 +57,7 @@ type ReportCmd struct { ovalDBURL string toSlack bool + toHipChat bool toEMail bool toSyslog bool toLocalFile bool @@ -114,6 +115,7 @@ func (*ReportCmd) Usage() string { [-ignore-unfixed] [-to-email] [-to-slack] + [-to-hipchat] [-to-localfile] [-to-s3] [-to-azure-blob] @@ -264,6 +266,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.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") f.BoolVar(&p.toLocalFile, @@ -361,6 +364,10 @@ func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{} reports = append(reports, report.SlackWriter{}) } + if p.toHipChat { + reports = append(reports, report.HipChatWriter{}) + } + if p.toEMail { reports = append(reports, report.EMailWriter{}) } diff --git a/config/config.go b/config/config.go index 76c0e38e..eb188830 100644 --- a/config/config.go +++ b/config/config.go @@ -96,6 +96,7 @@ type Config struct { EMail SMTPConf Slack SlackConf + HipChat HipChatConf Syslog SyslogConf Default ServerInfo Servers map[string]ServerInfo @@ -263,6 +264,10 @@ func (c Config) ValidateOnReport() bool { errs = append(errs, slackerrs...) } + if hipchaterrs := c.HipChat.Validate(); 0 < len(hipchaterrs) { + errs = append(errs, hipchaterrs...) + } + if syslogerrs := c.Syslog.Validate(); 0 < len(syslogerrs) { errs = append(errs, syslogerrs...) } @@ -451,6 +456,30 @@ func (c *SlackConf) Validate() (errs []error) { return } +// HipChatConf is HipChat config +type HipChatConf struct { + AuthToken string `json:"AuthToken"` + Room string `json:"Room"` +} + +// Validate validates configuration +func (c *HipChatConf) Validate() (errs []error) { + if len(c.Room) == 0 { + errs = append(errs, fmt.Errorf("room must not be empty")) + } + + if len(c.AuthToken) == 0 { + errs = append(errs, fmt.Errorf("AuthToken must not be empty")) + } + + _, err := valid.ValidateStruct(c) + if err != nil { + errs = append(errs, err) + } + + return +} + // SyslogConf is syslog config type SyslogConf struct { Protocol string diff --git a/config/tomlloader.go b/config/tomlloader.go index 3ed52ce1..890a730e 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.HipChat = conf.HipChat Conf.Syslog = conf.Syslog d := conf.Default diff --git a/report/hipchat.go b/report/hipchat.go new file mode 100644 index 00000000..896e7292 --- /dev/null +++ b/report/hipchat.go @@ -0,0 +1,70 @@ +package report + +import ( + "fmt" + "net/http" + "net/url" + "strconv" + "strings" + + "github.com/future-architect/vuls/config" + "github.com/future-architect/vuls/models" +) + +// HipChatWriter send report to HipChat +type HipChatWriter struct{} + +func (w HipChatWriter) Write(rs ...models.ScanResult) (err error) { + conf := config.Conf.HipChat + + var message string + for _, r := range rs { + err = postMessage(conf.Room, conf.AuthToken, r.ServerName) + if err != nil { + return err + } + + for _, vinfo := range r.ScannedCves { + maxCvss := vinfo.MaxCvssScore() + severity := strings.ToUpper(maxCvss.Value.Severity) + if severity == "" { + severity = "?" + } + + message = `` + vinfo.CveID + "" + "
" + strconv.FormatFloat(maxCvss.Value.Score, 'f', 1, 64) + " " + "(" + severity + ")" + "
" + vinfo.Summaries(config.Conf.Lang, r.Family)[0].Value + + err = postMessage(conf.Room, conf.AuthToken, message) + if err != nil { + return err + } + } + + } + return nil +} + +func postMessage(room, token, message string) error { + uri := fmt.Sprintf("https://api.hipchat.com/v2/room/%s/notification?auth_token=%s", room, token) + + payload := url.Values{ + "color": {"purple"}, + "message_format": {"html"}, + "message": {message}, + } + reqs, err := http.NewRequest("POST", uri, strings.NewReader(payload.Encode())) + + reqs.Header.Add("Content-Type", "application/x-www-form-urlencoded") + + 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/hipchat_test.go b/report/hipchat_test.go new file mode 100644 index 00000000..80c499fb --- /dev/null +++ b/report/hipchat_test.go @@ -0,0 +1 @@ +package report