diff --git a/management-team-account/inspector-delegation/organizations/backend.tf b/management-team-account/inspector-delegation/organizations/backend.tf new file mode 100644 index 00000000..62f53b08 --- /dev/null +++ b/management-team-account/inspector-delegation/organizations/backend.tf @@ -0,0 +1,9 @@ +terraform { + backend "s3" { + bucket = "cloudfence-management-state" + key = "inspector-delegation/organizations.tfstate" + region = "ap-northeast-2" + encrypt = true + dynamodb_table = "s3-management-lock" + } +} \ No newline at end of file diff --git a/management-team-account/inspector-delegation/organizations/main.tf b/management-team-account/inspector-delegation/organizations/main.tf new file mode 100644 index 00000000..3f02a968 --- /dev/null +++ b/management-team-account/inspector-delegation/organizations/main.tf @@ -0,0 +1,26 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + } + +} + +provider "aws" { + region = "ap-northeast-2" +} + +provider "aws" { + alias = "operation" + region = "ap-northeast-2" +} + +data "aws_caller_identity" "operation" { + provider = aws.operation +} + +resource "aws_inspector2_delegated_admin_account" "this" { + account_id = data.aws_caller_identity.operation.account_id +} \ No newline at end of file diff --git a/operation-team-account/runtime-verification/eventbridge/backend.tf b/operation-team-account/runtime-verification/eventbridge/backend.tf new file mode 100644 index 00000000..596aa632 --- /dev/null +++ b/operation-team-account/runtime-verification/eventbridge/backend.tf @@ -0,0 +1,9 @@ +terraform { + backend "s3" { + bucket = "cloudfence-operation-state" + key = "runtime-verification/eventbridge.tfstate" + region = "ap-northeast-2" + dynamodb_table = "s3-operation-lock" + encrypt = true + } +} \ No newline at end of file diff --git a/operation-team-account/runtime-verification/eventbridge/main.tf b/operation-team-account/runtime-verification/eventbridge/main.tf new file mode 100644 index 00000000..3589cbf6 --- /dev/null +++ b/operation-team-account/runtime-verification/eventbridge/main.tf @@ -0,0 +1,51 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + } + +} + +provider "aws" { + region = "ap-northeast-2" +} + +data "terraform_remote_state" "lambda" { + backend = "s3" + config = { + bucket = "cloudfence-operation-state" + key = "runtime-verification/lambda.tfstate" + region = "ap-northeast-2" + dynamodb_table = "s3-operation-lock" + } +} + +resource "aws_cloudwatch_event_rule" "inspector_event_rule" { + name = "inspector-event-rule" + description = "Event rule for AWS Inspector findings" + + event_pattern = jsonencode({ + source = ["aws.inspector2"], + detail-type = ["Inspector2 Finding"], + detail = { + finding = { + severity = ["HIGH", "CRITICAL"], + } + } + }) +} + +resource "aws_cloudwatch_event_target" "inspector_event_target" { + rule = aws_cloudwatch_event_rule.inspector_event_rule.name + arn = data.terraform_remote_state.lambda.outputs.lambda_function_arn +} + +resource "aws_lambda_permission" "inspector_event_permission" { + statement_id = "AllowExecutionFromEventBridge" + action = "lambda:InvokeFunction" + function_name = data.terraform_remote_state.lambda.outputs.lambda_function_name + principal = "events.amazonaws.com" + source_arn = aws_cloudwatch_event_rule.inspector_event_rule.arn +} \ No newline at end of file diff --git a/operation-team-account/runtime-verification/iam/backend.tf b/operation-team-account/runtime-verification/iam/backend.tf new file mode 100644 index 00000000..8b820ee8 --- /dev/null +++ b/operation-team-account/runtime-verification/iam/backend.tf @@ -0,0 +1,9 @@ +terraform { + backend "s3" { + bucket = "cloudfence-operation-state" + key = "runtime-verification/iam.tfstate" + region = "ap-northeast-2" + dynamodb_table = "s3-operation-lock" + encrypt = true + } +} \ No newline at end of file diff --git a/operation-team-account/runtime-verification/iam/main.tf b/operation-team-account/runtime-verification/iam/main.tf new file mode 100644 index 00000000..d6431474 --- /dev/null +++ b/operation-team-account/runtime-verification/iam/main.tf @@ -0,0 +1,33 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + } + +} + +provider "aws" { + region = "ap-northeast-2" +} + +resource "aws_iam_role" "lambda_exec_role" { + name = "${var.project_name}-inspector-lambda-exec-role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17", + Statement = [{ + Action = "sts:AssumeRole", + Effect = "Allow", + Principal = { + Service = "lambda.amazonaws.com" + } + }] + }) +} + +resource "aws_iam_role_policy_attachment" "lambda_logs" { + role = aws_iam_role.lambda_exec_role.name + policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" +} \ No newline at end of file diff --git a/operation-team-account/runtime-verification/iam/outputs.tf b/operation-team-account/runtime-verification/iam/outputs.tf new file mode 100644 index 00000000..a662f1bb --- /dev/null +++ b/operation-team-account/runtime-verification/iam/outputs.tf @@ -0,0 +1,4 @@ +output "lambda_exec_role_arn" { + description = "The ARN of the IAM role for the Lambda function" + value = aws_iam_role.lambda_exec_role.arn +} \ No newline at end of file diff --git a/operation-team-account/runtime-verification/iam/variables.tf b/operation-team-account/runtime-verification/iam/variables.tf new file mode 100644 index 00000000..7c839dd8 --- /dev/null +++ b/operation-team-account/runtime-verification/iam/variables.tf @@ -0,0 +1,5 @@ +variable "project_name" { + description = "The name of the project" + type = string + default = "cloudfence" +} \ No newline at end of file diff --git a/operation-team-account/runtime-verification/inspector/backend.tf b/operation-team-account/runtime-verification/inspector/backend.tf new file mode 100644 index 00000000..cb20378c --- /dev/null +++ b/operation-team-account/runtime-verification/inspector/backend.tf @@ -0,0 +1,9 @@ +terraform { + backend "s3" { + bucket = "cloudfence-operation-state" + key = "runtime-verification/inspector.tfstate" + region = "ap-northeast-2" + dynamodb_table = "s3-operation-lock" + encrypt = true + } +} \ No newline at end of file diff --git a/operation-team-account/runtime-verification/inspector/main.tf b/operation-team-account/runtime-verification/inspector/main.tf new file mode 100644 index 00000000..cb6f1ffa --- /dev/null +++ b/operation-team-account/runtime-verification/inspector/main.tf @@ -0,0 +1,33 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + } + +} + +provider "aws" { + region = "ap-northeast-2" +} + +provider "aws" { + alias = "prod" + region = "ap-northeast-2" +} + +data "aws_caller_identity" "prod" { + provider = aws.prod +} + +data "aws_caller_identity" "current" {} + +resource "aws_inspector2_enabler" "this" { + account_ids = [data.aws_caller_identity.current.account_id] + resource_types = ["EC2"] +} + +resource "aws_inspector2_delegated_admin_account" "prod_account" { + account_id = data.aws_caller_identity.prod.account_id +} \ No newline at end of file diff --git a/operation-team-account/runtime-verification/lambda/backend.tf b/operation-team-account/runtime-verification/lambda/backend.tf new file mode 100644 index 00000000..59c89a02 --- /dev/null +++ b/operation-team-account/runtime-verification/lambda/backend.tf @@ -0,0 +1,9 @@ +terraform { + backend "s3" { + bucket = "cloudfence-operation-state" + key = "runtime-verification/lambda.tfstate" + region = "ap-northeast-2" + dynamodb_table = "s3-operation-lock" + encrypt = true + } +} \ No newline at end of file diff --git a/operation-team-account/runtime-verification/lambda/lambda_function.py b/operation-team-account/runtime-verification/lambda/lambda_function.py new file mode 100644 index 00000000..add59de3 --- /dev/null +++ b/operation-team-account/runtime-verification/lambda/lambda_function.py @@ -0,0 +1,44 @@ +import json +import os +import urllib.request + +SLACK_WEBHOOK_URL = os.environ.get('SLACK_WEBHOOK_URL', '') + +def lambda_handler(event, context): + print(json.dumps(event)) # 디버깅을 위해 전체 이벤트를 로그로 남깁니다. + + # Inspector 이벤트에서 필요한 정보 추출 + finding = event.get('detail', {}) + title = finding.get('title', 'N/A') + severity = finding.get('severity', 'N/A') + + resources = finding.get('resources', [{}]) + resource_id = resources[0].get('id', 'N/A') + + region = event.get('region', 'N/A') + finding_arn = finding.get('findingArn', '') + console_url = f"https://{region}.console.aws.amazon.com/inspector/v2/findings/details?finding-arn={finding_arn}" if finding_arn else "#" + + # 슬랙 메시지 구성 + slack_message = { + "text": f"*New High-Severity Inspector Finding*", + "blocks": [ + { "type": "header", "text": { "type": "plain_text", "text": f"{severity}: {title}" } }, + { "type": "section", "text": { "type": "mrkdwn", "text": f"*Affected Resource:*\n```{resource_id}```" } }, + { "type": "actions", "elements": [{ "type": "button", "text": { "type": "plain_text", "text": "View Finding Details" }, "url": console_url, "style": "primary" }] } + ] + } + + if not SLACK_WEBHOOK_URL: + print("Slack Webhook URL is not set.") + return {'statusCode': 500} + + req = urllib.request.Request(SLACK_WEBHOOK_URL, data=json.dumps(slack_message).encode('utf-8'), headers={'Content-Type': 'application/json'}) + + try: + with urllib.request.urlopen(req) as response: + print(f"Message posted to Slack, status: {response.status}") + except urllib.error.HTTPError as e: + print(f"Error posting to Slack: {e.code} {e.reason}") + + return {'statusCode': 200} \ No newline at end of file diff --git a/operation-team-account/runtime-verification/lambda/lambda_function_payload.zip b/operation-team-account/runtime-verification/lambda/lambda_function_payload.zip new file mode 100644 index 00000000..76a73226 Binary files /dev/null and b/operation-team-account/runtime-verification/lambda/lambda_function_payload.zip differ diff --git a/operation-team-account/runtime-verification/lambda/main.tf b/operation-team-account/runtime-verification/lambda/main.tf new file mode 100644 index 00000000..371c3bec --- /dev/null +++ b/operation-team-account/runtime-verification/lambda/main.tf @@ -0,0 +1,40 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + } + +} + +provider "aws" { + region = "ap-northeast-2" +} + +data "terraform_remote_state" "iam" { + backend = "s3" + config = { + bucket = "cloudfence-operation-state" + key = "runtime-verification/iam.tfstate" + region = "ap-northeast-2" + dynamodb_table = "s3-operation-lock" + } +} + +# 이 Lambda 함수는 단순 알림 기능만 수행하므로 X-Ray 옵션을 제외 +#tfsec:ignore:aws-lambda-enable-tracing +resource "aws_lambda_function" "inspector_slack_notification" { + function_name = "inspector_slack_notification" + role = data.terraform_remote_state.iam.outputs.lambda_exec_role_arn + handler = "index.lambda_handler" + runtime = "python3.9" + + environment { + variables = { + SLACK_WEBHOOK_URL = var.slack_webhook_url + } + } + filename = "lambda_function_payload.zip" + source_code_hash = filebase64sha256("lambda_function_payload.zip") +} \ No newline at end of file diff --git a/operation-team-account/runtime-verification/lambda/outputs.tf b/operation-team-account/runtime-verification/lambda/outputs.tf new file mode 100644 index 00000000..b6b761dd --- /dev/null +++ b/operation-team-account/runtime-verification/lambda/outputs.tf @@ -0,0 +1,9 @@ +output "lambda_function_arn" { + description = "The ARN of the Inspector Slack notification Lambda function" + value = aws_lambda_function.inspector_slack_notification.arn +} + +output "lambda_function_name" { + description = "The name of the Inspector Slack notification Lambda function" + value = aws_lambda_function.inspector_slack_notification.function_name +} \ No newline at end of file diff --git a/operation-team-account/runtime-verification/lambda/variables.tf b/operation-team-account/runtime-verification/lambda/variables.tf new file mode 100644 index 00000000..5ecd0a22 --- /dev/null +++ b/operation-team-account/runtime-verification/lambda/variables.tf @@ -0,0 +1,5 @@ +variable "slack_webhook_url" { + description = "The incoming webhook URL for Slack notifications" + type = string + sensitive = true +} \ No newline at end of file