이전 기록을 찾아서 자료 공유합니다.

이전 자료이다보니 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();
      
}

 

'기술 노트 > AWS' 카테고리의 다른 글

AWS 콘솔 점검 스크립트  (0) 2023.02.17

+ Recent posts