Slack/Teams GuardDuty에 대한 위협 탐지 결과 알림

개시하다


나는 생산기술부에서 제품 검사 공정을 책임지는 엔지니어다.AWS의 보안에 대응하기 위해 GuardDuty를 사용했습니다.GuardDuty를 사용하면 악의적인 활동과 비정상적인 동작을 지속적으로 모니터링할 수 있습니다.하지만 검출된 결과에 주의하지 않으면 의미가 없다.결과를 메신저에 전달함으로써 조속히 대응하는 체제를 목표로 한다.

Slack/Teams에 대한 알림 방법


위협이 감지된 결과는 자동으로 이벤트브릿지(구 Amazon CloudWatch Events)로 전송되기 때문에 이벤트브릿지로 이벤트를 트리거합니다.
  • 슬랙을 사용할 때 이벤트브릿지에서 SNS로 전달한다.슬랙은 챗봇과 협업할 수 있기 때문에 챗봇을 SNS 타깃으로 삼았다.
  • 팀 사용 시 엠바다를 이벤트브릿지의 목표로 삼고, 엠바다 가공 결과를 통해 인컴핑 웹훅을 통해 팀스로 전달한다.

  • Slack 사용 시 Cloudformation


    GuardDuty는 클라우드 트레일, Kubernetes, VPC 프로세스 로그, DNS 로그 등에 따라 위협 검사를 한다.그러나 이러한 설정은 필요하지 않습니다. GuardDuty를 사용하면 위협 탐지가 자동으로 시작됩니다.EventBridge는 다음 이벤트에 대한 규칙을 설정하고 Target에서 SNS 주제를 지정합니다.
    https://docs.aws.amazon.com/ja_jp/guardduty/latest/ug/guardduty_findings_cloudwatch.html
    SNS의 끝점은 Chatbot의 APIhttps://global.sns-api.chatbot.amazonaws.com를 지정합니다.ChatBot은 AWS 콘솔에서 Slack과 사전에 연결합니다.Cloudformation의 ChatBot에서 다음을 설정합니다.
  • SNS 화제의 ARN을 SnsTopicArns에 지정합니다.
  • SlackChannelId를 지정합니다.대상의 Slack 채널을 마우스 오른쪽 버튼으로 클릭하고 링크 복사를 선택합니다.복사된 URL의 마지막 슬래시 이후 문자열은 객체의 Id입니다.https://sample.slack.com/archives/XXXXXXXXXXX
  • Slack Workspace Id를 지정합니다.ChatBot의 AWS 콘솔에서 확인할 수 있습니다.
  • 이러한 Id는 매개변수 저장소에 저장되어 읽은 후에 사용됩니다.
    AWSTemplateFormatVersion: "2010-09-09"
    Description: Guard Duty
    # ------------------------------------------------------------------------------
    # Resources
    # ------------------------------------------------------------------------------
    Resources:
      GuardDuty:
        Type: AWS::GuardDuty::Detector
        Properties:
          Enable: True
          FindingPublishingFrequency: FIFTEEN_MINUTES
      EventRule:
        Type: AWS::Events::Rule
        Properties:
          Description: guardduty notification
          Name: guardduty-notification
          EventPattern:
            source:
              - aws.guardduty
            detail-type:
              - GuardDuty Finding
          State: ENABLED
          Targets:
            - Arn: !Ref Topic
              Id: sns-topic
      Topic:
        Type: AWS::SNS::Topic
      Subscription:
        Type: AWS::SNS::Subscription
        Properties:
          Protocol: https
          TopicArn: !Ref Topic
          Endpoint: "https://global.sns-api.chatbot.amazonaws.com"
      ChatBot:
        Type: AWS::Chatbot::SlackChannelConfiguration
        Properties:
          ConfigurationName: GuardDutyNotification
          GuardrailPolicies:
            - "arn:aws:iam::aws:policy/ReadOnlyAccess"
          IamRoleArn: !GetAtt ChatBotRole.Arn
          LoggingLevel: NONE
          SlackChannelId: "{{resolve:ssm:GuardDutySlackChannelId:1}}"
          SlackWorkspaceId: "{{resolve:ssm:GuardDutySlackWorkspaceId:1}}"
          SnsTopicArns:
            - !Ref Topic
          UserRoleRequired: false
      ChatBotRole:
        Type: AWS::IAM::Role
        Properties:
          AssumeRolePolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Principal:
                  Service: chatbot.amazonaws.com
                Action: sts:AssumeRole
          ManagedPolicyArns:
            - arn:aws:iam::aws:policy/ReadOnlyAccess
    

    Slack에 대한 알림 확인


    Chatbot과 Slack 사이의 소통은ChatAWS 콘솔에서'테스트 메시지 보내기'를 클릭하면 확인할 수 있다.Slack에서 다음 메시지를 보냅니다.

    GuardDuty에서 Slack까지의 소통은 GuardDuty의AWS 콘솔에서 설정한'결과 샘플의 생성'을 클릭하면 결과의 샘플을 자동으로 생성하고 확인할 수 있다.Slack에서 다음 메시지를 보냅니다.새로운 결과는 5분 이내에 EventBridge에 전송됩니다.

    Teams 사용 시 Cloudformation


    슬랙에 알릴 때와 달리 이벤트 브릿지의 목표는 람바다로 설정됩니다.
    AWSTemplateFormatVersion: "2010-09-09"
    Description: Guard Duty
    # ------------------------------------------------------------------------------
    # Resources
    # ------------------------------------------------------------------------------
    Resources:
      GuardDuty:
        Type: AWS::GuardDuty::Detector
        Properties:
          Enable: True
          FindingPublishingFrequency: FIFTEEN_MINUTES
      EventRule:
        Type: AWS::Events::Rule
        Properties:
          Description: guardduty notification
          Name: guardduty-notification
          EventPattern:
            source:
              - aws.guardduty
            detail-type:
              - GuardDuty Finding
          State: ENABLED
          Targets:
            - Arn: !GetAtt Function.Arn
              Id: lambda
      EventPermission:
        Type: AWS::Lambda::Permission
        Properties:
          Action: lambda:InvokeFunction
          FunctionName: !Ref Function
          Principal: events.amazonaws.com
          SourceAccount: !Ref AWS::AccountId
          SourceArn: !GetAtt EventRule.Arn
      Function:
        Type: AWS::Lambda::Function
        Properties:
          Handler: guardduty-notification
          Role: !GetAtt LambdaRole.Arn
          Code:
            S3Bucket: "{{resolve:ssm:S3BacketLambda:1}}"
            S3Key: guardduty-notification.zip
          Runtime: go1.x
          ReservedConcurrentExecutions: 1
          Timeout: 5
          TracingConfig:
            Mode: Active
          Environment:
            Variables:
              TeamsUrl: "{{resolve:ssm:TeamsUrl:1}}"
      LambdaRole:
        Properties:
          AssumeRolePolicyDocument:
            Statement:
              - Action:
                  - sts:AssumeRole
                Condition: {}
                Effect: Allow
                Principal:
                  Service: lambda.amazonaws.com
            Version: 2012-10-17
          ManagedPolicyArns:
            - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
          Tags:
            - Key: f-iot.service.name
              Value: lambda
        Type: AWS::IAM::Role
    

    Teams 사용 시 Lambda


    Event Bridge에서 받은 이벤트를 처리하고 환경 변수에서 TeamsUrl로 필요한 정보를 읽어 보냅니다.구현 단계는 GuardDuty에서 EventBridge에 전송된 이벤트의 샘플 제작 구조체를 참조하여 필요한 데이터를 추출하는 것입니다.Teams의 Webhook 데이터 형식으로 가공하여 POST를 진행하면 완성됩니다.
    /// main GuardDutyが脅威検出した結果を通知します。
    package main
    
    import (
    	"bytes"
    	"context"
    	"encoding/json"
    	"fmt"
    	"net/http"
    	"os"
    	"strconv"
    
    	"github.com/aws/aws-lambda-go/events"
    	"github.com/aws/aws-lambda-go/lambda"
    )
    
    type Service struct {
    	EventFirstSeen string `json:"eventFirstSeen"`
    	EventLastSeen  string `json:"eventLastSeen"`
    	Count          int    `json:"count"`
    }
    
    type GuardDutyEvent struct {
    	AccountId   string  `json:"accountId"`
    	Id          string  `json:"id"`
    	Type        string  `json:"type"`
    	Region      string  `json:"region"`
    	Service     Service `json:"service"`
    	Severity    float32 `json:"severity"`
    	Description string  `json:"description"`
    }
    
    type Fact struct {
    	Name  string `json:"name"`
    	Value string `json:"value"`
    }
    
    type Section struct {
    	Facts []Fact `json:"facts"`
    }
    
    type Target struct {
    	Os  string `json:"os"`
    	Uri string `json:"uri"`
    }
    
    type Link struct {
    	Type    string   `json:"@type"`
    	Name    string   `json:"name"`
    	Targets []Target `json:"targets"`
    }
    
    func HandleLambdaEvent(_ context.Context, event events.CloudWatchEvent) {
    	var guardDutyEvent GuardDutyEvent
    	if err := json.Unmarshal(event.Detail, &guardDutyEvent); err != nil {
    		os.Exit(1)
    	}
    	TeamsUrl := os.Getenv("TeamsUrl")
    
    	var color string
    	var servirityCategory string
    	if guardDutyEvent.Severity >= 7.0 {
    		color = "#ff0000"
    		servirityCategory = "High"
    	} else if guardDutyEvent.Severity >= 4.0 {
    		color = "#fd7e00"
    		servirityCategory = "MEDIUM"
    	} else {
    		color = "#0000ff"
    		servirityCategory = "LOW"
    	}
    
    	facts := []Fact{
    		{Name: "Finding type", Value: guardDutyEvent.Type},
    		{Name: "Description", Value: guardDutyEvent.Description},
    		{Name: "Severity", Value: servirityCategory},
    		{Name: "First Seen", Value: guardDutyEvent.Service.EventFirstSeen},
    		{Name: "Last Seen", Value: guardDutyEvent.Service.EventLastSeen},
    		{Name: "Threat Count", Value: strconv.Itoa(guardDutyEvent.Service.Count)},
    	}
    	sections := []Section{{Facts: facts}}
    
    	guarddutyURL := "https://console.aws.amazon.com/guardduty/home?region=" + guardDutyEvent.Region + "#/findings?search=id=" + guardDutyEvent.Id
    	targets := []Target{{Os: "default", Uri: guarddutyURL}}
    	links := []Link{{Type: "OpenUri", Name: "Jump To GuardDuty", Targets: targets}}
    
    	payload, err := json.Marshal(struct {
    		Summary         string    `json:"summary"`
    		Type            string    `json:"@type"`
    		ThemeColor      string    `json:"themeColor"`
    		Title           string    `json:"title"`
    		Sections        []Section `json:"sections"`
    		PotentialAction []Link    `json:"potentialAction"`
    	}{
    		Summary:         "Summary",
    		Type:            "MessageCard",
    		ThemeColor:      color,
    		Title:           "GuardDuty Finding | " + guardDutyEvent.Region + " | Account: " + guardDutyEvent.AccountId,
    		Sections:        sections,
    		PotentialAction: links,
    	})
    	if err != nil {
    		fmt.Println(err)
    		return
    	}
    
    	resp, err := http.Post(TeamsUrl, "application/json; charset=UTF-8", bytes.NewReader(payload))
    	if err != nil {
    		fmt.Println(err)
    		return
    	}
    	defer resp.Body.Close()
    	if resp.StatusCode != http.StatusOK {
    		fmt.Printf("HTTP: %v\n", resp.StatusCode)
    	}
    }
    
    func main() {
    	lambda.Start(HandleLambdaEvent)
    }
    

    Teams 알림 확인


    람바다와 팀스 간 소통은 람바다의 AWS 콘솔에서 테스트 이벤트를 만들어'테스트'를 클릭해 확인할 수 있다.테스트 이벤트 JSON은 이벤트브릿지의 AWS 콘솔에서 샌드박스로 이동해 샘플 이벤트를'GuardDuty Finding'으로 설정하면 샘플을 쉽게 얻을 수 있다.취득한 샘플에 따라 람다를 실행하면 팀스는 다음과 같은 정보를 보낸다.

    GuardDuty에서 Teams까지의 소통은 슬랙과 마찬가지로'결과 샘플 생성'을 누르면 확인할 수 있다.

    최후


    GuardDuty에서 Slack/Teams에 위협 검출 결과를 보내는 메커니즘을 실현했다.채트봇이면 편하고 람바다면 자유롭게 맞춤 제작이 가능하니 어디든 괜찮은 것 같아요.GuardDuty의 결과뿐만 아니라 CodePipeline의 실행 결과 등도 같은 방법으로 메신저에 알릴 수 있으니 꼭 시도해 보세요.

    좋은 웹페이지 즐겨찾기