Add Telegram support (#762)

* add telegram support

* format message

* remove debug print

* fix linting error

* add telegram to discover; group message by 10

* use chatID instead of channel

* apply refactor

* remove reduntant space
This commit is contained in:
Yao Ding
2019-01-22 10:19:16 -05:00
committed by Kota Kanbe
parent 967c56909d
commit 3d8753c621
6 changed files with 122 additions and 0 deletions

View File

@@ -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"

View File

@@ -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{})
}

View File

@@ -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:"-"`

View File

@@ -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

77
report/telegram.go Normal file
View File

@@ -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)
}

1
report/telegram_test.go Normal file
View File

@@ -0,0 +1 @@
package report