Ssoon

[6주차] EKS Security - Real Time Threat Detection with Falco 본문

AWS EKS Workshop Study

[6주차] EKS Security - Real Time Threat Detection with Falco

구구달스 2023. 6. 2. 06:08
CloudNet@ 팀의 AWS EKS Workshop Study 6주차 정리입니다.
#EKS Immersion Workshop 의 내용입니다.

Falco는 오픈 소스 컨테이너 보안 도구로, 컨테이너 및 오케스트레이션 환경에서 악의적인 활동을 탐지하고 예방하는 데 사용됩니다. Falco는 컨테이너 환경에서 이벤트를 실시간으로 모니터링하고 시스템, 네트워크 및 애플리케이션 수준에서의 보안 위반을 식별합니다.

 

Falco는 이벤트 기반 규칙 엔진을 사용하여 행위 기반 보안 감사 및 탐지를 제공합니다. 사용자는 시스템 및 애플리케이션의 정상 동작에 대한 규칙을 정의할 수 있으며, Falco는 이러한 규칙에 따라 비정상적인 행위를 탐지하고 경고를 생성합니다. 예를 들어, 파일 시스템 접근, 프로세스 실행, 네트워크 통신 등의 이벤트를 모니터링하고, 악성 소프트웨어 감염, 권한 위반, 보안 설정 위반 등을 식별할 수 있습니다.

https://yomon.hatenablog.com/entry/2020/12/falco_fluentbit_cloudwatch

  • Falco 알림을 CloudWatch로 전송할 수 있도록 Fluentbit 설치
  • Helm를 사용한 Falco 설치
  • 위협 시뮬레이션 및 실시간 탐지

Installing Fluentbit

  • Fluentbit 는 오픈 소스 로그 수집 도구로, Falco alerts 를 수집하여 Cloudwatch와 같은 AWS 서비스로 전송합니다.
    Fluentbit 를 설정하기 전에 먼저 EKS 노드에 로그를 CloudWatch로 전송할 수 있는 올바른 IAM 권한이 있는지 확인합니다.
  • 역할 정책을 생성하여 EKS 노드 역할에 첨부합니다:
TEMPOUT=$(mktemp)
cat << EoF > $TEMPOUT 
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents",
                "logs:DescribeLogStreams"
            ],
            "Resource": [
                "arn:aws:logs:*:*:*"
            ]
        }
    ]
}
EoF

export CLUSTER_NAME=$(eksctl get clusters -o json | jq -r '.[0].Name')
export AWS_REGION=$(curl -s 169.254.169.254/latest/dynamic/instance-identity/document | jq -r '.region')
export NODE_ROLE_ARN=$(eksctl get iamidentitymapping --cluster ${CLUSTER_NAME} --region=${AWS_REGION} | grep -i node | awk '{print $1}'|tail -1)
export NODE_ROLE_NAME=$(echo $NODE_ROLE_ARN | cut -d'/' -f2)
aws iam create-policy --policy-name Falco-CloudWatchLogs --policy-document file://$TEMPOUT
aws iam attach-role-policy --role-name ${NODE_ROLE_NAME} --policy-arn `aws iam list-policies | jq -r '.[][] | select(.PolicyName == "Falco-CloudWatchLogs") | .Arn'`

  • 다음 명령을 실행하여 클러스터 이름과 로그를 전송할 Region이 포함된 cluster-info라는 이름의 구성 맵을 생성합니다. 클러스터 이름과 클러스터 지역을 클러스터의 이름과 지역으로 바꿉니다.
ClusterName=myeks
RegionName=ap-northeast-2
FluentBitHttpPort='2020'
FluentBitReadFromHead='Off'
[[ ${FluentBitReadFromHead} = 'On' ]] && FluentBitReadFromTail='Off'|| FluentBitReadFromTail='On'
[[ -z ${FluentBitHttpPort} ]] && FluentBitHttpServer='Off' || FluentBitHttpServer='On'
kubectl create configmap fluent-bit-cluster-info \
--from-literal=cluster.name=${ClusterName} \
--from-literal=http.server=${FluentBitHttpServer} \
--from-literal=http.port=${FluentBitHttpPort} \
--from-literal=read.head=${FluentBitReadFromHead} \
--from-literal=read.tail=${FluentBitReadFromTail} \
--from-literal=logs.region=${RegionName} -n amazon-cloudwatch
  • fluentbit 용 daemonset 리소스를 생성합니다.
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluent-bit
  namespace: falco
  labels:
    k8s-app: fluent-bit
    version: v1
    kubernetes.io/cluster-service: "true"
spec:
  selector:
    matchLabels:
      k8s-app: fluent-bit
  template:
    metadata:
      labels:
        k8s-app: fluent-bit
        version: v1
        kubernetes.io/cluster-service: "true"
    spec:
      containers:
      - name: fluent-bit
        image: public.ecr.aws/aws-observability/aws-for-fluent-bit:stable
        imagePullPolicy: Always
        env:
            - name: AWS_REGION
              valueFrom:
                configMapKeyRef:
                  name: fluent-bit-cluster-info
                  key: logs.region
            - name: CLUSTER_NAME
              valueFrom:
                configMapKeyRef:
                  name: fluent-bit-cluster-info
                  key: cluster.name
            - name: HTTP_SERVER
              valueFrom:
                configMapKeyRef:
                  name: fluent-bit-cluster-info
                  key: http.server
            - name: HTTP_PORT
              valueFrom:
                configMapKeyRef:
                  name: fluent-bit-cluster-info
                  key: http.port
            - name: READ_FROM_HEAD
              valueFrom:
                configMapKeyRef:
                  name: fluent-bit-cluster-info
                  key: read.head
            - name: READ_FROM_TAIL
              valueFrom:
                configMapKeyRef:
                  name: fluent-bit-cluster-info
                  key: read.tail
            - name: HOST_NAME
              valueFrom:
                fieldRef:
                  fieldPath: spec.nodeName
            - name: HOSTNAME
              valueFrom:
                fieldRef:
                  apiVersion: v1
                  fieldPath: metadata.name
            - name: CI_VERSION
              value: "k8s/1.3.15"
        resources:
            limits:
              memory: 200Mi
            requests:
              cpu: 500m
              memory: 100Mi
        volumeMounts:
        # Please don't change below read-only permissions
        - name: fluentbitstate
          mountPath: /var/fluent-bit/state
        - name: varlog
          mountPath: /var/log
          readOnly: true
        - name: varlibdockercontainers
          mountPath: /var/lib/docker/containers
          readOnly: true
        - name: fluent-bit-config
          mountPath: /fluent-bit/etc/
        - name: runlogjournal
          mountPath: /run/log/journal
          readOnly: true
        - name: dmesg
          mountPath: /var/log/dmesg
          readOnly: true
      terminationGracePeriodSeconds: 10
      hostNetwork: true
      dnsPolicy: ClusterFirstWithHostNet
      volumes:
      - name: fluentbitstate
        hostPath:
          path: /var/fluent-bit/state
      - name: varlog
        hostPath:
          path: /var/log
      - name: varlibdockercontainers
        hostPath:
          path: /var/lib/docker/containers
      - name: fluent-bit-config
        configMap:
          name: fluent-bit-config
      - name: runlogjournal
        hostPath:
          path: /run/log/journal
      - name: dmesg
        hostPath:
          path: /var/log/dmesg
      serviceAccountName: fluent-bit
      tolerations:
      - key: node-role.kubernetes.io/master
        operator: Exists
        effect: NoSchedule
      - operator: "Exists"
        effect: "NoExecute"
      - operator: "Exists"
        effect: "NoSchedule"
  • fluentbit 구성을 위한 configmap 리소스를 생성합니다:
apiVersion: v1
kind: ConfigMap
metadata:
  name: fluent-bit-config
  namespace: falco
  labels:
    k8s-app: fluent-bit
data:
  fluent-bit.conf: |
    [SERVICE]
        Flush                     5
        Grace                     30
        Log_Level                 info
        Daemon                    off
        Parsers_File              parsers.conf
        HTTP_Server               ${HTTP_SERVER}
        HTTP_Listen               0.0.0.0
        HTTP_Port                 ${HTTP_PORT}
        storage.path              /var/fluent-bit/state/flb-storage/
        storage.sync              normal
        storage.checksum          off
        storage.backlog.mem_limit 5M

    @INCLUDE application-log.conf
    @INCLUDE dataplane-log.conf
    @INCLUDE host-log.conf

  application-log.conf: |
    [INPUT]
        Name                tail
        Tag                 falco.*
        Exclude_Path        /var/log/containers/cloudwatch-agent*, /var/log/containers/fluent-bit*, /var/log/containers/aws-node*, /var/log/containers/kube-proxy*
        Path                /var/log/containers/falco*.log
        multiline.parser    docker, cri
        DB                  /var/fluent-bit/state/flb_container.db
        Mem_Buf_Limit       50MB
        Skip_Long_Lines     On
        Refresh_Interval    10
        Rotate_Wait         30
        storage.type        filesystem
        Read_from_Head      ${READ_FROM_HEAD}

    [INPUT]
        Name                tail
        Tag                 application.*
        Path                /var/log/containers/fluent-bit*
        multiline.parser    docker, cri
        DB                  /var/fluent-bit/state/flb_log.db
        Mem_Buf_Limit       5MB
        Skip_Long_Lines     On
        Refresh_Interval    10
        Read_from_Head      ${READ_FROM_HEAD}

    [INPUT]
        Name                tail
        Tag                 application.*
        Path                /var/log/containers/cloudwatch-agent*
        multiline.parser    docker, cri
        DB                  /var/fluent-bit/state/flb_cwagent.db
        Mem_Buf_Limit       5MB
        Skip_Long_Lines     On
        Refresh_Interval    10
        Read_from_Head      ${READ_FROM_HEAD}

    [FILTER]
        Name                kubernetes
        Match               application.*
        Kube_URL            https://kubernetes.default.svc:443
        Kube_Tag_Prefix     application.var.log.containers.
        Merge_Log           On
        Merge_Log_Key       log_processed
        K8S-Logging.Parser  On
        K8S-Logging.Exclude Off
        Labels              Off
        Annotations         Off
        Use_Kubelet         On
        Kubelet_Port        10250
        Buffer_Size         0

    [OUTPUT]
        Name                cloudwatch_logs
        Match               application.*
        region              ${AWS_REGION}
        log_group_name      /aws/containerinsights/${CLUSTER_NAME}/application
        log_stream_prefix   ${HOST_NAME}-
        auto_create_group   true
        extra_user_agent    container-insights

  dataplane-log.conf: |
    [INPUT]
        Name                systemd
        Tag                 dataplane.systemd.*
        Systemd_Filter      _SYSTEMD_UNIT=docker.service
        Systemd_Filter      _SYSTEMD_UNIT=containerd.service
        Systemd_Filter      _SYSTEMD_UNIT=kubelet.service
        DB                  /var/fluent-bit/state/systemd.db
        Path                /var/log/journal
        Read_From_Tail      ${READ_FROM_TAIL}

    [INPUT]
        Name                tail
        Tag                 dataplane.tail.*
        Path                /var/log/containers/aws-node*, /var/log/containers/kube-proxy*
        multiline.parser    docker, cri
        DB                  /var/fluent-bit/state/flb_dataplane_tail.db
        Mem_Buf_Limit       50MB
        Skip_Long_Lines     On
        Refresh_Interval    10
        Rotate_Wait         30
        storage.type        filesystem
        Read_from_Head      ${READ_FROM_HEAD}

    [FILTER]
        Name                modify
        Match               dataplane.systemd.*
        Rename              _HOSTNAME                   hostname
        Rename              _SYSTEMD_UNIT               systemd_unit
        Rename              MESSAGE                     message
        Remove_regex        ^((?!hostname|systemd_unit|message).)*$

    [FILTER]
        Name                aws
        Match               dataplane.*
        imds_version        v1


  host-log.conf: |
    [INPUT]
        Name                tail
        Tag                 host.dmesg
        Path                /var/log/dmesg
        Key                 message
        DB                  /var/fluent-bit/state/flb_dmesg.db
        Mem_Buf_Limit       5MB
        Skip_Long_Lines     On
        Refresh_Interval    10
        Read_from_Head      ${READ_FROM_HEAD}

    [INPUT]
        Name                tail
        Tag                 host.messages
        Path                /var/log/messages
        Parser              syslog
        DB                  /var/fluent-bit/state/flb_messages.db
        Mem_Buf_Limit       5MB
        Skip_Long_Lines     On
        Refresh_Interval    10
        Read_from_Head      ${READ_FROM_HEAD}

    [INPUT]
        Name                tail
        Tag                 host.secure
        Path                /var/log/secure
        Parser              syslog
        DB                  /var/fluent-bit/state/flb_secure.db
        Mem_Buf_Limit       5MB
        Skip_Long_Lines     On
        Refresh_Interval    10
        Read_from_Head      ${READ_FROM_HEAD}

    [FILTER]
        Name                aws
        Match               host.*
        imds_version        v1


  parsers.conf: |
    [PARSER]
        Name                syslog
        Format              regex
        Regex               ^(?<time>[^ ]* {1,2}[^ ]* [^ ]*) (?<host>[^ ]*) (?<ident>[a-zA-Z0-9_\/\.\-]*)(?:\[(?<pid>[0-9]+)\])?(?:[^\:]*\:)? *(?<message>.*)$
        Time_Key            time
        Time_Format         %b %d %H:%M:%S

    [PARSER]
        Name                container_firstline
        Format              regex
        Regex               (?<log>(?<="log":")\S(?!\.).*?)(?<!\\)".*(?<stream>(?<="stream":").*?)".*(?<time>\d{4}-\d{1,2}-\d{1,2}T\d{2}:\d{2}:\d{2}\.\w*).*(?=})
        Time_Key            time
        Time_Format         %Y-%m-%dT%H:%M:%S.%LZ

    [PARSER]
        Name                cwagent_firstline
        Format              regex
        Regex               (?<log>(?<="log":")\d{4}[\/-]\d{1,2}[\/-]\d{1,2}[ T]\d{2}:\d{2}:\d{2}(?!\.).*?)(?<!\\)".*(?<stream>(?<="stream":").*?)".*(?<time>\d{4}-\d{1,2}-\d{1,2}T\d{2}:\d{2}:\d{2}\.\w*).*(?=})
        Time_Key            time
        Time_Format         %Y-%m-%dT%H:%M:%S.%LZ
  • fluentbit에 대한 서비스 계정을 생성하고 클러스터 역할을 바인딩하여 POD 로그를 읽을 수 있도록 합니다:
cat << EoF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: fluent-bit
  namespace: falco
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: pod-log-reader
rules:
- apiGroups: [""]
  resources:
  - namespaces
  - pods
  verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: pod-log-crb
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: pod-log-reader
subjects:
- kind: ServiceAccount
  name: fluent-bit
  namespace: falco
EoF
  • fluentbit POD 를 확인합니다.


Installing Falco

Helm 차트를 통해 쿠버네티스 클러스터에 Falco 를 배포합니다:

helm repo add falcosecurity https://falcosecurity.github.io/charts
helm repo update
helm install falco falcosecurity/falco --namespace falco --create-namespace --set tty=true --set jsonOutput=true

 


위협 모방 & 실시간 탐지

  • 프론트엔드 애플리케이션에서 몇 가지 위협을 시뮬레이션합니다.
  • 프론트엔드 앱의 POD 이름을 얻습니다:
export TARGET_APP=$(kubectl get pods -n workshop | awk '/frontend/ {print $1; exit}')
echo $TARGET_APP

  • 암호화된 사용자 비밀번호 세부 정보가 들어 있는 /etc/shadow를 읽어 보겠습니다:
kubectl -n workshop exec -it $TARGET_APP -- cat /etc/shadow

  • AWS 콘솔에서 Cloudwatch → Logs → Log 그룹에서 해당 로그를 확인합니다.

  • 다음으로 스토리지 시스템 구성 파일, 시스템 부팅에 필요한 실행 파일 및 일부 로그 파일을 저장하는 중요한 디렉토리인 /etc 폴더에 파일을 생성합니다.
kubectl -n workshop exec -it $TARGET_APP -- touch /etc/badconfigfile
  • AWS 콘솔에서 Cloudwatch → Logs → Log 그룹에서 해당 로그를 확인합니다.

  • 시스템 관리자와 사용자 모두가 사용할 수 있는 명령이 포함된 /bin 디렉토리를 대상으로 지정할 수 있습니다. 폴더를 생성합니다.
kubectl -n workshop exec -it $TARGET_APP -- mkdir /bin/dangerousfolder
  • AWS 콘솔에서 Cloudwatch → Logs → Log 그룹에서 해당 로그를 확인합니다.

 

 

 

 

 

 

 

 

 

 

 

 

Comments