diff --git a/README.md b/README.md index 9260952..20ad0d0 100644 --- a/README.md +++ b/README.md @@ -88,12 +88,14 @@ slack: slack_channel: "recon" slack_username: "test" slack_format: "{{data}}" + slack_icon_emoji: ":ghost:" slack_webhook_url: "https://hooks.slack.com/services/XXXXXX" - id: "vulns" slack_channel: "vulns" slack_username: "test" slack_format: "{{data}}" + slack_icon_emoji: ":ghost:" slack_webhook_url: "https://hooks.slack.com/services/XXXXXX" discord: diff --git a/pkg/providers/slack/slack.go b/pkg/providers/slack/slack.go index 85bf7cf..e7e6185 100644 --- a/pkg/providers/slack/slack.go +++ b/pkg/providers/slack/slack.go @@ -1,11 +1,12 @@ package slack import ( + "bytes" + "encoding/json" "fmt" - "net/url" + "net/http" "strings" - "github.com/containrrr/shoutrrr" "github.com/pkg/errors" "go.uber.org/multierr" @@ -28,6 +29,7 @@ type Options struct { SlackThreadTS string `yaml:"slack_thread_ts,omitempty"` SlackToken string `yaml:"slack_token,omitempty"` SlackFormat string `yaml:"slack_format,omitempty"` + SlackIconEmoji string `yaml:"slack_icon_emoji,omitempty"` } func New(options []*Options, ids []string) (*Provider, error) { @@ -50,6 +52,7 @@ func (p *Provider) Send(message, CliFormat string) error { for _, pr := range p.Slack { msg := utils.FormatMessage(message, utils.SelectFormat(CliFormat, pr.SlackFormat), p.counter) + // Handle threaded messages separately if pr.SlackThreads { if pr.SlackToken == "" { err := errors.Wrap(fmt.Errorf("slack_token value is required to start a thread"), @@ -70,22 +73,42 @@ func (p *Provider) Send(message, CliFormat string) error { continue } } else { - slackTokens := strings.TrimPrefix(pr.SlackWebHookURL, "https://hooks.slack.com/services/") - url := &url.URL{ - Scheme: "slack", - Path: slackTokens, + // Send via webhook with emoji and username + if !strings.HasPrefix(pr.SlackWebHookURL, "https://hooks.slack.com/services/") { + err := errors.Wrap(fmt.Errorf("invalid slack webhook URL"), + fmt.Sprintf("failed to send slack notification for id: %s ", pr.ID)) + SlackErr = multierr.Append(SlackErr, err) + continue + } + + payload := map[string]interface{}{ + "text": msg, + } + if pr.SlackUsername != "" { + payload["username"] = pr.SlackUsername + } + if pr.SlackIconEmoji != "" { + payload["icon_emoji"] = pr.SlackIconEmoji } - err := shoutrrr.Send(url.String(), msg) + jsonPayload, err := json.Marshal(payload) if err != nil { - err = errors.Wrap(err, - fmt.Sprintf("failed to send slack notification for id: %s ", pr.ID)) + err = errors.Wrap(err, fmt.Sprintf("failed to marshal Slack payload for id: %s", pr.ID)) + SlackErr = multierr.Append(SlackErr, err) + continue + } + + resp, err := http.Post(pr.SlackWebHookURL, "application/json", bytes.NewBuffer(jsonPayload)) + if err != nil || resp.StatusCode >= 400 { + if err == nil { + err = fmt.Errorf("received non-success status: %s", resp.Status) + } + err = errors.Wrap(err, fmt.Sprintf("failed to send slack notification for id: %s", pr.ID)) SlackErr = multierr.Append(SlackErr, err) continue } } gologger.Verbose().Msgf("Slack notification sent for id: %s", pr.ID) - } return SlackErr }