1. 개요
Multi Account 환경에서 IAM 관련 Action에 대한 감사를 하기 위해서는 Log들을 Security 관련 Account에서 수집해야 한다.
이에 대한 인프라 프로비저닝은 Terraform을 사용하였다.
(부분 부분 웹 콘솔로 진행한 부분도 있기 때문에 참고만 하면 좋을 것이다.)
2. 구조
프로젝트의 폴더 구조는 아래와 같다.
별도의 모듈은 굳이 사용하지 않았으며, resource, data, variable, local 등을 활용하여 리소스를 Code로써 표현하였다.
# another-accounts
EventBridge에서 기 정의한 IAM Action이 Pattern에 걸릴 경우 Security Account의 EventBus로 전달한다.
# security-accounts
IAM Audit 내역을 중앙에서 수집하고, 중앙에서 수집한 뒤 1) Lambda를 활용한 Slack Notification, 2) OpenSearch를 활용한 Logging 중앙화 및 시각화
3. Terraform
## Security Account
1) _backend.tf
: Backend를 S3로 정의하고, tflock을 위해 DynamoDB를 설정한다.
각 폴더 별로 S3의 폴더 객체를 만들어서 Backend State를 관리하는 것을 자동화하기 위해 Terragrunt를 사용할 수 있지만 굳이 그렇게 하진 않았다.
2) _data.tf
: IAM Policy를 설정하기 위해 data block을 사용하였다.
eventBus는 같은 OrganizationID일 경우에만 받을 수 있다.
data "aws_caller_identity" "current" {}
data "aws_iam_policy_document" "eventBusPolicy" {
statement {
sid = "allow_all_accounts_from_organization_to_put_events"
effect = "Allow"
resources = ["arn:aws:events:${local.region}:${local.account_id}:event-bus/${aws_cloudwatch_event_bus.this.name}"]
actions = ["events:PutEvents"]
condition {
test = "StringEquals"
variable = "aws:PrincipalOrgID"
values = [local.org_id]
}
principals {
type = "*"
identifiers = ["*"]
}
}
}
data "aws_iam_policy_document" "es_access_policy" {
statement {
sid = ""
effect = "Allow"
resources = ["arn:aws:es:${local.region}:${local.account_id}:domain/${local.prefix}-domain/*"]
actions = ["es:*"]
principals {
type = "*"
identifiers = ["*"]
}
}
}
data "aws_iam_role" "security-service-profile-iam-role" {
name = "security-service-profile-iam-role"
}
3) _provider.tf
: Provider(AWS)를 설정하기 위한 파일이다.
4) _shared_vars.tf
: 전체 폴더에 공통적으로 사용되는 변수들을 모아놓은 변수 파일이다.
현재는 각각 따로 생성해주어 복붙했지만, 추후 Terragrunt나 스크립트 파일을 별도 만들어 관리하는 것이 좀 더 좋을 것으로 보인다.
# Shared Variables
variable "env" {}
#variable "owner" {}
variable "region" {}
variable "account" {}
variable "cred_file" {}
variable "tags" {}
variable "team" {}
5) _vars.tf
: 해당 폴더에서 필요한 변수들을 모아놓은 파일이다.
# EventBus
variable "org_id" {}
# CloudWatch Logs
variable "retention_in_days" {}
variable "vpc_id" {}
variable "subnet_ids" {}
variable "security_group_ids" {}
variable "es_version" {}
variable "es_instance_type" {}
variable "es_instance_count" {}
variable "es_volume_size" {}
variable "es_volume_type" {}
variable "es_custom_endpoint_enabled" {}
variable "acm_arn" {}
variable "es_custom_endpoint_domain_name" {}
variable "es_custom_endpoint_tls_policy" {}
# Route 53
variable "route53_zone_id" {}
6) _terraform.auto.tfvars
terraform apply를 수행할 시 자동으로 이 파일을 읽어들이게 된다.
auto를 붙이지 않을 경우 추가 옵션을 넣어주어야 한다.
env = "xxxx"
team = "xxxx"
account = "xxxx"
cred_file = "~/.aws/credentials"
region = "us-east-1"
tags = {}
org_id = "o-xxxxxxxxx"
retention_in_days = 7
vpc_id = "vpc-xxxxxxxxxx"
subnet_ids = ["subnet-xxxxxxxxx"]
security_group_ids = ["sg-xxxxxxxxx"]
es_instance_type = "t3.medium.elasticsearch"
es_instance_count = 1
es_version = "OpenSearch_1.2" # Before : 7.10
es_volume_size = 20
es_volume_type = "gp2"
acm_arn = "arn:aws:acm:us-east-1:xxxxxxx:certificate/xxxxxxxxxx"
es_custom_endpoint_enabled = true
es_custom_endpoint_domain_name = "xxxxx.security.xxxx.xxxx"
es_custom_endpoint_tls_policy = "Policy-Min-TLS-1-0-2019-07"
route53_zone_id = "xxxxxxxxxxx"
7) cwLogs.tf
아래의 Error 문구를 보면 CloudWatch Logs에서 Subscription Filter로 ES를 설정할 경우, Resource가 생성되지 않는다.
이는 내부적으로 Lambda를 구성하기 때문에 Terraform으로 관리하기 위해서는 별도 Lambda 및 소스 코드를 Terraform으로 관리해야 한다.
필자는 이 부분은 웹 콘솔에서 진행
resource "aws_cloudwatch_log_group" "this" {
name = "/aws/events/${local.prefix}-logs"
retention_in_days = local.retention_in_days
tags = merge(
local.tags,
{
Name = "${local.prefix}-logs"
}
)
}
#│ Error: Error creating Cloudwatch log subscription filter: InvalidParameterException: PutSubscriptionFilter operation cannot work with destinationArn for vendor es
# Error가 발생. 수동으로 생성
#resource "aws_cloudwatch_log_subscription_filter" "this" {
# name = "${local.prefix}-to-es-subs-filter"
# role_arn = data.aws_iam_role.security-service-profile-iam-role.arn
#
# log_group_name = aws_cloudwatch_log_group.this.name
# filter_pattern = ""
#
# destination_arn = aws_elasticsearch_domain.this.arn # 5.10
#}
8) elasticSearch.tf
: OpenSearch를 생성하기 위한 테라폼 파일이다.
resource "aws_iam_service_linked_role" "es" {
aws_service_name = "es.amazonaws.com"
description = "AWSServiceRoleForAmazonElasticsearchService Service-Linked Role"
}
resource "aws_elasticsearch_domain" "this" {
domain_name = "${local.prefix}-domain"
elasticsearch_version = local.es.version
auto_tune_options {
desired_state = "DISABLED"
rollback_on_disable = "NO_ROLLBACK"
}
cluster_config {
instance_count = local.es.instance_count
instance_type = local.es.instance_type
zone_awareness_enabled = false
}
ebs_options {
ebs_enabled = true
volume_size = local.es.volume_size
volume_type = local.es.volume_type
}
vpc_options {
subnet_ids = local.es.subnet_ids
security_group_ids = local.es.security_group_ids
}
domain_endpoint_options {
enforce_https = true
tls_security_policy = local.es.custom_endpoint.tls_policy
custom_endpoint_enabled = local.es.custom_endpoint.enabled
custom_endpoint_certificate_arn = local.es.custom_endpoint.enabled ? local.acm_arn : null
custom_endpoint = local.es.custom_endpoint.enabled ? local.es.custom_endpoint.domain_name : null
}
access_policies = data.aws_iam_policy_document.es_access_policy.json
tags = merge(
local.tags,
{
Name = "${local.prefix}-domain"
}
)
depends_on = [aws_iam_service_linked_role.es]
lifecycle {
ignore_changes = [
auto_tune_options
]
}
}
9) eventBridge.tf
event_bus_name은 해당 폴더에서 생성한 Custom EventBus를 지정해주었다. 지정해주지 않으면 default eventBus를 바라보게 될 것이다.
Target으로는 기 생성한 CloudWatch Logs로 보낼 것이다.
(Slack Noti 용도의 Lambda 타겟은 웹 콘솔에서 진행하였음)
resource "aws_cloudwatch_event_rule" "this" {
name = "${local.prefix}-event-rule"
description = "Capture IAM API Actions for security audit"
event_bus_name = aws_cloudwatch_event_bus.this.name
event_pattern = <<EOF
{
"source": ["aws.iam"],
"detail-type": ["AWS API Call via CloudTrail"],
"detail": {
"eventSource": ["iam.amazonaws.com"]
}
}
EOF
depends_on = [
aws_cloudwatch_event_bus.this
]
# lifecycle {
# create_before_destroy = true
# }
}
resource "aws_cloudwatch_event_target" "this" {
arn = aws_cloudwatch_log_group.this.arn
rule = aws_cloudwatch_event_rule.this.name
event_bus_name = aws_cloudwatch_event_bus.this.name
target_id = "SendToCloudWatchLogs"
}
10) eventBus.tf
: default eventBus와 별개로 IAM Audit 내역만 받아내기 위한 EventBus를 생성하였다.
resource "aws_cloudwatch_event_bus" "this" {
name = "${local.prefix}-eventbus"
tags = merge(
local.tags,
{
Name = "${local.prefix}-eventbus"
}
)
}
resource "aws_cloudwatch_event_bus_policy" "this" {
event_bus_name = aws_cloudwatch_event_bus.this.name
policy = data.aws_iam_policy_document.eventBusPolicy.json
}
11) _local.tf
한번 더 감싸서 관리하기 위해 locals 블록을 사용하였다.
locals {
prefix = format("%s-%s-audit", var.account, var.env)
account = var.account
cred_file = var.cred_file
region = var.region
env = var.env
tags = merge(var.tags,
{
# Owner = var.owner,
Environment = var.env
Team = var.team
})
org_id = var.org_id
account_id = data.aws_caller_identity.current.account_id
retention_in_days = var.retention_in_days
es = {
security_group_ids = var.security_group_ids,
subnet_ids = var.subnet_ids,
vpc_id = var.vpc_id,
version = var.es_version,
instance_type = var.es_instance_type
instance_count = var.es_instance_count
volume_size = var.es_volume_size
volume_type = var.es_volume_type
custom_endpoint = {
enabled = var.es_custom_endpoint_enabled
domain_name = var.es_custom_endpoint_domain_name
tls_policy = var.es_custom_endpoint_tls_policy
}
}
acm_arn = var.acm_arn
route53_zone_id = var.route53_zone_id
}
여기까지 security-account의 Terraform 코드였고, 그 외 다른 어카운트들에 대한 테라폼 코드는 다음 글에서 작성하도록 할 예정이다.
생략한 내용들이 좀 있지만, 위의 큰 순서대로 작성한 뒤에 terraform fmt, terraform validate -> terraform plan , terraform apply를 수행하면 인프라가 프로비저닝이 된다.
EvnetBridge -> SNS -> Lambda 부분은 따로 빼서 글을 작성해 볼 생각이다.
'AWS' 카테고리의 다른 글
[AWS] Multi Account에서 IAM 내역 감사하기 #4 - Lambda를 활용한 Slack Notification (2) | 2022.04.17 |
---|---|
[AWS] Multi Account에서 IAM 내역 감사하기 #3 - 테라폼으로 인프라 프로비저닝 2 (0) | 2022.04.17 |
[AWS] Multi Account에서 IAM 내역 감사하기 #1 - 개요, 아키텍쳐, 고려 사항 (0) | 2022.04.06 |
[Python] CLB 인스턴스 상태 및 등록, 해제하는 파이썬 스크립트 (0) | 2022.01.06 |
[Lambda] IAM, SG 변경 사항을 Slack으로 알람 받기 (0) | 2022.01.03 |