Skip to content

Instantly share code, notes, and snippets.

@so0k
Last active January 5, 2024 11:02
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save so0k/6562264cc0e4d4d9a4441b8f892386f7 to your computer and use it in GitHub Desktop.
Save so0k/6562264cc0e4d4d9a4441b8f892386f7 to your computer and use it in GitHub Desktop.
Swatmobile - AWS Bootstrap gists
resource "aws_budgets_budget" "cloudwatch" {
provider = "aws.billing"
name = "budget-cloudwatch-monthly"
budget_type = "COST"
limit_amount = "1000"
limit_unit = "USD"
time_period_end = "2087-06-15_00:00"
time_period_start = "2017-07-01_00:00"
time_unit = "MONTHLY"
cost_filters = {
Service = "AmazonCloudWatch"
}
notification {
comparison_operator = "GREATER_THAN"
threshold = 100
threshold_type = "PERCENTAGE"
notification_type = "FORECASTED"
subscriber_sns_topic_arns = [
"${aws_sns_topic.budget_alerts.arn}",
]
}
}
resource "aws_budgets_budget" "cloudwatch" {
provider = "aws.billing"
name = "budget-ec2-other-monthly"
budget_type = "COST"
limit_amount = "300"
limit_unit = "USD"
time_period_end = "2087-06-15_00:00"
time_period_start = "2017-07-01_00:00"
time_unit = "MONTHLY"
cost_filters = {
Service = "Amazon Elastic Block Store"
}
notification {
comparison_operator = "GREATER_THAN"
threshold = 100
threshold_type = "PERCENTAGE"
notification_type = "FORECASTED"
subscriber_sns_topic_arns = [
"${aws_sns_topic.budget_alerts.arn}",
]
}
}
# give AWS Budgets permissions to publish to budget_alerts sns topic
resource "aws_sns_topic_policy" "budget_alerts" {
provider = "aws.billing"
arn = "${aws_sns_topic.budget_alerts.arn}"
policy = "${data.aws_iam_policy_document.sns_budget_alerts.json}"
}
data "aws_iam_policy_document" "sns_budget_alerts" {
provider = "aws.billing"
policy_id = "__default_policy_ID"
statement {
sid = "AWSBudgetsAlerts"
principals {
type = "Service"
identifiers = ["budgets.amazonaws.com"]
}
actions = [
"SNS:Publish",
]
resources = [
"${aws_sns_topic.budget_alerts.arn}",
]
}
statement {
sid = "__default_statement_ID"
principals {
type = "AWS"
identifiers = ["*"]
}
actions = [
"SNS:GetTopicAttributes",
"SNS:SetTopicAttributes",
"SNS:AddPermission",
"SNS:RemovePermission",
"SNS:DeleteTopic",
"SNS:Subscribe",
"SNS:ListSubscriptionsByTopic",
"SNS:Publish",
"SNS:Receive",
]
condition {
test = "StringEquals"
variable = "AWS:SourceOwner"
values = [
"${data.aws_caller_identity.current.account_id}",
]
}
resources = [
"${aws_sns_topic.budget_alerts.arn}",
]
}
}
# set up account wide budget alert for EstimatedCharges over billing_threshhold
module "swatmobile_budget_label" {
source = "git::https://github.com/cloudposse/terraform-null-label.git?ref=master"
namespace = "swat"
stage = "svc"
name = "budget"
}
resource "aws_cloudwatch_metric_alarm" "account_billing" {
provider = "aws.billing"
alarm_name = "account-billing-alarm"
comparison_operator = "GreaterThanOrEqualToThreshold"
evaluation_periods = "1" # every 6 hours
period = "21600" # The period in seconds ~ 6 hours
metric_name = "EstimatedCharges"
namespace = "AWS/Billing"
statistic = "Average"
threshold = "${var.billing_threshhold}"
alarm_description = "Billing alarm by account"
alarm_actions = ["${aws_sns_topic.budget_alerts.arn}"]
dimensions {
Currency = "USD"
LinkedAccount = "${data.aws_caller_identity.current.account_id}"
}
}
resource "aws_sns_topic" "budget_alerts" {
provider = "aws.billing"
name = "${module.swatmobile_budget_label.id}-alerts"
tags = "${module.swatmobile_budget_label.tags}"
}
# CloudWatch billing alerts must be in US-EAST-1
provider "aws" {
version = "~> 2.0"
region = "us-east-1"
alias = "billing"
}
resource "aws_sns_topic_subscription" "billing_notify_slack" {
provider = "aws.billing"
topic_arn = "${aws_sns_topic.budget_alerts.arn}"
protocol = "lambda"
endpoint = "${aws_lambda_function.notify_slack.arn}"
}
resource "aws_lambda_permission" "billing_notify_slack" {
provider = "aws.billing"
statement_id = "AllowExecutionFromBudgetSNS"
action = "lambda:InvokeFunction"
function_name = "${aws_lambda_function.notify_slack.function_name}"
principal = "sns.amazonaws.com"
source_arn = "${aws_sns_topic.budget_alerts.arn}"
}
resource "aws_iam_role" "lambda" {
provider = "aws.billing"
name_prefix = "lambda"
assume_role_policy = "${data.aws_iam_policy_document.assume_lambda_role.json}"
}
data "aws_iam_policy_document" "assume_lambda_role" {
statement {
effect = "Allow"
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["lambda.amazonaws.com"]
}
}
}
# currently only CloudWatch rights granted
data "aws_iam_policy_document" "lambda_basic" {
statement {
sid = "AllowWriteToCloudwatchLogs"
effect = "Allow"
actions = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
]
resources = ["arn:aws:logs:*:*:*"]
}
}
resource "aws_iam_role_policy" "lambda" {
provider = "aws.billing"
name_prefix = "lambda-policy-"
role = "${aws_iam_role.lambda.id}"
policy = "${data.aws_iam_policy_document.lambda_basic.json}"
}
module "swatmobile_notify_slack_label" {
source = "git::https://github.com/cloudposse/terraform-null-label.git?ref=master"
namespace = "swat"
stage = "svc"
name = "notify-slack"
}
# add *.zip to .gitignore!
data "archive_file" "notify_slack" {
type = "zip"
source_file = "./functions/notify_slack.py"
output_path = "./functions/notify_slack.zip"
}
resource "aws_lambda_function" "notify_slack" {
provider = "aws.billing"
filename = "${data.archive_file.notify_slack.output_path}"
function_name = "${module.swatmobile_notify_slack_label.id}"
role = "${aws_iam_role.lambda.arn}"
handler = "notify_slack.lambda_handler"
source_code_hash = "${data.archive_file.notify_slack.output_base64sha256}"
runtime = "python3.6"
timeout = 30
environment {
variables = {
SLACK_WEBHOOK_URL = "${var.notify_slack_webhook_url}"
SLACK_CHANNEL = "${var.notify_slack_channel}"
SLACK_USERNAME = "${var.notify_slack_username}"
SLACK_EMOJI = "${var.notify_slack_emoji}"
}
}
lifecycle {
ignore_changes = [
"filename",
"last_modified",
]
}
tags = "${module.swatmobile_notify_slack_label.tags}"
}
output "bucket" {
description = "The s3 bucket to use as Terraform Backend"
value = "${aws_s3_bucket.tfstate.id}"
}
output "lock_table" {
description = "The DynamoDb table for s3 backend locking"
value = "${aws_dynamodb_table.tfstate_lock.id}"
}
output "region" {
description = "The region used for remote state"
value = "${var.aws_region}"
}
output "kms_key_id" {
description = "kms key for encryption of state"
value = "${aws_kms_key.master.key_id}"
}
terraform {
required_version = ">= 0.11"
version = "~1.17"
backend "s3" {
# A project specific key
key = "tf-remote-state/tfstate" # remember to choose unique key for each project
# team shared resources
bucket = "swat-tf-state"
region = "ap-southeast-1"
dynamodb_table = "swat-tf-state-lock"
encrypt = true # recommended to use encryption
kms_key_id = "1d3f..."
}
}
data "aws_caller_identity" "current" {}
resource "aws_iam_group" "tfusers" {
name = "${var.namespace}-${var.name}-tfusers"
}
resource "aws_iam_group_policy_attachment" "tfstate" {
group = "${aws_iam_group.tfusers.name}"
policy_arn = "${aws_iam_policy.tfstate.arn}"
}
resource "aws_iam_policy" "tfstate" {
name = "${var.namespace}-${var.name}-tfusers-policy"
description = "User policy managing access to Terraform state for ${var.name}"
policy = "${data.aws_iam_policy_document.tfstate.json}"
}
data "aws_iam_policy_document" "tfstate" {
# allow user to inspect himself
statement {
sid = "AllowSelfInspection"
actions = [
"iam:GetUser",
]
resources = [
"arn:aws:iam::${data.aws_caller_identity.current.account_id}:user/$${aws:username}",
]
}
# allow listing of all buckets
statement {
sid = "ListS3Buckets"
actions = [
"s3:ListAllMyBuckets",
]
resources = [
"arn:aws:s3:::*",
]
}
# allow usage of kms key for decryption
statement {
sid = "KMSAccess"
# "Effect": "Allow",
actions = [
"kms:Encrypt",
"kms:Decrypt",
"kms:GenerateDataKey",
]
resources = [
"${aws_kms_key.master.arn}",
]
}
# allow all access to specific s3 tfsate buckets
statement {
sid = "AccessTerraformBuckets"
actions = [
"s3:*",
]
resources = [
"${aws_s3_bucket.tfstate.arn}",
"${aws_s3_bucket.tfstate.arn}/*",
]
}
# allow usage of lock table in dynamodb
# ref http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/using-identity-based-policies.html#access-policy-examples-for-sdk-cli
statement {
sid = "AllAPIActionsOnTerraformLocksTable"
actions = [
"dynamodb:*",
]
resources = [
"${aws_dynamodb_table.tfstate_lock.arn}",
"${aws_dynamodb_table.tfstate_lock.arn}/index/*",
]
}
}
provider "aws" {
region = "${var.aws_region}"
}
module "swatmobile_state_label" {
source = "git::https://github.com/cloudposse/terraform-null-label.git?ref=master"
namespace = "${var.namespace}"
stage = "${var.stage}"
name = "${var.name}"
}
resource "aws_s3_bucket" "tfstate" {
bucket = "${module.swatmobile_state_label.id}"
acl = "private"
versioning {
enabled = true
}
lifecycle {
prevent_destroy = true
}
tags = "${module.swatmobile_state_label.tags}"
}
resource "aws_kms_key" "master" {
description = "${module.swatmobile_state_label.id} master encryption key for Terraform state"
deletion_window_in_days = 10
tags = "${module.swatmobile_state_label.tags}"
}
# allows ease of use on cli
resource "aws_kms_alias" "secrets" {
name = "alias/${module.swatmobile_state_label.id}-tfstate"
target_key_id = "${aws_kms_key.master.key_id}"
}
resource "aws_dynamodb_table" "tfstate_lock" {
name = "${module.swatmobile_state_label.id}-lock"
#http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ProvisionedThroughput.html
read_capacity = 1
write_capacity = 1
hash_key = "LockID"
attribute {
name = "LockID"
type = "S" # Attribute type, which must be a scalar type: (S)tring, (N)umber or (B)inary data
}
tags = "${module.swatmobile_state_label.tags}"
}
variable "namespace" {
description = "Namespace for terraform state resources"
}
variable "stage" {
# We use:
# - Prod for production
# - Stage for staging
# - Dev for development
# - Svc for services (used across environments)
description = "Stage for terraform state resources"
}
variable "name" {
description = "Name for terraform state resources"
}
variable "aws_region" {
description = "The AWS region to create things in."
default = "ap-southeast-1"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment