SPIRE Credential Composer plugin

## Credential Composer pluginとは

SPIRE Serverが発行するX509-SVID, JWT-SVIDのフィールドをカスタマイズすることができるpluginです。

これまでも、Workload X509-SVIDのSubjectの`CN`や`DNS SAN`についてはRegistration Entryにて`-dns`で値をセットすることで任意の値を設定することができましたが、Credential  Composer pluginではWorkload X509-SVIDに対する他の属性だけでなく、Server X509-SVID、Agent X509-SVID、X509CA-SVIDといった他のSVIDの属性もカスタマイズ可能になります。

例えば X509 SVIDであればSubjectの`DN`全体や`DNS SAN`、他の`X.509 Extensions`(OIDを指定)などがカスタマイズ可能です。

```
message ComposeWorkloadX509SVIDResponse {
    // The attributes for the workload X509-SVID. To maintain forward
    // compatibility with future attribute field additions, these attributes
    // SHOULD be populated with the mutated attributes field in the
    // ComposeWorkloadX509SVIDRequest. If this message is not included in the
    // response, the original attributes sent in the request will be used.
    X509SVIDAttributes attributes = 1;
}

message X509SVIDAttributes {
    // The subject of the X509-SVID.
    DistinguishedName subject = 1;

    // Zero or more DNS SANs to apply to the X509-SVID.
    repeated string dns_sans = 2;

    // Zero or more extensions to apply to the X509-SVID . These will override
    // any extensions otherwise added by the other fields. This field cannot
    // be used to change the URI SAN of the X509-SVID (i.e. the SPIFFE ID).
    repeated X509Extension extra_extensions = 3;
}
```

Pluginでカスタマイズ可能なSVIDタイプ毎のフィールドや詳細は以下のprotoファイルを参照してください。

- https://github.com/spiffe/spire-plugin-sdk/blob/v1.8.5/proto/spire/plugin/server/credentialcomposer/v1/credentialcomposer.proto

SPIREのCAはSVID生成前にCredential Composer pluginを呼び出し(設定されていれば)、カスタマイズされた値を受け取ります。現在の実装では、Pluginはデフォルト値を利用する場合はリクエストパラメータとしてデフォルト値が渡されるのでPlugin側でマージが必要そうです。また、Credential Composer pluginは複数設定できるようですが、上書きされるため同じフィールド(attributes)をカスタマイズする場合は注意が必要そうです。

e.g. external pluginとして実装された場合
```mermaid
sequenceDiagram
	box SPIRE Server
	participant SPIRE Server API
	participant SPIRE Server CA
	end
	
	box Exteanal plugin
	participant Credential Composer plugin
	end

	SPIRE Agent ->>+ SPIRE Server API: Request MitX509SVID
	SPIRE Server API ->>+ SPIRE Server CA: Requst Signing
	SPIRE Server CA->>+Credential Composer plugin: Compose workload X509-SVID template
	Credential Composer plugin ->>+ Credential Composer plugin: Customize
	Credential Composer plugin ->>+ SPIRE Server CA: Customized X509-SVID template
	SPIRE Server CA ->>+ SPIRE Server CA: Signing (...omit)
	SPIRE Server CA ->>+ SPIRE Server API: X509-SVID
	SPIRE Server API ->>+ SPIRE Agent: X509-SVID
```

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

Subjectに関していえば、現在ではSANが利用されるようになっていることや、SPIFFEの世界ではURI SANにセットされている`SPIFEFE ID`を識別子とするため、当初はそれほどカスタマイズが必要と考えられていなかったように思います。

しかし、現実では様々な実装があり、SPIREがリリースされてからこれまでにSVIDのフィールドをカスタマイズしたいという要望が多くありました。例えば [X509-SVIDのSubjectをユニークなものにしたい](https://github.com/spiffe/spire/issues/3110)や、[CNにSPIFFE IDをセットしたい](https://github.com/spiffe/spire/issues/1795)など。

これらの要望に対して柔軟に対応するための手段としてフィールドをカスタマイズ可能なpluginが提供されることになりました。

## Plugins
### Buit-in

Credential Composer pluginにはビルトインされたものはありません。

### External

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

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

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

## Examples

### Plugin コード

`credentialcomposer.go` (spire-plugin-sdkリポジトリのtemplateをコピー)
```go
package main

...省略...

type Config struct {
	Country string `hcl:"country"`
	Organization string `hcl:"organization"`
	CommonName string `hcl:"common_name"`
}

...省略...

func (p *Plugin) ComposeWorkloadX509SVID(ctx context.Context, req *credentialcomposerv1.ComposeWorkloadX509SVIDRequest) (*credentialcomposerv1.ComposeWorkloadX509SVIDResponse, error) {

	config, err := p.getConfig()
	if err != nil {
		return nil, err
	}

	return &credentialcomposerv1.ComposeWorkloadX509SVIDResponse{
		Attributes: &credentialcomposerv1.X509SVIDAttributes{
			Subject: &credentialcomposerv1.DistinguishedName{
				Country:      []string{config.Country},
				Organization: []string{config.Organization},
				CommonName:   config.CommonName,
			},
		},
	}, nil
}

...省略...

```

これを適当にビルドしてSPIRE Serverのバイナリと同じノードに配置します。
今回の設定では `/tmp`配下に`my-credential-composer`として配置しました。
### Server 設定ファイル

`server.conf`
```hcl
server {  
    bind_address = "127.0.0.1"  
    bind_port = "8081"  
    socket_path = "/tmp/spire-server/private/api.sock"  
    trust_domain = "example.org"  
    data_dir = "./.data"  
    log_level = "DEBUG"  
}  
  
plugins {  
    DataStore "sql" {  
        plugin_data {  
            database_type = "sqlite3"  
            connection_string = "./.data/datastore.sqlite3"  
        }  
    }  
  
    NodeAttestor "join_token" {  
        plugin_data {  
        }  
    }  
  
    KeyManager "memory" {  
        plugin_data = {}  
    }  
  
    UpstreamAuthority "disk" {  
        plugin_data {  
            key_file_path = "./conf/server/dummy_upstream_ca.key"  
            cert_file_path = "./conf/server/dummy_upstream_ca.crt"  
        }  
    }  
  
    CredentialComposer "my-composer" {  
        plugin_cmd = "/tmp/my-credential-composer"  
        plugin_data {  
            country = "JP"  
            organization = "Z Lab Corporation"  
            common_name = "my-workload.example.org"  
        }  
    }  
}
```

配置したExternal pluginを参照するようにCredentialComposer pluginの設定を追加します。

### 動作確認

SPIRE Serverを起動します。
```
# ./spire-server run -config conf/server/server.conf
```

SPIRE AgentのNode Attestation用Tokenを発行します。

```
# ./spire-server token generate -spiffeID spiffe://example.org/token
Token: xxx
```

Workload用のRegistration Entryを作成します。

```
# ./spire-server entry create -parentID spiffe://example.org/token -spiffeID spiffe://example.org/workload -selector unix:uid:0                                                                                                                
Entry ID         : 35d7a63c-b67a-43db-9933-01336d371772
SPIFFE ID        : spiffe://example.org/workload
Parent ID        : spiffe://example.org/token
Revision         : 0
X509-SVID TTL    : default
JWT-SVID TTL     : default
Selector         : unix:uid:0
```

SPIRE Agentを起動します。Agentの設定は[設定例](https://github.com/spiffe/spire/blob/main/conf/agent/agent.conf)をそのまま利用しています。

```
# ./spire-agent run -config conf/agent/agent.conf -joinToken xxx
```

Workload X509-SVIDを取得します。SPIRE AgentのCLIを使って取得します。`uid=0`に対するEntryを作成しているためルートで取得しています。
```
# ./bin/spire-agent api fetch x509 -write .

SPIFFE ID:              spiffe://example.org/workload
SVID Valid After:       2023-11-30 01:23:20 +0000 UTC
SVID Valid Until:       2023-11-30 02:23:30 +0000 UTC
Intermediate #1 Valid After:    2023-11-30 01:21:59 +0000 UTC
Intermediate #1 Valid Until:    2023-12-01 01:22:09 +0000 UTC
CA #1 Valid After:      2023-05-15 02:05:06 +0000 UTC
CA #1 Valid Until:      2028-05-13 02:05:06 +0000 UTC
```

X509-SVIDのSubjectを見てみます。

```
# openssl x509 -in svid.0.pem -noout -subject
subject= /C=JP/O=Z Lab Corporation/CN=my-workload.example.org
```

テスト用のプラグインに対して設定した値と同じものが入っていることが確認できました。

Agent SVIDのカスタマイズは実装していないのでデフォルト値のままです。

```
# openssl x509 -inform DER -in .data/agent_svid.der -noout -subject
subject= /C=US/O=SPIRE/x500UniqueIdentifier=07c74a3d23b588f875d23ee96e070217
```

## まとめ

Credential Composer pluginを実装することで簡単にSVIDの属性パラメータをカスタマイズできることがわかりました。

システムのアクセス制御にRBACのような仕組みを使っており、X.509証明書のSubjectの値を参照して制御しているような環境へのSVIDの適用など、利用範囲がより広がりそうです。

## Links

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