핵심은 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" {}
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
}
다음으로 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에서 추적을 생성합니다. 추적은 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
]
}
추적
이 이벤트 로그를 전송했을 때 이벤트 로그를 저장할 로그 그룹
을 생성합니다.
# 로그 그룹 생성
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 주제
를 생성합니다. 주제를 구독하는 이메일 엔드포인트를 생성하고, 알림을 받을 이메일을 작성합니다.
# 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
}