diff --git a/operation-team-account/.gitignore b/operation-team-account/.gitignore new file mode 100644 index 00000000..e5ab9a69 --- /dev/null +++ b/operation-team-account/.gitignore @@ -0,0 +1,22 @@ +# Terraform 관련 파일 무시 +.terraform/ # terraform init 시 생성되는 폴더 +*.tfstate # 상태 파일 (리소스 실제 정보 포함) +*.tfstate.* # 상태 파일 백업 +*.tfvars +*.tfstate.backup +.terraform.lock.hcl +terraform.tfvars # 민감 정보 입력용 파일 +crash.log # Terraform 충돌 로그 +.terraform +# AWS CLI 자격 증명 +.aws/ # ~/.aws/credentials, config 등 + +# 시스템 자동 생성 파일 (Windows/macOS) +.DS_Store # macOS +Thumbs.db # Windows +ehthumbs.db # Windows +*.log # 일반 로그 파일 +*.tmp # 임시 파일 + +# VSCode 설정 (선택사항) +.vscode/ diff --git a/operation-team-account/main.tf b/operation-team-account/main.tf new file mode 100644 index 00000000..6f2bf2b1 --- /dev/null +++ b/operation-team-account/main.tf @@ -0,0 +1,28 @@ +module "s3" { + source = "./modules/s3" +} + +module "opensearch" { + source = "./modules/opensearch" + sso_role_name = var.sso_role_name + sso_user_name = var.sso_user_name + firehose_role_arn = module.firehose.firehose_role_arn +} + +module "firehose" { + source = "./modules/firehose" + opensearch_domain_arn = module.opensearch.domain_arn + s3_bucket_arn = module.s3.bucket_arn +} + +module "cloudwatch" { + source = "./modules/cloudwatch" + + subscription_filter_name = "cw-to-firehose" + log_group_name = var.log_group_name + firehose_arn = module.firehose.delivery_stream_arn + role_arn = module.firehose.firehose_role_arn +} +module "securitylake" { + source = "./modules/securitylake" +} diff --git a/operation-team-account/modules/cloudwatch/main.tf b/operation-team-account/modules/cloudwatch/main.tf new file mode 100644 index 00000000..90ed3de3 --- /dev/null +++ b/operation-team-account/modules/cloudwatch/main.tf @@ -0,0 +1,12 @@ +resource "aws_cloudwatch_log_group" "target" { + name = var.log_group_name + retention_in_days = 14 +} + +resource "aws_cloudwatch_log_subscription_filter" "logs_to_firehose" { + name = var.subscription_filter_name + log_group_name = var.log_group_name + filter_pattern = "{ $.level = \"ERROR\" }" + destination_arn = var.firehose_arn + role_arn = var.role_arn +} diff --git a/operation-team-account/modules/cloudwatch/variables.tf b/operation-team-account/modules/cloudwatch/variables.tf new file mode 100644 index 00000000..254047c4 --- /dev/null +++ b/operation-team-account/modules/cloudwatch/variables.tf @@ -0,0 +1,19 @@ +variable "subscription_filter_name" { + description = "CloudWatch Logs Subscription Filter 이름" + type = string +} + +variable "log_group_name" { + description = "대상 CloudWatch 로그 그룹 이름" + type = string +} + +variable "firehose_arn" { + description = "로그를 전송할 Kinesis Firehose ARN" + type = string +} + +variable "role_arn" { + description = "CloudWatch Logs → Firehose 연결용 IAM Role ARN" + type = string +} diff --git a/operation-team-account/modules/firehose/main.tf b/operation-team-account/modules/firehose/main.tf new file mode 100644 index 00000000..2edc5de5 --- /dev/null +++ b/operation-team-account/modules/firehose/main.tf @@ -0,0 +1,84 @@ +resource "aws_iam_role" "firehose_role" { + name = "siem-firehose-role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Effect = "Allow", + Principal = { + Service = "firehose.amazonaws.com" + }, + Action = "sts:AssumeRole" + } + ] + }) +} + +resource "aws_iam_role_policy" "firehose_policy" { + name = "firehose-access" + role = aws_iam_role.firehose_role.id + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + # 1) OpenSearch 접근 권한: 도메인 자체와 색인 아래 객체만 허용 + { + Effect = "Allow" + Action = [ + "es:DescribeElasticsearchDomain", + "es:DescribeElasticsearchDomains", # Note: 이 액션은 리소스 지정이 불가능하여 "*" 사용 필요 + "es:DescribeElasticsearchDomainConfig", + "es:ESHttpPost", + "es:ESHttpPut", + "es:ESHttpGet", + ] + Resource = [ + var.opensearch_domain_arn, + "${var.opensearch_domain_arn}/*", + ] + }, + + # 2) S3 버킷 접근 권한: 버킷 메타데이터 조회와 객체 Put만 해당 버킷으로 제한 + { + Effect = "Allow" + Action = [ + "s3:ListBucket", # 버킷 목록 조회(버킷 이름에 한정) + "s3:GetBucketLocation", # 리전 조회 + ] + Resource = var.s3_bucket_arn + }, + { + Effect = "Allow" + Action = [ + "s3:PutObject", # 객체 업로드 + ] + Resource = "${var.s3_bucket_arn}/*" + } + ] + }) +} + + +resource "aws_kinesis_firehose_delivery_stream" "to_opensearch" { + name = "siem-delivery" + destination = "opensearch" + + opensearch_configuration { + domain_arn = var.opensearch_domain_arn + role_arn = aws_iam_role.firehose_role.arn + index_name = "cw-logs" + type_name = "log" + + s3_backup_mode = "FailedDocumentsOnly" + + s3_configuration { + role_arn = aws_iam_role.firehose_role.arn + bucket_arn = var.s3_bucket_arn + buffering_interval = 300 + buffering_size = 5 + compression_format = "GZIP" + prefix = "backup/" + } + } +} diff --git a/operation-team-account/modules/firehose/outputs.tf b/operation-team-account/modules/firehose/outputs.tf new file mode 100644 index 00000000..02f54d53 --- /dev/null +++ b/operation-team-account/modules/firehose/outputs.tf @@ -0,0 +1,9 @@ +output "delivery_stream_arn" { + value = aws_kinesis_firehose_delivery_stream.to_opensearch.arn + description = "ARN of the Firehose delivery stream" +} + +output "firehose_role_arn" { + value = aws_iam_role.firehose_role.arn + description = "ARN of the Firehose IAM role" +} diff --git a/operation-team-account/modules/firehose/variables.tf b/operation-team-account/modules/firehose/variables.tf new file mode 100644 index 00000000..93dd87a1 --- /dev/null +++ b/operation-team-account/modules/firehose/variables.tf @@ -0,0 +1,7 @@ +variable "opensearch_domain_arn" { + type = string +} + +variable "s3_bucket_arn" { + type = string +} \ No newline at end of file diff --git a/operation-team-account/modules/opensearch/main.tf b/operation-team-account/modules/opensearch/main.tf new file mode 100644 index 00000000..a3ca76ea --- /dev/null +++ b/operation-team-account/modules/opensearch/main.tf @@ -0,0 +1,50 @@ +data "aws_caller_identity" "current" {} + +resource "aws_opensearch_domain" "siem" { + domain_name = "siem-domain" + engine_version = "OpenSearch_2.9" + + cluster_config { + instance_type = "t3.small.search" + instance_count = 1 + } + + ebs_options { + ebs_enabled = true + volume_size = 10 + } + + domain_endpoint_options { + enforce_https = true + tls_security_policy = "Policy-Min-TLS-1-2-2019-07" + } + + node_to_node_encryption { + enabled = true + } + + encrypt_at_rest { + enabled = true + } + + access_policies = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Principal = { + AWS = [ + "arn:aws:sts::${data.aws_caller_identity.current.account_id}:assumed-role/${var.sso_role_name}/${var.sso_user_name}", + var.firehose_role_arn, + ] + } + Action = "es:*" + # 도메인 및 그 하위 인덱스·도큐먼트 전체 리소스 + Resource = [ + aws_opensearch_domain.siem.arn, + "${aws_opensearch_domain.siem.arn}/*", + ] + } + ] + }) +} diff --git a/operation-team-account/modules/opensearch/outputs.tf b/operation-team-account/modules/opensearch/outputs.tf new file mode 100644 index 00000000..5272c407 --- /dev/null +++ b/operation-team-account/modules/opensearch/outputs.tf @@ -0,0 +1,3 @@ +output "domain_arn" { + value = aws_opensearch_domain.siem.arn +} \ No newline at end of file diff --git a/operation-team-account/modules/opensearch/variables.tf b/operation-team-account/modules/opensearch/variables.tf new file mode 100644 index 00000000..449a8fe3 --- /dev/null +++ b/operation-team-account/modules/opensearch/variables.tf @@ -0,0 +1,15 @@ +variable "sso_role_name" { + description = "SSO에서 부여된 IAM 역할 이름" + type = string +} + +variable "sso_user_name" { + description = "SSO 세션 사용자 이름 (예: 이메일)" + type = string +} + +variable "firehose_role_arn" { + description = "ARN of the Firehose IAM role to allow write access" + type = string +} + diff --git a/operation-team-account/modules/s3/main.tf b/operation-team-account/modules/s3/main.tf new file mode 100644 index 00000000..5f469c5a --- /dev/null +++ b/operation-team-account/modules/s3/main.tf @@ -0,0 +1,49 @@ +resource "random_id" "bucket_id" { + byte_length = 4 +} + +resource "aws_s3_bucket" "firehose_backup" { + bucket = "siem-firehose-backup-${random_id.bucket_id.hex}" + force_destroy = true +} + +resource "aws_s3_bucket_public_access_block" "block" { + bucket = aws_s3_bucket.firehose_backup.id + + block_public_acls = true + block_public_policy = true + ignore_public_acls = true + restrict_public_buckets = true +} + +# S3 버킷에 Lifecycle 정책 붙이기 +resource "aws_s3_bucket_lifecycle_configuration" "firehose_backup" { + bucket = aws_s3_bucket.firehose_backup.id + + rule { + id = "ExpireBackup" + status = "Enabled" + + filter { prefix = "backup/" } + + expiration { days = 7 } # 7일 지난 백업은 자동 삭제 + } +} + +resource "aws_kms_key" "s3_cmk" { + description = "KMS key for encrypting Firehose backup S3 bucket" + deletion_window_in_days = 10 + enable_key_rotation = true +} + +# 암호화 설정 +resource "aws_s3_bucket_server_side_encryption_configuration" "default" { + bucket = aws_s3_bucket.firehose_backup.id + + rule { + apply_server_side_encryption_by_default { + sse_algorithm = "aws:kms" + kms_master_key_id = aws_kms_key.s3_cmk.arn + } + } +} diff --git a/operation-team-account/modules/s3/outputs.tf b/operation-team-account/modules/s3/outputs.tf new file mode 100644 index 00000000..f4bd5c26 --- /dev/null +++ b/operation-team-account/modules/s3/outputs.tf @@ -0,0 +1,3 @@ +output "bucket_arn" { + value = aws_s3_bucket.firehose_backup.arn +} \ No newline at end of file diff --git a/operation-team-account/modules/securitylake/main.tf b/operation-team-account/modules/securitylake/main.tf new file mode 100644 index 00000000..01a11d10 --- /dev/null +++ b/operation-team-account/modules/securitylake/main.tf @@ -0,0 +1,42 @@ +# # 1. 메타스토어 관리자 IAM 역할 생성 +# resource "aws_iam_role" "securitylake_manager" { +# name = "securitylake-meta-store-manager" + +# assume_role_policy = jsonencode({ +# Version = "2012-10-17", +# Statement = [ +# { +# Effect = "Allow", +# Principal = { Service = "securitylake.amazonaws.com" }, +# Action = "sts:AssumeRole" +# } +# ] +# }) +# } + +# # 2. 조직 단위 Security Lake 활성화 (조직 전체 설정) +# resource "aws_securitylake_organization_configuration" "org_config" { +# auto_enable { +# all_regions = false +# regions = ["ap-northeast-2"] +# } +# } + +# # 3. 특정 리전(ap-northeast-2)에서 Data Lake 활성화 및 라이프사이클 설정 +# resource "aws_securitylake_data_lake" "this" { +# meta_store_manager_role_arn = aws_iam_role.securitylake_manager.arn + +# configuration { +# region = "ap-northeast-2" + +# lifecycle_configuration { +# expiration { +# days = 30 # 데이터 30일 보관 +# } +# transition { +# days = 7 +# storage_class = "ONEZONE_IA" # 7일 후 저비용 스토리지로 전환 +# } +# } +# } +# } diff --git a/operation-team-account/outputs.tf b/operation-team-account/outputs.tf new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/operation-team-account/outputs.tf @@ -0,0 +1 @@ + diff --git a/operation-team-account/providers.tf b/operation-team-account/providers.tf new file mode 100644 index 00000000..39e5a494 --- /dev/null +++ b/operation-team-account/providers.tf @@ -0,0 +1,13 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.11.0" + } + } +} + +provider "aws" { + region = "ap-northeast-2" + profile = "my-profile" +} \ No newline at end of file diff --git a/operation-team-account/variables.tf b/operation-team-account/variables.tf new file mode 100644 index 00000000..d532342d --- /dev/null +++ b/operation-team-account/variables.tf @@ -0,0 +1,14 @@ +variable "sso_role_name" { + description = "SSO에서 부여된 IAM 역할 이름" + type = string +} + +variable "sso_user_name" { + description = "SSO 세션 사용자 이름" + type = string +} + +variable "log_group_name" { + description = "전송할 CloudWatch Log Group 이름" + type = string +}