KubernetesのAudit webhook backendを試す
はじめに
KubernetesのAudit周りをいろいろ調べていたところFalcoという製品を教えてもらった。(Kuromatsuさんありがとう。)
サンプルを疎通しようとしたのだが、自分の設定がよろしくないのかFalco PodにKubernetesのAudit Logが流れてこない。
問題の切り分けとして、勉強がてら自分でAudit Backendを立てて疎通してみようというのが本記事の主旨。
Kubernetes Audit Backendsとは?
公式ドキュメントより引用。
Audit backends persist audit events to an external storage. Kube-apiserver out of the box provides three backends:
- Log backend, which writes events to a disk
- Webhook backend, which sends events to an external API
- Dynamic backend, which configures webhook backends through an AuditSink API object.
簡単に言うとAudit Eventを外部に永続化させるために仕組みで、今回は3つ目のDynamic Backendの方式で実装してみる。
(ちなみにDynamic Backendは1.18時点でalphaなことに注意)
1つ目については過去に記事があるのでそちらをどうぞ。
環境
- Host
- Ubuntu 18.04
- Kubernetes
- Minikube v1.10.1
- Kubernetes v1.18.2
実装
Backend Serverの構築
Audit Logを受け取るBackend Serverはクラスタ内で起動させても良いし、外部で起動させても良い。FalcoのサンプルではDaemonSetとして各Nodeに起動するような構成になっていたが、今回はHost上に直接立てることにする。
// HTTPエコーサーバー HTTPリクエストボディデータを返す let http = require('http'); let server = http.createServer(); let port = process.env.SERVER_PORT || 8080; // クライアントからリクエストボディデータをレスポンスとして返す server.on('request', function (req, res) { var data = ''; req.on('data', function(chunk) { data += chunk; }); req.on('end', function () { res.writeHead(200, {'Content-Type': 'application/json'}); res.end(JSON.stringify({ "body": data, } )); }); }); // HTTPの生データを出力する server.on('connection', function (socket) { console.log('=== Raw Socket Data Start ==='); socket.on('data', function (chunk) { console.log(chunk.toString()); }); socket.on('end', function () { console.log('=== Raw Socket Data End ==='); }); }); server.listen(port, function () { console.log('listening on ' + port); })
API Serverからのリクエストを標準出力に出す簡単なもの。
API Serverへのレスポンスの形式についてはどのようにすればいいかちょっとわからなかったので、とりあえず適当に返却。Falcoはどうやってるかなと思いソースを読んでみたが、下記のようなHTMLを返しているようにみえる。
bool k8s_audit_handler::handlePost(CivetServer *server, struct mg_connection *conn) { // Ensure that the content-type is application/json const char *ct = server->getHeader(conn, string("Content-Type")); if(ct == NULL || string(ct) != "application/json") { mg_send_http_error(conn, 400, "Wrong Content Type"); return true; } std::string post_data; get_post_data(conn, post_data); std::string errstr; if(!accept_uploaded_data(post_data, errstr)) { errstr = "Bad Request: " + errstr; mg_send_http_error(conn, 400, "%s", errstr.c_str()); return true; } std::string ok_body = "<html><body>Ok</body></html>"; mg_send_http_ok(conn, "text/html", ok_body.size()); mg_printf(conn, "%s", ok_body.c_str()); return true; }
https://github.com/falcosecurity/falco/blob/master/userspace/falco/webserver.cpp#L151
Minikubeの起動
minikube start --driver=virtualbox
MinikubeのAPI ServerにAudit Backendを設定
Falcoのドキュメントを読む限り、Audit Webhook Backendの設定はminikube startの引数では現状渡せないらしい。
Falcoのサンプルで提供されてるパッチあてスクリプトがほぼ流用できるので、それを利用してAPI Serverに設定する。
まずはFalcoのclone
git clone https://github.com/falcosecurity/falco.git
examples/k8s_audit_configに移動したら、
bash enable-k8s-audit.sh minikube dynamic
で有効化する。
Falcoのドキュメントではaudit-sink.yaml
にFalco ServiceのIPを設定しているが、今回は上記で作成したNode.jsサーバに向けたいので、IPを適宜変更する。疎通環境のaudit-sink.yaml
は下記の通り。
apiVersion: auditregistration.k8s.io/v1alpha1 kind: AuditSink metadata: name: falco-audit-sink spec: policy: level: RequestResponse stages: - ResponseComplete - ResponseStarted webhook: throttle: qps: 10 burst: 15 clientConfig: url: "http://192.168.254.128:8080/"
最後にaudit-sink.yaml
をkubectl apply -f
すればOK。
疎通確認
Node.jsで起動させたBackend Serverに下記のようなリクエストが送られていることが確認できた。 EventListをPostしてきている模様。EventListを加工するもよし、別情報を加えて出力するもよし、いろいろできると思う。
POST /?timeout=30s HTTP/1.1 Host: 192.168.254.128:8080 User-Agent: kube-apiserver-admission Content-Length: 1755 Accept: application/json, */* Content-Type: application/json Accept-Encoding: gzip {"kind":"EventList","apiVersion":"audit.k8s.io/v1","metadata":{},"items":[{"level":"RequestResponse","auditID":"f65a0d0e-01db-4c0f-980b-7631b4fad9ab","stage":"ResponseComplete","requestURI":"/api/v1/namespaces/kube-system/endpoints/kube-scheduler?timeout=10s","verb":"get","user":{"username":"system:kube-scheduler","groups":["system:authenticated"]},"sourceIPs":["192.168.99.100"],"userAgent":"kube-scheduler/v1.18.2 (linux/amd64) kubernetes/52c56ce/leader-election","objectRef":{"resource":"endpoints","namespace":"kube-system","name":"kube-scheduler","apiVersion":"v1"},"responseStatus":{"metadata":{},"code":200},"responseObject":{"kind":"Endpoints","apiVersion":"v1","metadata":{"name":"kube-scheduler","namespace":"kube-system","selfLink":"/api/v1/namespaces/kube-system/endpoints/kube-scheduler","uid":"d17b474d-8d66-4fa6-9c23-8dddec463a39","resourceVersion":"3479","creationTimestamp":"2020-05-14T08:04:21Z","annotations":{"control-plane.alpha.kubernetes.io/leader":"{\"holderIdentity\":\"minikube_e8dce5a6-8c40-4313-8685-39a3cf67bf2d\",\"leaseDurationSeconds\":15,\"acquireTime\":\"2020-05-14T08:04:21Z\",\"renewTime\":\"2020-05-14T08:27:49Z\",\"leaderTransitions\":0}"},"managedFields":[{"manager":"kube-scheduler","operation":"Update","apiVersion":"v1","time":"2020-05-14T08:27:49Z","fieldsType":"FieldsV1","fieldsV1":{"f:metadata":{"f:annotations":{".":{},"f:control-plane.alpha.kubernetes.io/leader":{}}}}}]}},"requestReceivedTimestamp":"2020-05-14T08:27:51.878748Z","stageTimestamp":"2020-05-14T08:27:51.881105Z","annotations":{"authorization.k8s.io/decision":"allow","authorization.k8s.io/reason":"RBAC: allowed by ClusterRoleBinding \"system:kube-scheduler\" of ClusterRole \"system:kube-scheduler\" to User \"system:kube-scheduler\""}}]}
まとめ
- Audit BackendでKubernetesのAudit Eventを外部に送ることができる
- 今回はWebhookを設定して外部のサーバに送信
本番環境上で動かす場合はセキュアな通信にしたり、AuditSinkのパラメータチューニングも必要だと思う。
とりあえず動きはつかめたので、もともとの目的であるFalcoもやっていきたい。