Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Slack's Thread in notification #525

Merged
merged 1 commit into from
Oct 26, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -732,17 +732,20 @@ host = "172.31.4.82"
```
[slack]
hookURL = "https://hooks.slack.com/services/abc123/defghijklmnopqrstuvwxyz"
#legacyToken = "xoxp-11111111111-222222222222-3333333333"
channel = "#channel-name"
#channel = "${servername}"
iconEmoji = ":ghost:"
authUser = "username"
notifyUsers = ["@username"]
```

- hookURL : Incoming webhook's URL
- hookURL : Incoming webhook's URL (legacyTokenが設定されている場合、hookURLは無視される。)
- legacyToken : slack legacy token (https://api.slack.com/custom-integrations/legacy-tokens)
- channel : channel name.
channelに`${servername}`を指定すると、結果レポートをサーバごとに別チャネルにすることが出来る。
以下のサンプルでは、`#server1`チャネルと`#server2`チャネルに送信される。スキャン前にチャネルを作成する必要がある。
**legacyTokenが設定されている場合、channelは実在するchannelでなければならない。**
```
[slack]
channel = "${servername}"
Expand Down
18 changes: 16 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -748,18 +748,21 @@ You can customize your configuration using this template.
```
[slack]
hookURL = "https://hooks.slack.com/services/abc123/defghijklmnopqrstuvwxyz"
#legacyToken = "xoxp-11111111111-222222222222-3333333333"
channel = "#channel-name"
#channel = "${servername}"
iconEmoji = ":ghost:"
authUser = "username"
notifyUsers = ["@username"]
```

- hookURL : Incoming webhook's URL
- channel : channel name.
- hookURL : Incoming webhook's URL (hookURL is ignored when legacyToken is set.)
- legacyToken : slack legacy token (https://api.slack.com/custom-integrations/legacy-tokens)
- channel : channel name.
If you set `${servername}` to channel, the report will be sent to each channel.
In the following example, the report will be sent to the `#server1` and `#server2`.
Be sure to create these channels before scanning.
**if legacyToken is set, you must set up an existing channel**
```
[slack]
channel = "${servername}"
Expand Down Expand Up @@ -1383,6 +1386,17 @@ With this sample command, it will ..
- Only Report CVEs that CVSS score is over 7


```
$ vuls report \
-to-slack \
-cvss-over=7 \
-cvedb-path=$PWD/cve.sqlite3
```
With this sample command, it will ..
- Send scan results to slack
- Only Report CVEs that CVSS score is over 7


## Example: Put results in S3 bucket
To put results in S3 bucket, configure following settings in AWS before reporting.
- Create S3 bucket. see [Creating a Bucket](http://docs.aws.amazon.com/AmazonS3/latest/UG/CreatingaBucket.html)
Expand Down
9 changes: 5 additions & 4 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -379,10 +379,11 @@ func (c *SMTPConf) Validate() (errs []error) {

// SlackConf is slack config
type SlackConf struct {
HookURL string `valid:"url" json:"-"`
Channel string `json:"channel"`
IconEmoji string `json:"icon_emoji"`
AuthUser string `json:"username"`
HookURL string `valid:"url" json:"-"`
LegacyToken string `json:"token" toml:"legacyToken,omitempty"`
Channel string `json:"channel"`
IconEmoji string `json:"icon_emoji"`
AuthUser string `json:"username"`

NotifyUsers []string
Text string `json:"text"`
Expand Down
99 changes: 60 additions & 39 deletions report/slack.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/cenkalti/backoff"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/models"
"github.com/nlopes/slack"
"github.com/parnurzeal/gorequest"
log "github.com/sirupsen/logrus"
)
Expand All @@ -36,31 +37,23 @@ type field struct {
Value string `json:"value"`
Short bool `json:"short"`
}
type attachment struct {
Title string `json:"title"`
TitleLink string `json:"title_link"`
Fallback string `json:"fallback"`
Text string `json:"text"`
Pretext string `json:"pretext"`
Color string `json:"color"`
Fields []*field `json:"fields"`
MrkdwnIn []string `json:"mrkdwn_in"`
Footer string `json:"footer"`
}

type message struct {
Text string `json:"text"`
Username string `json:"username"`
IconEmoji string `json:"icon_emoji"`
Channel string `json:"channel"`
Attachments []*attachment `json:"attachments"`
Text string `json:"text"`
Username string `json:"username"`
IconEmoji string `json:"icon_emoji"`
Channel string `json:"channel"`
ThreadTimeStamp string `json:"thread_ts"`
Attachments []slack.Attachment `json:"attachments"`
}

// SlackWriter send report to slack
type SlackWriter struct{}

func (w SlackWriter) Write(rs ...models.ScanResult) error {
func (w SlackWriter) Write(rs ...models.ScanResult) (err error) {
conf := config.Conf.Slack
channel := conf.Channel
token := conf.LegacyToken

for _, r := range rs {
if channel == "${servername}" {
Expand All @@ -78,7 +71,7 @@ func (w SlackWriter) Write(rs ...models.ScanResult) error {
IconEmoji: conf.IconEmoji,
Channel: channel,
}
if err := send(msg); err != nil {
if err = send(msg); err != nil {
return err
}
continue
Expand All @@ -88,7 +81,7 @@ func (w SlackWriter) Write(rs ...models.ScanResult) error {
// Split into chunks with 100 elements
// https://api.slack.com/methods/chat.postMessage
maxAttachments := 100
m := map[int][]*attachment{}
m := map[int][]slack.Attachment{}
for i, a := range toSlackAttachments(r) {
m[i/maxAttachments] = append(m[i/maxAttachments], a)
}
Expand All @@ -98,21 +91,49 @@ func (w SlackWriter) Write(rs ...models.ScanResult) error {
}
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],
// Send slack by API
if 0 < len(token) {
api := slack.New(token)
ParentMsg := slack.PostMessageParameters{
// Text: msgText(r),
Username: conf.AuthUser,
IconEmoji: conf.IconEmoji,
}
if err := send(msg); err != nil {

var ts string
if _, ts, err = api.PostMessage(channel, msgText(r), ParentMsg); err != nil {
return err
}

for _, k := range chunkKeys {
params := slack.PostMessageParameters{
// Text: msgText(r),
Username: conf.AuthUser,
IconEmoji: conf.IconEmoji,
Attachments: m[k],
ThreadTimestamp: ts,
}
if _, _, err = api.PostMessage(channel, msgText(r), params); err != nil {
return err
}
}
} else {
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
Expand Down Expand Up @@ -164,7 +185,7 @@ func msgText(r models.ScanResult) string {
r.ScannedCves.FormatCveSummary())
}

func toSlackAttachments(r models.ScanResult) (attaches []*attachment) {
func toSlackAttachments(r models.ScanResult) (attaches []slack.Attachment) {
vinfos := r.ScannedCves.ToSortedSlice()
for _, vinfo := range vinfos {
curent := []string{}
Expand Down Expand Up @@ -196,12 +217,12 @@ func toSlackAttachments(r models.ScanResult) (attaches []*attachment) {
new = append(new, "?")
}

a := attachment{
Title: vinfo.CveID,
TitleLink: "https://nvd.nist.gov/vuln/detail/" + vinfo.CveID,
Text: attachmentText(vinfo, r.Family),
MrkdwnIn: []string{"text", "pretext"},
Fields: []*field{
a := slack.Attachment{
Title: vinfo.CveID,
TitleLink: "https://nvd.nist.gov/vuln/detail/" + vinfo.CveID,
Text: attachmentText(vinfo, r.Family),
MarkdownIn: []string{"text", "pretext"},
Fields: []slack.AttachmentField{
{
// Title: "Current Package/CPE",
Title: "Installed",
Expand All @@ -216,7 +237,7 @@ func toSlackAttachments(r models.ScanResult) (attaches []*attachment) {
},
Color: color(vinfo.MaxCvssScore().Value.Score),
}
attaches = append(attaches, &a)
attaches = append(attaches, a)
}
return
}
Expand Down