Kubernetes: v1.7 で導入された Node Authorization とは

[Kubernetes v1.7](https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG.md#v170) で導入された [Node Authorization](https://kubernetes.io/docs/admin/authorization/node/) について概要と [kubeadm](https://kubernetes.io/docs/setup/independent/create-cluster-kubeadm/) で構築して試してみた結果をまとめました。確認は Kubernetes v1.7.0 で行っています。

## 概要

[Node Authorization](https://kubernetes.io/docs/admin/authorization/node/) とは Node (Kubelet) の権限管理をより厳密に行う機能です。この機能を有効にすることで、Node はその Node が関連するオブジェクトにのみ権限が制限されるようになります。例えば Node は割り当てられた Pod が参照する Secret 以外にはアクセスできなくなります。これにより Node のクレデンシャルが漏洩したときのセキュリティリスクを最小限にすることができます。

## 制限されるリソース

Node Authorization を有効にすると Node がアクセスする必要があるリソースをその Node に関連あるものだけに自動的に絞られます。権限は以下のように制御されます。実装上の都合で認可フェーズと Admission Control のフェーズ、の2フェーズで権限の制御が行われています。

- 認可 (Node Authorizer)による機能
  - その Node に紐づく Pod が参照している Secret, ConfigMap, PersistentVolume, PersistentVolumeClaim のみ参照できる
- Admission Control (NodeRestriction)による機能
  - その Node に紐づく Node オブジェクトのみを変更できる
  - その Node に紐づく Pod オブジェクトのみステータスを変更できる
  - その Node に紐づく Static な Pod オブジェクト ([mirror pod](https://kubernetes.io/docs/tasks/administer-cluster/static-pod/))のみを作成・更新できる
    - mirror pod は Secret などの他のオブジェクトの参照はできないようになっている

その他の Kubelet が参照するリソースに関しては、RBAC の `system:node` の権限が参考になります。[kubernetes/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy.go](https://github.com/kubernetes/kubernetes/blob/9848cdb3ac67d64e2f446a69928b7c0610f0f56a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy.go#L89)

### 補足: 実装上の都合

認可フェーズでは、リクエストの body 部分は見ることができず、また Admission Control では読み込みにはかからないという制限があるため、このような 2 つを合わせた実装になっているようです。

参考: https://github.com/kubernetes/community/blob/master/contributors/design-proposals/node/kubelet-authorizer.md#alternatives-considered


## 有効にするための設定

Node Authorization を有効にするためには下記の3点の設定が必要になります。

### Node 側のクレデンシャル情報

Node のクレデンシャルには以下の情報が含まれている必要があります

- Group が `system:nodes` であること
- User 名が `system:node:<nodeName>` であること

例えばクライアント証明書による認証を使っている場合は、下記のような Subject になっている必要があります。

```
Subject: O=system:nodes, CN=system:node:<nodeName>
```

### 認可 (Node Authorizer) の設定

API Server の認可の設定に`Node`を追加する必要があります。

- `--authorization-mode=Node,...`
  - 必要に応じてカンマ区切りで複数かけます。例) `Node,RBAC`

### Admission Control (NodeRestriction) の設定

API Server の Admission Control の設定に`NodeRestriction`を追加する必要があります。

- `--admission-control=NodeRestriction,...`

## kubeadm で Node Authorization を試してみる

[kubeadm](https://kubernetes.io/docs/setup/independent/create-cluster-kubeadm/) を使って Node Authorization の機能の効果を試してみます。v1.7.0 の時点で kubeadm のデフォルトで Node Authorization の機能が有効になっているようです。

kubeadm による構築は [Using kubeadm to Create a Cluster](https://kubernetes.io/docs/setup/independent/create-cluster-kubeadm/) のドキュメントどおりに行いました。[Pod Network](https://kubernetes.io/docs/concepts/cluster-administration/addons/) には Calico を用いています。


### 構成

以下のようにMaster 1台、Node 2台の計3台で構成されています。

```bash
$ kubectl get nodes
NAME       STATUS    AGE       VERSION
master     Ready     4h        v1.7.0
worker-1   Ready     4h        v1.7.0
worker-2   Ready     4h        v1.7.0
```

kubeadm によって worker-1, 2 のクライアント証明書の Group 名、Node 名が仕様どおり設定されています。

```bash
# worker-1
$ cat /etc/kubernetes/kubelet.conf | grep client-certificate-data | awk '{print $2;}' | base64 -d | openssl x509 -text | grep Subject:
        Subject: O=system:nodes, CN=system:node:worker-1
                   ^^^^^^^^^^^^     ^^^^^^^^^^^^^^^^^^^^
                   Group 名         Node 名
# worker-2
$ cat /etc/kubernetes/kubelet.conf | grep client-certificate-data | awk '{print $2;}' | base64 -d | openssl x509 -text | grep Subject:
        Subject: O=system:nodes, CN=system:node:worker-2
```

API サーバーの Node Authorization の設定 (`--admission-control=...,NodeRestriction,..`, `--authorization-mode=Node,...` の設定)が有効になっていることを確認します。

```bash
# master
$ cat /etc/kubernetes/manifests/kube-apiserver.yaml  | grep Node
    - --admission-control=Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,ResourceQuota
    - --authorization-mode=Node,RBAC
```

### アクセス制御の確認

Node Authorization によって Node に割り当てられた Pod のマウントした Secret のみにアクセスできるよう制御されていることを確認してみます。

まずはアクセス制御の対象となる Secret を作成します。

```bash
$ kubectl create secret generic mysecret --from-literal=username=myusername --from-literal=password=mypassword
secret "mysecret" created
```

次にこの Secret  (mysecret)をマウントした Deployment を定義します。

```bash
$ kubectl apply -f myapp-dep.yaml 
deployment "myapp" created
```

Pod がスケジューリングされた Node を確認します。worker-1 に割り当てられたようです。

```bash
$ kubectl get pods -o wide
NAME                     READY     STATUS    RESTARTS   AGE       IP               NODE
myapp-1607853247-kmgrp   1/1       Running   0          25s       192.168.226.67   worker-1
                                                                                   ^^^^^^^^
```

worker-1 のクレデンシャル (`/etc/kubernetes/kubelet.conf ` の接続情報) を使ってこの Secret (mysecret) が読めることを確認します。

```bash
# worker-1
$ KUBECONFIG=/etc/kubernetes/kubelet.conf kubectl get secrets mysecret
NAME       TYPE      DATA      AGE
mysecret   Opaque    2         28m
# read できている
```

worker-1 でPod がマウントしていない他の Secret (othersecret) は**読めない**ことを確認します。

```bash
# worker-1
$ KUBECONFIG=/etc/kubernetes/kubelet.conf kubectl get secret othersecret
Error from server (Forbidden): User "system:node:worker-1" cannot get secrets in the namespace "default".: "no path found to object" (get secrets othersecret)
# エラーになった
```

また Pod が割り当てられていない worker-2 のクレデンシャルを使ってこの Secret が**読めない**ことを確認します。

```bash
# worker-2
$ KUBECONFIG=/etc/kubernetes/kubelet.conf kubectl get secrets mysecret
Error from server (Forbidden): User "system:node:worker-2" cannot get secrets in the namespace "default".: "no path found to object" (get secrets mysecret)
```

Node に関連ある Secret のみにアクセスできるようアクセス制御が正しく動作していることが確認できました :tada: 


## 関連ソースコード

- 認可 (Node Authorizer)
  - https://github.com/kubernetes/kubernetes/tree/master/plugin/pkg/auth/authorizer/node
- Admission Control (NodeRestriction)
  - https://github.com/kubernetes/kubernetes/blob/master/plugin/pkg/admission/noderestriction/admission.go

## 参考

- [Using Node Authorization](https://kubernetes.io/docs/admin/authorization/node/)
- [Scoped Kubelet API Access](https://github.com/kubernetes/community/blob/master/contributors/design-proposals/node/kubelet-authorizer.md)
- [Node authorizer #46076](https://github.com/kubernetes/kubernetes/pull/46076)