Skip to content

Commit

Permalink
Add 'aws-go-console-slack-notification' example. (pulumi#587)
Browse files Browse the repository at this point in the history
* Add a Go example to notify Slack when changes are made via the AWS Console.

* Use `regions` config list to provision multiple regions.

* Use the AWS SDK instead of config to get list of regions.

* Format Slack message values as code with backticks.

* Update README.md about multi-region deployment.

* Ignore changes on eventSelectors due to upstream Terraform bug.

hashicorp/terraform-provider-aws#11712

* Minor casing and function name change.

* Change logical name of AWS provider.

* Add bucket lifecycle support and create `getRegions` function.

* Allow regions to be specified in config, otherwise use AWS SDK.

* Update `aws-go-console-slack-notification` for additional user agents and include errorCode.

* Add Bucket Public Access Block, configurable resource name prefix. Move to Go modules.

* Update for Pulumi 2.0.

* Re-order resource options to be consistent. Remove `dep` references.
  • Loading branch information
Cameron Stokes committed Apr 22, 2020
1 parent 24343b7 commit f0ba1c8
Show file tree
Hide file tree
Showing 8 changed files with 752 additions and 0 deletions.
1 change: 1 addition & 0 deletions aws-go-console-slack-notification/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
handler/dist
3 changes: 3 additions & 0 deletions aws-go-console-slack-notification/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
build::
GOOS=linux GOARCH=amd64 go build -o ./handler/dist/handler ./handler/handler.go
zip -j ./handler/dist/handler.zip ./handler/dist/handler
3 changes: 3 additions & 0 deletions aws-go-console-slack-notification/Pulumi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
name: aws-go-console-slack-notification
runtime: go
description: Deploy necessary resources to notify Slack when an operation is performed via the AWS Console.
87 changes: 87 additions & 0 deletions aws-go-console-slack-notification/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# AWS Console Change Slack Notifier in Go

This example deploys a Lambda function and relevant CloudTrail and CloudWatch resources to send a
Slack notification for any resource operation that is performed via the AWS Console.

Note: This application sets up the necessary infrastructure across _each_ AWS region in your
account that is `opt-in-not-required` or `opted-in`. The Pulumi application uses the
[DescribeRegions](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeRegions.html) API
via [aws-sdk-go](https://github.com/aws/aws-sdk-go) to query for available regions.

## Deploying the App

To deploy your infrastructure, follow the below steps.

### Prerequisites

1. [Install Pulumi](https://www.pulumi.com/docs/get-started/install/)
1. [Configure AWS Credentials](https://www.pulumi.com/docs/intro/cloud-providers/aws/setup/)

### Steps

After cloning this repo, run these commands from the working directory:

1. Build the handler:

- For developers on Linux and macOS:

```bash
make
```
- For developers on Windows:
- Get the `build-lambda-zip` tool:
```bash
set GO111MODULE=on
go.exe get -u github.com/aws/aws-lambda-go/cmd/build-lambda-zip
```
- Use the tool from your GOPATH:
```bash
set GOOS=linux
set GOARCH=amd64
set CGO_ENABLED=0
go build -o handler\dist\handler handler\handler.go
%USERPROFILE%\Go\bin\build-lambda-zip.exe -o handler\dist\handler.zip handler\dist\handler
```

1. Create a new Pulumi stack, which is an isolated deployment target for this example:

```bash
pulumi stack init
```

1. Set the required configuration variables for this program:

```bash
pulumi config set slackWebhookURL 'YOUR_SLACK_WEBHOOK_URL'
```

1. Execute the Pulumi program to create our lambda:

```bash
pulumi up
```

1. Perform a change in the AWS Console and look for a notification in your Slack channel. Note: you
must perform a _write_ such as adding or removing tags from a resource, launching an instance, or
deleting a resource.

1. From there, feel free to experiment. Simply making edits, rebuilding your handler, and running
`pulumi up` will update your lambda. Customize the Slack message username or text with the following
configuration values:

```bash
pulumi config set slackMessageUsername 'Console Change Monitor'
pulumi config set slackMessageText ':warning: Somebody made a change in the console!'
```

1. Afterwards, destroy your stack and remove it:

```bash
pulumi destroy --yes
pulumi stack rm --yes
```
10 changes: 10 additions & 0 deletions aws-go-console-slack-notification/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module github.com/pulumi/examples/aws-go-console-slack-notification

go 1.14

require (
github.com/aws/aws-lambda-go v1.16.0 // indirect
github.com/aws/aws-sdk-go v1.30.8
github.com/pulumi/pulumi-aws/sdk/v2 v2.1.0
github.com/pulumi/pulumi/sdk/v2 v2.0.0
)
281 changes: 281 additions & 0 deletions aws-go-console-slack-notification/go.sum

Large diffs are not rendered by default.

117 changes: 117 additions & 0 deletions aws-go-console-slack-notification/handler/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package main

import (
"bytes"
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"strings"

"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
)

func handler(ctx context.Context, event events.CloudWatchEvent) {
var eventDetail eventDetail
if err := json.Unmarshal(event.Detail, &eventDetail); err != nil {
panic(err)
}

// see https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-event-reference-record-contents.html
if eventDetail.UserAgent != "signin.amazonaws.com" &&
eventDetail.UserAgent != "console.ec2.amazonaws.com" &&
!strings.HasPrefix(eventDetail.UserAgent, "[S3Console/0.4, aws-internal/3 ") {
fmt.Printf("Skipping event [%s] from user agent [%s]", eventDetail.EventName, eventDetail.UserAgent)
return
}

fmt.Printf("Processing event [%s] from user [%s]", eventDetail.EventName, eventDetail.UserIdentity.UserName)
fmt.Printf("%s", event.Detail)

slackMessageUsername := os.Getenv("SLACK_WEBHOOK_USERNAME")
slackMessageText := os.Getenv("SLACK_MESSAGE_TEXT")
if slackMessageText == "" {
slackMessageText = ":rotating_light: A change was made via the AWS Console."
}

message := &slackMessage{
Username: slackMessageUsername,
Text: slackMessageText,
Attachments: []slackMessageAttachment{
slackMessageAttachment{
Fields: []slackMessageAttachmentField{
getSlackMessageAttachmentField("AWS Account", eventDetail.UserIdentity.AWSAccountID),
getSlackMessageAttachmentField("Region", eventDetail.AWSRegion),
getSlackMessageAttachmentField("Event Source", eventDetail.EventSource),
getSlackMessageAttachmentField("Event Name", eventDetail.EventName),
getSlackMessageAttachmentField("User", eventDetail.UserIdentity.UserName),
getSlackMessageAttachmentField("Result", getResultText(eventDetail.ErrorCode)),
},
},
},
}

bytesRepresentation, err := json.Marshal(message)
if err != nil {
log.Fatalln(err)
}

_, err = http.Post(os.Getenv("SLACK_WEBHOOK_URL"), "application/json", bytes.NewBuffer(bytesRepresentation))
if err != nil {
log.Fatalln(err)
}
}

func getSlackMessageAttachmentField(title string, value string) slackMessageAttachmentField {
return slackMessageAttachmentField{
Title: title,
Value: fmt.Sprintf("`%s`", value),
Short: true,
}
}

func getResultText(errorCode string) string {
if errorCode != "" {
return errorCode
}
return "Success"
}

type eventDetail struct {
UserIdentity userIdentity `json:"userIdentity"`
UserAgent string `json:"userAgent"`
EventSource string `json:"eventSource"`
EventName string `json:"eventName"`
AWSRegion string `json:"awsRegion"`
ErrorCode string `json:"errorCode"`
}

type userIdentity struct {
AWSAccountID string `json:"accountId"`
UserName string `json:"userName"`
}

type slackMessage struct {
Username string `json:"username"`
Text string `json:"text"`
Attachments []slackMessageAttachment `json:"attachments"`
}

type slackMessageAttachment struct {
Pretext string `json:"pretext"`
Text string `json:"text"`
Fields []slackMessageAttachmentField `json:"fields"`
}

type slackMessageAttachmentField struct {
Title string `json:"title"`
Value string `json:"value"`
Short bool `json:"short"`
}

func main() {
lambda.Start(handler)
}
Loading

0 comments on commit f0ba1c8

Please sign in to comment.