Skip to content

Commit

Permalink
feat: add support for pulling webhook url from SSM or Secrets Manager
Browse files Browse the repository at this point in the history
  • Loading branch information
bryantbiggs committed Dec 13, 2021
1 parent 320f738 commit 45365ed
Show file tree
Hide file tree
Showing 11 changed files with 372 additions and 291 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/antonbabenko/pre-commit-terraform
rev: v1.61.0
rev: v1.62.0
hooks:
- id: terraform_fmt
- id: terraform_validate
Expand Down
21 changes: 7 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,11 @@ module "notify_slack" {
sns_topic_name = "slack-topic"
slack_webhook_url = "https://hooks.slack.com/services/AAA/BBB/CCC"
slack_channel = "aws-notification"
slack_username = "reporter"
}
```

## Using with Terraform Cloud Agents
### Using with Terraform Cloud Agents

[Terraform Cloud Agents](https://www.terraform.io/docs/cloud/workspaces/agent.html) are a paid feature, available as part of the Terraform Cloud for Business upgrade package.

Expand All @@ -45,19 +44,15 @@ RUN apt-get -y update && apt-get -y install python3.9 python3-pip
ENTRYPOINT ["/bin/tfc-agent"]
```

## Use existing SNS topic or create new
### Local Development and Testing

If you want to subscribe the AWS Lambda Function created by this module to an existing SNS topic you should specify `create_sns_topic = false` as an argument and specify the name of existing SNS topic name in `sns_topic_name`.
See the [functions](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/tree/master/functions) for further details.

## Examples

- [notify-slack-simple](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/tree/master/examples/notify-slack-simple) - Creates SNS topic which sends messages to Slack channel.
- [cloudwatch-alerts-to-slack](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/tree/master/examples/cloudwatch-alerts-to-slack) - End to end example which shows how to send AWS Cloudwatch alerts to Slack channel and use KMS to encrypt webhook URL.

## Local Development and Testing

See the [functions](https://github.com/terraform-aws-modules/terraform-aws-notify-slack/tree/master/functions) for further details.

<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
## Requirements

Expand Down Expand Up @@ -96,10 +91,11 @@ See the [functions](https://github.com/terraform-aws-modules/terraform-aws-notif
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_cloudwatch_log_group_kms_key_id"></a> [cloudwatch\_log\_group\_kms\_key\_id](#input\_cloudwatch\_log\_group\_kms\_key\_id) | The ARN of the KMS Key to use when encrypting log data for Lambda | `string` | `null` | no |
| <a name="input_cloudwatch_log_group_retention_in_days"></a> [cloudwatch\_log\_group\_retention\_in\_days](#input\_cloudwatch\_log\_group\_retention\_in\_days) | Specifies the number of days you want to retain log events in log group for Lambda | `number` | `0` | no |
| <a name="input_cloudwatch_log_group_retention_in_days"></a> [cloudwatch\_log\_group\_retention\_in\_days](#input\_cloudwatch\_log\_group\_retention\_in\_days) | Specifies the number of days you want to retain log events in log group for Lambda | `number` | `7` | no |
| <a name="input_cloudwatch_log_group_tags"></a> [cloudwatch\_log\_group\_tags](#input\_cloudwatch\_log\_group\_tags) | Additional tags for the Cloudwatch log group | `map(string)` | `{}` | no |
| <a name="input_create"></a> [create](#input\_create) | Whether to create all resources | `bool` | `true` | no |
| <a name="input_create_sns_topic"></a> [create\_sns\_topic](#input\_create\_sns\_topic) | Whether to create new SNS topic | `bool` | `true` | no |
| <a name="input_environment_variables"></a> [environment\_variables](#input\_environment\_variables) | A map that defines environment variables for the Lambda Function | `map(string)` | `{}` | no |
| <a name="input_iam_policy_path"></a> [iam\_policy\_path](#input\_iam\_policy\_path) | Path of policies to that should be added to IAM role for Lambda Function | `string` | `null` | no |
| <a name="input_iam_role_boundary_policy_arn"></a> [iam\_role\_boundary\_policy\_arn](#input\_iam\_role\_boundary\_policy\_arn) | The ARN of the policy that is used to set the permissions boundary for the role | `string` | `null` | no |
| <a name="input_iam_role_name_prefix"></a> [iam\_role\_name\_prefix](#input\_iam\_role\_name\_prefix) | A unique role name beginning with the specified prefix | `string` | `"lambda"` | no |
Expand All @@ -117,13 +113,10 @@ See the [functions](https://github.com/terraform-aws-modules/terraform-aws-notif
| <a name="input_lambda_role"></a> [lambda\_role](#input\_lambda\_role) | IAM role attached to the Lambda Function. If this is set then a role will not be created for you | `string` | `""` | no |
| <a name="input_lambda_runtime"></a> [lambda\_runtime](#input\_lambda\_runtime) | Lambda Function runtime | `string` | `"python3.9"` | no |
| <a name="input_lambda_timeout"></a> [lambda\_timeout](#input\_lambda\_timeout) | The amount of time your Lambda Function has to run in seconds | `number` | `30` | no |
| <a name="input_log_events"></a> [log\_events](#input\_log\_events) | Boolean flag to enabled/disable logging of incoming events | `bool` | `false` | no |
| <a name="input_recreate_missing_package"></a> [recreate\_missing\_package](#input\_recreate\_missing\_package) | Whether to recreate missing Lambda package if it is missing locally or not | `bool` | `true` | no |
| <a name="input_reserved_concurrent_executions"></a> [reserved\_concurrent\_executions](#input\_reserved\_concurrent\_executions) | The amount of reserved concurrent executions for this lambda function. A value of 0 disables lambda from being triggered and -1 removes any concurrency limitations | `number` | `-1` | no |
| <a name="input_slack_channel"></a> [slack\_channel](#input\_slack\_channel) | The name of the channel in Slack for notifications | `string` | n/a | yes |
| <a name="input_slack_emoji"></a> [slack\_emoji](#input\_slack\_emoji) | A custom emoji that will appear on Slack messages | `string` | `":aws:"` | no |
| <a name="input_slack_username"></a> [slack\_username](#input\_slack\_username) | The username that will appear on Slack messages | `string` | n/a | yes |
| <a name="input_slack_webhook_url"></a> [slack\_webhook\_url](#input\_slack\_webhook\_url) | The URL of Slack webhook | `string` | n/a | yes |
| <a name="input_slack_webhook_url_secret_name"></a> [slack\_webhook\_url\_secret\_name](#input\_slack\_webhook\_url\_secret\_name) | Name of Secrets Manager secret that contains the Slack webhook URL. If not provided, `slack_webhook_url_ssm_parameter_name` must be provided | `string` | `""` | no |
| <a name="input_slack_webhook_url_ssm_parameter_name"></a> [slack\_webhook\_url\_ssm\_parameter\_name](#input\_slack\_webhook\_url\_ssm\_parameter\_name) | Name of SSM parameter that contains the Slack webhook URL (must be type `SecureString`). If not provided, `slack_webhook_url_secret_name` must be provided | `string` | `""` | no |
| <a name="input_sns_topic_kms_key_id"></a> [sns\_topic\_kms\_key\_id](#input\_sns\_topic\_kms\_key\_id) | ARN of the KMS key used for enabling SSE on the topic | `string` | `""` | no |
| <a name="input_sns_topic_name"></a> [sns\_topic\_name](#input\_sns\_topic\_name) | The name of the SNS topic to create | `string` | n/a | yes |
| <a name="input_sns_topic_tags"></a> [sns\_topic\_tags](#input\_sns\_topic\_tags) | Additional tags for the SNS topic | `map(string)` | `{}` | no |
Expand Down
12 changes: 8 additions & 4 deletions examples/cloudwatch-alerts-to-slack/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,16 @@ module "notify_slack" {

lambda_function_name = "notify_slack_${each.value}"

slack_webhook_url = "https://hooks.slack.com/services/AAA/BBB/CCC"
slack_channel = "aws-notification"
slack_username = "reporter"
# Note: this needs to exist in your account already in SSM
# and should be set as a SecureString
slack_webhook_url_ssm_parameter_name = "/example/notify_slack/webhook_url"
environment_variables = {
SLACK_USERNAME = "reporter"
SLACK_EMOJI = ":wave:"
LOG_EVENTS = "True"
}

lambda_description = "Lambda function which sends notifications to Slack"
log_events = true

# VPC
# lambda_function_vpc_subnet_ids = module.vpc.intra_subnets
Expand Down
12 changes: 8 additions & 4 deletions examples/notify-slack-simple/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,14 @@ module "notify_slack" {
sns_topic_name = aws_sns_topic.example.name
create_sns_topic = false

slack_webhook_url = "https://hooks.slack.com/services/AAA/BBB/CCC"
slack_channel = "aws-notification"
slack_username = "reporter"
log_events = true
# Note: this needs to exist in your account already in SSM
# and should be set as a SecureString
slack_webhook_url_ssm_parameter_name = "/example/notify_slack/webhook_url"
environment_variables = {
SLACK_USERNAME = "reporter"
SLACK_EMOJI = ":wave:"
LOG_EVENTS = "True"
}

tags = local.tags
}
Expand Down
1 change: 0 additions & 1 deletion functions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ To run the unit tests:

```hcl
slack_webhook_url = "https://hooks.slack.com/services/AAA/BBB/CCC"
slack_channel = "aws-notification"
slack_username = "reporter"
```

Expand Down
19 changes: 15 additions & 4 deletions functions/notify_slack.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from urllib.error import HTTPError

from aws_lambda_powertools import Logger # type: ignore
from aws_lambda_powertools.utilities import parameters
from aws_lambda_powertools.utilities.data_classes import SNSEvent, event_source

from cloudwatch import get_slack_attachment as get_cloudwatch_slack_attachment
Expand Down Expand Up @@ -66,12 +67,10 @@ def get_slack_message_payload(message: Union[str, Dict], region: str, subject: O
:returns: Slack message payload
"""

slack_channel = os.environ["SLACK_CHANNEL"]
slack_username = os.environ["SLACK_USERNAME"]
slack_emoji = os.environ["SLACK_EMOJI"]

payload = {
"channel": slack_channel,
"username": slack_username,
"icon_emoji": slack_emoji,
}
Expand Down Expand Up @@ -111,9 +110,21 @@ def send_slack_notification(payload: Dict[str, Any]) -> str:
:returns: response details from sending notification
"""

slack_url = os.environ["SLACK_WEBHOOK_URL"]
# Pull from SSM parameter
webhook_url_ssm_param_name = os.environ.get("SLACK_WEBHOOK_URL_SSM_PARAM_NAME")
if webhook_url_ssm_param_name:
slack_webhook_url = parameters.get_parameter(webhook_url_ssm_param_name, decrypt=True, max_age=300)

# Pull from Secrets Manager
webhook_url_secret_name = os.environ.get("SLACK_WEBHOOK_URL_SECRET_NAME")
if webhook_url_secret_name:
slack_webhook_url = parameters.get_secret(webhook_url_secret_name, max_age=300)

if not slack_webhook_url:
raise KeyError("One of `SLACK_WEBHOOK_URL_SSM_PARAM_NAME` or `SLACK_WEBHOOK_URL_SECRET_NAME` must be provided")

data = urllib.parse.urlencode({"payload": json.dumps(payload)}).encode("utf-8")
req = urllib.request.Request(slack_url)
req = urllib.request.Request(cast(str, slack_webhook_url))

try:
result = urllib.request.urlopen(req, data)
Expand Down
1 change: 0 additions & 1 deletion functions/tests/integration_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ def publish_event_to_sns_topic():
response = sns_client.publish(
TopicArn=SNS_TOPIC_ARN,
Message=msg,
Subject=event,
)
pprint(response)

Expand Down
4 changes: 0 additions & 4 deletions functions/tests/notify_slack_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,8 @@

@pytest.fixture(autouse=True)
def mock_settings_env_vars(monkeypatch):
monkeypatch.setenv("SLACK_CHANNEL", "slack_testing_sandbox")
monkeypatch.setenv("SLACK_USERNAME", "notify_slack_test")
monkeypatch.setenv("SLACK_EMOJI", ":aws:")
monkeypatch.setenv("SLACK_WEBHOOK_URL", "https://hooks.slack.com/services/YOUR/WEBOOK/URL")


def test_sns_get_slack_message_payload_snapshots(snapshot, monkeypatch):
Expand Down Expand Up @@ -99,10 +97,8 @@ def test_environment_variables_missing(monkeypatch):
Should pass since environment variables are NOT provided and
will raise a `KeyError`
"""
monkeypatch.delenv("SLACK_CHANNEL")
monkeypatch.delenv("SLACK_USERNAME")
monkeypatch.delenv("SLACK_EMOJI")
monkeypatch.delenv("SLACK_WEBHOOK_URL")

with pytest.raises(KeyError):
# will raise before parsing/validation
Expand Down
Loading

0 comments on commit 45365ed

Please sign in to comment.