-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Close: #32 ``` ❯ go run . help slack Scan Slack team for sensitive information. Usage: 2ms slack --token TOKEN --team TEAM [flags] Flags: --channel stringArray Slack channels to scan --duration duration Slack backward duration for messages (ex: 24h, 7d, 1M, 1y) (default 336h0m0s) -h, --help help for slack --messages-count int Slack messages count to scan (0 = all messages) --team string Slack team name or ID [required] --token string Slack token [required] Global Flags: --all scan all plugins (default true) --log-level string log level (trace, debug, info, warn, error, fatal) (default "info") --tags strings select rules to be applied (default [all]) ``` Like in Discord, more knowledge is required to integrate this plugin into an E2E system. For example, except for retrieving the token (from **OAuth & Permissions** in the Slack App page), you have to add your app to each **channel** you want to read. ![image](https://github.com/Checkmarx/2ms/assets/17686879/0db4994a-7304-4e70-a268-fff242f6ca35) --------- Co-authored-by: Jossef Harush Kadouri <jossef12@gmail.com>
- Loading branch information
Showing
5 changed files
with
406 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
package plugins | ||
|
||
import ( | ||
"fmt" | ||
"strconv" | ||
"time" | ||
|
||
"github.com/rs/zerolog/log" | ||
"github.com/slack-go/slack" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
const ( | ||
slackTokenFlag = "token" | ||
slackTeamFlag = "team" | ||
slackChannelFlag = "channel" | ||
slackBackwardDurationFlag = "duration" | ||
slackMessagesCountFlag = "messages-count" | ||
) | ||
|
||
const slackDefaultDateFrom = time.Hour * 24 * 14 | ||
|
||
type SlackPlugin struct { | ||
Plugin | ||
Channels | ||
Token string | ||
} | ||
|
||
func (p *SlackPlugin) GetName() string { | ||
return "slack" | ||
} | ||
|
||
var ( | ||
tokenArg string | ||
teamArg string | ||
channelsArg []string | ||
backwardDurationArg time.Duration | ||
messagesCountArg int | ||
) | ||
|
||
func (p *SlackPlugin) DefineCommand(channels Channels) (*cobra.Command, error) { | ||
p.Channels = channels | ||
|
||
command := &cobra.Command{ | ||
Use: fmt.Sprintf("%s --%s TOKEN --%s TEAM", p.GetName(), slackTokenFlag, slackTeamFlag), | ||
Short: "Scan Slack team", | ||
Long: "Scan Slack team for sensitive information.", | ||
Run: func(cmd *cobra.Command, args []string) { | ||
p.getItems() | ||
}, | ||
} | ||
|
||
command.Flags().StringVar(&tokenArg, slackTokenFlag, "", "Slack token [required]") | ||
err := command.MarkFlagRequired(slackTokenFlag) | ||
if err != nil { | ||
return nil, fmt.Errorf("error while marking flag %s as required: %w", slackTokenFlag, err) | ||
} | ||
command.Flags().StringVar(&teamArg, slackTeamFlag, "", "Slack team name or ID [required]") | ||
err = command.MarkFlagRequired(slackTeamFlag) | ||
if err != nil { | ||
return nil, fmt.Errorf("error while marking flag %s as required: %w", slackTeamFlag, err) | ||
} | ||
command.Flags().StringArrayVar(&channelsArg, slackChannelFlag, []string{}, "Slack channels to scan") | ||
command.Flags().DurationVar(&backwardDurationArg, slackBackwardDurationFlag, slackDefaultDateFrom, "Slack backward duration for messages (ex: 24h, 7d, 1M, 1y)") | ||
command.Flags().IntVar(&messagesCountArg, slackMessagesCountFlag, 0, "Slack messages count to scan (0 = all messages)") | ||
|
||
return command, nil | ||
} | ||
|
||
func (p *SlackPlugin) getItems() { | ||
slackApi := slack.New(tokenArg) | ||
|
||
team, err := getTeam(slackApi, teamArg) | ||
if err != nil { | ||
p.Errors <- fmt.Errorf("error while getting team: %w", err) | ||
return | ||
} | ||
|
||
channels, err := getChannels(slackApi, team.ID, channelsArg) | ||
if err != nil { | ||
p.Errors <- fmt.Errorf("error while getting channels for team %s: %w", team.Name, err) | ||
return | ||
} | ||
if len(*channels) == 0 { | ||
log.Warn().Msgf("No channels found for team %s", team.Name) | ||
return | ||
} | ||
|
||
log.Info().Msgf("Found %d channels for team %s", len(*channels), team.Name) | ||
p.WaitGroup.Add(len(*channels)) | ||
for _, channel := range *channels { | ||
go p.getItemsFromChannel(slackApi, channel) | ||
} | ||
} | ||
|
||
func (p *SlackPlugin) getItemsFromChannel(slackApi *slack.Client, channel slack.Channel) { | ||
defer p.WaitGroup.Done() | ||
log.Info().Msgf("Getting items from channel %s", channel.Name) | ||
|
||
cursor := "" | ||
counter := 0 | ||
for { | ||
history, err := slackApi.GetConversationHistory(&slack.GetConversationHistoryParameters{ | ||
Cursor: cursor, | ||
ChannelID: channel.ID, | ||
}) | ||
if err != nil { | ||
p.Errors <- fmt.Errorf("error while getting history for channel %s: %w", channel.Name, err) | ||
return | ||
} | ||
for _, message := range history.Messages { | ||
outOfRange, err := isMessageOutOfRange(message, backwardDurationArg, counter, messagesCountArg) | ||
if err != nil { | ||
p.Errors <- fmt.Errorf("error while checking message: %w", err) | ||
return | ||
} | ||
if outOfRange { | ||
break | ||
} | ||
if message.Text != "" { | ||
p.Items <- Item{ | ||
Content: message.Text, | ||
Source: channel.Name, | ||
ID: message.Timestamp, | ||
} | ||
} | ||
counter++ | ||
} | ||
if history.ResponseMetaData.NextCursor == "" { | ||
break | ||
} | ||
cursor = history.ResponseMetaData.NextCursor | ||
} | ||
} | ||
|
||
// Declare it to be consistent with all comparaisons | ||
var timeNow = time.Now() | ||
|
||
func isMessageOutOfRange(message slack.Message, backwardDuration time.Duration, currentMessagesCount int, limitMessagesCount int) (bool, error) { | ||
if backwardDuration != 0 { | ||
timestamp, err := strconv.ParseFloat(message.Timestamp, 64) | ||
if err != nil { | ||
return true, fmt.Errorf("error while parsing timestamp: %w", err) | ||
} | ||
messageDate := time.Unix(int64(timestamp), 0) | ||
if messageDate.Before(timeNow.Add(-backwardDuration)) { | ||
return true, nil | ||
} | ||
} | ||
if limitMessagesCount != 0 && currentMessagesCount >= limitMessagesCount { | ||
return true, nil | ||
} | ||
return false, nil | ||
} | ||
|
||
type ISlackClient interface { | ||
GetConversations(*slack.GetConversationsParameters) ([]slack.Channel, string, error) | ||
ListTeams(slack.ListTeamsParameters) ([]slack.Team, string, error) | ||
} | ||
|
||
func getTeam(slackApi ISlackClient, teamName string) (*slack.Team, error) { | ||
cursorHolder := "" | ||
for { | ||
teams, cursor, err := slackApi.ListTeams(slack.ListTeamsParameters{Cursor: cursorHolder}) | ||
if err != nil { | ||
return nil, fmt.Errorf("error while getting teams: %w", err) | ||
} | ||
for _, team := range teams { | ||
if team.Name == teamName || team.ID == teamName { | ||
return &team, nil | ||
} | ||
} | ||
if cursor == "" { | ||
break | ||
} | ||
cursorHolder = cursor | ||
} | ||
return nil, fmt.Errorf("team '%s' not found", teamName) | ||
} | ||
|
||
func getChannels(slackApi ISlackClient, teamId string, wantedChannels []string) (*[]slack.Channel, error) { | ||
cursorHolder := "" | ||
selectedChannels := []slack.Channel{} | ||
for { | ||
channels, cursor, err := slackApi.GetConversations(&slack.GetConversationsParameters{ | ||
Cursor: cursorHolder, | ||
TeamID: teamId, | ||
}) | ||
if err != nil { | ||
return nil, fmt.Errorf("error while getting channels: %w", err) | ||
} | ||
if len(wantedChannels) == 0 { | ||
selectedChannels = append(selectedChannels, channels...) | ||
} else { | ||
for _, channel := range wantedChannels { | ||
for _, c := range channels { | ||
if c.Name == channel || c.ID == channel { | ||
selectedChannels = append(selectedChannels, c) | ||
} | ||
} | ||
} | ||
if len(selectedChannels) == len(wantedChannels) { | ||
return &selectedChannels, nil | ||
} | ||
} | ||
if cursor == "" { | ||
return &selectedChannels, nil | ||
} | ||
cursorHolder = cursor | ||
} | ||
} |
Oops, something went wrong.