diff --git a/commands/discover.go b/commands/discover.go index 1af12c97..aa15f4e6 100644 --- a/commands/discover.go +++ b/commands/discover.go @@ -176,6 +176,11 @@ sqlite3Path = "/path/to/go-exploitdb.sqlite3" #room = "xxxxxxxxxxx" #apiToken = "xxxxxxxxxxxxxxxxxx" +# https://vuls.io/docs/en/usage-settings.html#telegram-section +#[telegram] +#chatID = "xxxxxxxxxxx" +#token = "xxxxxxxxxxxxxxxxxx" + # https://vuls.io/docs/en/usage-settings.html#default-section [default] #port = "22" diff --git a/commands/report.go b/commands/report.go index 577f75af..aa862bc7 100644 --- a/commands/report.go +++ b/commands/report.go @@ -70,6 +70,7 @@ func (*ReportCmd) Usage() string { [-to-stride] [-to-hipchat] [-to-chatwork] + [-to-telegram] [-to-localfile] [-to-s3] [-to-azure-blob] @@ -154,6 +155,7 @@ func (p *ReportCmd) SetFlags(f *flag.FlagSet) { f.BoolVar(&c.Conf.ToStride, "to-stride", false, "Send report via Stride") f.BoolVar(&c.Conf.ToHipChat, "to-hipchat", false, "Send report via hipchat") f.BoolVar(&c.Conf.ToChatWork, "to-chatwork", false, "Send report via chatwork") + f.BoolVar(&c.Conf.ToTelegram, "to-telegram", false, "Send report via Telegram") f.BoolVar(&c.Conf.ToEmail, "to-email", false, "Send report via Email") f.BoolVar(&c.Conf.ToSyslog, "to-syslog", false, "Send report via Syslog") f.BoolVar(&c.Conf.ToLocalFile, "to-localfile", false, "Write report to localfile") @@ -247,6 +249,10 @@ func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{} reports = append(reports, report.ChatWorkWriter{}) } + if c.Conf.ToTelegram { + reports = append(reports, report.TelegramWriter{}) + } + if c.Conf.ToEmail { reports = append(reports, report.EMailWriter{}) } diff --git a/config/config.go b/config/config.go index b1db8895..cd8e5266 100644 --- a/config/config.go +++ b/config/config.go @@ -133,6 +133,7 @@ type Config struct { Stride StrideConf `json:"-"` HipChat HipChatConf `json:"-"` ChatWork ChatWorkConf `json:"-"` + Telegram TelegramConf `json:"-"` Saas SaasConf `json:"-"` RefreshCve bool `json:"refreshCve"` @@ -140,6 +141,7 @@ type Config struct { ToStride bool `json:"toStride"` ToHipChat bool `json:"toHipChat"` ToChatWork bool `json:"toChatWork"` + ToTelegram bool `json:"ToTelegram"` ToEmail bool `json:"toEmail"` ToSyslog bool `json:"toSyslog"` ToLocalFile bool `json:"toLocalFile"` @@ -287,6 +289,10 @@ func (c Config) ValidateOnReport() bool { errs = append(errs, strideerrs...) } + if telegramerrs := c.Telegram.Validate(); 0 < len(telegramerrs) { + errs = append(errs, telegramerrs...) + } + if saaserrs := c.Saas.Validate(); 0 < len(saaserrs) { errs = append(errs, saaserrs...) } @@ -557,6 +563,32 @@ func (c *ChatWorkConf) Validate() (errs []error) { return } +// TelegramConf is Telegram config +type TelegramConf struct { + Token string `json:"-"` + ChatID string `json:"-"` +} + +// Validate validates configuration +func (c *TelegramConf) Validate() (errs []error) { + if !Conf.ToTelegram { + return + } + if len(c.ChatID) == 0 { + errs = append(errs, fmt.Errorf("TelegramConf.ChatID must not be empty")) + } + + if len(c.Token) == 0 { + errs = append(errs, fmt.Errorf("TelegramConf.Token must not be empty")) + } + + _, err := valid.ValidateStruct(c) + if err != nil { + errs = append(errs, err) + } + return +} + // SaasConf is stride config type SaasConf struct { GroupID int `json:"-"` diff --git a/config/tomlloader.go b/config/tomlloader.go index 917f4b95..6b657e79 100644 --- a/config/tomlloader.go +++ b/config/tomlloader.go @@ -42,6 +42,7 @@ func (c TOMLLoader) Load(pathToToml, keyPass string) error { Conf.Stride = conf.Stride Conf.HipChat = conf.HipChat Conf.ChatWork = conf.ChatWork + Conf.Telegram = conf.Telegram Conf.Saas = conf.Saas Conf.Syslog = conf.Syslog Conf.HTTP = conf.HTTP diff --git a/report/telegram.go b/report/telegram.go new file mode 100644 index 00000000..9cad68c7 --- /dev/null +++ b/report/telegram.go @@ -0,0 +1,77 @@ +package report + +import ( + "bytes" + "fmt" + "net/http" + "strconv" + "strings" + + "github.com/future-architect/vuls/config" + "github.com/future-architect/vuls/models" +) + +// TelegramWriter sends report to Telegram +type TelegramWriter struct{} + +func (w TelegramWriter) Write(rs ...models.ScanResult) (err error) { + conf := config.Conf.Telegram + for _, r := range rs { + msgs := []string{fmt.Sprintf("*%s*\n%s\n%s\n%s", + r.ServerInfo(), + r.ScannedCves.FormatCveSummary(), + r.ScannedCves.FormatFixedStatus(r.Packages), + r.FormatUpdatablePacksSummary())} + for _, vinfo := range r.ScannedCves { + maxCvss := vinfo.MaxCvssScore() + severity := strings.ToUpper(maxCvss.Value.Severity) + if severity == "" { + severity = "?" + } + msgs = append(msgs, fmt.Sprintf(`[%s](https://nvd.nist.gov/vuln/detail/%s) _%s %s %s_\n%s`, + vinfo.CveID, + vinfo.CveID, + strconv.FormatFloat(maxCvss.Value.Score, 'f', 1, 64), + severity, + maxCvss.Value.Vector, + vinfo.Summaries(config.Conf.Lang, r.Family)[0].Value)) + if len(msgs) == 5 { + if err = sendMessage(conf.ChatID, conf.Token, strings.Join(msgs, "\n\n")); err != nil { + return err + } + msgs = []string{} + } + } + if len(msgs) != 0 { + if err = sendMessage(conf.ChatID, conf.Token, strings.Join(msgs, "\n\n")); err != nil { + return err + } + } + } + return nil +} + +func sendMessage(chatID, token, message string) error { + uri := fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", token) + payload := `{"text": "` + strings.Replace(message, `"`, `\"`, -1) + `", "chat_id": "` + chatID + `", "parse_mode": "Markdown" }` + req, err := http.NewRequest("POST", uri, bytes.NewBuffer([]byte(payload))) + req.Header.Add("Content-Type", "application/json") + if err != nil { + return err + } + client := &http.Client{} + resp, err := client.Do(req) + if checkResponse(resp) != nil && err != nil { + fmt.Println(err) + return err + } + defer resp.Body.Close() + return nil +} + +func checkResponse(r *http.Response) error { + if c := r.StatusCode; 200 <= c && c <= 299 { + return nil + } + return fmt.Errorf("API call to %s failed: %s", r.Request.URL.String(), r.Status) +} diff --git a/report/telegram_test.go b/report/telegram_test.go new file mode 100644 index 00000000..80c499fb --- /dev/null +++ b/report/telegram_test.go @@ -0,0 +1 @@ +package report