vim-lsp/clangd/bear/compdb

clangd

clangdはclangのLSPサーバ。
例えば、Makefileで-Iオプションとかを使ってて、それによってエディタがincludeパスを正しく見つけられなくて、エラーを出すみたいな問題を解決できる。

vim-lsp

LSPクライアントはvim-lspをつかって、その設定はvim-lsp-settingsに任せている。

github.com

github.com

bear/compdb

# bear -- <BUILD COMMAND>
bear -- make 

上記を実行するとcompile_commands.jsonというのがカレントディレクトリに生成される。 compile_commands.jsonが見るコンパイル情報のデータベースと思えば良い。
そして、bearはmakeの実行内容を見てくれて、compile_commands.jsonを生成してくれる。 compile_commands.jsonのあるディレクトリ以下ではエディタはclangd経由で適切にincludeパス等を解決できるようになる。

ヘッダファイルについてLSPで面倒みてもらうためにはcompdbが使える。 以下を実行するとcompile_commands.jsonにヘッダファイルの情報が追記される。

compdb list | sponge compile_commands.json

github.com

github.com

uchan.hateblo.jp

ヘッダーファイル中に未定義の型がある問題

ヘッダーファイルとかだと、ヘッダーファイル単体では未定義の型とかがある場合がある(そのヘッダファイルが依存する他のヘッダーファイルをincludeしてない場合等)。 この状態で最終的なビルドでは問題なくても、vimでそういうヘッダーファイルを開くとLSPがエラーを出してしまう。 これに対するうまい解決方法がわからないので、現状だと、compile_commands.jsonの該当のヘッダーファイルに関する記述の部分にgccの-includeオプションを手動で追記して無理やり依存を解決させている。

perfがうまくいかなかったメモ

xdp_exceptionのイベントを確認したくて、perfを実行したら、以下のようにエラーに遭遇した。

sudo perf record -a -e xdp:xdp_exception sleep 4 
event syntax error: 'xdp:xdp_exception'
                     \___ unsupported tracepoint

libtraceevent is necessary for tracepoint support
Run 'perf list' for a list of valid events

 Usage: perf record [<options>] [<command>]
    or: perf record [<options>] -- <command> [<options>]

    -e, --event <event>   event selector. use 'perf list' to list available events

以下を確認したこと

  • perf listの出力にはxdp:xdp_exceptionはちゃんと載っている。
  • libtraceeventがないかと思ってみて調べたが、ちゃんとlibtraceevent-devパッケージはインストールされている。

原因わからないので、ワークアラウンドとして以下でxdp_exceptionのイベントを確認することにした。

sudo trace-cmd record -e 'xdp:xdp_exception' sleep 4
sudo trace-cmd report

valinux.hatenablog.com

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プログラムの簡単な操作を行える。

github.com

ロード

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の意味は下記に記載されている。

kubernetes.io

試してみる

まずは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

何も出ない。つまり、暗号化された。

参照

kubernetes.io

etcdv3の探索でetcdctl lsはできない

etcdv3ではもはやetcdctl lsはできない。。。
というより、もはやディレクトリライクな構造になっていないようだ。

似たようなことをやりたければ以下のようにする。

etcdctl get / --keys-only --prefix