Untitled

오늘은 IaC 마무리 작업 후 아키텍처 발표 준비를 한다.

data "aws_caller_identity" "current" {}
data "aws_region" "current" {}

terraform 코드에는 aws configure에 등록된 리전과 계정아이디를 데이터로 가져올 수 있는 코드가 있다. 이 코드는 apply하는 폴더의 모든 테라폼 코드에 들어가는 것이 아니라 하나에만 들어가면 데이터를 가져올 수 있다.

import gzip
import json
import base64
import urllib.request
import datetime
import pytz
import re
import os
from base64 import b64decode
import boto3

def send_email(message):
    CHARSET = "UTF-8"

    ses_client = boto3.client('ses', region_name='ap-northeast-2')
    sender_email = os.environ['SENDER_EMAIL']
    recipient_emails = os.environ['RECIPIENT_EMAILS'].split(',')
    message = message.replace("\\n>", "</p><p>").replace(">*","<h1>").replace("*</p>","</h1>").replace(" `", "<span style=\\"color:red;font-weight:bold;\\">").replace("`", "</span>")

    response = ses_client.send_email(
        Source=sender_email,
        Destination={
            'ToAddresses': recipient_emails
        },
        Message={
            'Subject': {
                'Data': "[🚨SECURITY NOTIFICATION🚨]"
            },
            'Body': {
                'Html': {
                    'Charset': CHARSET,
                    'Data': message
                }
            }
        }
    )

# slack에 메시지 전송
def post_slack(argStr):
    message = argStr
    send_data = {
        "text": message,
    }
    send_text = json.dumps(send_data)

    slack_url = os.environ['SLACK_URL']
    request = urllib.request.Request(
        slack_url, 
        data=send_text.encode('utf-8'), 
    )

    with urllib.request.urlopen(request) as response:
        message = response.read()        

def lambda_handler(event, context):
    if 'awslogs' in event:
        cw_data = event['awslogs']['data']

        # base64 디코딩
        compressed_payload = base64.b64decode(cw_data)
        uncompressed_payload = gzip.decompress(compressed_payload).decode('utf-8')
        payload = json.loads(uncompressed_payload)
        #print(f"[payload] {payload}")

        log_events = payload['logEvents']
        for log_event in log_events:
            #print(f'[LogEvent]: {log_event}')    
        
            messages = log_event['message']
            if messages is not None:
                #print(messages)
                messages = json.loads(messages)
            
            detail = messages['detail']
            if detail is not None:
                #print(detail)
                findings = detail['findings']
            if findings is not None:
                #print(findings)
                message = ">*⚠️ SECURITY NOTIFICATION*\\n>\\n>\\n"

                for finding in findings:
                    # 보고 구성 항목
                    ## 주요 항목
                    types = finding['Types'][0]
                    title = finding['Title']
                    severity_label = finding['Severity']['Label']
                    severity_score = finding['Severity']['Normalized']

                    target_info = ""
                    for resource in finding['Resources']:
                        # 리소스 마스킹처리
                        pat = re.compile("(\\d{12})")
                        target = pat.sub("************", resource['Id'])
                        target_info += f">리소스 ID: {target}\\n>리소스 유형: {resource['Type']}\\n"

                    ## 세부 항목
                    # 시간 한국 시간으로 변경
                    dt = datetime.datetime.strptime(finding['CreatedAt'][:19], '%Y-%m-%dT%H:%M:%S')
                    KST = pytz.timezone("Asia/Seoul")
                    time = dt.astimezone(KST)

                    productName = f"{finding['CompanyName']} {finding['ProductName']}"
                    record_state = finding['RecordState']
                    workflow_status = finding['Workflow']['Status']

                    description = finding['Description'].replace("\\n", "")

                    # slack 메시지 생성        
                    message += f">*📌 취약점 정보*\\n>탐지 서비스: *{productName}*\\n>현지 발생 시각: {time} \\n>심각도 수준: `{severity_label}({severity_score}점)`\\n>취약점 유형: `{types}`\\n>취약점명: `{title}`\\n>\\n>\\n>*📌 취약점 발생 리소스*\\n{target_info}>\\n>\\n>*📌 취약점 현황*\\n>활성화 상태: {record_state}\\n>생성 상태: {workflow_status}\\n>설명: {description}\\n"

                    if 'Compliance' in finding:
                        message += f">준수 상태: {finding['Compliance']['Status']}\\n"
                    
                    if 'Remediation' in finding:
                        if 'Url' in finding['Remediation']:
                            message += f">참고 URL: {finding['Remediation']['Url']}\\n"

                    # inspector일 경우 디테일 내용 추가
                    if "Inspector" in productName:
                        vulnerability_info = ">\\n>\\n>*📌 취약점 상세 정보*\\n"
                        for vulnerability in finding['Vulnerabilities']:
                            vulnerability_info += f">CVE ID: {vulnerability['Id']}\\n>해결 가능 여부: {vulnerability['FixAvailable']}\\n>익스플로잇 가능 여부: {vulnerability['ExploitAvailable']}\\n>취약점 상세 내용 URL: {vulnerability['Vendor']['Url']}\\n"

                            for package_info in vulnerability['VulnerablePackages']:
                                vulnerability_info += f">해결 방법: `{package_info['Remediation']}`\\n"
                        message += vulnerability_info

                    #print(message)        
                post_slack(message)
                send_email(message)
    else:
        print("'awslogs' key not found in event data")

Lambda 함수의 수정이 있었다. python을 잘 못해서 팀원과 같이 했다.

최초에 함수 테스트를 진행할 때 더미데이터를 만들어서 진행하였을 때는 몰랐는데

securityhub에서 넘어오는 로그 데이터가 inspector, config, securityhub 모두 조금씩의 차이가 있었다. 해서 값이 있을 경우 추가로 데이터를 보여주고 없을 경우는 기본적으로 알려줘야 하는 데이터를 알림 메세지에 담을 수 있도록 했다.

# ses email 인증
resource "aws_ses_email_identity" "example" {
  email = var.email
}

Lambda 함수가 작동할 때 ses의 서비스를 이용하지는 않지만 이메일 인증이 필요하다.

해서 terraform apply 시 이메일 인증이 갈 수 있도록 추가했다.

# securityhub 로그 그룹 아웃풋
output "securityhub_log_output" {
        value = "aws_cloudwatch_log_group.securityhub_logs.arn"
}

grafana의 데시보드에 정보를 전달하기 위해 CloudWatch Log Group의 arn 주소가 필요해 생성해주는 코드를 추가했다.

resource "aws_budgets_budget" "FinalProject_team9" {
  name              = "FinalProject_team9_budget"
  budget_type       = "COST"
  limit_amount      = "130" #할당 요금
  limit_unit        = "USD"
  time_period_end   = "2023-06-30_00:00" #기간 종료
  time_period_start = "2023-06-17_00:00" #기간 시작
  time_unit         = "MONTHLY" #월별

  notification {
    comparison_operator        = "GREATER_THAN"
    threshold                  = 100
    threshold_type             = "PERCENTAGE"
    notification_type          = "FORECASTED"
    subscriber_email_addresses = var.SUBSCRIBER_EMAIL_ADDRESSES
  }
}