send slack msg by api (#525)

This commit is contained in:
sadayuki-matsuno
2017-10-26 13:30:01 +09:00
committed by Kota Kanbe
parent 84d0655c52
commit eb2acaff22
5 changed files with 93 additions and 48 deletions

10
Gopkg.lock generated
View File

@@ -198,6 +198,12 @@
packages = ["."]
revision = "9ac6cf4d929b2fa8fd2d2e6dec5bb0feb4f4911d"
[[projects]]
name = "github.com/nlopes/slack"
packages = ["."]
revision = "c86337c0ef2486a15edd804355d9c73d2f2caed1"
version = "v0.1.0"
[[projects]]
branch = "master"
name = "github.com/nsf/termbox-go"
@@ -249,7 +255,7 @@
[[projects]]
branch = "master"
name = "golang.org/x/net"
packages = ["context","idna","publicsuffix"]
packages = ["context","idna","publicsuffix","websocket"]
revision = "0a9397675ba34b2845f758fe3cd68828369c6517"
[[projects]]
@@ -267,6 +273,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "36d700add80d36c56484ed310b9a7e622b3e308ab22eb42bdfb02fd8f5c90407"
inputs-digest = "a0cf09ec0aec110a8743e9c6085f35052dcb9f1c5c9174efd89f179f5df64dfc"
solver-name = "gps-cdcl"
solver-version = 1

View File

@@ -732,6 +732,7 @@ 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:"
@@ -739,10 +740,12 @@ host = "172.31.4.82"
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}"

View File

@@ -748,6 +748,7 @@ 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:"
@@ -755,11 +756,13 @@ You can customize your configuration using this template.
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}"
@@ -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)

View File

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

View File

@@ -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"
)
@@ -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}" {
@@ -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
@@ -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)
}
@@ -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)
// Send slack by API
if 0 < len(token) {
api := slack.New(token)
ParentMsg := slack.PostMessageParameters{
// Text: msgText(r),
Username: conf.AuthUser,
IconEmoji: conf.IconEmoji,
}
msg := message{
Text: txt,
Username: conf.AuthUser,
IconEmoji: conf.IconEmoji,
Channel: channel,
Attachments: m[k],
}
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
@@ -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{}
@@ -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",
@@ -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
}