IT/DevSecOps

Falco Operator로 K8s 런타임 보안 이벤트 잡기 가이드

gfrog 2026. 5. 12. 15:15
반응형

왜 Operator인가

이미지 스캔(Trivy)이랑 시그니처 검증(cosign)까지는 잘 깔아뒀는데, 정작 "이 컨테이너가 지금 이상한 짓을 하고 있는지"는 안 보이더라. 빌드 타임 보안과 런타임 보안은 다른 문제다. 컨테이너 안에서 nc -e /bin/sh가 떠도 우리는 모른다. 그래서 팀에서 Falco를 본격적으로 깔았다. 헬름 차트로 한 번 깔아본 적은 있는데 이번에 Falco Operator(0.43 기준)로 새로 정비하면서 정리한 가이드다.

대상 독자는 EKS/온프렘 K8s에 Falco를 처음 도입하거나, 옛날 헬름 설치를 Operator로 옮기려는 사람.

지난해까진 우리도 falcosecurity/falco 헬름 차트로 깔았다. 근데 룰을 ConfigMap으로 관리하다 보니 룰 한 줄 바꾸면 DaemonSet rollout이 돌고, 룰 검증은 적용 후에야 알 수 있는 구조였다. 적용했더니 syntax 오류로 Falco가 CrashLoop 돌면서 노드 보안이 통째로 빈 시간이 5분 정도 생긴 적도 있다.

Falco 0.40부터 정식 권장된 Falco Operator는 다음을 CR(Custom Resource)로 관리한다.

  • Falco — Falco 인스턴스 자체 (DaemonSet 스펙, 드라이버, 리소스)
  • FalcoRules — 탐지 룰 묶음
  • FalcoPlugins — k8saudit, cloudtrail 같은 플러그인

룰만 따로 CR로 분리되니까 룰 변경이 Falco 재시작을 트리거하지 않는다. 그리고 webhook으로 룰 syntax를 미리 검증한다. 이게 사실 가장 큰 이유였다.

설치

helm repo add falcosecurity https://falcosecurity.github.io/charts
helm repo update

helm install falco-operator falcosecurity/falco-operator \
  --namespace falco-system --create-namespace \
  --set webhook.enabled=true

웹훅이 뜨는지 확인.

kubectl get validatingwebhookconfigurations | grep falco

그 다음 Falco 인스턴스를 CR로 띄운다. 드라이버는 modern_ebpf를 추천한다. 커널 6.x 노드면 모듈 빌드 없이 동작한다.

apiVersion: install.falcosecurity.dev/v1alpha2
kind: Falco
metadata:
  name: falco
  namespace: falco-system
spec:
  driver:
    kind: modern_ebpf
  resources:
    requests:
      cpu: 100m
      memory: 256Mi
    limits:
      memory: 512Mi
  outputs:
    rate: 1
    maxBurst: 1000
  jsonOutput: true
  logLevel: info

modern_ebpf은 노드 커널 5.8 이상이 필요하다. Bottlerocket 1.20대나 AL2023이면 그냥 동작한다. 옛날 CentOS 7 노드면 kmod로 떨어지는데 컴파일러 의존성이 골치아프니까 가급적 노드 커널 올리는 게 낫다.

룰 작성 — 정말 중요한 건 노이즈 줄이기

기본 룰만 켜두면 하루에 수천 건이 뜬다. Read sensitive file untrusted, Write below etc 같은 게 정상 부트스트랩 과정에서도 잔뜩 잡힌다. 우리 팀은 첫 일주일을 룰 튜닝에만 썼다.

진짜 잡고 싶은 건 결국 이 정도다.

  • 컨테이너 안에서 shell 실행 (spawned process in container)
  • 평소 안 쓰던 바이너리 실행 (unexpected outbound connection)
  • 시크릿 마운트 경로 외 읽기
  • 권한 상승 시도 (setuid, chmod +s)

커스텀 룰을 CR로 분리해서 관리한다.

apiVersion: install.falcosecurity.dev/v1alpha2
kind: FalcoRules
metadata:
  name: team-custom-rules
  namespace: falco-system
spec:
  rules: |
    - macro: prod_namespace
      condition: k8s.ns.name in (prod, payment, order)

    - rule: Shell in prod container
      desc: 운영 네임스페이스 컨테이너에서 shell이 떴다
      condition: >
        spawned_process and container and prod_namespace and
        proc.name in (sh, bash, zsh, ash, dash) and
        not proc.pname in (entrypoint.sh, init)
      output: >
        prod container shell (user=%user.name ns=%k8s.ns.name
        pod=%k8s.pod.name container=%container.name cmd=%proc.cmdline)
      priority: WARNING
      tags: [prod, shell, T1059]

    - rule: Outbound to non-allowlisted IP
      desc: 외부로 나가면 안 되는 컨테이너가 외부 IP로 connect
      condition: >
        outbound and container and prod_namespace and
        k8s.pod.label[network-egress] = "deny" and
        not fd.sip in (allowed_outbound_ips)
      output: >
        unexpected outbound (pod=%k8s.pod.name dst=%fd.sip:%fd.sport)
      priority: CRITICAL
      tags: [prod, network, T1041]

k8s.pod.label[network-egress] = "deny" 같은 식으로 Pod 레이블 기반 조건을 거니까 NetworkPolicy랑 일관되게 관리할 수 있다. 우리는 거의 모든 룰을 prod_namespace 매크로로 묶어서 dev/stg는 알람 안 가게 했다. 안 그러면 정말 알람만 보다가 하루가 간다.

Falcosidekick으로 알람 보내기

Falco 자체는 stdout에 JSON을 쏟아낼 뿐이라 Slack/PagerDuty로 보내려면 Falcosidekick이 필요하다. 같은 Operator가 사이드킥도 같이 관리한다.

apiVersion: install.falcosecurity.dev/v1alpha2
kind: Falco
metadata:
  name: falco
spec:
  # ... 위 설정 ...
  falcosidekick:
    enabled: true
    config:
      slack:
        webhookurl_secret:
          name: slack-webhook
          key: url
        minimumpriority: warning
        outputformat: fields
      pagerduty:
        routingkey_secret:
          name: pagerduty-key
          key: routing_key
        minimumpriority: critical

minimumpriority를 다르게 줘서 WARNING은 Slack, CRITICAL은 PagerDuty로 보내는 게 정석이다. 처음에 둘 다 CRITICAL로 묶어놨다가 새벽 3시에 stg 환경 shell 알람으로 oncall이 깨서 욕먹은 적이 있다.

추가로 Falcosidekick-UI를 켜면 최근 이벤트가 웹 대시보드로 보인다. 우리는 사내 망에만 노출시켜서 보안팀이 자체 모니터링하는 용도로 쓴다.

운영하면서 알게 된 것들

modern_ebpf 드라이버 메모리 사용량이 노드당 평균 200~300MB 정도 나온다. 200대 클러스터면 무시 못 할 양이다. 처음에 limit 128Mi로 줬다가 OOMKill 폭주했다. 256~512Mi가 현실적이다.

룰 적용 후 syscall throughput이 높은 워커(예: 인그레스 컨트롤러 노드)에선 CPU가 살짝 튄다. P99 기준 1~2% 정도라 무시할 만하지만, latency-sensitive 워크로드 노드면 outputs.rate 낮춰서 throttle 거는 게 안전하다.

플러그인 중에 k8saudit은 audit 로그를 받아서 Falco 룰로 평가한다. 이걸 켜면 exec, pod-exec, secret read 같은 컨트롤 플레인 레벨 행위까지 잡힌다. 노드 syscall만 보는 것보다 시야가 훨씬 넓어진다. EKS면 audit 로그를 CloudWatch로 보낸 다음 Falco의 cloudtrail/cloudwatch 플러그인으로 다시 읽는 식으로 구성할 수 있다.

그래서

Falco 자체는 옛날부터 있었지만 Operator로 가면서 운영 부담이 확 줄었다. 룰 검증 webhook이 진짜 좋다. 다음 글에선 우리가 만든 룰 셋을 어떻게 GitOps로 관리하는지 정리해볼까 한다.

혹시 Falco 룰 튜닝 노하우 가지신 분 있으면 댓글로 공유 부탁드린다.

반응형