Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: make it possible to use ssm parameters as slack_webhook_url #107

Closed
Closed
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
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: git://github.com/antonbabenko/pre-commit-terraform
rev: v1.41.0
rev: v1.43.0
hooks:
- id: terraform_fmt
- id: terraform_docs
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ To run the tests:
| slack\_emoji | A custom emoji that will appear on Slack messages | `string` | `":aws:"` | no |
| slack\_username | The username that will appear on Slack messages | `string` | n/a | yes |
| slack\_webhook\_url | The URL of Slack webhook | `string` | n/a | yes |
| slack\_webhook\_url\_is\_ssm\_param | Whether slack\_webhook\_url is an ssm parameter | `bool` | `false` | no |
| sns\_topic\_kms\_key\_id | ARN of the KMS key used for enabling SSE on the topic | `string` | `""` | no |
| sns\_topic\_name | The name of the SNS topic to create | `string` | n/a | yes |
| sns\_topic\_tags | Additional tags for the SNS topic | `map(string)` | `{}` | no |
Expand Down
13 changes: 12 additions & 1 deletion functions/notify_slack.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ def decrypt(encrypted_url):
except Exception:
logging.exception("Failed to decrypt URL with KMS")

def get_ssm_param(ssm_param):
try:
ssm = boto3.client("ssm")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i would move the client init outside of the function to cache it and open connection each time :D

return ssm.get_parameter(
Name=ssm_param,
WithDecryption=True
).get("Parameter").get("Value")
except Exception:
logging.exception("Failed to get URL from SSM")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably need to raise an exception here and possible change the message to Failed to get Slack webhook URL from SSM


def cloudwatch_notification(message, region):
states = {'OK': 'good', 'INSUFFICIENT_DATA': 'warning', 'ALARM': 'danger'}
Expand Down Expand Up @@ -47,7 +56,9 @@ def default_notification(subject, message):
# Send a message to a slack channel
def notify_slack(subject, message, region):
slack_url = os.environ['SLACK_WEBHOOK_URL']
if not slack_url.startswith("http"):
if 'SLACK_WEBHOOK_URL_IS_SSM_PARAM' in os.environ and os.environ['SLACK_WEBHOOK_URL_IS_SSM_PARAM'] == 'True':
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if 'SLACK_WEBHOOK_URL_IS_SSM_PARAM' in os.environ and os.environ['SLACK_WEBHOOK_URL_IS_SSM_PARAM']:

Copy link
Member

@bryantbiggs bryantbiggs Dec 9, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would propose a slightly more "pythontic" way (less chained conditionals):

	slack_webhook_url = os.environ.get('SLACK_WEBHOOK_URL')
	slack_webhook_ssm_path = os.environ.get('SLACK_WEBHOOK_URL_SSM_PATH')
	
	if slack_webhook_url:
		slack_url = decrypt(slack_url)
	elif slack_webhook_ssm_path:
		slack_url = get_ssm_param(slack_url)
	else:
		raise Exception('Either `SLACK_WEBHOOK_URL` or `SLACK_WEBHOOK_URL_SSM_PATH` value must be provided')

adding the .get() will try to retrieve the env var and if not it will by default return None. You can also add a fallback value of your choosing as well like os.environ.get('MY_ENV_VAR', 17)

slack_url = get_ssm_param(slack_url)
elif not slack_url.startswith("http"):
slack_url = decrypt(slack_url)

slack_channel = os.environ['SLACK_CHANNEL']
Expand Down
31 changes: 24 additions & 7 deletions main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ resource "aws_sns_topic" "this" {
tags = merge(var.tags, var.sns_topic_tags)
}

data "aws_ssm_parameter" "this" {
count = var.slack_webhook_url_is_ssm_param ? 1 : 0
name = var.slack_webhook_url
}

locals {
sns_topic_arn = element(concat(aws_sns_topic.this.*.arn, data.aws_sns_topic.this.*.arn, [""]), 0)

Expand All @@ -30,13 +35,24 @@ locals {
actions = ["kms:Decrypt"]
resources = [var.kms_key_arn]
}

lambda_policy_document_ssm = {
sid = "AllowSSMGetParameter"
effect = "Allow"
actions = ["ssm:GetParameter"]
resources = [var.slack_webhook_url_is_ssm_param ? data.aws_ssm_parameter.this[0].arn : ""]
}
}

data "aws_iam_policy_document" "lambda" {
count = var.create ? 1 : 0

dynamic "statement" {
for_each = concat([local.lambda_policy_document], var.kms_key_arn != "" ? [local.lambda_policy_document_kms] : [])
for_each = concat(
[local.lambda_policy_document],
var.kms_key_arn != "" ? [local.lambda_policy_document_kms] : [],
var.slack_webhook_url_is_ssm_param ? [local.lambda_policy_document_ssm] : []
)
content {
sid = statement.value.sid
effect = statement.value.effect
Expand Down Expand Up @@ -67,7 +83,7 @@ resource "aws_sns_topic_subscription" "sns_notify_slack" {

module "lambda" {
source = "terraform-aws-modules/lambda/aws"
version = "1.18.0"
version = "1.24.0"

create = var.create

Expand All @@ -85,11 +101,12 @@ module "lambda" {
publish = true

environment_variables = {
SLACK_WEBHOOK_URL = var.slack_webhook_url
SLACK_CHANNEL = var.slack_channel
SLACK_USERNAME = var.slack_username
SLACK_EMOJI = var.slack_emoji
LOG_EVENTS = var.log_events ? "True" : "False"
SLACK_WEBHOOK_URL = var.slack_webhook_url
SLACK_WEBHOOK_URL_IS_SSM_PARAM = var.slack_webhook_url_is_ssm_param ? "True" : "False"
SLACK_CHANNEL = var.slack_channel
SLACK_USERNAME = var.slack_username
SLACK_EMOJI = var.slack_emoji
LOG_EVENTS = var.log_events ? "True" : "False"
}

create_role = var.lambda_role == ""
Expand Down
6 changes: 6 additions & 0 deletions variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ variable "slack_webhook_url" {
type = string
}

variable "slack_webhook_url_is_ssm_param" {
description = "Whether slack_webhook_url is an ssm parameter"
type = bool
default = false
}

variable "slack_channel" {
description = "The name of the channel in Slack for notifications"
type = string
Expand Down