この記事はBeeX Advent Calendar 2020の12/12の記事です。
EKSのデータプレーンをどう選ぶか考えたとき、個人的にはFargateを積極的に使いたいところですが、まだ制限も多くて要件にフィットしない場合もあります。
そんな時に選択肢として挙がるManaged Node Groupですが、ReInvent2020でSpotインスタンスの利用できる機能が発表されたり、また使いやすくなったと感じています。
ここではそんな、Managed Node Groupで展開したEC2ノードのランタイムセキュリティをチェックする方法としてFalcoを使う手順を書いていきます。
概要としては以下の図の通りです。
Falcoは何か公式のドキュメントから引用します。
Falcoは実行時にカーネルからのLinuxシステムコールを解析し、強力なルールエンジンに対してストリームをアサートします。 ルールに違反した場合は、Falcoのアラートが発せられます。
セキュリティ的に問題がありそうなシステムコールをルールベースで検知できます。ルールはデフォルトで用意されているものも利用できますし、自身で追加することも可能です。
では設定入れていきます。
EKS情報確認
以下のEKSクラスタで作業します。クラスタ名は MyCluster
でプライベートクラスタとして構築しています。
$ aws eks describe-cluster --name MyCluster --query 'cluster | {"platformVersion": platformVersion, "k8sVersion": version}' { "platformVersion": "eks.3", "k8sVersion": "1.18" }
Managed Node Groupで2台起動しています。
$ kubectl get node NAME STATUS ROLES AGE VERSION ip-10-0-1-254.ap-northeast-1.compute.internal Ready <none> 4m48s v1.18.9-eks-d1db3c ip-10-0-2-180.ap-northeast-1.compute.internal Ready <none> 5m44s v1.18.9-eks-d1db3c
この記事の手順でProxy経由でイメージを取得できるようにしています。
Falcoのインストール
名前空間作成
今回利用する名前空間を作成します。この記事では falco-ns
として作成します。
kubectl create namespace falco-ns
HelmでFalcoのインストール
完全プライベート環境なのでProxy通してHelmでインストールします。Proxyが無ければPorxy関係のオプションは外して実行してください。
オプションに指定できる値はこちらを参考にしてください。
PROXY_HOST=YOUR_PROXY_HOST PORXY_PORT=8080 # Proxy設定をExport export http_proxy=http://${PROXY_HOST}:${PORXY_PORT} export https_proxy=http://${PROXY_HOST}:${PORXY_PORT} helm repo add falcosecurity https://falcosecurity.github.io/charts helm repo update #-> Update Complete. ⎈Happy Helming!⎈ # 名前空間falco-nsにFalcoをインストール # 要求リソースも調整しています # それぞれデフォルト値は以下になります # resources.limits.cpu = 200m # resources.limits.memory = 1024Mi = 1Gi helm install -n falco-ns falco falcosecurity/falco \ --set proxy.httpProxy=${http_proxy} \ --set proxy.httpsProxy=${https_proxy} \ --set resources.limits.cpu=100m \ --set resources.limits.memory=512Mi
DaemonSetなのでノード毎にFalcoのPodが1つ立ち上がります。
$ kubectl get po -n falco-ns -owide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES falco-cpqhc 1/1 Running 0 59s 10.0.2.116 ip-10-0-2-180.ap-northeast-1.compute.internal <none> <none> falco-t6r7z 1/1 Running 0 59s 10.0.1.228 ip-10-0-1-254.ap-northeast-1.compute.internal <none> <none>
Falcoの挙動確認
Falcoの挙動を動かしながら確認してみます。ここからはターミナルを2つ立ち上げて作業します。
説明上以下の番号付けます。
- ① 今作業してるターミナル
- ② 新しいターミナル
[ターミナル②] 被監視用コンソール
新しいターミナルを開き、Pod上のセッションに接続します。
kubectl run --generator=run-pod/v1 -it --rm console --image=debian:bullseye-slim #↓接続できました root@console:/#
[ターミナル①] イベントをキャプチャするFalco Podの特定
元のターミナルから先程起動したconsoleという名前のPodがどのノードで起動しているか確認します。
kubectl get po console -o=jsonpath='{.spec.nodeName}' ip-10-0-1-254.ap-northeast-1.compute.internal
次にFalcoのDaemonsetがどこで起動しているか確認します。 今回は falco-t6r7z
が console
と同じノードで起動しているので、ここで危険なシステムコールをキャプチャしたイベントがログ出力されるはずなのでこれを確認します。
$ kubectl get po -n falco-ns -owide $ kubectl get po -n falco-ns -owide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES falco-cpqhc 1/1 Running 0 36m 10.0.2.116 ip-10-0-2-180.ap-northeast-1.compute.internal <none> <none> falco-t6r7z 1/1 Running 0 36m 10.0.1.228 ip-10-0-1-254.ap-northeast-1.compute.internal <none> <none>
tail -f をして次の待ち受けます。
$ kubectl logs -f -n falco-ns falco-t6r7z
[ターミナル②] セキュリティイベントの発生
ここで以下のように /etc/shdow
の読み込みと、 /bin/
配下へのファイル作成を行ってみます。
$ root@console:/# cat /etc/shadow
$ root@console:/# touch /bin/dummy
[ターミナル①] イベントのログ出力確認
Falcoの方でtail -fしていたと思いますが、そちらに戻るとイベントがキャプチャできているのがわかります。
[ターミナル②] ログオフ
動きは確認できたので、console
Podからはログオフしておきます。ログオフすると自動でPodも削除されます。
CloudWatchへの転送
Service Account作成
FluentbitのDaemonSetからEC2ノードのIAMロールを使ってCloudWatchに転送することも可能ですが、ここではサービスアカウントを作成しようと思います。
eksctl使ってない環境なので、こちらで使ったスクリプト使います。
以下の通り実行してService Accountと紐付いたIAMロールを作成します。
CLUSTER_NAME=MyCluster SA_NAME=fluent-bit SA_NAMESPACE=falco-ns /bin/bash ./create_service_account.sh ${CLUSTER_NAME} ${SA_NAMESPACE} ${SA_NAME} #-> serviceaccount/fluent-bit created
上記のスクリプトで作成されたIAMロールに設定する権限は以下を参考に設定します。
実際に設定した画面です。
ここから先の 内容は以下のリポジトリを元に調整していきます。
Kubernetesロールとバインディングの作成
先程作成したService AccountにK8sのロールを設定していきます。
Manifestを作成します。
SA_NAMESPACE=falco-ns cat <<EOF > fluent-bit-sa-role-bindings.yml apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRole metadata: name: pod-log-reader namespace: ${SA_NAMESPACE} rules: - apiGroups: [""] resources: - namespaces - pods verbs: ["get", "list", "watch"] --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRoleBinding metadata: name: pod-log-crb namespace: ${SA_NAMESPACE} roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: pod-log-reader subjects: - kind: ServiceAccount name: fluent-bit namespace: ${SA_NAMESPACE} EOF
適用します。
kubectl apply -f ./fluent-bit-sa-role-bindings.yml #->clusterrole.rbac.authorization.k8s.io/pod-log-reader created #->clusterrolebinding.rbac.authorization.k8s.io/pod-log-crb created
FluentBitの設定をConfigMapで作成
ConfigMapを作成しますが、ここの [Output]
セクションを調整すると出力先のCloudWatch Logsのグループやログ名を指定できます。
設定については以下などが参考になります。
Amazon CloudWatch - Fluent Bit: Official Manual
今回はProxy配下のプライベート環境だったので、VPC Endpointを経由してログを渡したかったので、以下の設定を調整しています。
[OUTPUT] #省略~~~~~~~~ endpoint logs.ap-northeast-1.amazonaws.com sts_endpoint sts.ap-northeast-1.amazonaws.com #省略~~~~~~~~
ConfigMapのManifestを作成します。
SA_NAMESPACE=falco-ns cat <<EOF > fluentbit-configmap.yml apiVersion: v1 kind: ConfigMap metadata: name: fluent-bit-config namespace: ${SA_NAMESPACE} labels: app.kubernetes.io/name: fluentbit data: fluent-bit.conf: | [SERVICE] Parsers_File parsers.conf [INPUT] Name tail Tag falco.* Path /var/log/containers/falco*.log Parser falco DB /var/log/flb_falco.db Mem_Buf_Limit 5MB Skip_Long_Lines On Refresh_Interval 10 [OUTPUT] Name cloudwatch_logs Match falco.** region ap-northeast-1 log_group_name falco log_stream_prefix fluentbit_alerts_ auto_create_group true endpoint logs.ap-northeast-1.amazonaws.com sts_endpoint sts.ap-northeast-1.amazonaws.com parsers.conf: | [PARSER] Name falco Format json Time_Key time Time_Format %Y-%m-%dT%H:%M:%S.%L Time_Keep Off # Command | Decoder | Field | Optional Action # =============|==================|================= Decode_Field_As json log EOF
適用します。
kubectl apply -f ./fluentbit-configmap.yml #->configmap/fluent-bit-config created
FluentBitのDaemonSet作成
なおこの手順で利用するのはこちらのイメージとなります。
DockerHubからイメージを取得する場合は以下ですし、
https://hub.docker.com/r/amazon/aws-for-fluent-bit/tags
ECRからもイメージを取得できます。その場合は以下のssmコマンドでURL取得できます。
$ aws ssm get-parameters-by-path --path /aws/service/aws-for-fluent-bit/ --query 'Parameters[*].{Name:Name,URL:Value}' --output table --------------------------------------------------------------------------------------------------------------------------- | GetParametersByPath | +-----------------------------------------+-------------------------------------------------------------------------------+ | Name | URL | +-----------------------------------------+-------------------------------------------------------------------------------+ | /aws/service/aws-for-fluent-bit/2.0.0 | 906394416424.dkr.ecr.ap-northeast-1.amazonaws.com/aws-for-fluent-bit:2.0.0 | | /aws/service/aws-for-fluent-bit/2.1.0 | 906394416424.dkr.ecr.ap-northeast-1.amazonaws.com/aws-for-fluent-bit:2.1.0 | | /aws/service/aws-for-fluent-bit/2.10.0 | 906394416424.dkr.ecr.ap-northeast-1.amazonaws.com/aws-for-fluent-bit:2.10.0 | | /aws/service/aws-for-fluent-bit/2.2.0 | 906394416424.dkr.ecr.ap-northeast-1.amazonaws.com/aws-for-fluent-bit:2.2.0 | | /aws/service/aws-for-fluent-bit/2.4.0 | 906394416424.dkr.ecr.ap-northeast-1.amazonaws.com/aws-for-fluent-bit:2.4.0 | | /aws/service/aws-for-fluent-bit/2.5.0 | 906394416424.dkr.ecr.ap-northeast-1.amazonaws.com/aws-for-fluent-bit:2.5.0 | | /aws/service/aws-for-fluent-bit/2.6.1 | 906394416424.dkr.ecr.ap-northeast-1.amazonaws.com/aws-for-fluent-bit:2.6.1 | | /aws/service/aws-for-fluent-bit/2.7.0 | 906394416424.dkr.ecr.ap-northeast-1.amazonaws.com/aws-for-fluent-bit:2.7.0 | | /aws/service/aws-for-fluent-bit/2.9.0 | 906394416424.dkr.ecr.ap-northeast-1.amazonaws.com/aws-for-fluent-bit:2.9.0 | | /aws/service/aws-for-fluent-bit/latest | 906394416424.dkr.ecr.ap-northeast-1.amazonaws.com/aws-for-fluent-bit:latest | | /aws/service/aws-for-fluent-bit/2.1.1 | 906394416424.dkr.ecr.ap-northeast-1.amazonaws.com/aws-for-fluent-bit:2.1.1 | | /aws/service/aws-for-fluent-bit/2.3.0 | 906394416424.dkr.ecr.ap-northeast-1.amazonaws.com/aws-for-fluent-bit:2.3.0 | | /aws/service/aws-for-fluent-bit/2.3.1 | 906394416424.dkr.ecr.ap-northeast-1.amazonaws.com/aws-for-fluent-bit:2.3.1 | | /aws/service/aws-for-fluent-bit/2.6.0 | 906394416424.dkr.ecr.ap-northeast-1.amazonaws.com/aws-for-fluent-bit:2.6.0 | | /aws/service/aws-for-fluent-bit/2.8.0 | 906394416424.dkr.ecr.ap-northeast-1.amazonaws.com/aws-for-fluent-bit:2.8.0 | +-----------------------------------------+-------------------------------------------------------------------------------+
今回は以下を使うことにします。
906394416424.dkr.ecr.ap-northeast-1.amazonaws.com/aws-for-fluent-bit:2.10.0
DaemonSetのManifestを作成します。
SA_NAMESPACE=falco-ns FLUENTBIT_IMAGE=906394416424.dkr.ecr.ap-northeast-1.amazonaws.com/aws-for-fluent-bit:2.10.0 cat <<EOF > fluentbit-daemonset.yml apiVersion: apps/v1 kind: DaemonSet metadata: name: fluentbit namespace: ${SA_NAMESPACE} labels: app.kubernetes.io/name: fluentbit spec: selector: matchLabels: name: fluentbit template: metadata: labels: name: fluentbit spec: serviceAccountName: fluent-bit containers: - name: aws-for-fluent-bit image: ${FLUENTBIT_IMAGE} volumeMounts: - name: varlog mountPath: /var/log - name: varlibdockercontainers mountPath: /var/lib/docker/containers readOnly: true - name: fluent-bit-config mountPath: /fluent-bit/etc/ - name: mnt mountPath: /mnt readOnly: true resources: limits: memory: 500Mi requests: cpu: 500m memory: 100Mi volumes: - name: varlog hostPath: path: /var/log - name: varlibdockercontainers hostPath: path: /var/lib/docker/containers - name: fluent-bit-config configMap: name: fluent-bit-config - name: mnt hostPath: path: /mnt EOF
kubectl apply -f ./fluentbit-daemonset.yml #-> daemonset.apps/fluentbit created
FalcoとFluentBitがノード数(今回は2台)分、それぞれのノード上で起動しました。
$ kubectl get po -n falco-ns -owide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES falco-cpqhc 1/1 Running 0 126m 10.0.2.116 ip-10-0-2-180.ap-northeast-1.compute.internal <none> <none> falco-t6r7z 1/1 Running 0 126m 10.0.1.228 ip-10-0-1-254.ap-northeast-1.compute.internal <none> <none> fluentbit-4ftrj 1/1 Running 0 65s 10.0.2.53 ip-10-0-2-180.ap-northeast-1.compute.internal <none> <none> fluentbit-n2hz6 1/1 Running 0 65s 10.0.1.212 ip-10-0-1-254.ap-northeast-1.compute.internal <none> <none>
動作確認
では、検知されるような作業をしてみます。
$ kubectl run --generator=run-pod/v1 -it --rm console --image=debian:bullseye-slim $ root@console:/# cat /etc/shadow $ root@console:/# touch /bin/dummy2 $ root@console:/# exit #->pod "console" deleted
ログが出てます。上記の動作から、以下が検知されているのがわかります。
- ターミナルアタッチされた
- /etc/shadowが読まれた
- /binディレクトリに書き込まれた
- .bash_historyが書き換えられた
Tips
どんなルールが設定されているか
以下のコマンドで設定ファイルそのものを見ることができます。
$ kubectl exec -n falco-ns falco-t6r7z cat /etc/falco/falco_rules.yaml | grep -e "rule:" -e "desc:"
バージョン合っていれば、こちらの内容とも同じです。 https://github.com/falcosecurity/charts/blob/master/falco/rules/falco_rules.yaml
掃除
kubectl delete -f ./fluentbit-daemonset.yml kubectl delete -f ./fluentbit-configmap.yml kubectl delete -f ./fluent-bit-sa-role-bindings.yml kubectl delete sa -n falco-ns fluent-bit helm uninstall -n falco-ns falco kubectl delete ns falco-ns
後は、SAに紐付けたIAMロールを削除します。
参考URL
GitHub - sysdiglabs/falco-aws-firelens-integration
charts/README.md at master · falcosecurity/charts · GitHub
https://hub.docker.com/r/amazon/aws-for-fluent-bit
falco/falco_rules.yaml at master · falcosecurity/falco · GitHub