이전 기록을 찾아서 자료 공유합니다.
이전 자료이다보니 UI가 최신이 아니에요. 소스코드를 중점으로 보시길~
제 기준으로 AWS의 정보보안 업무를 위해서 모니터링을 진행한 코드입니다.
각 담당자 분들 상황에 맞게 소스코드 수정해서 사용하시면 좋을 것 같아요.
모니터링 구성도
aws cloudtrail 추적 생성
CloudTrail → CloudWatch → Lambda로 로그 전송 설정
람다 설정
생성 함수는 CloudWatch Logs → Lambda로 전달 받을 수 있어야 함.
Runtime은 Node.js로 설정
소스코드 (본인에 맞게 수정해서 활용하세요!!!)
//------------------------------------------------------------------------------------------------
// Import Library
//------------------------------------------------------------------------------------------------
var https = require('https');
var util = require('util');
var zlib = require('zlib');
//------------------------------------------------------------------------------------------------
// Prototype Overriding
//------------------------------------------------------------------------------------------------
Date.prototype.format = function(f) {
if (!this.valueOf()) return " ";
var weekName = ["일요일", "월요일", "화요일", "수요일", "목요일", "금요일", "토요일"];
var d = this;
return f.replace(/(yyyy|yy|MM|dd|E|hh|mm|ss|a\/p)/gi, function($1) {
switch ($1) {
case "yyyy": return d.getFullYear();
case "yy": return (d.getFullYear() % 1000).zf(2);
case "MM": return (d.getMonth() + 1).zf(2);
case "dd": return d.getDate().zf(2);
case "E": return weekName[d.getDay()];
case "HH": return d.getHours().zf(2);
case "hh": return ((d.getHours() % 12) ? d.getHours() : 12).zf(2);
case "mm": return d.getMinutes().zf(2);
case "ss": return d.getSeconds().zf(2);
case "a/p": return d.getHours() < 12 ? "오전" : "오후";
default: return $1;
}
});
};
String.prototype.string = function(len){var s = '', i = 0; while (i++ < len) { s += this; } return s;};
String.prototype.zf = function(len){return "0".string(len - this.length) + this;};
Number.prototype.zf = function(len){return this.toString().zf(len);};
//------------------------------------------------------------------------------------------------
// Global Variable Define
//------------------------------------------------------------------------------------------------
var FINAL_CONTEXT;
var attachment = {
"title" : null,
"color" : null,
"pretext" : null,
"title_link" : null,
"text" : null,
};
//------------------------------------------------------------------------------------------------
// Function Define
//------------------------------------------------------------------------------------------------
// function makeSlackAttachment(title, body, color){
function makeSlackAttachment(body, color){ // <- Slack 메시지 알림시 title 제거해야 보기가 좋음
var newAttachment = attachment.constructor();
// newAttachment.title = title;
newAttachment.text = body;
newAttachment.color = color;
return newAttachment;
}
// slackMessage를 게시하도록 요청
function sendSlackMessage(attachments, channel) {
// console.log("\n 슬랙 메세지 전송할 내용 >>> " + JSON.stringify(attachments));
var slackApiBody = {
"channel": "aws_log",
"username": "CloudTrail",
"text": null,
"icon_emoji": ":cloudtrail:",
"attachments" : []
};
// var newSlackApiBody = slackApiBody; // .constructor();
slackApiBody.attachments.push(attachments);
// newSlackApiBody.channel = channel;
try {
console.log("\n 최종 바디 메세지 >>>> " + JSON.stringify(slackApiBody));
var options = {
method: 'POST',
hostname: 'hooks.slack.com',
port: 443,
path: '???????????????????????????????????????????????????'
};
var req = https.request(options, function (res) {
res.setEncoding('utf8');
res.on('data', function (chunk) {
console.log('Slack Message SUCCESS !!');
// FINAL_CONTEXT.done('Slack Message SUCCESS !!');
FINAL_CONTEXT.succeed('Slack Message SUCCESS !!');
});
});
req.on('error', function (e) {
console.log('problem with request: ' + e.message);
FINAL_CONTEXT.fail('Slack Message FAIL !!');
});
req.write(util.format("%j", slackApiBody));
req.end();
console.log('Slack Message Request complete !!');
} catch (error) {
console.log('Slack Send Exception : ' + error.Message);
FINAL_CONTEXT.fail('Slack Message FAIL !!');
} finally {
console.log('Slack Message Request END !!');
// FINAL_CONTEXT.done('Slack Message END !!');
}
}
// CloudWatch Log는 zlib를 통해 복호화 과정을 거쳐야만 해석이 가능하다.
function cloudWatchUnzip(input) {
// console.log(input);
var payload = new Buffer(input.awslogs.data, 'base64');
zlib.gunzip(payload, function(e, result) {
if (e) {
FINAL_CONTEXT.fail(e);
} else {
result = JSON.parse(result.toString('ascii'));
if (result.logEvents != undefined) {
loopEvents(result.logEvents);
} else {
console.log("\n cloudWatchUnzip Result is >>>> " + JSON.stringify(result));
console.log("\n cloudWatchUnzip Type Is Un Correct !! [ 예상하지 못한 유형의 Result ] !! ");
}
}
});
}
// 정책 내 전체 허용 값의 존재 유무
function ipOpenPublic(ipArray, eventName) {
var isPublic = false;
if (ipArray != undefined) {
for (var j = 0; j < ipArray.length; j++) {
var targetIp = (ipArray[j].cidrIp != undefined) ? ipArray[j].cidrIp : ipArray[j].cidrIpv6;
// 전체 허용한 정책이 있는 경우, 경고메세지 추가
if (eventName === "AuthorizeSecurityGroupIngress" && (targetIp === "0.0.0.0/0" || targetIp === "::/0")) {
isPublic = true;
break;
}
}
}
return isPublic;
}
// Target IP 에 대한 메세지를 생성한다.
function addMessageFromIpArray(ipArray, originMessage, ipMessageHead, eventName) {
if (ipArray != undefined) {
for (var j = 0; j < ipArray.length; j++) {
var cidrIp = ipArray[j].cidrIp;
var ipMessage = ipMessageHead + " / 대상: " + cidrIp;
// 전체 허용한 정책이 있는 경우, 경고메세지 추가
if (eventName === "AuthorizeSecurityGroupIngress" && (cidrIp === "0.0.0.0/0" || cidrIp === "::/0")) {
ipMessage = ipMessage + " - 대상 지정 필요 여부 확";
}
originMessage = addMessage(originMessage, ipMessage);
}
}
return originMessage;
}
// 실제 이벤트를 해석하여 각 이벤트명에 따라 구분 동작하도록 하는 함수이다.
function parsingEvent(eventLog) {
// 실제 원하는 로그는 로그이벤트 내 Message 항목이다.
var log = eventLog;
var oneHour = 3600000;
var koreaOffsetHour = 9;
console.log("\n\n 실제 이벤트 로그 >> " + JSON.stringify(log) + "\n\n");
// 로그 공통 포맷
var utc = new Date(log.eventTime).getTime() + (new Date(log.eventTime).getTimezoneOffset() * 60000);
var exetime = new Date(utc + (oneHour*koreaOffsetHour)).format("yyyy년 MM월 dd일 a/p hh시 mm분 ss초");
var sourceip = log.sourceIPAddress;
var eventName = log.eventName;
var exeregion = log.awsRegion;
var userType = log.userIdentity.type;
var useragent = (log.userAgent === undefined) ? "" : log.userAgent;
var username = (log.userIdentity.type === "IAMUser") ? "Account: " + log.userIdentity.userName + " (" + sourceip + ")" : "Account: Root (" + sourceip + ")";
// var channel = (log.userIdentity.type == "IAMUser") ? "@" + log.userIdentity.userName : "@sangmin";
var channel = "aws_log";
// 기본 공통 메세지
var headMessage = `${username}`;
var tailMessage1 = `\n Event Time: ${exetime}`;
var tailMessage2 = `\n Region: ${exeregion} `;
var detailMessage = "";
// 슬랙 메세지 양식
var slackTitle;
var slackBody;
var slackColor = "good"; // danger:빨간색 | warning:주황색 | good:녹색
if (eventName != undefined && (eventName).indexOf('Create') > -1) {
if (eventName != "CreateLogStream"){
// Create 값이 있는 경우
console.log("로그수집 >>>> " + JSON.stringify(log));
}
}
// 채널이 있는 경우이거나, 사용자가 Root나 User가 아닌 경우이거나, 회사 IP가 아닌 경우
// if (channel != "" && (userType === "Root" || userType === "IAMUser") && (sourceip != "121.165.242.121")) {
if (channel != "" && (userType === "Root" || userType === "IAMUser")) {
// EC2 인스턴스를 실행한 경우
if (eventName === "RunInstances") {
var items = log.responseElements.instancesSet.items;
for (var i = 0; i < items.length; i++) {
var exeinstance = items[i].instanceId;
slackTitle = "EC2 인스턴스 생성"
slackColor = "good";
detailMessage = `\n 시작된 인스턴스 ID: ${exeinstance} (${exeregion})`;
slackBody = `${slackTitle} \n` + headMessage + tailMessage1 + detailMessage;
sendSlackMessage(makeSlackAttachment(slackBody, slackColor), channel);
// sendSlackMessage(makeSlackAttachment(slackTitle, slackBody, slackColor), channel);
}
}
// EC2 인스턴스를 중지한 경우
if (eventName === "StopInstances") {
var items = log.responseElements.instancesSet.items;
for (var i = 0; i < items.length; i++) {
var exeinstance = items[i].instanceId;
slackTitle = "EC2 인스턴스 중지"
slackColor = "danger";
detailMessage = `\n 중지된 인스턴스 ID: ${exeinstance} (${exeregion})`;
slackBody = `${slackTitle} \n` + headMessage + tailMessage1 + detailMessage;
sendSlackMessage(makeSlackAttachment(slackBody, slackColor), channel);
// sendSlackMessage(makeSlackAttachment(slackTitle, slackBody, slackColor), channel);
}
}
// EC2 인스턴스를 종료한 경우
if (eventName === "TerminateInstances") {
var items = log.responseElements.instancesSet.items;
for (var i = 0; i < items.length; i++) {
var exeinstance = items[i].instanceId;
slackTitle = "EC2 인스턴스 종료"
slackColor = "danger";
detailMessage = `\n 종료된 인스턴스 ID: ${exeinstance} (${exeregion})`;
slackBody = `${slackTitle} \n` + headMessage + tailMessage1 + detailMessage;
sendSlackMessage(makeSlackAttachment(slackBody, slackColor), channel);
// sendSlackMessage(makeSlackAttachment(slackTitle, slackBody, slackColor), channel);
}
}
// IAM을 이용하여 User를 생성한 경우
if (eventName === "CreateUser") {
var createuser = log.responseElements.user.userName;
slackTitle = "User 생성"
slackColor = "good";
detailMessage = `\n 생성된 User ID: ${createuser} (${exeregion})`;
slackBody = `${slackTitle} \n` + `User를 생성한 ${headMessage}` + tailMessage1 + detailMessage;
sendSlackMessage(makeSlackAttachment(slackBody, slackColor), channel);
// sendSlackMessage(makeSlackAttachment(slackTitle, slackBody, slackColor), channel);
}
// IAM을 이용하여 User를 Group에 넣은 경우
if (eventName === "AddUserToGroup") {
var createuser = log.requestParameters.userName;
var usergroup = log.requestParameters.groupName;
slackTitle = "User 그룹 지정"
slackColor = "good";
detailMessage = `\n User: ${createuser} (${exeregion}) \n User Group: ${usergroup}`;
slackBody = `${slackTitle} \n` + `User Group을 지정한 ${headMessage}` + tailMessage1 + detailMessage;
sendSlackMessage(makeSlackAttachment(slackBody, slackColor), channel);
// sendSlackMessage(makeSlackAttachment(slackTitle, slackBody, slackColor), channel);
}
// RDS를 생성한 경우
else if (eventName === "CreateDBInstance") {
slackTitle = "RDS 생성";
slackColor = "good";
detailMessage = `\n DB Name:` + log.responseElements.dBName + ` \ DB Engine :` + log.responseElements.engine;
slackBody = `${slackTitle} \n` + headMessage + tailMessage1 + tailMessage2 + `( ${exeregion})` + detailMessage;
sendSlackMessage(makeSlackAttachment(slackBody, slackColor), channel);
// sendSlackMessage(makeSlackAttachment(slackTitle, slackBody, slackColor), channel);
}
// DynamoDB 테이블을 생성한 경우
else if (log.eventSource === "dynamodb.amazonaws.com" && eventName == "CreateTable") {
slackTitle = "DynamoDB 테이블 생성";
slackColor = "good";
detailMessage = `\n Table Name: ` + log.requestParameters.tableName;
slackBody = `${slackTitle} \n` + headMessage + tailMessage1 + `( ${exeregion})` + detailMessage;
sendSlackMessage(makeSlackAttachment(slackBody, slackColor), channel);
// sendSlackMessage(makeSlackAttachment(slackTitle, slackBody, slackColor), channel);
}
// Elasticache를 생성한 경우
else if (log.eventSource === "elasticache.amazonaws.com" && eventName == "CreateCacheCluster") {
slackTitle = "Elasticache 생성";
slackColor = "good";
detailMessage = `\n Elasticache Engine : ` + log.requestParameters.engine;
slackBody = `${slackTitle} \n` + headMessage + tailMessage1 + `( ${exeregion})` + detailMessage;
sendSlackMessage(makeSlackAttachment(slackBody, slackColor), channel);
// sendSlackMessage(makeSlackAttachment(slackTitle, slackBody, slackColor), channel);
}
// S3 버킷을 생성한 경우
else if (log.eventSource === "s3.amazonaws.com" && eventName == "CreateBucket") {
slackTitle = "S3 버킷 생성";
slackColor = "good";
detailMessage = `\n S3 Bucket Name : ` + log.requestParameters.bucketName;
slackBody = `${slackTitle} \n` + headMessage + tailMessage1 + `( ${exeregion})` + detailMessage;
sendSlackMessage(makeSlackAttachment(slackBody, slackColor), channel);
// sendSlackMessage(makeSlackAttachment(slackTitle, slackBody, slackColor), channel);
}
// 콘솔을 로그인한 경우
// else if (eventName === "ConsoleLogin" && log.responseElements.ConsoleLogin == "Success") {
else if (eventName === "ConsoleLogin") {
var loginResult = (log.responseElements.ConsoleLogin == "Success") ? "성공" : "실패";
slackTitle = "AWS Console 로그인 " + loginResult;
slackColor = (log.responseElements.ConsoleLogin == "Success") ? "good" : "danger";
slackBody = `\n ${slackTitle} \n ${username} \n 접속 시간: ${exetime}`;
sendSlackMessage(makeSlackAttachment(slackBody, slackColor), channel);
// sendSlackMessage(makeSlackAttachment(slackTitle, slackBody, slackColor), channel);
}
// SecurityGroup의 정책을 수정하거나 생성할 경우 정책이 ALL로 Open 된 경우
else if (eventName === "AuthorizeSecurityGroupIngress" || eventName === "RevokeSecurityGroupIngress") {
var items = log.requestParameters.ipPermissions.items;
var sgname = log.requestParameters.groupId;
for (var i = 0; i < items.length; i++) {
var toport = items[i].toPort;
var ipV4Array = items[i].ipRanges.items;
var ipV6Array = items[i].ipv6Ranges.items;
// 80 또는 443포트가 아닌데, IP 전체 허용을 한 경우에만 SlackMessage 발송되도록 처리
if (toport != "80" && toport != "443" && (ipOpenPublic(ipV4Array, eventName) || ipOpenPublic(ipV6Array, eventName))) {
var protocol = items[i].ipProtocol;
var fromport = items[i].fromPort;
var action = (eventName === "AuthorizeSecurityGroupIngress") ? "추가 / 변경" :"삭제";
var ipMessageFormat = `\n SecurityGroup Name: ${sgname} (${exeregion}) \n Inbound / ${protocol} / ${fromport} -> ${toport}`;
detailMessage = addMessageFromIpArray(ipV6Array, detailMessage, ipMessageFormat, eventName); // IP v6
detailMessage = addMessageFromIpArray(ipV4Array, detailMessage, ipMessageFormat, eventName); // IP v4
slackTitle = `Security Group 정책 ${action}`;
slackColor = (eventName === "AuthorizeSecurityGroupIngress") ? "warning" :"good";
slackBody = `${slackTitle} \n` + headMessage + detailMessage;
sendSlackMessage(makeSlackAttachment(slackBody, slackColor), channel);
// sendSlackMessage(makeSlackAttachment(slackTitle, slackBody, slackColor), channel);
}
}
}
else {
console.log("EventName 해당사항 없음 !! >>>> " + JSON.stringify(log));
}
}
}
// 이벤트가 Array형태를 갖는 경우, 반복문을 통해 각각의 이벤트를 파싱하도록 한다.
function loopEvents(logEvents) {
// 로그 이벤트가 여러개이므로, 반복하여 각 이벤트를 뽑아 낸다.
logEvents.forEach(function(element) {
if (element.eventName != undefined) {
parsingEvent(element);
} else if (element.message != undefined) {
console.log("\n element.message is >>>> " + JSON.stringify(JSON.parse(element.message)));
parsingEvent(JSON.parse(element.message));
} else {
console.log("\n element is >>>> " + JSON.stringify(element));
console.log("\n element Type Is Un Correct !! [ 예상하지 못한 유형의 element ] !! ");
}
});
}
// mainMessage 의 유무에 따라, 메세지를 합친다.
function addMessage(mainMessage, addMessage) {
// 사실 구분할 필요 없음
if (mainMessage === "") {
return addMessage;
} else {
return mainMessage = mainMessage + "\n" + addMessage;
}
}
//------------------------------------------------------------------------------------------------
// S T A R T !!
//------------------------------------------------------------------------------------------------
// Lamda에서 호출하는 핸들러 (시작점)
exports.handler = function(input, context) {
console.log("\n Lamda Trigger Event Catch !! \n input is >>>> " + JSON.stringify(input));
FINAL_CONTEXT = context;
if (input.Records != undefined) {
console.log("\n Input from [ S3 ] !! ");
loopEvents(input.Records);
} else if (input.awslogs != undefined && input.awslogs.data != undefined) {
console.log("\n Input from [ CloudWatch ] !! ");
cloudWatchUnzip(input);
} else if (input.eventName != undefined){
console.log("\n Input from [ JSON TEST ] !! ");
parsingEvent(input);
} else {
console.log("\n Input Type Is Un Correct !! [ 예상하지 못한 유형의 Input ] !! ");
}
// context.succeed();
}