사용자 계정이 무분별하게 생성 또는 삭제 되는것은 매우 위험한 행위이다.
그렇기에 IAM + CloudTrail + CloudEventBridge + Lambda를 통해 Slack으로 알람 받는 방법에 대해 알아보자!!
전체적인 로직은 다음과 같다.
Slack알람 조건은 1. IAM 계정이 생성 또는 삭제되는 경우 2. AWS Console Login하는 경우이다.
1. Slack webhook 설정
가장 먼저 Slack webhook을 설정해야 한다. webhook은 해당 slack 채널로 메시지를 전송할 수 있는 Key를 의미한다.
최종적으로는 Lambda를 통해 Slack으로 메시지를 보내야 하므로 꼭 필요하다. 그리고 다른 사람에게 유출해서는 안된다.
2. Slack 알람 설정
CloudTrail은 AWS에서 발생하는 모든 API 사용 이력을 저장하는곳이다.
물론 IAM 계정 생성/삭제 그리고 Console Login도 API 기반이기에 CloudTrail에 모두 기록된다.
이미 사용하고 있는 CloudTrail이 있다면, 해당 CloudTrail을 사용해도 되고 아니면 신규로 만들어도 무방하다.
1) CloudTrail 생성
생성하는 방법은 어렵지 않다. 물론 기업이라면 조금 더 설정해야 할 것이 있겠지만 실습하는 환경이므로 별도 설정없이 계속 다음을 누른다.
2) CloudWatch 로그그룹 생성
CloudTrail의 로그를 받을 수 있는 CloudWatch 로그그룹을 만든다.
로그그룹 생성에는 다른 입력 없이 로그 그룹 이름만으로 간단하게 생성할 수 있다.
(만약 기존 로그그룹을 사용한다면, 별도로 생성하지 않아도 된다.)
3) CloudWatch 로그그룹 설정(From. CloudTrail)
우리의 목표는 CloudTrail에서 발생하는 로그를 CloudWatch 로그 그룹으로 전송하고 최종적으로 EventBridge에서 특정 규칙을 트리거 삼아서 Slack 알람을 보내는 Lambda를 호출하는 것이 목표이다. 그렇기 때문에 CloudTrail 로그를 CloudWatch로 전송해야 한다.
아래 화면에서 CloudWatch Logs를 활성화 한 다음에 로그 그룹 이름을 지정하면 된다.
로그 그룹 이름은 위 2)로그그룹 생성에서 생성한 로그그룹 이름을 지정하면 된다.
그리고 역할은 기본으로 생성되어 있는 "CloudTrail_CloudWatchLogs_Role"을 입력하면 된다.
해당 Role은 IAM에 기본적으로 생성되어 있는 역할로 로그 스트림을 만들고, 로그 이벤트를 전송할 수 있는 권한이 있다.
4) CloudEventBridge 설정
CloudWatch 로그그룹까지 설정했으면 절반이상은 온것이다!!
CloudEventBridge에서는 CloudWatch로 전달받는 로그 중 내가 트리거로 삼을 Event 규칙에 대해 지정하는 것이다.
Event가 들어올때마다 Slack 알람을 보내야하므로 규칙유형은 "이벤트 패턴이 있는 규칙"을 설정한다.
그리고 이벤트 패턴을 아래와 같이 수정한다.
이벤트 유형을 CloudTrail로 설정한 다음에 내가 트리거로 삼을 EventName을 지정하면 된다.
필자는 계정 생성(CreateUser), 계정 삭제(DeleteUser), Root 로그인(ConsoleLogin)을 트리거로 하고자 한다.
이후 해당 EventName이 들어왔을 때, 수행을 할 대상을 정하면 된다. Lambda 함수를 통해 Slack으로 알람을 보낼 예정이므로 Lambda를 선택한 다음에 Slack으로 알람을 보낼 함수를 선택하면 된다!!
5) Slack 알람 전송 Lambda 생성
IAM이 글로벌서비스이므로 us-east-1에 Lambda를 생성해야 한다!! 처음에 이걸 몰라서.. 몇시간이나 허비했다.
Lambda 함수를 생성하고, CloudEventBridge를 연결하면 위 그림처럼 트리거탭에 EventBridge가 추가된다.
그리고 트리거는 위 4)에서 지정한 CloudTrail의 특정 Event Name이다.
이제 마지막은 Lambda 함수를 생성하는 것이다!
필자가 작성한 코드이다. 별 볼일 없지만 바쁜 업무담당자를 위해 준비하였다..ㅎㅎ 마음껏 쓰세요~
import boto3
import json
import logging
import os
from base64 import b64decode
from urllib.request import Request, urlopen
from urllib.error import URLError, HTTPError
# SLACK 환경 설정
SLACK_CHANNEL = os.environ['SLACK_CHANNEL']
HOOK_URL = os.environ['HOOK_URL']
logger = logging.getLogger()
logger.setLevel(logging.INFO)
# SLACK 메시지 보내기
def send_message(message):
req = Request(HOOK_URL, data = json.dumps(message).encode('utf-8'))
# 에러 발생 시, 에러 로그 남기기
try:
response = urlopen(req)
response.read()
logger.info("Message posted to %s", SLACK_CHANNEL)
except HTTPError as e:
logger.error("Request failed: %d %s", e.code, e.reason)
except URLError as e:
logger.error("Server connection failed: %s", e.reason)
# EventBridge에서 오는 로그 파싱
# 실제 운영에서는 아래 변수에서 각 변수마다['detail'] 추가
def lambda_handler(event, context):
event_time = event['eventTime']
event_type = event['eventName']
#Slack 어디로 보냈는지 확인하기
logger.info("Event : " + str(event))
logger.info("SLACK Channel: #" + SLACK_CHANNEL)
logger.info("HOOK URL : " + HOOK_URL)
#Delete User면 강조하기 위해 붉은색으로 변경
if (event_type.find("DeleteUser") >= 0) or event_type.find("ConsoleLogin") >= 0 :
color = "#eb4034"
else:
color = "#0c3f7d"
#IAM 계정 삭제 또는 생성에 대한 알림
if (event_type.find("DeleteUser") >=0) or (event_type.find("CreateUser") >= 0):
host = event['userIdentity']['userName']
iam_user = event['requestParameters']['userName']
if event_type.find("DeleteUser") >= 0 :
event_type2 = " deleted a "
elif event_type.find("CreateUser") >= 0 :
event_type2 = " created a "
slack_message = {
"channel": SLACK_CHANNEL,
"attachments": [{
"color": color,
"blocks": [
{
"type": "section",
"fields": [
{
"type": "mrkdwn",
"text": '*Event Type:*\n' + event_type
},
{
"type": "mrkdwn",
"text": '*Host Name:*\n' + host
},
{
"type": "mrkdwn",
"text": '*Account Name:*\n' + iam_user
},
{
"type": "mrkdwn",
"text": '*Event Time:*\n' + event_time
}
]
}
]
}],
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": iam_user + ' account is changed by ' + host
}
},
{
"type": "divider"
}
]
}
# IAM Root 계정 콘솔 로그인에 대한 알림
elif event_type.find("ConsoleLogin") >= 0 :
event_type2 = " account access "
root = event['userIdentity']['type']
res = event['responseElements']['ConsoleLogin']
mfa = event['additionalEventData']['MFAUsed']
ip_addr = event['sourceIPAddress']
slack_message = {
"channel": SLACK_CHANNEL,
"attachments": [{
"color": color,
"blocks": [
{
"type": "section",
"fields": [
{
"type": "mrkdwn",
"text": '*Event Type:*\n' + event_type
},
{
"type": "mrkdwn",
"text": '*IAM account:*\n' + root
},
{
"type": "mrkdwn",
"text": '*Login result:*\n' + res
},
{
"type": "mrkdwn",
"text": '*MFA use:*\n' + mfa
},
{
"type": "mrkdwn",
"text": '*Access ip:*\n' + ip_addr
}
]
}
]
}],
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": root + event_type2 + res + ' in ' + ip_addr
}
},
{
"type": "divider"
}
]
}
send_message(slack_message)
그리고 코드를 보면 HOOK_URL, SKACL_CHANNEL 환경 변수를 지정해줘야 한다.
위 1.에서 생성한 Slack Webhook을 넣으면 된다.
3. 테스트
테스트를 하고, 실제 이벤트를 발생시키면 아래와 같이 아주 이쁜 Slack 알람이 생기는걸 볼 수 있다!!
끝!!! Slack 채널을 통해서 위와 같은 알람을 받으면 굳이 담당자가 IAM을 모니터링할 필요가 없으니까
해당 시간에 다른 업무를 진행하면되기에 업무 효율이 올라간다!!
AWS 너무 어려워효