SPIRE Bundle Publisher plugin

## Bundle Publisherとは

SPIRE Serverの最新のCA バンドル(Trust Bundles)を外部ストレージに公開するためのプラグインです。

SPIRE ServerにはTrust Bundlesを取得できるgRPC APIのエンドポイントがありますが、そのSPIRE Serverが信頼できるものかどうか確認(サーバ証明書の検証)が必要であるため、初回接続時などはこのAPIは利用できません。そのためSPIRE Agentでは初回起動時にSPIRE Serverのサーバ証明書を検証するためのTrust BundlesはSPIRE Server API以外の場所から取得するようになっています。ローカルファイルシステムやHTTPS経由でリモートから取得するなどが可能です。

Upstream CAでそのようなTrust Bundlesを公開できない場合や、SPIRE Server自体がRoot CAの場合などでは外部ストレージに公開し、SPIRE Agentはそこから取得するというようなことができると、初回起動時のサーバ証明書検証で必要となるTrust Bundlesの配布問題を解消できます。K8s環境であればBundle PublisherはConfig Map等にTrust Bundlesを保管し更新したり、その他の環境ではGCSやS3などのリモートストレージに公開することで、SPIRE Agentやその他SVIDの検証が必要なWorkloadがTrust Bundlesを取得することができます。

e.g. k8s 環境 

```mermaid
flowchart TB
	subgraph K8s
		subgraph ConfigMap
			TB[Trust Bundles]
		end
		subgraph SPIRE Server Pod
			SSAPI[Server API]
			BP[Bundle Publisher] --Publish--> TB
		end
		subgraph SPIRE Agent Pod
			TB -.Mount.-> SA[SPIRE Agent]
			SA -.Read Trust Bundles.->SA
			SA --Verify server certificate --> SSAPI
		end
	end
```
e.g. その他
```mermaid
flowchart LR
	subgraph S3
		TB[Trust Bundles]
	end
	subgraph SPIRE Server
		SSAPI[Server API]
		BP[Bundle Publisher] --publish--> TB
	end
	subgraph SPIRE Agent
		SA[SPIRE Agent] -.Fetch Trust Bundles.-> TB
		SA --Verify server certificate --> SSAPI
	end
```

## なぜ必要となったのか

SPIREには、似たようなことを実現するpluginとしてNotifier pluginがあります。Notifier pluginはイベントドリブンなユースケースを満たすために用意されたpluginです。これはServerからあるイベント(e.g. データの追加・更新など)を受けとり任意の処理を実装できる仕組みです。
ひとつのユースケースとしてTrust Bundlesをリモートストレージへ公開するものがあります。

notifier.proto
```go
service Notifier {
    // Notify notifies the plugin that an event occurred. Errors returned by
    // the plugin are logged but otherwise ignored.
    rpc Notify(NotifyRequest) returns (NotifyResponse);

    // NotifyAndAdvise notifies the plugin that an event occurred and waits
    // for a response. Errors returned by the plugin control SPIRE Server
    // behavior. See NotifyAndAdviseRequest for per-event details.
    rpc NotifyAndAdvise(NotifyAndAdviseRequest) returns (NotifyAndAdviseResponse);
}

message NotifyRequest {
    // Required. The event the plugin is being notified for.
    oneof event {
        // BundleUpdated is emitted whenever SPIRE Server changes the trust
        // bundle.
        BundleUpdated bundle_updated = 1;
    }
}

message NotifyAndAdviseRequest {
    // Required. The event the plugin is being notified for.
    oneof event {
        // BundleLoaded is emitted on startup after SPIRE Server creates/loads
        // the trust bundle. If an error is returned SPIRE Server is shut down.
        BundleLoaded bundle_loaded = 1;
    }
}
```

ビルトインとして実装済みのpluginとしてK8s ConfigMapやGCSへ公開するものがあります。ServerからTrust Bundlesの更新イベントを受け取ると、read-modify-writeサイクルで更新するように作られています。これらの実装では1つのオブジェクトを更新しているため(※1)、異なるイベントからの通知や異なるサーバが**古いデータで更新を上書きしてしまう事故を防ぐ必要があります**。

K8s ConfigMapやGCSのようにオブジェクトの世代などの条件により更新の競合を検知できてるものは問題ありませんが、S3などそのような情報が提供されないストレージがあることもわかり、Trust Bundlesを公開して起動時に取得してもらうユースケースにはNotifier pluginは適していないのではないか(※2)という議論が起こり、新しくよりシンプルなBundle Publisher pluginの仕組みが追加されました。

Bundle Publisher pluginでは、Serverから定期的にTrust Bundleが送られてくるため、更新されているようであれば送られてきたデータでリモートのデータを更新するだけのシンプルな作りになっており、Notifier pluginでTrust Bundlesを更新する際のような問題も起こりません。

bundlepublisher.proto
```go
// The BundlePublisher plugin publishes a trust bundle to a store.
service BundlePublisher {
    // PublishBundle publishes the trust bundle that is in the request
    // to a store.
    rpc PublishBundle(PublishBundleRequest) returns (PublishBundleResponse);
}

message PublishBundleRequest {
    // Required. The trust bundle to publish.
    spire.plugin.types.Bundle bundle = 1;
}
```

※1 K8sならConfigMapをマウントしたり、GCSならそのオブジェクト(ファイルを)HTTPSで取得して起動時のTrust Bundleとして読み込ませたいため、と思われる。(e.g `trust_bundle_path`, `trust_bundle_url` で指定する)

※2 リモートストレージを参照して公開するAPIを別途用意するなどの解決策もあるのかもしれませんが、そのAPIのサーバ証明書の検証は、という問題が出てきてしまうように思われる。

## Plugins
### Built-in

SPIRE v1.8.5 現在は[aws_s3](https://github.com/spiffe/spire/blob/main/doc/plugin_server_bundlepublisher_aws_s3.md)pluginがビルトインされています。

### External

GitHubのパブリックリポジトリを検索してみましたが、公開されているExtenalプラグインは見つかりませんでした。

[spire-plugin-sdk](https://github.com/spiffe/spire-plugin-sdk/tree/main/proto/spire/plugin/server/bundlepublisher/v1)でインタフェースが公開されています。それを使った実装の[テンプレート](https://github.com/spiffe/spire-plugin-sdk/blob/main/templates/server/bundlepublisher/bundlepublisher.go) されているため、必要な機能をプラグインとしてSPIREリポジトリとは別に実装することができます。

この場合SPIREとは別のバイナリとしてインストールしてSPIRE ServerからPluginとして起動してもらいます。詳細はリポジトリを参照してください。

## 今後

Notifier pluginはより一般化されたインタフェースで様々なユースケースに対応できるものでしたが、これまでのところTrust Bundles更新以外のユースケースの要望は無いようです。(当初はTransparency Logsへの追加などの話もあったようですが)。

他のユースケースの要望が無いのであればBundle Publisherに置き換えてNotifier pluginを廃止するかどうかを検討するissueが作られています。

https://github.com/spiffe/spire/issues/2909

もしNotifier pluginを実装して使っている人がいれば上記issueで伝えるのが良さそうです。

## Links

Issues
- https://github.com/spiffe/spire/issues/1512

PRs
- https://github.com/spiffe/spire/pull/4022
- https://github.com/spiffe/spire/pull/4355