AWS Chatbot using Terraform

이언철·2024년 9월 2일
0

DevOps

목록 보기
13/15

AWS Chatbot 서비스를 사용하여 Slack으로 메시지 받는 방법에 대해 다뤄보고자 한다.
랜딩존 구성에 활용할 수 있고, 쉽고 빠른 Alert 환경을 구성하기에 적합하다.
Terraform으로 쉽게 생성하고 관리할 수 있는 구조를 만들어보고자 한다.

Step 1. AWS Chatbot connection to slack

  • Slack과 연결하여 워크스페이스를 구성

Step 2. Create KMS for region

  • 사용하고자 하는 리전(ap-notheast-2)에 Chatbot에 사용할 KMS를 생성 및 권한 설정
{    
  "Sid": "Allow use of the key",  
  "Effect": "Allow",  
  "Principal": {  
    "Service": [
      "events.amazonaws.com",
      "cloudwatch.amazonaws.com"
    ],
  },  
  "Action": [  
    "kms:Encrypt",
    "kms:Decrypt",
    "kms:ReEncrypt*",
    "kms:GenerateDataKey*",
    "kms:DescribeKey"
  ],  
  "Resource": "*"  
}

Step 3. Terraform code

  • 리소스를 주입할 tpl을 구성하고 main.tf 구성
# ./chatbot-slack.tpl
Resources:
  chatbottest:
    Type: AWS::Chatbot::SlackChannelConfiguration
    Properties:
      ConfigurationName: ${config_name}
      IamRoleArn: ${iam_role_arn}
      SlackChannelId: ${slack_channel_id}
      SlackWorkspaceId: ${slack_workspace_id}
      LoggingLevel: ${log_level}
      SnsTopicArns:
        - ${sns_topic_arn}

# ./main.tf 
terraform {
  required_version = ">= 1.3"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = ">= 5.34"
    }
  }
}

provider "aws" {
  region = local.region
}

data "aws_region" "current" {}
data "aws_caller_identity" "current" {}

locals {
  region     = data.aws_region.current.name
  account_id = data.aws_caller_identity.current.account_id
}

## SNS Topic

resource "aws_sns_topic" "chatbot" {
  count = var.enabled ? 1 : 0

  name              = var.sns_topic_name
  display_name      = "SNS topic for AWS Chatbot"
  kms_master_key_id = var.kms_key_id
  policy            = data.aws_iam_policy_document.sns_chatbot[0].json
}

data "aws_iam_policy_document" "sns_chatbot" {
  count = var.enabled ? 1 : 0

  statement {
    sid = "GrantPublishToChatbotSupportedServices"

    actions   = ["sns:Publish"]
    resources = ["arn:aws:sns:${local.region}:${local.account_id}:${var.sns_topic_name}"]

    principals {
      type = "Service"
      identifiers = [
        "cloudwatch.amazonaws.com",
        "events.amazonaws.com",
      ]
    }
  }
}

## Slack Channel Configuration

resource "aws_cloudformation_stack" "slack_channel_config" {
  count = var.enabled ? 1 : 0

  name = "chatbot-slack-channel-config-${var.chatbot_config_name}"
  template_body = jsonencode(yamldecode(templatefile("${path.module}/chatbot-slack.tpl", {
    config_name        = var.chatbot_config_name
    iam_role_arn       = aws_iam_role.chatbot[0].arn
    slack_workspace_id = var.slack_workspace_id
    slack_channel_id   = var.slack_channel_id
    log_level          = var.log_level
    sns_topic_arn      = aws_sns_topic.chatbot[0].arn
  })))
}

## Chatbot IAM Role

resource "aws_iam_role" "chatbot" {
  count = var.enabled ? 1 : 0

  name                 = "AWSChatbotRole-${var.chatbot_config_name}"
  assume_role_policy   = data.aws_iam_policy_document.chatbot_assume_role[0].json
  permissions_boundary = var.chatbot_role_permissions_boundary_policy_arn
}

data "aws_iam_policy_document" "chatbot_assume_role" {
  count = var.enabled ? 1 : 0

  statement {
    actions = ["sts:AssumeRole"]
    principals {
      type        = "Service"
      identifiers = ["chatbot.amazonaws.com"]
    }
  }
}

resource "aws_iam_role_policy" "chatbot_notifications" {
  count = var.enabled && var.chatbot_role_allow_notifications ? 1 : 0

  name   = "NotificationsOnly"
  role   = aws_iam_role.chatbot[0].id
  policy = data.aws_iam_policy_document.chatbot_notifications_only[0].json
}

data "aws_iam_policy_document" "chatbot_notifications_only" {
  count = var.enabled ? 1 : 0

  statement {
    actions = [
      "cloudwatch:Describe*",
      "cloudwatch:Get*",
      "cloudwatch:List*",
    ]
    resources = ["*"]
  }
}

resource "aws_iam_role_policy" "chatbot_lambda_invoke" {
  count = var.enabled && var.chatbot_role_allow_labmda_invoke ? 1 : 0

  name   = "LambdaInvoke"
  role   = aws_iam_role.chatbot[0].id
  policy = data.aws_iam_policy_document.chatbot_lambda_invoke[0].json
}

data "aws_iam_policy_document" "chatbot_lambda_invoke" {
  count = var.enabled ? 1 : 0

  statement {
    actions = [
      "lambda:invokeAsync",
      "lambda:invokeFunction",
    ]
    resources = ["*"]
  }
}

resource "aws_iam_role_policy_attachment" "chatbot_support_access" {
  count = var.enabled && var.chatbot_role_allow_support_access ? 1 : 0

  role       = aws_iam_role.chatbot[0].name
  policy_arn = "arn:aws:iam::aws:policy/AWSSupportAccess"
}

resource "aws_iam_role_policy_attachment" "chatbot_read_only_access" {
  count = var.enabled && var.chatbot_role_allow_read_only_access ? 1 : 0

  role       = aws_iam_role.chatbot[0].name
  policy_arn = "arn:aws:iam::aws:policy/ReadOnlyAccess"
}

resource "aws_cloudwatch_event_rule" "health" {
  count = var.enabled ? 1 : 0

  name          = "chatbot-health-alerts"
  description   = "Send AWS Health events to AWS Chatbot"
  event_pattern = <<-EOT
    {
      "source": [
        "aws.health"
      ]
    }
    EOT
}

resource "aws_cloudwatch_event_target" "health" {
  count = var.enabled ? 1 : 0

  rule      = aws_cloudwatch_event_rule.health[0].name
  target_id = "chatbot-health-alerts"
  arn       = aws_sns_topic.chatbot[0].arn
}

resource "aws_cloudwatch_event_rule" "rds" {
  count = var.enabled ? 1 : 0

  name          = "chatbot-rds-alerts"
  description   = "Send AWS RDS events to AWS Chatbot"
  event_pattern = <<-EOT
    {
      "source": [
        "aws.rds"
      ]
    }
    EOT
}

resource "aws_cloudwatch_event_target" "rds" {
  count = var.enabled ? 1 : 0

  rule      = aws_cloudwatch_event_rule.rds[0].name
  target_id = "chatbot-rds-alerts"
  arn       = aws_sns_topic.chatbot[0].arn
}

resource "aws_cloudwatch_event_rule" "elasticache" {
  count = var.enabled ? 1 : 0

  name          = "chatbot-elasticache-alerts"
  description   = "Send AWS ElastiCache events to AWS Chatbot"
  event_pattern = <<-EOT
    {
      "source": [
        "aws.elasticache"
      ]
    }
    EOT
}

resource "aws_cloudwatch_event_target" "elasticache" {
  count = var.enabled ? 1 : 0

  rule      = aws_cloudwatch_event_rule.elasticache[0].name
  target_id = "chatbot-elasticache-alerts"
  arn       = aws_sns_topic.chatbot[0].arn
}
  • 적용할 환경의 tf 구성
# ./dev/main.tf
module "chatbot" {
  source = "../"

  enabled = true

  chatbot_config_name = "chatbot-slack-test"
  sns_topic_name      = "chatbot-slack-test"
  kms_key_id          = var.kms_key_id
  slack_workspace_id  = var.slack_workspace_id
  slack_channel_id    = var.slack_channel_id
  log_level           = "INFO"

  chatbot_role_allow_notifications             = true
  chatbot_role_allow_labmda_invoke             = true
  chatbot_role_allow_support_access            = true
  chatbot_role_allow_read_only_access          = true
  chatbot_role_permissions_boundary_policy_arn = var.chatbot_role_permissions_boundary_policy_arn
}

# ./dev/variables.tf
variable "kms_key_id" {
  type    = string
  default = "YOUR_KMS_ID"
}

variable "slack_workspace_id" {
  type    = string
  default = "{YOUR_SLACK_WORKSPACE_ID}"
}

variable "slack_channel_id" {
  type    = string
  default = "YOUR_SLACK_CHANNEL_ID"
}

variable "chatbot_role_permissions_boundary_policy_arn" {
  type    = string
  default = "arn:aws:iam::aws:policy/aws-service-role/AWSChatbotServiceLinkedRolePolicy"
}

Step 4. Applied terraform code

terraform init
AWS_PROFILE=${YOUR_PROFILE} terraform plan
AWS_PROFILE=${YOUR_PROFILE} terraform apply -auto-approve

Step 5. Send test message

상세 내용은 GitHub Repo에서 확인 가능

profile
Soomgo, DevOps

0개의 댓글