XDPプログラムをCLIでロード/アンロードする方法
iproute2
ロード
sudo ip link set dev DEVICE [
{ xdp | xdpgeneric | xdpdrv | xdpoffload }
{ object FILE [ section NAME ] [ verbose ] | pinned FILE }
]
アンロード
sudo ip link set dev DEVICE xdpgeneric off
xdp-loader
xdp-loaderはxdp-toolsについてくるコマンドツールで、XDPプログラムの簡単な操作を行える。
ロード
sudo xdp-loader load [options] <ifname> <filenames>
例えば、loにhoge.oというXDPプログラムをxdpgenericモードでロードするには以下のようにする。
sudo xdp-loader load -m skb lo hoge.o
アンロード
sudo xdp-loader unload [options] <ifname>
例えば、loにロードされている全てのXDPプログラムをアンロードするには以下のようにする。
sudo xdp-loader unload --all lo
seccomp
seccompとは
Seccompとは、Linuxカーネルが持つセキュリティ機構の一つで、Secure Computing Modeの略です。 簡単に言うと、Seccompはシステムコールの許可・不許可を設定できるようにし、危険なシステムコールを実行できなくするためのものです。
突然の「Operation not permitted」-Dockerが採用するセキュリティ機構「Seccomp」とは何か? #docker #seccomp #mirantis - クリエーションライン株式会社
2つのモード
linuxのseccompにはSECCOMP_MODE_STRICTとSECCOMP_MODE_FILTERという2つのモードがある。
前者はread, write, exit, sigreturn以外のシステムコールを禁止するシンプルなものになっており、後者はBPFを利用して開発者が任意のシステムコールをフィルタできる柔軟なものになっている。
SECCOMP_MODE_STRICT
SECCOMP_MODE_STRICTで許可されるシステムコールはread, write, exit, sigreturnのみ。
違反するとプロセスにSIGKILLが送られる。
検証
#include <fcntl.h> #include <stdio.h> #include <unistd.h> #include <string.h> #include <linux/seccomp.h> #include <sys/prctl.h> int main(int argc, char *argv[]) { int before_seccomp = open("before_seccomp.txt", O_RDWR); close(before_seccomp); prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT); int after_seccomp = open("after_seccomp.txt", O_RDWR); close(after_seccomp); }
実行結果
$ clang seccomp_strict_test.c -o seccomp_strict_test $ ./seccomp_strict_test 1. invoked open system call 1. invoked close system call Killed
prctlでseccompが呼び出した後、openシステムコールが呼び出せずに、SIGKILLが送られている。
SECCOMP_MODE_FILTER
SECCOMP_MODE_STRICTでは許可されるシステムコールが予め決まっていたが、SECCOMP_MODE_FILTERでは任意のシステムコールを指定できる。
また、違反した際のアクションについてもいろいろと設定可能になっている。例えば、プロセスSIGKILLを送るのではなく、ログに記載するだけみたいなこともできる。
検証
#include <seccomp.h> #include <stdio.h> #include <sys/utsname.h> #include <unistd.h> int main(void) { scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_ALLOW); if (ctx == NULL) { goto ERROR; } int rule_add_ret; rule_add_ret = seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(uname), 0); if (rule_add_ret != 0) { goto ERROR; } seccomp_load(ctx); printf("before uname invocation\n"); struct utsname buf; if (uname(&buf) != 0) { goto ERROR; }; printf("after uname invocation\n"); printf("sysname: %s, nodename: %s, version:%s\n", buf.sysname, buf.nodename, buf.version); seccomp_release(ctx); return 0; ERROR: perror("failed"); return -1; }
※prctlやseccompシステムコールを利用するのではなく、便利なlibseccompを利用している。
実行結果
$ clang seccomp_mode_filter.c -lseccomp -o filter $ ./filter before uname invocation Bad system call (core dumped)
unameの呼び出しがフィルタされているのがわかる。
手元のクラスタ(kind)でgvisorを試す
kindでgvisorを使用できるようにする。
nodeに入る。(自分のケースでは1node構成なので、control-planeになっている。)
docker exec -it kind-control kind-control-plane bash
gvisorをインストールする。
以下はInstallation - gVisorにあるインストールスクリプトを少しいじったものになっている。
set -e
ARCH=$(uname -m)
URL=https://storage.googleapis.com/gvisor/releases/release/latest/${ARCH}
wget ${URL}/runsc ${URL}/runsc.sha512 \
${URL}/containerd-shim-runsc-v1 ${URL}/containerd-shim-runsc-v1.sha512
sha512sum -c runsc.sha512 \
-c containerd-shim-runsc-v1.sha512
rm -f *.sha512
chmod a+rx runsc containerd-shim-runsc-v1
mv runsc containerd-shim-runsc-v1 /usr/local/bin
containerdでrunscを利用できるように設定変更。
@@ -7,6 +7,9 @@ type = "snapshot" address = "/run/containerd-fuse-overlayfs.sock" +[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runsc] + runtime_type = "io.containerd.runsc.v1" + [plugins."io.containerd.grpc.v1.cri".containerd] # save disk space when using a single snapshotter discard_unpacked_layers = true
containedを再起動
systemctl restart containerd
RuntimeClassとテスト用podの作成
RuntimeClass
apiVersion: node.k8s.io/v1 kind: RuntimeClass metadata: name: gvisor handler: runsc
test用pod
apiVersion: v1 kind: Pod metadata: creationTimestamp: null labels: run: nginx name: nginx spec: runtimeClassName: gvisor containers: - image: nginx name: nginx resources: {} dnsPolicy: ClusterFirst restartPolicy: Always status: {}
gvisorが正しく動いていることを確認する
gvisorはuserspace kernelが動くので、コンテナ上のカーネルバージョンとノードのカーネルバージョンに差分がでるようになる。
これを確認する。
# hostmachine 6.5.0-25-generic
# nginx container root@nginx:/# uname -r 4.4.0
ちゃんと差分が出てる。
一応、カーネルのログも確認してみる。
# nginx container root@nginx:/# dmesg [ 0.000000] Starting gVisor... [ 0.506367] Moving files to filing cabinet... [ 0.907249] Segmenting fault lines... [ 1.396441] Constructing home... [ 1.678327] Creating cloned children... [ 1.952515] Daemonizing children... [ 2.002172] Reticulating splines... [ 2.237456] Verifying that no non-zero bytes made their way into /dev/zero... [ 2.422350] Letting the watchdogs out... [ 2.890127] Creating process schedule... [ 3.020322] Consulting tar man page... [ 3.473949] Setting up VFS... [ 3.623193] Setting up FUSE... [ 4.057643] Ready!
いい感じ。。
Kubernetesにおけるetcd内のデータの暗号化
問題点:etcdには平文でデータが格納されている。
k8sの各種マニフェストははetcdに平文で保存されている。 secretであっても平文で保存されている。
twoという文字列を隠蔽したくてone-plus-oneというsecretを作成する。
$ k create secret generic one-plus-one --from-literal=ans=two
etcdの中を見てみと、twoという文字が確認できる。
$ etcdctl get /registry/secrets/default/one-plus-one | hexdump -C | grep two 000000f0 00 12 0a 0a 03 61 6e 73 12 03 74 77 6f 1a 06 4f |.....ans..two..O|
解決策
Kubernetesにおいてetcdと通信を行うアプリはkube-api-serverのみである。
そのため、kube-api-serverでデータの暗号化と復号化を行う。つまり、データをetcdに書き込む際には暗号化して書き込みを行い、データを読み出す場合にはetcdから読み出したデータを復号化して使う。
方法
EncryptionConfigurationを用意して、それをkube-api-serverに--encryption-provider-configオプションを使って読み込ませる。
EncryptionConfiguration
サンプル (参照元: Encrypting Confidential Data at Rest | Kubernetes)
# # # CAUTION: this is an example configuration. # Do not use this for your own cluster! # apiVersion: apiserver.config.k8s.io/v1 kind: EncryptionConfiguration resources: - resources: - secrets - configmaps - pandas.awesome.bears.example # a custom resource API providers: - identity: {} - aesgcm: keys: - name: key1 secret: c2VjcmV0IGlzIHNlY3VyZQ== - name: key2 secret: dGhpcyBpcyBwYXNzd29yZA== - aescbc: keys: - name: key1 secret: c2VjcmV0IGlzIHNlY3VyZQ== - name: key2 secret: dGhpcyBpcyBwYXNzd29yZA== - resources: - events providers: - identity: {} - resources: - '*.apps' providers: - aescbc: keys: - name: key2 secret: c2VjcmV0IGlzIHNlY3VyZSwgb3IgaXMgaXQ/Cg==
.resources.resourcesで暗号化、復号化を行う対象を選択する.resources.providersで暗号化、復号化を行うproviderを選択する
.resources.providersに指定するproviderの順序には以下のような意味があるので、注意が必要。
- 暗号化する際:
.resources.providersに指定されている最初のproviderが利用される。 - 復号化する際:
.resources.providersに指定されているproviderの中で適したものが自動で選択され使用される。
各種providerの意味は下記に記載されている。
試してみる
まずはEncryptionConfigurationを作成する
# # # CAUTION: this is an example configuration. # Do not use this for your own cluster! # apiVersion: apiserver.config.k8s.io/v1 kind: EncryptionConfiguration resources: - resources: - secrets providers: - aescbc: keys: - name: key1 secret: aG9nZWhvZ2Vob2dlaG9nZQ== - identity: {}
aG9nZWhvZ2Vob2dlaG9nZQ==はecho -n hogehogehogehoge | base64で適当に作ったものになっている。
このファイルは/etc/kubernetes/etcd/ec.yamlに保存した。(別にファイルの場所はkube-api-serverが渡せるならばどこでもよい。)
kube-api-serverに先ほど作成したEncryptionConfigurationを読み込ませる。
以下のような感じでkube-api-serverのマニフェストを修正。
@@ -13,6 +13,7 @@ containers: - command: - kube-apiserver + - --encryption-provider-config=/etc/kubernetes/etcd/ec.yaml - --allow-privileged=true - --authorization-mode=Node,RBAC @@ -92,6 +93,9 @@ - mountPath: /usr/share/ca-certificates name: usr-share-ca-certificates readOnly: true + - mountPath: /etc/kubernetes/etcd + name: etcd + readOnly: true hostNetwork: true priority: 2000001000 priorityClassName: system-node-critical @@ -119,4 +123,8 @@ path: /usr/share/ca-certificates type: DirectoryOrCreate name: usr-share-ca-certificates + - hostPath: + path: /etc/kubernetes/etcd + type: DirectoryOrCreate + name: etcd status: {}
one-minus-oneというsecretを新たに作成してみる
k create secret generic one-minus-one --from-literal=ans=zero
etcdctlでちゃんと暗号化されているか確認
$etcdctl get /registry/secrets/default/one-minus-one | hexdump -C | grep zero
大丈夫そう
元々あったone-plus-oneというsecretも暗号化してみる
この時点では元々あったsecretは暗号化されてない。
$ etcdctl get /registry/secrets/default/one-plus-one | hexdump -C | grep two 000000f0 00 12 0a 0a 03 61 6e 73 12 03 74 77 6f 1a 06 4f |.....ans..two..O|
元々あったsecretを全て再作成する。
kubectl get secrets --all-namespaces -o json | kubectl replace -f -
改めてetcdctlでone-plus-oneを見てみる
$ etcdctl get /registry/secrets/default/one-plus-one | hexdump -C | grep two
何も出ない。つまり、暗号化された。
参照
etcdv3の探索でetcdctl lsはできない
etcdv3ではもはやetcdctl lsはできない。。。
というより、もはやディレクトリライクな構造になっていないようだ。
似たようなことをやりたければ以下のようにする。
etcdctl get / --keys-only --prefix
NodeRestriction
NodeRestrictionはKuberntesによって提供されるAdmissionPluginでkube-api-serverで--enable-admission-plugins=NodeRestionを指定することで有効化できる。
効果1
nodeが他のnodeのラベルを変更することを禁じる。
効果2
nodeにおけるnode-restriction.kubernetes.io/のプレフィックスがついたラベルの変更を禁じる。(自分自身のnodeであっても禁止)
検証
効果1を検証
root@kind-worker:/# kubectl --kubeconfig=/etc/kubernetes/kubelet.conf label nodes kind-worker hoge=fuga node/kind-worker labeled root@kind-worker:/# kubectl --kubeconfig=/etc/kubernetes/kubelet.conf label nodes kind-worker2 hoge=fuga Error from server (Forbidden): nodes "kind-worker2" is forbidden: node "kind-worker" is not allowed to modify node "kind-worker2"
kind-workerは自身のnodeのラベルの変更はできるが、kind-worker2のラベルを変更できない。
効果2を検証
root@kind-worker:/# kubectl --kubeconfig=/etc/kubernetes/kubelet.conf label nodes kind-worker node-restriction.kubernetes.io/hoge=fuga Error from server (Forbidden): nodes "kind-worker" is forbidden: is not allowed to modify labels: node-restriction.kubernetes.io/hoge
kind-workerは自身のnodeのラベルの変更であっても、node-restriction.kubernetes.io/のプレフィックスがついたラベルの変更はできない。
curlでKubernetesAPI
方法
- caとclient-certとclient-keyをkubeconfigから抽出する。
k config view --raw | yq .clusters[0].cluster.certificate-authority-data | tr -d '\n' | base64 -d > ca k config view --raw | yq .users[0].user.client-certificate-data | tr -d '\n' | base64 -d > cert k config view --raw | yq .users[0].user.client-key-data | tr -d '\n' | base64 -d > key
curl $(k config view | yq .clusters[0].cluster.server) --cacert ca --key key --cert cert
※ 対象のclusterとuserがkubeconfigの.clusterと.userの最初のエントリになっていることを想定している。
kubectl get podsをcurlでやる例
curl $(k config view | yq .clusters[0].cluster.server)/api/v1/namespaces/default/pods --cacert ca --key key --cert cert