SPIFFEとその実装であるSPIREについて

# はじめに

SPIFFEという名前を聞いたことはあるでしょうか。KubernetesのSIG-AUTHなんかの議事録などに目を通している方は最近よく目にするようになっているのでないかと思います。

![spiffe-sum.png](https://qiita-image-store.s3.amazonaws.com/0/15164/e99ed8b2-27ea-fccb-73c2-3f90bf32d6e4.png)

<https://spiffe.io/>

簡単にまとめてしまえばSPIFFEとはサービス間認証のフレームワークと仕様を定めたものでSPIREはそのひとつの実装になります。

Kubernetesクラスタ内においてはpod間の通信を制限したい場合はPodSecurityPolicyを使ったりすることで実現できますが、podとクラスタ外のサービスの通信を制限したい場合はどうすればいいでしょうか。そういった場合にはSPIFFEなどを使うことで解決するかもしれません。

# SPIFFE
SPIFFEとは「Secure Production Identity Framework For Everyone」の頭文字を取ったものです。
ではSPIFFEが定義している仕様とはどのようなものなのでしょうか。現在は以下の3つにについて仕様を策定しています。

-   SPIFFE ID
-   SVID
-   Workload API

それぞれ簡単に整理していきましょう。

## SPIFFE ID

<https://github.com/spiffe/spiffe/blob/master/standards/SPIFFE-ID.md#2-spiffe-identity>

SPIFFE IDとはURI形式の構造化された文字列によってSPIFFEでの認証の対象(ワークロード)を表す文字列です。

```
spiffe://trust-domain/path
```

上記のようにschemeが `spiffe` となっておりドメインとパスで表現します。具体的には以下のようにあるドメインにおけるpaymentsサービスのstaging環境で動くmysqlやweb-feを表したりできます。

```
spiffe://staging.acme.com/payments/mysql
```

```
spiffe://staging.acme.com/payments/web-fe
```

こんな風にKubernetesのある名前空間に紐づくサービスアカウントを表現することもできます。

```
spiffe://k8s-west.acme.com/ns/staging/sa/default
```

SPIFFEではこのSPIFFE IDという文字列を使って対象(ワークロード)を特定し認証などに使います。

## SVID

<https://github.com/spiffe/spiffe/blob/master/standards/SPIFFE-ID.md#3-spiffe-verifiable-identity-document>

そのSPIFFE IDを使うための手段のひとつとしてSVIDというものがあります。SVIDとは「SPIFFE Verifiable Identity Document」の略ですが、ワークロードが自身のSPIFFE IDをリソースや通信対象に伝えるための仕組みであり、SVIIDは３つのコンポーネントにより構成されるものだとされています。

-   A SPIFFE ID
-   A public key
-   A valid signature

SPIFFE IDとpublic keyがペイロードに含まれており、そのペイロードの署名をもつデータということですが、SPIFFEではこれらを表現するフォーマットを独自に定めていません。もうすでに要件を満たすフォーマットがあるのでそれを使うということらしいです。

では今の時点でどのようなフォーマットが使えるのでしょうか。2017/12月時点では[X.509](https://www.ietf.org/rfc/rfc2459.txt)がサポートされています。

<https://github.com/spiffe/spiffe/blob/master/standards/X509-SVID.md>

Extension | Field | Description
----------|-------|------------
Subject Alternate Name | uniformResourceIdentifier | This field is set equal to the SPIFFE ID. Only one instance of this field is permitted.
Basic Constraints | CA | This field must be set to `true` if and only if the SVID is a signing certificate.
Basic Constraints | pathLenConstraint | This field must not be set.
Name Constraints | permittedSubtrees | This field may be set if the implementor wishes to use URI name constraints. It will be required in a future version of this document.
Key Usage | keyCertSign | This field must be set if and only if the SVID is a signing certificate.
Key Usage | cRLSign | This field must be set if and only if the SVID is a signing certificate.
Key Usage | keyAgreement | This field must be set if and only if the SVID is a leaf certificate.
Key Usage | keyEncipherment | This field must be set if and only if the SVID is a leaf certificate.
Key Usage | digitalSignature | This field must be set if and only if the SVID is a leaf certificate.
Extended Key Usage | id-kp-serverAuth | This field may be set if and only if the SVID is a leaf certificate.
Extended Key Usage | id-kp-clientAuth | This field may be set if and only if the SVID is a leaf certificate.

[1]: https://tools.ietf.org/html/rfc5280
[2]: https://tools.ietf.org/html/rfc5280#section-4.2.1.6
[3]: https://tools.ietf.org/html/rfc5280#section-4.2.1.9
[4]: https://tools.ietf.org/html/rfc5280#section-4.2.1.10
[5]: https://tools.ietf.org/html/rfc5280#section-4.2.1.3
[6]: https://tools.ietf.org/html/rfc5280#section-4.2.1.2

とドキュメントから参照すると細かいパラメータも含めて記載されています。SANの値として `URI:spiffe://example.org/my-host` のような値が設定されているというのは分かりやすい点かなと思います。

## Workload API

Workload APIについてですが、現時点ではTBDとなっておりまだドキュメントが公開されていません。

概要だけでも説明しておきますと、Workload から呼びされるSVIDや必要なCA証明書を取得するためのAPIを定義します。これらはローカル通信でのみ利用されることを想定し、別のノード上のワークロードからはそのノードのWorkload APIは呼び出せません。あるノードで動くワークロードという単位で認証を可能にするための制約のような気がしています

仕様は公開されていませんが、後述のSPIREでは実装されておりそちらで詳しく説明しようと思いますので、実装を見つつ、こういうことを定義したいのかなと思っていただければと思います。

# SPIRE

![spire-design.png](https://qiita-image-store.s3.amazonaws.com/0/15164/16c914eb-2851-fef4-5ca3-c649d61599cc.png)

「SPIFFE Runtime Environment」の略であり、SPIFFEの参照実装という位置づけです。Control Planeと表現されるSPIRE-Serverとクライアント側のSPIRE-Agentで構成され、現在のバージョンは0.2(beatrice)です。

ではその実装について少し詳しく見ていきましょう。

## SPIRE-Server

Signing Authority として機能し、Agentから送られてきたCSRに署名してX.509 SVIDを発行します。またそのために、Nodeやその上で動くWorkloadの情報を管理しそれぞれSPIFFE IDを紐づけています。

![spire-server.png](https://qiita-image-store.s3.amazonaws.com/0/15164/7c3afeac-0a91-ed0e-12aa-78b2e8663a87.png)

SPIRE-Serverはいくつかのコンポーネントで構成されます。

-   Node API
-   Registration API
-   Node Attestor(server)
-   Node Resolver
-   Datastore
-   CA management


### Node API

SPIRE-Agentが動くNodeから呼び出されるAPI。SPIRE-Agentを動かすNodeが自身の正当性を証明し結果としてそのNodeのSVIDや必要なCA証明書を得ることができます。

SPIREではSVIDを使って認証する仕組みにNodeが参加するためには、何らかの仕組みによって自身の正当性をServerに証明し自身のSVIDを発行してもらう必要があります。正当なNode上で動くことを許可されたWorkloadのSVIDを使ってそれぞれが認証をするわけです。また何らかの仕組みとはプラグイン形式になっており、利用するプラットフォームなどによって選択できるようになっています。そしてこれをNode Attestorと呼びます。


### Node Attestor

Nodeの正当性を証明するための仕組み。現在はJoinTokenという仕組みのプラグインが実装されており、これは事前に発行したワンタイムトークンによってNodeを認証する仕組みです。これではNode Agentが起動する毎にワンタイムトークンの発行が必要になってしまうので、現在はプラットフォーム毎に自動でNodeを証明できるようなプラグインを開発中です。時期バージョンではAWS向けのプラグインが実装される予定です。

JoinTokenでは以下のようなコマンドで事前にあるNode(のSPIFFE ID)に対してワンタイムトークンを発行します。

```
$ spire-server token generate -spiffeID spiffe://example.org/db-host
```

Agent側では発行したトークンを使ってドメインに参加します。

```
$ spire-server run -joinToken <TOKEN>
```

正当なトークンであればBase SVIDと呼ばれるNodeに発行されたSVIDを得ることができます。ここでこのSVIDをデコードしてみるとわかるのですが、X.509のSANの値はワンタイムトークンを発行するときに指定したSPIFFE IDではありません。

```
X509v3 Subject Alternative Name:
    URI:spiffe://example.org/spire/agent/join_token/b47c1f7a-b274-4ecf-a26a-b0223140bb8b
```

Server側は上記のようにNode Attestorに紐づいたSPIFFE IDからワンタイムトークンを発行するときに指定したSPIFFE IDを解決します。これはあるSPIFFE IDに対してSVIDは１つでありそれを複数のNodeと紐づけるためではないかと思います。つまり、db-hostという抽象的なまとまりに対して実Nodeは複数台存在することが可能です。


### Registration API

Workloadに対応するSPIFFE IDを登録するためのAPI。Selectorと呼ばれるWorkloadを特定する情報(どのような環境で動くワークロードなのか)をkeyとしてそのワークロードに対するSPIFFE IDを登録します。

Selectorとはどんなデータなのでしょうか。例を使って説明していきます。

まずUnix上で動作する一般的なプロセスをワークロードとしてSelectorで表現します。これはUnix OS上で動作するuid 1000番のプロセスということを表現しています。

```
unix:uid:1000
```

Selectorは複数を組み合わせて登録することもできます。次の例はKuberentes上のあるpodをワークロードとしてSelectorで表現しています。これが表現するのはsample-nsという名前空間に紐づくsample-saというサービスアカウントをもつpodです。

```
k8s:ns:sample-ns
k8s:sa:sample-sa
```

なんとなくイメージがつかめてきたでしょうか？

### Node Resolver

Node ResolverはNodeのSPIFFE IDからそのNodeで動作することを許可されたWorkloadの情報を取得するためのAPIです。結果としてはSelectorの一覧を得ることができます。

しかしSPIRE 0.2ではNOOPの実装しかありません。

ここまでで SPIRE-Serverの実装について重要な部分について見てきました。ここからはクライアント側のAgentを見ていきましょう。

## SPIRE-Agent

SPIRE-Agentは連携したいすべてのNode上で動かす必要があります。

SPIRE-Agentを構成するコンポーネントとしては以下になります。

-   Node Attestor(agent)
-   Key Manager
-   Workload API
-   Workload Attestor

### Node Attestor(agent)

SPIRE-Server側で説明したNode Attestorのクライアント側の話です。自身を証明するデータを渡してBase SVIDとCA証明書などを得ます。

### Workload API

自身の上で動作するWorkloadから呼ばれるAPI。Workload APIが呼ばれると後述のWorkload Attestorを経由してワークロードのSelectorを特定します。特定したSelectorが事前にSPIRE-Server側で登録済みであれば(一致するものがあれば)ワークロードに対してSVIDやCA証明書などを返します。

### Key Manager

WorkloadのSVIDを発行するにあたり必要な鍵を生成、管理します。

### Workload Attestor

ワークロードのpidかSelectorを生成します。例えばunixプロセスの場合には以下のようなデータを返却します。

```
Selector {
    Type: unix,
    Value: unix:uid:1000,
}
```

このSelectorを生成する仕組みはどのようなワークロードをサポートするかにより、それぞれプラグインが提供されています。Unixプロセスをサポートするもの、KubernetesのPodをサポートするものなど、順次追加されていく予定です。

プラグインがどのような仕組みでSelectorを生成しているのかについては、明日のエントリでKubernertes pluginを例に説明したいと思います。

![spire-agent.png](https://qiita-image-store.s3.amazonaws.com/0/15164/868c9aaf-6321-33c8-824b-38e013f1efcd.png)

## Workload Components

では実際に認証するワークロード側ではどのようなコンポーネントが必要なのかについて見ていきましょう。ワークロードでは認証をするための `Proxy` とWorkload APIを呼び出して必要なSVIDを取得する `Workload API Client` が必要になります。Kubernetesなどで動かす場合には、ワークロードのコンテナのサイドカーとして定義したりします。

### Proxy

v0.2の時点では [ghostunnel](https://github.com/spiffe/ghostunnel) というソフトウェアをサポートしており、これを使いX.509 SVIDによるmTLSで相手のワークロードと接続します。SPIREではghostunnelのサーバ側の `--allow-uri-san` やクライアント側の `--verify-dns-san` といったパラメータを使い、SVIDに含まれるSPIFEE IDによりmTLS接続を制御します。

v0.3ではEnvoyをサポートする予定という話もあります。

### Workload API Client

SPIREでは [sidecar](https://github.com/spiffe/sidecar) というソフトウェアがWorkload API Clientの役割を果たしています。同時にこれはghostunnelのwrapperとしても動作していますので、Workload APIを呼び出してSVIDを取得し、それをghostunnelに与えて起動します。

## How to authenticate

![spire-auth.png](https://qiita-image-store.s3.amazonaws.com/0/15164/96f06116-52cc-64ec-6de8-1c48eedde89f.png)

## Working on k8s environment

![spire-demo.png](https://qiita-image-store.s3.amazonaws.com/0/15164/c3f7c707-7979-703c-8bc7-0ae0b7b6ead8.png)

kubernetesのPodとKubernertesクラスタ外で動作するベアメタル上のMySQLサーバの認証をデモしています。

<https://www.youtube.com/watch?v=uDHNcZ0eGHI>

<https://github.com/spiffe/spiffe-example/tree/master/beatrice>あたりを参照するとどういう設定を使っているのか見ることができます。Kubernetesのmanifestなんかはちょっと古いものもありますが&#x2026;

## 今後について

SPIFFEコミュニティではv1.0までのロードマップを提示しています。

![spire-roadmap-1.png](https://qiita-image-store.s3.amazonaws.com/0/15164/1bcc9b96-1db2-2ba6-1cb9-307678329b62.png)
![spire-roadmap-2.png](https://qiita-image-store.s3.amazonaws.com/0/15164/7d09ebc9-8f33-5deb-d7ca-307783b7863e.png)

v0.3でのAWSサポートやそれ移行のGCP,Azureのサポート、OpenStackのサポートなんかも記載されています。将来的にはX.509 SVIDだけでなく、JWTを使った認証もサポートされるようでどういった実装になるのかまだまだ目が放せません。

また、先日 CNCF のメンバーになるべくProposalのissueが作られており、そちらのissueも盛り上がってきています。<https://github.com/cncf/toc/pull/68>

SPIFFEはまだまだこれからのプロダクトですが、クラウドネイティブ環境での認証の１つの選択肢になりうると思っています。興味があればぜひ使ってみて、コミュニティにフィードバックしてあげてください。よろしくお願いします。

## あとがき

本日ここの記載したエントリとほぼ同じ内容のスライドをspeaker deckに公開していますので、よければそちらも参考にしてみてください。

https://speakerdeck.com/hiyosi/spire

明日は Kubernetes workload attestor plugin について少し書こうと思います。