SPIRE Delegated Identity API

#

## Delegated Identity APIとは

SPIRE Agentが直接的に認証(attestation)できないワークロードのかわりに、委任されたワークロード(delegated workload)がSVIDとBundleを取得できる機能です。
この機能はSPIRE AgentのAdmin APIのエンドで提供されます。(Admin APIはデフォルト無効。)

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

これは [Cilium](https://cilium.io/)コミュニティからの[提案](https://github.com/spiffe/spire/issues/2246)から始まったもので、この機能が実装されるまでのSPIREでは、Workload APIを呼び出したプロセス(ワークロード)に対して認証(attestation)しSVIDを発行するように設計されていました。
しかし、あるノードに単一のエージェントプロセスがあり、それが他のプロセスに変わって処理を実行するようなケースなどをカバーしきれていませんでした。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15164/caf7751e-5de5-c995-2c85-36da3de1382b.png)

## 必要な設定

Delegated Identity APIはAdmin API Endpointで提供されるため、Admin API Endpointの有効化と委任するワークロードのSPIFFE IDを設定ファイルで指定する必要があります。

```hcl
agent {  
    server_address = "127.0.0.1"  
    ...
  
    admin_socket_path = "/tmp/admin.sock"  
    authorized_delegates = [  
        "spiffe://example.org/authorized-delegate",  
    ]  
}  
```

## 他のSVIDの取得方法

Delegated Identity  APIを呼び出すワークロードは、リクエストパラメータとしてかわりに取得するワークロードに対応するSelectorを指定する必要があります。
以下は、uid=1002のワークロードのSVIDをかわりに取得するリクエスト例です。

```go
streamSubscribeToX509SVIDs, err := client.SubscribeToX509SVIDs(ctx, &delegatedidentityv1.SubscribeToX509SVIDsRequest{  
    Selectors: []*types.Selector{  
       {  
          Type:  "unix",  
          Value: "uid:1002",  
       },  
    },  
})  
```

## 試して見る
 
```
$ git clone https://github.com/spiffe/spire.git; cd spire
$ make dev-shell

// SPIRE本体をビルド
# make build
# PATH=.build/linux-x86_64/go/1.21.4/bin:$PATH

// テストツールをビルド
# go build -o delegatedidentityclient test/integration/setup/delegatedidentity/main.go
```

Serverの起動。設定ファイルの内容は[設定ファイル](#Agent設定ファイル)参照。ただしServer側は特にDelegated Identity API向けの設定は必要ありません。
```
# ./bin/spire-server run -config conf/server/server.conf
```

JoinTokenの発行
```
# ./bin/spire-server token generate -spiffeID spiffe://example.org/token
```

RegistrationEntryの作成
```
# ./bin/spire-server entry create \
  -parentID spiffe://example.org/token \
  -spiffeID spiffe://example.org/authorized-delegate \
  -selector unix:uid:1001

# ./bin/spire-server entry create \
  -parentID spiffe://example.org/token \
  -spiffeID spiffe://example.org/test-workload \
  -selector unix:uid:1002
```

Agentの起動。設定ファイルの内容は[設定ファイル](#Agent設定ファイル)参照。冒頭で紹介した必要となる設定を追加しています。
```
# ./bin/spire-agent run -config conf/agent/agent.conf -joinToken xxx
```

ワークロード=`uid=1001`によるSVIDの取得

```
$ docker exec -it --user 1001 <Container ID> bash

$ ./bin/spire-agent api fetch x509
Received 1 svid after 2.340039ms

SPIFFE ID:              spiffe://example.org/authorized-delegate
SVID Valid After:       2023-11-21 06:25:37 +0000 UTC
SVID Valid Until:       2023-11-21 07:25:47 +0000 UTC
Intermediate #1 Valid After:    2023-11-21 06:15:09 +0000 UTC
Intermediate #1 Valid Until:    2023-11-22 06:15:19 +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
```

`uid=1001`のワークロードはSPIRE AgentのWorkload APIにアクセスし、SVIDを取得することができました。この通常の認証(attestation)プロセスでは、自身のSVIDしか取得することができません。

Delegated Identity APIの例として、ここで`uid=1002`のワークロードがWorkload APIにアクセスできないものとし、かわりに`uid=1001`のワークロードに取得してもらいます。

```
$ ./delegatedidentityclient -adminSocketPath unix:///tmp/admin.sock
SPIFFE ID: spiffe://example.org/test-workload
```

`uid=1001`のワークロードの識別子(SPIFFE ID) `spiffe://example.org/authorized_delegate`はあらかじめ設定ファイルでdelegationを許可されているため、リクエストした`uid=1002`のワークロード分のSVIDを取得することができます。

SPIRE Agentのログ
```
DEBU[0003] Caller authorized as delegate                 delegate_id="spiffe://example.org/authorized-delegate" delegate_selectors="[type:\"unix\"  value:\"uid:1001\" type:\"unix\"  value:\"gid:0\" type:\"unix\"  value:\"group:root\" type:\"unix\"  value:\"supplementary_gid:0\" type:\"unix\"  value:\"supplementary_group:root\"]" method=SubscribeToX509SVIDs pid=5848 service=DelegatedIdentity

DEBU[0003] Fetched X.509 SVID for delegated identity     count=1 method=SubscribeToX509SVIDs pid=5848 service=DelegatedIdentity spiffe_id="trust_domain:\"example.org\"  path:\"/test-workload\"" ttl=3596.540921648
```

## テスト用の設定など
### Server設定ファイル
```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"  
        }  
    }  
}
```

### Agent 設定ファイル

``` hcl
agent {  
    data_dir = "./.data"  
    log_level = "DEBUG"  
    server_address = "127.0.0.1"  
    server_port = "8081"  
    socket_path ="/tmp/spire-agent/public/api.sock"  
    trust_bundle_path = "./conf/agent/dummy_root_ca.crt"  
    trust_domain = "example.org"  
  
    admin_socket_path = "/tmp/admin.sock"  
    authorized_delegates = [  
        "spiffe://example.org/authorized-delegate",  
    ]  
}  
  
plugins {  
    NodeAttestor "join_token" {  
        plugin_data {  
        }  
    }  
    KeyManager "disk" {  
        plugin_data {  
            directory = "./.data"  
        }  
    }  
    WorkloadAttestor "unix" {  
        plugin_data {  
        }  
    }  
}
```

### コード
```go
package main  
  
import (  
    "context"  
    "flag"    "fmt"    "log"    "time"  
    delegatedidentityv1 "github.com/spiffe/spire-api-sdk/proto/spire/api/agent/delegatedidentity/v1"  
    "github.com/spiffe/spire-api-sdk/proto/spire/api/types"    "github.com/spiffe/spire/pkg/common/idutil"    "google.golang.org/grpc"    "google.golang.org/grpc/credentials/insecure")  
  
var (  
    socketPathFlag = flag.String("adminSocketPath", "unix:///opt/admin.sock", "admin agent socket path")  
)  
  
func main() {  
    flag.Parse()  
  
    if err := run(); err != nil {  
       log.Fatalf("Test failed: %v", err)  
    }  
}  
  
func run() error {  
    ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)  
    defer cancel()  
  
    conn, err := grpc.Dial(*socketPathFlag, grpc.WithTransportCredentials(insecure.NewCredentials()))  
    if err != nil {  
       return fmt.Errorf("failed to connect server: %w", err)  
    }  
    defer conn.Close()  
  
    client := delegatedidentityv1.NewDelegatedIdentityClient(conn)  
  
    streamSubscribeToX509SVIDs, err := client.SubscribeToX509SVIDs(ctx, &delegatedidentityv1.SubscribeToX509SVIDsRequest{  
       Selectors: []*types.Selector{  
          {  
             Type:  "unix",  
             Value: "uid:1002",  
          },  
       },  
    })  
    if err != nil {  
       return err  
    }  
  
    subscribeToX509SVIDsResp, err := streamSubscribeToX509SVIDs.Recv()  
    if err != nil {  
       return err  
    }  
  
    for _, v := range subscribeToX509SVIDsResp.X509Svids {  
       fmt.Printf("SPIFFE ID: %s\n", idutil.RequireIDFromProto(v.X509Svid.Id).String())  
    }  
  
    return nil  
}
```

### Links

PRs
- https://github.com/spiffe/spire/pull/2481

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