send slack msg by api (#525)
This commit is contained in:
committed by
Kota Kanbe
parent
84d0655c52
commit
eb2acaff22
10
Gopkg.lock
generated
10
Gopkg.lock
generated
@@ -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
|
||||
|
||||
@@ -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}"
|
||||
|
||||
18
README.md
18
README.md
@@ -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)
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user