SPIRE の Node Resolver の機能と役割について

# はじめに

この記事では、SPIRE Server のプラグインの1つである Node Resolver が持つ機能と、その機能が何に役立つのか？を紹介していきたいと思います。

# Node Resolver の機能について

Node Attestation が完了して、認証済み Node を識別するため情報（SPIFFE ID）が SPIRE Server のデータストアに保存された後に、その Node に紐づく情報を Selector として追加保存するのが Node Resolver の機能となります。また、この Node Resolver による処理は [Node Resolution](https://spiffe.io/spire/overview/#node-resolution) と呼ばれています。

補足ですが、Node Attestor の中には Node Resolver の機能が組み込まれているものもあります。例えば、[NodeAttestor "k8s_sat"](https://github.com/spiffe/spire/blob/0.8.1/doc/plugin_server_nodeattestor_k8s_sat.md) では Node Attestation の際に以下の情報が Selector として保存されるようになっています。

- Cluster Name
- Namespace
- Servie Account

~~Node Attestation の際に取得できる情報から生成できる Selector があれば生成して、そこで生成できなかった Selector が必要になれば Node Resolver で補完するといった設計思想なのかなと理解しています。~~

上記が気になったので [SPIFFE Slack で質問してみた](https://spiffe.slack.com/archives/C7XDP01HB/p1566983207000800) ところ、初期段階では Node Resolver でしか Selector の作成が行えない仕様だったようです。しかし、[NodeAttestor "x509pop"](https://github.com/spiffe/spire/blob/master/doc/plugin_server_nodeattestor_x509pop.md) などで Common Name や Fingerprint を Selector として作成したいときに、Node Resolver では難しかった（Node Attestation 時に得られた Common Name や Fingerprint などの情報が SPIRE に保存されないので Node Resolver で取得できない）ので、 そのタイミングで Node Attestor でも Selector 作成が行えるように仕様変更されたようです。また、Node Attestor と Node Resolver はもうマージすれば良いのでは？という議論も挙がったみたいですが、進展なしで流れてしまったそうです。

次に、この Node Resolution が何に役立つのかについて説明していきたいと思います。

# Node Resolution が何に役立つのか

Workload Attestation を行うためには、事前に対象の Workload がどの SPIFFE ID を持つ Node で稼働するのかを Parent ID として定義した上で、Registration Entry に登録する必要があります。

以下は `spiffe://example.org/node` という SPIFFE ID を持つ Node で uid 10000 のユーザーから実行されるUNIXプロセスを Workload として登録した例となります。

```bash
Entry ID      : xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
SPIFFE ID     : spiffe://example.org/workload
Parent ID     : spiffe://example.org/node
TTL           : 3600
Selector      : unix:uid:10000
```

単一の Node で Workload を実行する分には手間は掛からないのですが、複数の Node で同一の Workload を実行する際には、以下のように Node の数だけ Registration Entry を登録する必要があり、更にはスケールアウトのことも考えると非常に手間が掛かります。

```bash
Entry ID      : xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
SPIFFE ID     : spiffe://example.org/workload
Parent ID     : spiffe://example.org/node1
TTL           : 3600
Selector      : unix:uid:10000

Entry ID      : xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
SPIFFE ID     : spiffe://example.org/workload
Parent ID     : spiffe://example.org/node2
TTL           : 3600
Selector      : unix:uid:10000

Entry ID      : xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
SPIFFE ID     : spiffe://example.org/workload
Parent ID     : spiffe://example.org/node3
TTL           : 3600
Selector      : unix:uid:10000
```

この問題を解決するのが Node Resoltion です。

Node Resoltion で登録した Node に紐づく Selector を使うことで、Node を論理的にグルーピングすることが可能になります。そして、そのグループに単一の SPIFFE ID を割り当て、それを Workload の Parent ID として登録することが出来ます。

先述の `spiffe://example.org/node[1-3]` という SPIFFE ID が割り当てられた Node をグルーピングして `spiffe://example.org/node-group` という SPIFEE ID を割り当てることで、以下の登録だけで済むというイメージです。

```bash
Entry ID      : xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
SPIFFE ID     : spiffe://example.org/workload
Parent ID     : spiffe://example.org/node-group
TTL           : 3600
Selector      : unix:uid:10000
```

# 実際に動かして理解を深めていきます

SPIRE 0.8.1 を使って [NodeResolver "aws_iid"](https://github.com/spiffe/spire/blob/0.8.1/doc/plugin_server_noderesolver_aws_iid.md) を例に説明していきます。

[NodeAttestor "aws_iid"](https://github.com/spiffe/spire/blob/0.8.1/doc/plugin_server_nodeattestor_aws_iid.md) による Node Attestation が完了した後に、追加で認証済み Node に紐づく以下の情報を Selector として保存するのが、このプラグインの機能となります。

- Instance Tag
- Security Group ID
- Security Group Name
- IAM role

SPIRE Server 用に1台、Workload を実行する Node 用に2台（`type:spire-node` という Instance Tag を付与済み）の EC2インスタンス を起動して、NodeAttestor/NodeResolver "aws_iid" を使うための設定ファイルを用意（コメント参照）した上で [こちらの手順](https://spiffe.io/spire/getting-started-linux/) を参考に Node Attestation が完了した状態の環境を用意します。

認証済みの Node が2台用意できている状態です。なお、NodeAttestor "aws_iid" によって生成される SPIFFE ID にはアカウントの情報が含まれているため適当な値を入れております。

```bash
ubuntu@ip-172-31-29-20:~$ sudo spire-server agent list
Found 2 attested agents:

Spiffe ID         : spiffe://example.org/spire/agent/aws_iid/xxxxxxxxxxxx/ap-northeast-1/i-xxxxxxxxxxxxxx001
Attestation type  : aws_iid
Expiration time   : 2019-08-05 10:29:26 +0000 UTC
Serial number     : 2

Spiffe ID         : spiffe://example.org/spire/agent/aws_iid/xxxxxxxxxxxx/ap-northeast-1/i-xxxxxxxxxxxxxx002
Attestation type  : aws_iid
Expiration time   : 2019-08-05 10:29:26 +0000 UTC
Serial number     : 3
```

ここで Node Resolution によって各 Node に紐付けられた Selector を確認していきたいのですが、0.8.1 時点で CLI から取得する機能がサポートされていない? ので、直接DBを覗いていきます。

Node Resolution で追加された Selector が格納されている `node_resolver_map_entries` を見てみると、各 Node 毎の Security Group ID/Name と Instace Tag の情報が追加されている（1番右の value カラム）ことがわかります。

```bash
ubuntu@ip-172-31-29-20:~$ sudo sqlite3 /opt/spire/.data/datastore.sqlite3 '.headers on' 'select * from node_resolver_map_entries'
id|created_at|updated_at|spiffe_id|type|value
1|2019-08-05 09:30:00.522594723+00:00|2019-08-05 09:30:00.522594723+00:00|spiffe://example.org/spire/agent/aws_iid/xxxxxxxxxxxx/ap-northeast-1/i-xxxxxxxxxxxxxx001|aws_iid|sg:id:sg-xxxxxxxxxxxxxxxxx
2|2019-08-05 09:30:00.522667443+00:00|2019-08-05 09:30:00.522667443+00:00|spiffe://example.org/spire/agent/aws_iid/xxxxxxxxxxxx/ap-northeast-1/i-xxxxxxxxxxxxxx001|aws_iid|sg:name:Ubuntu 16-04 LTS - Xenial -HVM--Ubuntu 16-04 20190628-AutogenByAWSMP-1
3|2019-08-05 09:30:00.522742101+00:00|2019-08-05 09:30:00.522742101+00:00|spiffe://example.org/spire/agent/aws_iid/xxxxxxxxxxxx/ap-northeast-1/i-xxxxxxxxxxxxxx001|aws_iid|tag:type:spire-node
4|2019-08-05 09:30:07.366829279+00:00|2019-08-05 09:30:07.366829279+00:00|spiffe://example.org/spire/agent/aws_iid/xxxxxxxxxxxx/ap-northeast-1/i-xxxxxxxxxxxxxx002|aws_iid|sg:id:sg-xxxxxxxxxxxxxxxxx
5|2019-08-05 09:30:07.366907324+00:00|2019-08-05 09:30:07.366907324+00:00|spiffe://example.org/spire/agent/aws_iid/xxxxxxxxxxxx/ap-northeast-1/i-xxxxxxxxxxxxxx002|aws_iid|sg:name:Ubuntu 16-04 LTS - Xenial -HVM--Ubuntu 16-04 20190628-AutogenByAWSMP-1
6|2019-08-05 09:30:07.366981906+00:00|2019-08-05 09:30:07.366981906+00:00|spiffe://example.org/spire/agent/aws_iid/xxxxxxxxxxxx/ap-northeast-1/i-xxxxxxxxxxxxxx002|aws_iid|tag:type:spire-node
```

それでは、この Selector を使って Node のグルーピングを行っていきます。`spire-server create entry` で Registration Entry の登録を行う際に `-node` オプションを付与することで、Node のグループ（Node Entry や Instance Group Entry と呼ばれている?ようです）を定義することが出来ます。

今回は Instance Tag `type:spire-node` を保持している Node をグルーピングして、`spiffe://example.org/node-group` という SPIFFE ID を割り当てたいと思います。

```bash
sudo spire-server entry create \
    -parentID spiffe://example.org/spire/server \
    -spiffeID spiffe://example.org/node-group \
    -selector tag:type:spire-node \
    -node
```

次に Node をグルーピングした SPIFFE ID を Parent ID に指定して Workload を登録します。

```bash
sudo spire-server entry create \
    -parentID spiffe://example.org/node-group \
    -spiffeID spiffe://example.org/workload \
    -selector unix:uid:10000
```

Registration Entry を確認します。

```bash
ubuntu@ip-172-31-29-20:~$ sudo spire-server entry show
Found 2 entries
Entry ID      : 2f36f7b0-dda0-4b60-b5e0-5d36c0843ee8
SPIFFE ID     : spiffe://example.org/node-group
Parent ID     : spiffe://example.org/spire/server
TTL           : 3600
Selector      : tag:type:spire-node

Entry ID      : 80d609ab-dd49-4048-8216-db958bfee4fd
SPIFFE ID     : spiffe://example.org/workload
Parent ID     : spiffe://example.org/node-group
TTL           : 3600
Selector      : unix:uid:10000
```

最後に 2台の Node で Workload Attestation を実行していきます。

```bash
# 1台目
ubuntu@ip-172-31-26-46:~$ sudo -u workload spire-agent api fetch x509
Received 1 bundle after 1.70399ms

SPIFFE ID:		spiffe://example.org/workload
SVID Valid After:	2019-08-05 11:19:42 +0000 UTC
SVID Valid Until:	2019-08-05 12:11:06 +0000 UTC
Intermediate #1 Valid After:	2019-08-05 11:10:56 +0000 UTC
Intermediate #1 Valid Until:	2019-08-05 12:11:06 +0000 UTC
CA #1 Valid After:	2018-05-13 19:33:47 +0000 UTC
CA #1 Valid Until:	2023-05-12 19:33:47 +0000 UTC

# 2台目
ubuntu@ip-172-31-30-149:~$ sudo -u workload spire-agent api fetch x509
Received 1 bundle after 1.661441ms

SPIFFE ID:		spiffe://example.org/workload
SVID Valid After:	2019-08-05 11:18:18 +0000 UTC
SVID Valid Until:	2019-08-05 12:11:06 +0000 UTC
Intermediate #1 Valid After:	2019-08-05 11:10:56 +0000 UTC
Intermediate #1 Valid Until:	2019-08-05 12:11:06 +0000 UTC
CA #1 Valid After:	2018-05-13 19:33:47 +0000 UTC
CA #1 Valid Until:	2023-05-12 19:33:47 +0000 UTC
```

このように Node Resolution によって Node に紐付く Selector が登録されることで Node のグルーピングが可能になります。

なお、Node のグループを追加したからといって Node Attestor によって付与された SPIFFE ID が無効になるわけではありません。Node Attestor 由来の SPIFFE ID に追加で Node のグループの SPIFFE ID が付与されるイメージを描いてもらえば良いかなと思います。

# さいごに

今回は、SPIRE Server のプラグインの1つである Node Resolver が持つ機能と、その機能が何に役立つのか？を紹介しました。私自身 SPIRE を触り始めたときに Node Resolver の働きだけがよく理解できなかったので、同じように困っている人の役に立てたら幸いです。

# 参考資料

- [SPIRE Overview - spiffe.io](https://spiffe.io/spire/overview/)

# おまけ

Node Resolution 起因の SPIFFE ID を使って Registration Entry を取得する処理を追いかけてみた。（興味本位で追いかけているので間違ってる可能性もありますが悪しからず...。）

1. SPIRE Server から Attested Node に SVIDs や Registration Entries を送信する処理（Workload Attestation の前段処理）の中で、対象 Node の SPIFFE ID を元に Registration Entries を取得している。（ https://github.com/spiffe/spire/blob/0.8.1/pkg/server/endpoints/node/handler.go#L252 ）
2. 実処理としては `regentryutil` パッケージの関数を使っての取得となっている。（Attested Node の SPIFFE ID を Parent ID とする Entry と、Node Resolution 起因の SPIFFE ID を Parent ID とする Entry を合算している）
    3. https://github.com/spiffe/spire/blob/0.8.1/pkg/server/util/regentryutil/fetch.go#L18
    4. https://github.com/spiffe/spire/blob/0.8.1/pkg/server/util/regentryutil/fetch.go#L32
    5. https://github.com/spiffe/spire/blob/0.8.1/pkg/server/util/regentryutil/fetch.go#L45
    6. https://github.com/spiffe/spire/blob/0.8.1/pkg/server/util/regentryutil/fetch.go#L62-L76
7. Node Resolution 起因の SPIFFE ID を使って Registration Entry を取得する関数はこちら。（ https://github.com/spiffe/spire/blob/0.8.1/pkg/server/util/regentryutil/fetch.go#L94-L127 ）