Skip to content
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
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,13 @@ cd dist && zip deployment.zip bootstrap && cd ..

## Optional Environment Variables

| name | example | purpose | default |
| --------------------------- | ------------------------------------------ | ------------------------------------------------------------ | --------------------------------- |
| `APP_DEBUG_ENABLED` | `true` | verbose logging & event dump | `false` |
| `APP_AWS_CONSOLE_URL` | `https://console.aws.amazon.com` | base AWS console URL | `https://console.aws.amazon.com` |
| `APP_AWS_ACCESS_PORTAL_URL` | `https://myorg.awsapps.com/start` | AWS access portal URL (for federated access) | _(none - direct console links)_ |
| `APP_AWS_ACCESS_ROLE_NAME` | `SecurityAuditor` | IAM role name for access portal | _(none - direct console links)_ |
| name | example | purpose | default |
| ------------------------------ | ------------------------------------------ | ------------------------------------------------------------ | --------------------------------- |
| `APP_DEBUG_ENABLED` | `true` | verbose logging & event dump | `false` |
| `APP_AWS_CONSOLE_URL` | `https://console.aws.amazon.com` | base AWS console URL | `https://console.aws.amazon.com` |
| `APP_AWS_ACCESS_PORTAL_URL` | `https://myorg.awsapps.com/start` | AWS access portal URL (for federated access) | _(none - direct console links)_ |
| `APP_AWS_ACCESS_ROLE_NAME` | `SecurityAuditor` | IAM role name for access portal | _(none - direct console links)_ |
| `APP_AWS_SECURITYHUBV2_REGION` | `us-east-1` | AWS region for centralized SecurityHub v2 if applicable | _(none - direct console links)_ |

## Create Lambda Function

Expand Down
2 changes: 1 addition & 1 deletion internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func (a *App) Process(evt awsEvent.CloudWatchEvent) error {
if err != nil || !e.IsAlertable() {
return err
}
m0, m1 := e.SlackMessage(a.Config.AwsConsoleURL, a.Config.AwsAccessPortalURL, a.Config.AwsAccessRoleName)
m0, m1 := e.SlackMessage(a.Config.AwsConsoleURL, a.Config.AwsAccessPortalURL, a.Config.AwsAccessRoleName, a.Config.AWSSecurityHubv2Region)
_, _, err = a.SlackClient.PostMessage(a.Config.SlackChannel, m0, m1)
return err
}
26 changes: 14 additions & 12 deletions internal/app/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,26 @@ import (
)

type Config struct {
DebugEnabled bool
AwsConsoleURL string
AwsAccessPortalURL string
AwsAccessRoleName string
SlackToken string
SlackChannel string
DebugEnabled bool
AwsConsoleURL string
AwsAccessPortalURL string
AwsAccessRoleName string
AWSSecurityHubv2Region string
SlackToken string
SlackChannel string
}

func NewConfig() (*Config, error) {
debugEnabled, _ := strconv.ParseBool(os.Getenv("APP_DEBUG_ENABLED"))

cfg := Config{
DebugEnabled: debugEnabled,
AwsConsoleURL: os.Getenv("APP_AWS_CONSOLE_URL"),
AwsAccessPortalURL: os.Getenv("APP_AWS_ACCESS_PORTAL_URL"),
AwsAccessRoleName: os.Getenv("APP_AWS_ACCESS_ROLE_NAME"),
SlackToken: os.Getenv("APP_SLACK_TOKEN"),
SlackChannel: os.Getenv("APP_SLACK_CHANNEL"),
DebugEnabled: debugEnabled,
AwsConsoleURL: os.Getenv("APP_AWS_CONSOLE_URL"),
AwsAccessPortalURL: os.Getenv("APP_AWS_ACCESS_PORTAL_URL"),
AwsAccessRoleName: os.Getenv("APP_AWS_ACCESS_ROLE_NAME"),
AWSSecurityHubv2Region: os.Getenv("APP_AWS_SECURITYHUBV2_REGION"),
SlackToken: os.Getenv("APP_SLACK_TOKEN"),
SlackChannel: os.Getenv("APP_SLACK_CHANNEL"),
}

if cfg.AwsConsoleURL == "" {
Expand Down
2 changes: 1 addition & 1 deletion internal/events/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ import (

type SecurityHubEvent interface {
IsAlertable() bool
SlackMessage(consoleURL, accessPortalURL, accessRoleName string) (slack.MsgOption, slack.MsgOption)
SlackMessage(consoleURL, accessPortalURL, accessRoleName, shRegion string) (slack.MsgOption, slack.MsgOption)
}
37 changes: 30 additions & 7 deletions internal/events/finding.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ type ResourceTag struct {
Value string `json:"value"`
}

func (shf *SecurityHubV2Finding) SlackMessage(consoleURL, accessPortalURL, accessRoleName string) (slack.MsgOption, slack.MsgOption) {
func (shf *SecurityHubV2Finding) SlackMessage(consoleURL, accessPortalURL, accessRoleName, shRegion string) (slack.MsgOption, slack.MsgOption) {
var blocks []slack.Block

severityEmoji := shf.GetSeverityEmoji()
Expand All @@ -161,6 +161,12 @@ func (shf *SecurityHubV2Finding) SlackMessage(consoleURL, accessPortalURL, acces
details := slack.NewSectionBlock(nil, detailFields, nil)
blocks = append(blocks, details)

findingIDSection := slack.NewSectionBlock(
slack.NewTextBlockObject("mrkdwn", fmt.Sprintf("*Finding ID*\n`%s`", shf.Metadata.UID), false, false),
nil, nil,
)
blocks = append(blocks, findingIDSection)

if len(shf.Resources) > 0 {
resource := shf.Resources[0]
var resourceFields []*slack.TextBlockObject
Expand Down Expand Up @@ -192,7 +198,7 @@ func (shf *SecurityHubV2Finding) SlackMessage(consoleURL, accessPortalURL, acces
blocks = append(blocks, remediationSection)
}

consoleUrl := shf.BuildConsoleUrl(consoleURL, accessPortalURL, accessRoleName)
consoleUrl := shf.BuildConsoleUrl(consoleURL, accessPortalURL, accessRoleName, shRegion)
buttonSection := slack.NewActionBlock(
"actions",
slack.NewButtonBlockElement(
Expand Down Expand Up @@ -270,13 +276,30 @@ func (shf *SecurityHubV2Finding) GetSeverityEmoji() string {
}
}

func (shf *SecurityHubV2Finding) BuildConsoleUrl(consoleURL, accessPortalURL, accessRoleName string) string {
encodedId := url.QueryEscape(shf.FindingInfo.UID)
region := shf.Cloud.Region
func (shf *SecurityHubV2Finding) BuildConsoleUrl(consoleURL, accessPortalURL, accessRoleName, shRegion string) string {
region := shRegion
if region == "" {
region = shf.Cloud.Region
}

var view string
findingType := shf.GetFindingCategory()

switch findingType {
case "Exposure":
view = "exposure"
case "Posture Management":
view = "postureManagement"
case "Threats":
view = "threats"
case "Vulnerabilities":
view = "vulnerabilities"
}

// https://883776786067-fwrss4sa.us-east-1.console.aws.amazon.com/securityhub/v2/home?region=us-east-1#/postureManagement?findingDetailId=b864b75ebfd1bf2a9c0353af5a446dd521ca0af231d56d671311494ecdcedbb8&detailPanelTabId=Resources
dst := fmt.Sprintf(
"%s/securityhub/home?region=%s#/findings?search=Id%%3D%%255Coperator%%255C%%253AEQUALS%%255C%%253A%s",
consoleURL, region, encodedId,
"%s/securityhub/v2/home?region=%s#/%s?findingDetailId=%s",
consoleURL, region, view, shf.Metadata.UID,
)

if accessPortalURL != "" && accessRoleName != "" {
Expand Down