[Terraform] CloudTrail 이벤트 로그를 CloudWatch Logs로 모니터링하는 인프라 생성 자동화

HYEOB KIM·2022년 6월 17일
1

Terraform

목록 보기
9/11

개요

  • CloudTrail의 이벤트 로그를 CloudWatch Logs로 보내고, 이를 모니터링하는 아키텍쳐 리소스 생성을 테라폼으로 자동화 합니다.
  • 특정 이벤트에 대해 임계치를 넘어가면 경보 상태가 되어 알림이 울리도록 설정합니다.
  • IAM 정책이 1회 이상 변경되었을 때, 로그인 시도를 5분 동안 3회 이상 실패했을 때 경보가 울리도록 설정합니다.

아키텍처


디렉토리 구조

코드 리뷰

variables.tf

핵심은 local.metrics입니다. 이곳에 로그 그룹의 지표 필터, 경보 관련 속성들이 담겨 있습니다. resource "random_uuid"는 무작위 uuid 생성기입니다. 예를 들어 어떤 리소스의 name값 뒤에 ${random_uuid.generator.id}를 붙여주면 임의의 uuid가 끝에 붙습니다. data "aws_caller_identity"는 현재 AWS 계정에 대한 정보가 들어갑니다. 예를 들어 ${data.aws_caller_identity.current.account_id}의 값은 계정 ID 12자리입니다.

# 추적 이름
variable "trail_name" {
  type    = string
  default = "tf-test-hyeob-trail"
}

locals {
  profile = "hongikit"
  region  = "ap-northeast-2"
  email   = "khyup0629@hongikit.com"
  metrics = [
    {
      # IAM 정책 변경: 1회 이상 변경되면 경보
      "name" : "iam-policy-changed",   # 지표 필터 이름
      "pattern" : "{($.eventName=DeleteGroupPolicy)||($.eventName=DeleteRolePolicy)||($.eventName=DeleteUserPolicy)||($.eventName=PutGroupPolicy)||($.eventName=PutRolePolicy)||($.eventName=PutUserPolicy)||($.eventName=CreatePolicy)||($.eventName=DeletePolicy)||($.eventName=CreatePolicyVersion)||($.eventName=DeletePolicyVersion)||($.eventName=AttachRolePolicy)||($.eventName=DetachRolePolicy)||($.eventName=AttachUserPolicy)||($.eventName=DetachUserPolicy)||($.eventName=AttachGroupPolicy)||($.eventName=DetachGroupPolicy)}",
      "namespace" : "CloudTrailMetrics",
      "alarm" : {
        "comparison_operator" : "GreaterThanOrEqualToThreshold",   # 크거나 같을 때
        "evaluation_periods" : 1,   # 데이터 포인트 1개
        "period" : 300,   # 초 단위, 5분 동안
        "statistic" : "Sum",
        "threshold" : 1,   # 지표값
      }
    },
    {
      # 로그인 실패: 5분 동안 3회 이상 실패 시 경보
      "name" : "console-login-failed",   # 지표 필터 이름
      "pattern" : "{ ($.eventName = ConsoleLogin) && ($.errorMessage = \"Failed authentication\") }",
      "namespace" : "CloudTrailMetrics",
      "alarm" : {
        "comparison_operator" : "GreaterThanOrEqualToThreshold",   # 크거나 같을 때
        "evaluation_periods" : 1,   # 데이터 포인트 1개
        "period" : 300,   # 초 단위, 5분 동안
        "statistic" : "Sum",
        "threshold" : 3,   # 지표값
      }
    }
  ]
  tags = {
    Name = "test_hyeob"
  }
}

# 무작위 uuid 생성기: ${random_uuid.generator.id}
resource "random_uuid" "generator" {
}

# 현재 계정에 대한 정보: account_id, user_id, arn 등
data "aws_caller_identity" "current" {}

s3.tf

CloudTrail에서 추적을 생성할 때 이벤트 로그를 전송할 S3 버킷을 지정하게 됩니다. S3 버킷은 새로 생성할 수도, 기존의 버킷을 지정할 수도 있습니다. 콘솔에서 추적을 생성할 때 S3 버킷을 새로 생성하면 버킷 정책이 CloudTrail을 허용하도록 자동으로 업데이트 되지만, 테라폼으로 생성할 때는 버킷을 생성할 때 수동으로 버킷 정책을 작성해 주어야 합니다.

# 버킷 생성
resource "aws_s3_bucket" "test" {
  bucket        = "tf-test-hyeob-trail"
  force_destroy = true
}

# 버킷 정책 설정
resource "aws_s3_bucket_policy" "test" {
  bucket = aws_s3_bucket.test.id
  policy = <<POLICY
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
              "Service": "cloudtrail.amazonaws.com"
            },
            "Action": "s3:GetBucketAcl",
            "Resource": "${aws_s3_bucket.test.arn}"
        },
        {
            "Effect": "Allow",
            "Principal": {
              "Service": "cloudtrail.amazonaws.com"
            },
            "Action": "s3:PutObject",
            "Resource": "${aws_s3_bucket.test.arn}/AWSLogs/${data.aws_caller_identity.current.account_id}/*",
            "Condition": {
                "StringEquals": {
                    "s3:x-amz-acl": "bucket-owner-full-control",
                    "AWS:SourceArn": "arn:aws:cloudtrail:${local.region}:${data.aws_caller_identity.current.account_id}:trail/${var.trail_name}"
                }
            }
        }
    ]
}
POLICY
}

role.tf

다음으로 CloudTrail에서 CloudWatch Logs로 이벤트 로그를 보내도록 허용하는 역할을 생성해야 합니다. 저는 기존에 생성되어 있는 역할을 data source로 가져왔습니다. 이후 그 역할에 CloudTrail에서 CloudWatch Logs로 이벤트 로그를 보내도록 허용하는 정책을 연결시켜 주었습니다.

# CloudTrail에서 CloudWatch Logs로 이벤트 로그를 보내도록 허용하는 역할 가져오기
data "aws_iam_role" "CloudTrailRoleForCloudWatchLogs" {
  name = "CloudTrailRoleForCloudWatchLogs"
}

# CloudTrail에서 CloudWatch Logs로 이벤트 로그를 보내도록 허용하는 정책 생성
resource "aws_iam_policy" "test" {
  name        = "CloudTrailToCloudWatchLogs"
  path        = "/"
  description = "CloudTrailToCloudWatchLogs"

  policy = <<POLICY
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream"
            ],
            "Resource": [
                "arn:aws:logs:${local.region}:${data.aws_caller_identity.current.account_id}:log-group:${aws_cloudwatch_log_group.test.name}:log-stream:${data.aws_caller_identity.current.account_id}_CloudTrail_${local.region}*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "logs:PutLogEvents"
            ],
            "Resource": [
                "arn:aws:logs:${local.region}:${data.aws_caller_identity.current.account_id}:log-group:${aws_cloudwatch_log_group.test.name}:log-stream:${data.aws_caller_identity.current.account_id}_CloudTrail_${local.region}*"
            ]
        }
    ]
}
POLICY
}

# 해당 정책을 역할에 연결
resource "aws_iam_policy_attachment" "test" {
  name       = "test"
  roles      = [data.aws_iam_role.CloudTrailRoleForCloudWatchLogs.name]
  policy_arn = aws_iam_policy.test.arn
}

cloudtrail.tf

CloudTrail에서 추적을 생성합니다. 추적은 IAM과 같은 글로벌 이벤트를 로깅해야 하고(include_global_service_events), 다중 리전 추적을 허용해야 합니다(is_multi_region_trail). 이벤트 로그를 전송할 버킷을 지정해줍니다. kms_key_id를 통해 SSE-KMS를 이용할 수도 있지만, 비용 측면에서 훨씬 저렴한 SSE-S3를 이용하는 것이 낫다고 판단하여 SSE-KMS는 이용하지 않았습니다. s3로 이벤트가 보내질 때 다이제스트 파일을 이용해 내용의 무결성을 검증하는 로그 파일 검증을 활성화합니다(enable_log_file_validation). CloudWatch Logs로 이벤트 로그를 보내는 것을 허용하는 역할을 붙여주고(cloud_watch_logs_role_arn), 이벤트 로그가 저장될 로그 그룹을 지정해줍니다(cloud_watch_logs_group_arn). 마지막으로 해당 추적은 관리 이벤트의 읽기, 쓰기 관련 이벤트 로그를 모두 전송합니다. 로그 그룹과 버킷 정책이 모두 생성되고 난 뒤, 추적이 생성되도록 의존성을 줍니다.

# 추적 생성
resource "aws_cloudtrail" "test" {
  name = var.trail_name

  # IAM과 같은 글로벌 이벤트 로깅
  include_global_service_events = true

  # 다중 리전 추적 허용
  is_multi_region_trail = true

  # 이벤트 로그를 저장할 버킷 지정
  s3_bucket_name = aws_s3_bucket.test.id
  # s3_key_prefix                 = "trails"

  # kms 키는 콘솔에서 대칭 키로 생성되어 있는 것을 data source로 가져다 씀.
  # S3로 전송한다면 SSE-S3가 있는데, SSE-KMS를 사용하는 것보다 비용이 저렴해서 굳이 KMS를 사용할 필요가 없어요.
  # kms_key_id = aws_kms_key.test.key_id

  # 로그 파일 검증(다이제스트 파일로 s3로 보내질 때 내용 무결성 검증)
  enable_log_file_validation = true

  # cloudwatch logs 활성화
  cloud_watch_logs_role_arn  = data.aws_iam_role.CloudTrailRoleForCloudWatchLogs.arn
  cloud_watch_logs_group_arn = "${aws_cloudwatch_log_group.test.arn}:*" # 뒤에 :* 을 꼭 붙여줘야됩니다.

  # 관리 이벤트: 읽기, 쓰기
  event_selector {
    read_write_type = "All"
  }

  depends_on = [
    aws_cloudwatch_log_group.test,
    aws_s3_bucket_policy.test
  ]
}

cloudwatch.tf

추적이 이벤트 로그를 전송했을 때 이벤트 로그를 저장할 로그 그룹을 생성합니다.

# 로그 그룹 생성
resource "aws_cloudwatch_log_group" "test" {
  name = "tf-test-hyeob-cloudtrail"

  tags = local.tags
}

로그 그룹 내에서 지표 필터를 생성합니다. 지표 필터의 속성값들은 variables.tf 파일의 local.metrics에 정의한 변수들을 가져옵니다. 필터 패턴을 통해 전체 이벤트 로그 내용 중 원하는 이벤트를 필터링합니다(pattern). 필터링된 이벤트들은 따로 모아 하나의 지표로 만듭니다(metric_transformation).

# 로그 그룹 지표 필터 생성
resource "aws_cloudwatch_log_metric_filter" "trail-metrics" {
  log_group_name = aws_cloudwatch_log_group.test.name
  name = each.value.name

  for_each = {
    for m in local.metrics : m.name => m
  }

  # 패턴 정의
  pattern = each.value.pattern

  # 지표 할당
  metric_transformation {
    name      = each.value.name
    namespace = each.value.namespace
    value     = "1"
  }
}

지표 필터를 통해 필터링된 이벤트를 모은 지표에 대한 경보를 생성합니다. 지표가 어떤 조건일 때 경보가 발생할 것인지 속성값들을 통해 설정합니다(각 속성에 대한 의미는 local.metrics에 주석으로 적혀있습니다). 마지막으로 경보가 발생할 엔드포인트를 지정합니다(alarm_actions).

# 지표에 대한 경보 생성
resource "aws_cloudwatch_metric_alarm" "metric-alarms" {
  for_each = {
    for m in local.metrics : m.name => m
  }

  # 지표
  alarm_name        = "${each.value.name}-alarm"
  alarm_description = "metric from cloudtrail"
  metric_name       = each.value.name
  namespace         = each.value.namespace

  # 조건
  comparison_operator = each.value.alarm.comparison_operator
  evaluation_periods  = each.value.alarm.evaluation_periods
  period              = each.value.alarm.period
  statistic           = each.value.alarm.statistic
  threshold           = each.value.alarm.threshold

  # 경보 엔드포인트 지정
  alarm_actions = [
    aws_sns_topic.trail-log-metrics.arn
  ]
}

sns.tf

경보가 울릴 엔드포인트를 설정하기 위해 SNS 주제를 생성합니다. 주제를 구독하는 이메일 엔드포인트를 생성하고, 알림을 받을 이메일을 작성합니다.

# SNS 주제 생성
resource "aws_sns_topic" "trail-log-metrics" {
  name = "trail-log-metrics-topic"
}

# 구독 이메일 엔드포인트 설정
resource "aws_sns_topic_subscription" "email-target" {
  topic_arn = aws_sns_topic.trail-log-metrics.arn
  protocol  = "email"
  endpoint  = local.email
}
profile
Devops Engineer

0개의 댓글