Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
baf99b1
Inspector 스캔을 통한 취약점 검사
yunhoch0i Jul 24, 2025
f2bec4d
terraform format check
yunhoch0i Jul 24, 2025
c720ced
inspector 스캔 대상 추가
yunhoch0i Jul 24, 2025
f1e12b9
inspector 스캔 대상 추가
yunhoch0i Jul 24, 2025
0a2bbbd
webhook url 제거
yunhoch0i Jul 24, 2025
04e8f6f
delegate 설정
yunhoch0i Jul 25, 2025
d97daf2
feat: cloudtrail>s3&eventbridge>lambda>opensearch
3olly Jul 6, 2025
93e4206
fix: eventbridge, lambda 수정
3olly Jul 6, 2025
7df2190
feat: s3_aws_kms_key 추가
3olly Jul 6, 2025
5003565
fix: kms 권한 수정
3olly Jul 6, 2025
6e75451
fix: s3 main 권한 및 정책 수정
3olly Jul 6, 2025
e50d12a
feat: managemenet-s3 접근 허용
3olly Jul 6, 2025
49e99e5
fix: terraform 형식 수정
3olly Jul 6, 2025
c2d789c
fix: s3 main.tf kms 정책 수정사항
3olly Jul 7, 2025
412a729
fix: format 수정
3olly Jul 7, 2025
1198420
fix: lambda 폴더 통합
3olly Jul 7, 2025
98e0d45
fix:cloudtrail->s3 로그 전송 성공
3olly Jul 8, 2025
28b3c37
feat: SSE-KMS 암호화 설정
3olly Jul 8, 2025
ed68699
fix: cloudtrail module로 분리
3olly Jul 8, 2025
853ae66
fix: backend s3 migration (operation, management)
3olly Jul 8, 2025
97aabda
chore: 강화된 s3 bucket&KMS 정책 적용
3olly Jul 8, 2025
7d310be
chore: 강화된 S3 버킷·KMS 정책 적용
3olly Jul 8, 2025
8947aac
fix: cloudtrail 로깅 멈춤 문제 해결
3olly Jul 9, 2025
e2495f3
fix: cloudtrail 로깅 멈춤 문제 해결2
3olly Jul 9, 2025
7bbe219
feat: lambda>slack 알림 구현 완료
3olly Jul 10, 2025
7132650
fix: lambda 구성 수정
3olly Jul 13, 2025
a1eb8fb
feat: lambda>opensearch 연결
3olly Jul 15, 2025
ec764e1
chore: tfsec 관련 network main.tf 수정
3olly Jul 15, 2025
fe109da
fix: slack 알림까지 완료. opensearch연결 x
3olly Jul 15, 2025
7a36d0e
feat: lambda -> opensearch 전송 로직 추가, 알림 잘 옴
3olly Jul 15, 2025
bddc1c6
fix: slack 알림 반복되는 문제 해결
3olly Jul 16, 2025
a230959
refactor: 전체 폴더구조 수정(slack 알림 잘 옴)
3olly Jul 16, 2025
47dd02d
remove: s3 profile 삭제
3olly Jul 16, 2025
66f160c
chore: format 수정
3olly Jul 16, 2025
cf6a523
chore: .gitignor 수정
3olly Jul 16, 2025
6591bbc
fix: network_vpc main.tf 수정(알림 잘 옴)
3olly Jul 17, 2025
7977317
chore: tfsec 통과 위한 주석 추가
3olly Jul 17, 2025
1b27e51
feat: lambda to opensearch 로그 전송 성공(알림 O)
3olly Jul 18, 2025
5668736
fix: network_vpc>main.tf에서 opensearch쪽 tfsec 경고 해결(알림O)
3olly Jul 18, 2025
b9c6d22
fix: ci 코드 수정
3olly Jul 18, 2025
a1e3eb0
fix: ci 코드 2차 수정
3olly Jul 18, 2025
e88af54
feat: ci 쪽에 slack 정보 추가
3olly Jul 18, 2025
2f5b57b
fix: ci 코드 3차 수정
3olly Jul 18, 2025
a0d2216
fix: ci 4차 수정
3olly Jul 18, 2025
50263ab
feat: opensearch에 slack 채널 생성까지 성공
3olly Jul 23, 2025
f7b5ca3
Delete: lambda와 opensearch 구성 삭제
3olly Jul 23, 2025
9b5c59c
feat: cloudtrail 관련 opensearch 모니터링 환경 구성
3olly Jul 24, 2025
d993a80
fix: profile 삭제
3olly Jul 24, 2025
7cc672c
chore: tf format 수정
3olly Jul 24, 2025
7cba567
chore: 주석 수정
3olly Jul 24, 2025
7c6cb1f
chore: variables 수정
3olly Jul 27, 2025
4902e5c
fix: lmabda nodejs 버전 수정 18.x>22.x
3olly Jul 27, 2025
9c0fe2a
chore: output 수정
3olly Jul 27, 2025
eb68785
fix: cd slack 부분 수정
3olly Jul 27, 2025
c433599
chore: 각 account 별 main.tf 주석 추가
3olly Jul 27, 2025
1b17128
chore: management account 주석 추가
3olly Jul 27, 2025
f6498fc
Inspector 스캔을 통한 취약점 검사
yunhoch0i Jul 24, 2025
cd84075
format check
yunhoch0i Jul 29, 2025
abb4142
변수 처리 방식 변경
yunhoch0i Jul 29, 2025
710eecb
변수 처리 방식 변경
yunhoch0i Jul 29, 2025
e7fee8f
refactor: 전체 폴더구조 수정(slack 알림 잘 옴)
3olly Jul 16, 2025
179a57d
OIDC 정책 추가
rnjsdbwlsqwer Jul 29, 2025
baaddf7
ci 코드 수정
rnjsdbwlsqwer Jul 29, 2025
9ac6e16
Inspector 스캔을 통한 취약점 검사
yunhoch0i Jul 24, 2025
66e13ed
inspector 스캔 대상 추가
yunhoch0i Jul 24, 2025
6026b97
refactor: 전체 폴더구조 수정(slack 알림 잘 옴)
3olly Jul 16, 2025
6b4859f
Merge branch 'main' into feat/#55
yunhoch0i Jul 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,9 @@ ehthumbs.db # Windows
.vscode/

# OpenSearch alert 생성 시 생기는 임시 파일
modules/opensearch/slack_response.json
modules/opensearch/slack_response.json


# OpenSearch alert 생성 시 생기는 임시 파일
modules/opensearch/slack_response.json

Original file line number Diff line number Diff line change
@@ -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"
}
}
26 changes: 26 additions & 0 deletions management-team-account/inspector-delegation/organizations/main.tf
Original file line number Diff line number Diff line change
@@ -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
}
127 changes: 127 additions & 0 deletions modules/lambda_log_processor/lambda_function.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import os
import json
import gzip
import boto3
import urllib.request
import urllib.error
import requests

# OpenSearch로 로그 전송
def send_to_opensearch(record: dict):
endpoint = os.environ['OPENSEARCH_URL']
index = "security-alerts-" + record.get("eventName", "unknown").lower()
url = f"{endpoint}/{index}/_doc"
headers = {"Content-Type": "application/json"}
# 원하는 형태로 문서 구조를 정리
doc = {
"@timestamp": record.get("eventTime"),
"eventName": record.get("eventName"),
"user": record.get("userIdentity", {}).get("arn"),
"sourceIP": record.get("sourceIPAddress"),
"awsRegion": record.get("awsRegion"),
"accountId": record.get("recipientAccountId"),
"raw": record
}
resp = requests.post(url, headers=headers, data=json.dumps(doc), timeout=5)
resp.raise_for_status()


def send_slack_alert(record: dict):
webhook_url = os.environ['SLACK_WEBHOOK_URL']
user = record.get("userIdentity", {}).get("arn", "Unknown user")
event_name = record.get("eventName", "Unknown event")
source_ip = record.get("sourceIPAddress", "Unknown IP")
time = record.get("eventTime", "Unknown time")
region = record.get("awsRegion", "Unknown region")
account = record.get("recipientAccountId", "Unknown account")

slack_payload = {
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": ":rotating_light: AWS Security Alert",
"emoji": True
}
},
{
"type": "section",
"fields": [
{"type": "mrkdwn", "text": f"*Event:*\n`{event_name}`"},
{"type": "mrkdwn", "text": f"*User:*\n`{user}`"},
{"type": "mrkdwn", "text": f"*Source IP:*\n`{source_ip}`"},
{"type": "mrkdwn", "text": f"*Region:*\n`{region}`"},
{"type": "mrkdwn", "text": f"*Account:*\n`{account}`"},
{"type": "mrkdwn", "text": f"*Time:*\n`{time}`"}
]
},
{
"type": "divider"
}
]
}

data = json.dumps(slack_payload).encode('utf-8')
req = urllib.request.Request(webhook_url, data=data, headers={'Content-Type': 'application/json'})

try:
with urllib.request.urlopen(req, timeout=5) as response:
print("Slack message sent:", response.status)
except Exception as e:
print("Error sending Slack message:", str(e))


def lambda_handler(event, context):
print("Received S3 PutObject event:", json.dumps(event, indent=2))

# 1) EventBridge detail 로부터 버킷과 키 추출
detail = event.get("detail", {})
bucket = detail.get("requestParameters", {}).get("bucketName")
key = detail.get("requestParameters", {}).get("key")

if not bucket or not key:
print("Bucket or key missing in event detail")
return {"statusCode": 400, "body": json.dumps({"error": "Invalid event"})}

# 2) S3에서 gzip된 CloudTrail 로그 파일 다운로드 및 파싱
s3 = boto3.client("s3")
try:
obj = s3.get_object(Bucket=bucket, Key=key)
except Exception as e:
print(f"Error fetching object {bucket}/{key}: {e}")
return {"statusCode": 500, "body": json.dumps({"error": str(e)})}

try:
with gzip.GzipFile(fileobj=obj["Body"]) as gz:
log_data = json.load(gz)
except Exception as e:
print(f"Error decompressing/parsing CloudTrail log: {e}")
return {"statusCode": 500, "body": json.dumps({"error": str(e)})}

# 3) 각 레코드별 필터링 및 Slack 알림
alert_events = {
"DeleteUser", "DeleteRole", "DeleteLoginProfile",
"StopLogging", "DeleteTrail",
"DeactivateMFADevice", "DeleteVirtualMFADevice",
"AuthorizeSecurityGroupIngress", "RevokeSecurityGroupIngress",
"AuthorizeSecurityGroupEgress", "RevokeSecurityGroupEgress",
"AttachUserPolicy", "DetachUserPolicy",
"PutUserPolicy", "DeleteUserPolicy",
"CreatePolicy", "DeletePolicy",
"RunInstances"
}

for record in log_data.get("Records", []):
evt = record.get("eventName")
if evt in alert_events:
send_slack_alert(record)
try:
send_to_opensearch(record)
except Exception as e:
print(f"[Warning] OpenSearch indexing failed for {evt}: {e}")

return {
"statusCode": 200,
"body": json.dumps({"message": "Processed S3 log and sent alerts."})
}
Binary file added modules/lambda_log_processor/lambda_package.zip
Binary file not shown.
85 changes: 85 additions & 0 deletions modules/lambda_log_processor/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
resource "aws_iam_role" "lambda_exec" {
name = "lambda-log-processor-role"

assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = { Service = "lambda.amazonaws.com" }
Action = "sts:AssumeRole"
}
]
})
}

resource "aws_iam_role_policy" "lambda_policy" {
name = "lambda-log-policy"
role = aws_iam_role.lambda_exec.id

policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "AllowCloudWatchLogs"
Effect = "Allow"
Action = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
]
Resource = "*"
},
{
Sid = "AllowOpenSearchAccess"
Effect = "Allow"
Action = [
"es:ESHttpPost",
"es:ESHttpPut",
"es:ESHttpGet"
]
Resource = "${var.opensearch_domain_arn}/security-alerts-*/*"
},
{
Sid = "AllowKMSDecrypt"
Effect = "Allow"
Action = ["kms:Decrypt"]
Resource = var.kms_key_arn
},
{
Sid = "AllowS3Read"
Effect = "Allow"
Action = ["s3:GetObject"]
Resource = "${var.bucket_arn}/*"
}
]
})
}

resource "aws_lambda_function" "log_processor" {
function_name = var.lambda_function_name
handler = "lambda_function.lambda_handler"
runtime = "python3.11"
role = aws_iam_role.lambda_exec.arn
timeout = 30
memory_size = 256
filename = var.lambda_zip_path
source_code_hash = filebase64sha256(var.lambda_zip_path)

vpc_config {
subnet_ids = var.lambda_subnet_ids
security_group_ids = var.lambda_security_group_ids
}

environment {
variables = {
SLACK_WEBHOOK_URL = var.slack_webhook_url
OPENSEARCH_URL = "https://${var.opensearch_endpoint}"
}
}
}

resource "aws_iam_role_policy_attachment" "vpc_access" {
role = aws_iam_role.lambda_exec.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
}
14 changes: 14 additions & 0 deletions modules/lambda_log_processor/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
output "lambda_function_name" {
value = aws_lambda_function.log_processor.function_name
description = "Name of the deployed Lambda function"
}

output "lambda_function_role_arn" {
value = aws_iam_role.lambda_exec.arn
description = "IAM Role ARN for Lambda execution"
}

output "lambda_function_arn" {
value = aws_lambda_function.log_processor.arn
description = "ARN of the Lambda function"
}
45 changes: 45 additions & 0 deletions modules/lambda_log_processor/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
variable "lambda_function_name" {
type = string
description = "Name of the Lambda function"
}

variable "lambda_zip_path" {
type = string
description = "Path to the zipped Lambda package"
}

variable "opensearch_domain_arn" {
type = string
description = "ARN of the OpenSearch domain"
}

variable "opensearch_endpoint" {
type = string
description = "OpenSearch endpoint URL"
}

variable "slack_webhook_url" {
type = string
description = "Slack Webhook URL"
sensitive = true
}

variable "kms_key_arn" {
type = string
description = "KMS key for decrypting Slack secret (if encrypted)"
}

variable "bucket_arn" {
description = "ARN of the S3 bucket for CloudTrail logs"
type = string
}

variable "lambda_subnet_ids" {
description = "List of subnet IDs for the Lambda function to attach to the VPC"
type = list(string)
}

variable "lambda_security_group_ids" {
description = "Security group IDs for the Lambda function in the VPC"
type = list(string)
}
Loading
Loading