【OpenShift】FalcoでSlack通知してみた

最近導入事例をよく聞くFalcoから、Slack経由でアラート通知してみました。

- [Falcoとは](#falcoとは)
- [事前準備](#事前準備)
  - [Slack Incoming Webhook URLの作成](#slack-incoming-webhook-urlの作成)
  - [Falcoのインストール](#falcoのインストール) 
- [Falco Rulesの設定](#falco-rulesの設定)
- [Slack通知の動作確認](#slack通知の動作確認)
- [補足情報](#補足情報)
  - [eBPFとは](#ebpfとは)
  - [他の選択肢](#他の選択肢)
- [まとめ](#まとめ)

# Falcoとは

特定のルールに基づきコンテナ、VM、クラウドサービス等のセキュリティリスクに繋がり得るイベントをリアルタイムで検知する、OSSのセキュリティソフトウェアです。
sysdigカーネルモジュールや eBPF（Extended Berkeley Packet Filter）を利用してシステムコールをトレーシングし、プロセスを監視します。
詳細はsysdigさんのブログと公式ドキュメントを参照してください。

https://sysdig.jp/learn-cloud-native/what-is-falco/

https://falco.org/

# 事前準備

以下の環境で検証しました。

```
MacBook M3 Pro
OpenShift Local v2.49.0
Falco 4.21.3
```

## Slack Incoming Webhook URLの作成

Slackの公式ドキュメントを参考に、Webhook URLを追加しました。

https://api.slack.com/messaging/webhooks

## Falcoのインストール

公式が提供する[Helm Chart](https://artifacthub.io/packages/helm/falcosecurity/falco)をインストールしました。

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

helm install --replace falco --namespace falco --create-namespace falcosecurity/falco -f values-falco.yml
```

<details><summary>values-falco.yml</summary>

```yaml
# -- Attach the Falco process to a tty inside the container. Needed to flush Falco logs as soon as they are emitted.
# Set it to "true" when you need the Falco logs to be immediately displayed.
tty: true

# Driver settings (scenario requirement)
driver:
  # -- Set it to false if you want to deploy Falco without the drivers.
  # Always set it to false when using Falco with plugins.
  enabled: true
  # -- kind tells Falco which driver to use. Available options: kmod (kernel driver), ebpf (eBPF probe), modern_ebpf (modern eBPF probe).
  kind: auto

falco:
  # [Stable] `priority`
  #
  # -- Any rule with a priority level more severe than or equal to the specified
  # minimum level will be loaded and run by Falco. This allows you to filter and
  # control the rules based on their severity, ensuring that only rules of a
  # certain priority or higher are active and evaluated by Falco. Supported
  # levels: "emergency", "alert", "critical", "error", "warning", "notice",
  # "info", "debug"
  priority: warning

########################
# Falco integrations   #
########################

# -- For configuration values, see https://github.com/falcosecurity/charts/blob/master/charts/falcosidekick/values.yaml
falcosidekick:
  # -- Enable falcosidekick deployment.
  enabled: true
  # -- Enable usage of full FQDN of falcosidekick service (useful when a Proxy is used).
  fullfqdn: false
  # -- Listen port. Default value: 2801
  listenPort: "2801"

  # -- Sidekick pod securityContext
  podSecurityContext:
    runAsUser: 1234
    fsGroup: 1234
    runAsNonRoot: true

  # -- Sidekick container securityContext
  securityContext:
    allowPrivilegeEscalation: false
    capabilities:
      drop: ["ALL"]

  config:
    slack:
      # -- Slack Webhook URL (ex: <https://hooks.slack.com/services/XXXX/YYYY/ZZZZ>), if not `empty`, Slack output is *enabled*
      webhookurl: "https://hooks.slack.com/services/XXXX/YYYY/ZZZZ"
      # -- Slack channel (optionnal)
      channel: ""
      # -- Slack Footer
      footer: ""
      # -- Slack icon (avatar)
      icon: ""
      # -- Slack username
      username: ""
      # -- `all` (default), `text` (only text is displayed in Slack), `fields` (only fields are displayed in Slack)
      outputformat: "all"
      # -- minimum priority of event to use this output, order is `emergency\|alert\|critical\|error\|warning\|notice\|informational\|debug or ""`
      minimumpriority: ""
      # -- a Go template to format Slack Text above Attachment, displayed in addition to the output from `slack.outputformat`. If empty, no Text is displayed before Attachment
      messageformat: ""
```

</details>

`values-falco.yml`は、GitHubのdocsと各Chartの`values.yaml`を参考に設定しました。

- https://github.com/falcosecurity/charts/blob/master/charts/falco/values.yaml
- https://github.com/falcosecurity/charts/blob/master/charts/falcosidekick/values.yaml
- https://github.com/falcosecurity/falcosidekick/blob/master/docs/outputs/slack.md

手元の環境では[OpenShift Security Context Constraints](https://docs.redhat.com/ja/documentation/openshift_container_platform/4.18/html/authentication_and_authorization/managing-pod-security-policies#managing-pod-security-policies)の制約により、Podが起動しませんでした。
Falco, Falco sidekickのサービスアカウントに`privileged`のsccを付与することでエラーを回避しました。

```
oc adm policy add-scc-to-user privileged -z falco -n falco
oc adm policy add-scc-to-user privileged -z falco-falcosidekick -n falco
```

# Falco Rulesの設定

Falcoのデフォルトルールをそのまま利用します。

https://falco.org/docs/reference/rules/default-rules/

独自のポリシーを運用したい場合、カスタムルールを作成することができます。

https://falco.org/docs/concepts/rules/custom-ruleset/

Helm Chartの設定を変更しなければ、自動的にデフォルトルールが適用されます。
インストール時のオプションで`falco.priority`に`warning`を設定しているため、`priority: warning`以上のルールが照合されます。

# Slack通知の動作確認

適当にPodを起動して、SSHコマンドやcurlを実行してみます。

```
$ oc run --image alpine -it alpine -- sh
If you don't see a command prompt, try pressing enter.
/ # apk update
...

/ # apk add openssh curl -y
...

/ # ssh --help
ssh: unrecognized option: -
usage: ssh [-46AaCfGgKkMNnqsTtVvXxYy] [-B bind_interface] [-b bind_address]
           [-c cipher_spec] [-D [bind_address:]port] [-E log_file]
           [-e escape_char] [-F configfile] [-I pkcs11] [-i identity_file]
           [-J destination] [-L address] [-l login_name] [-m mac_spec]
           [-O ctl_cmd] [-o option] [-P tag] [-p port] [-R address]
           [-S ctl_path] [-W host:port] [-w local_tun[:remote_tun]]
           destination [command [argument ...]]
       ssh [-Q query_option]

/ # curl https://kubernetes.default.svc:443 -kv
* Host kubernetes.default.svc:443 was resolved.
* IPv6: (none)
* IPv4: 10.217.4.1
*   Trying 10.217.4.1:443...
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Request CERT (13):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Certificate (11):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 / x25519 / RSASSA-PSS
* ALPN: server accepted h2
* Server certificate:
*  subject: CN=10.217.4.1
*  start date: Mar 16 15:05:47 2025 GMT
*  expire date: Mar 16 15:05:48 2026 GMT
*  issuer: OU=openshift; CN=kube-apiserver-service-network-signer
*  SSL certificate verify result: self-signed certificate in certificate chain (19), continuing anyway.
*   Certificate level 0: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
*   Certificate level 1: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
* Connected to kubernetes.default.svc (10.217.4.1) port 443
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://kubernetes.default.svc:443/
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: kubernetes.default.svc]
* [HTTP/2] [1] [:path: /]
* [HTTP/2] [1] [user-agent: curl/8.12.1]
* [HTTP/2] [1] [accept: */*]
> GET / HTTP/2
> Host: kubernetes.default.svc
> User-Agent: curl/8.12.1
> Accept: */*
> 
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* Request completely sent off
* received GOAWAY, error=0, last_stream=1
< HTTP/2 403 
< audit-id: cc7f6007-b2d6-4584-baa5-44d93b855eca
< cache-control: no-cache, private
< content-type: application/json
< strict-transport-security: max-age=31536000; includeSubDomains; preload
< x-content-type-options: nosniff
< x-kubernetes-pf-flowschema-uid: d62aafa0-00ea-4bf5-986e-cd35691a2eb3
< x-kubernetes-pf-prioritylevel-uid: 9afd7176-4c2d-479b-82d4-1488dc281e4e
< content-length: 217
< date: Sun, 04 May 2025 02:43:11 GMT
< 
{
  "kind": "Status",
  "apiVersion": "v1",
  "metadata": {},
  "status": "Failure",
  "message": "forbidden: User \"system:anonymous\" cannot get path \"/\"",
  "reason": "Forbidden",
  "details": {},
  "code": 403
* shutting down connection #0
* TLSv1.3 (OUT), TLS alert, close notify (256):
}/ #
```

コマンドを実行してから約1分後、Slackに通知メッセージが届きました。

![スクリーンショット 2025-05-06 12.13.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3621075/bcfdc485-16a9-4288-8ed9-3d028c844729.png)

![スクリーンショット 2025-05-06 12.22.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3621075/97a8aebb-68f5-4778-be98-5d8cc279fecb.png)

# 補足情報

以下、補足情報です。

## eBPFとは

Linuxカーネルの機能を拡張するためのプログラムです。
eBPFプログラムをLinuxカーネルにロードすることで、システムコールをフックし任意のプログラムが実行されます。
eBPFには、以下のようなメリットがあります。

- eBPFプログラムはJIT コンパイルされるため、実行効率が高い
- OS内部でサンドボックス化されたプログラムが実行されるため、安全です
- OSコードの改変が不要なため、ポータビリティに優れます

詳細は公式ドキュメントを参照してください。

https://ebpf.io/ja/what-is-ebpf/

## 他の選択肢

`tetragon`と`Tracee`というツールがありました。
Falcoと同様にePBFを使用した、振る舞い監視を実現するためのセキュリティソフトウェアです。

https://tetragon.io/

https://www.aquasec.com/products/tracee/

## Falcoctl

Falcoルールやプラグインのバージョン管理を実現するためのコマンドラインツールです。
GitHub Packages等のアーティファクトストアに、[OCI artifacts](https://oras.land/docs/concepts/artifact/)として登録できるそうです。

https://falco.org/blog/falcoctl-install-manage-rules-plugins/

https://github.com/falcosecurity/falcoctl

# まとめ

FalcoのHelm Chartをインストールすることで、コンテナ内プロセスの振る舞い監視が簡単に実現できました。
実運用では誤検知や大量アラートを抑えるチューニングが求められそうですね。
もし導入を検討されている場合は、こちらの記事も読んでみてください。

https://engineering.mercari.com/blog/entry/20220928-kubernetes-threat-matrix-and-attack-detection-by-falco/