Skip to content

本文为 SPIFFE 官方规范 The SPIFFE Workload API 的中文译本,仅供学习参考;若与英文原文有出入,以英文版为准。

SPIFFE 工作负载 API(Workload API) ​

稳定性:Stable(稳定) - 参见 STABILITY.md。

本备忘录状态 ​

本文档为互联网社区规定了一项身份 API 标准,并征求各方对改进意见的讨论与建议。本文档的分发不受限制。

摘要 ​

为联网的工作负载(workload)提供可移植、可互操作的加密身份,或许正是 SPIFFE 最 核心的用例。为了完整地满足这一需求,社区必须就一套标准化的方式达成共识,以检索、验证 SPIFFE 身份并与之交互。本规范概述了支持基于 SPIFFE 的认证系统所需的 API 签名以及客户端/服务器行为。

1. Introduction ​

SPIFFE 工作负载 API(Workload API)是一套 API,它提供各类信息与服务,使工作负载(workload,即计算进程)能够利用 SPIFFE 身份以及基于 SPIFFE 的认证系统。它由 SPIFFE 工作负载端点(Workload Endpoint)提供服务,由若干服务(即 profile)构成。

目前共有三个 profile:

X.509-SVID 与 JWT-SVID profile 是强制性的,SPIFFE 实现必须(MUST)支持它们。不过,运维方可以(MAY)在其部署中通过管理手段禁用某个特定的 profile。

WIT-SVID Profile 是可选的,SPIFFE 实现可以(MAY)支持它。

本规范的未来版本可能会引入更多 profile,或将一个或多个 profile 改为可选。

2. Extensibility ​

禁止(MUST NOT)在本规范之外对 SPIFFE 工作负载 API 进行扩展。希望提供扩展功能的实现者,可以按照 SPIFFE 工作负载端点规范中所述的扩展方法,通过引入新的 gRPC 服务来实现。

3. Service Definition ​

SPIFFE 工作负载 API 由一份 Protocol Buffer(版本 3)服务定义所定义。完整定义见 workloadapi.proto。

各 profile 以一组相关的 RPC 的形式,实现在单个 WorkloadAPI 服务之内。

4. Client and Server Behavior ​

4.1 Identifying the Caller ​

SPIFFE 工作负载 API 支持任意数量的本地客户端,从而能够为任何可以访问到它的进程引导(bootstrap)其身份。通常,人们希望按进程(per-process)分配身份,即向特定进程授予特定身份。为此,SPIFFE 工作负载 API 的实现必须能够确定调用方(caller)的身份。

SPIFFE 工作负载端点规范要求不进行直接的客户端认证,而是依赖带外(out-of-band)的真实性检查。因此,识别调用方是 SPIFFE 工作负载端点实现的职责。随后,SPIFFE 工作负载 API 便可利用有关调用方的信息来决定应当提供何种内容。更多信息请参见 SPIFFE 工作负载端点规范的认证(Authentication)一节。

4.2 Connection Lifetime ​

SPIFFE 工作负载 API 的客户端应当(SHOULD)在合理可能的范围内尽可能长时间地保持连接开启,在流(stream)上等待接收服务器的响应消息。该连接可能在任意时刻被服务器或客户端终止。在这种情况下,客户端应当(SHOULD)立即建立新的连接。这有助于确保工作负载持有最新的一整套与身份相关的材料。SPIFFE 工作负载 API 服务器实现者可以假定这一特性;而若未能及时接收消息,工作负载可能会变得过时,从而可能影响其可用性。

4.3 Stream Responses ​

SPIFFE 工作负载 API 包含一些利用 gRPC 服务器端流(server-side stream)的 RPC,以便快速传播诸如吊销(revocation)和引入新 CA 证书之类的更新。这使得客户端能够循环处理服务器响应,在更新发生时接收更新后的响应。

服务器发送的每一条流式响应消息都必须(MUST)包含完整的信息集合,而不仅仅是已变更的信息。这样可以避免客户端与服务器实现两侧在状态跟踪方面的复杂性,包括对反熵(anti-entropy)机制的需求。

服务器响应消息的确切时机由具体实现决定,并且应当(SHOULD)由那些会改变响应内容的事件来触发,例如 SVID 轮换、CRL 更新等。收到来自客户端的请求消息必须(MUST)被视为一个会产生响应的事件。换言之,服务器响应流(以每条连接为单位)的第一条响应消息必须(MUST)尽快发送,不得延迟。

最后,SPIFFE 工作负载 API 服务器的实现者应注意不要 过于 频繁地推送更新后的响应消息。某些软件在收到新信息时会自动重新加载,若所有实例同时重新加载,可能导致一段时间的不可用。因此,实现者可以在大范围更新的传输中引入一定的散布/抖动(splay/jitter)。

为进一步阐明,请参见附录 A,其中给出了示例实现的状态机。

4.4 Default Values and Redacted Information ​

SPIFFE 工作负载 API 的响应消息是对先前发送的响应消息的完整更新。当某条响应消息中包含被设置为默认值或空值的字段时,客户端必须(MUST)将这些字段的取值理解为已被设置为其默认值或空值;在收到某字段的默认值或空值之后,客户端禁止(MUST NOT)继续保留先前收到的非默认或非空取值。例如,某客户端在 federated_bundles 字段中收到默认值时,应丢弃先前收到的 federated_bundles 取值。

由于每条消息都必须(MUST)包含完整的信息集合(参见流式响应一节),客户端应当(SHOULD)将数据的缺失理解为一次删减(redaction)。举例来说,如果某客户端已为 spiffe://foo.bar 加载了证书包,却又收到一条不包含 spiffe://foo.bar 对应证书包的消息,那么应当(SHOULD)卸载该证书包。

4.5 Mandatory Fields ​

在 profile RPC 中交换的消息由必填字段和可选字段共同构成。服务器若收到某条消息,其中某个必填字段取默认值,则应当(SHOULD)以 "InvalidArgument" gRPC 状态码作出响应(更多信息参见 SPIFFE 工作负载端点规范的错误码(Error Codes)一节)。客户端若收到某条消息,其中某个必填字段取默认值,则应当(SHOULD)报告错误并丢弃该消息。

4.6 Federated Bundles ​

本规范定义的多个 RPC 都可以返回外部信任域(foreign trust domain)的信任域证书包(trust bundle)。纳入外部证书包使工作负载能够 跨 信任域(trust domain)通信,这也是实现联邦(federation)的主要机制。表示某个外部信任域的证书包被称为 联邦证书包(Federated Bundle)。

在对客户端进行认证时,认证方会选择表示该客户端所出示信任域的证书包用于验证。类似地,在对服务器进行认证时,客户端会使用表示该服务器信任域的证书包。如果对于正在使用的 SVID 找不到匹配的证书包,则该对端(peer)不被信任。之所以需要采用这种做法,是因为常见的 X.509 库对 SAN URI 名称约束(Name Constraints)缺乏广泛支持。更多信息请参见 X509-SVID 规范的第 4.2 节。

5. X.509-SVID Profile ​

SPIFFE 工作负载 API 的 X.509-SVID Profile 提供了一组 gRPC 方法,工作负载可用它们来检索 X.509-SVID 及其相关的信任域证书包。本 profile 概述了这些方法的签名,以及相关的客户端与服务器行为。

5.1 Profile Definition ​

X.509-SVID Profile 的 RPC 及相关消息定义如下。完整的工作负载 API 服务定义见 workloadapi.proto。

protobuf
service SpiffeWorkloadAPI {
    /////////////////////////////////////////////////////////////////////////
    // X509-SVID Profile
    /////////////////////////////////////////////////////////////////////////

    // 为该工作负载有权获得的所有 SPIFFE 身份获取 X.509-SVID,以及诸如
    // 信任域证书包、CRL 之类的相关信息。随着这些信息发生变化,服务器
    // 会以流的形式向下推送后续消息。
    rpc FetchX509SVID(X509SVIDRequest) returns (stream X509SVIDResponse);

    // 获取信任域证书包与 CRL。适用于只需校验 SVID、而无需为自身获取
    // SVID 的客户端。随着这些信息发生变化,服务器会以流的形式向下推送
    // 后续消息。
    rpc FetchX509Bundles(X509BundlesRequest) returns (stream X509BundlesResponse);

    // ... 其他 profile 的 RPC ...
}


// X509SVIDRequest 消息用于传递请求 X.509-SVID 所需的参数。
// 目前尚无此类参数。
message X509SVIDRequest {  }

// X509SVIDResponse 消息携带 X.509-SVID 及相关信息,包括一组全局 CRL,
// 以及一份工作负载可用于与外部信任域进行联邦的证书包列表。
message X509SVIDResponse {
    // 必填。X509SVID 消息的列表,其中每一项都包含单个 X.509-SVID、
    // 其私钥,以及该信任域的证书包。
    repeated X509SVID svids = 1;

    // 可选。以 ASN.1 DER 编码(而非 PEM)的证书吊销列表(CRL)。
    repeated bytes crl = 2;

    // 可选。属于外部信任域、且工作负载应当信任的 CA 证书包,以该外部
    // 信任域的 SPIFFE ID 为键。证书包以 ASN.1 DER 编码(而非 PEM)。
    map<string, bytes> federated_bundles = 3;
}

// X509SVID 消息携带单个 SVID 及其全部关联信息,包括该信任域的
// X.509 证书包。
message X509SVID {
    // 必填。本条目中 SVID 的 SPIFFE ID
    string spiffe_id = 1;

    // 必填。以 ASN.1 DER 编码(而非 PEM)的证书链。可以(MAY)包含中间
    // 证书,但叶子证书(即 SVID 本身)必须(MUST)排在最前。
    bytes x509_svid = 2;

    // 必填。以 ASN.1 DER 编码(而非 PEM)的 PKCS#8 私钥。必须(MUST)为未加密形式。
    bytes x509_svid_key = 3;

    // 必填。该信任域的 X.509 证书包,以 ASN.1 DER 编码(而非 PEM)。
    bytes bundle = 4;

    // 可选。由运维方指定的字符串,用于在返回了多个 SVID 时,就工作负载
    // 应如何使用该身份提供指引。例如用 `internal`(内部)和 `external`
    //(外部)分别表示供内部或外部使用的 SVID。
    string hint = 5;
}

// X509BundlesRequest 消息用于传递请求 X.509 证书包所需的参数。
// 目前尚无此类参数。
message X509BundlesRequest {
}

// X509BundlesResponse 消息携带一组全局 CRL,以及一个包含工作负载
// 应当信任的信任域证书包的映射。
message X509BundlesResponse {
    // 可选。以 ASN.1 DER 编码(而非 PEM)的证书吊销列表(CRL)。
    repeated bytes crl = 1;

    // 必填。属于工作负载应当信任的信任域的 CA 证书包,以该信任域的
    // SPIFFE ID 为键。证书包以 ASN.1 DER 编码(而非 PEM)。
    map<string, bytes> bundles = 2;
}

5.2 Profile RPCs ​

5.2.1 FetchX509SVID ​

FetchX509SVID RPC 以流的形式返回 X509-SVID,以及服务器所在信任域和外部信任域两者的 X.509 证书包。这些证书包必须(MUST)仅用于认证 X509-SVID。

X509SVIDRequest 请求消息目前为空,是为将来扩展预留的占位符。

X509SVIDResponse 响应由一个必填的 svids 字段构成,该字段必须(MUST)包含一个或多个 X509SVID 消息(为授予客户端的每个身份各提供一个)。crl 与 federated_bundles 字段为可选。

X509SVID 消息中的所有字段均为必填,唯一的例外是 hint 字段。当设置了 hint 字段(即其值非空)时,SPIFFE 工作负载 API 服务器必须(MUST)确保:在任意给定的 X509SVIDResponse 消息中,该值在所返回的一组 SVID 里是唯一的。如果 SPIFFE 工作负载 API 客户端遇到多个 X509SVID 消息设置了相同的 hint 值,则应当(SHOULD)选择列表中的第一条消息。

如果客户端无权接收任何 X509-SVID,则服务器应当(SHOULD)以 "PermissionDenied" gRPC 状态码作出响应(更多信息参见 SPIFFE 工作负载端点规范的错误码一节)。在这种情况下,客户端可以(MAY)在一段退避(backoff)之后,通过再次调用 FetchX509SVID RPC 尝试重新连接。

正如流式响应一节所述,FetchX509SVID 流上返回的每条 X509SVIDResponse 消息,都包含在该时刻客户端被授权的完整 SVID 与证书包集合。因此,如果服务器在后续响应中删减了某些 SVID(或删减了全部 SVID,即返回 "PermissionDenied" gRPC 状态码),客户端应当(SHOULD)停止使用被删减的 SVID。

5.2.2 FetchX509Bundles ​

FetchX509Bundles RPC 以流的形式返回服务器所在信任域和外部信任域两者的 X.509 证书包。这些证书包必须(MUST)仅用于认证 X509-SVID。

X509BundlesRequest 请求消息目前为空,是为将来扩展预留的占位符。

X509BundlesResponse 响应消息有一个必填的 bundles 字段,该字段必须(MUST)至少包含服务器所在信任域的信任域证书包。crl 字段为可选。

如果客户端无权接收任何 X.509 证书包,则服务器应当(SHOULD)以 "PermissionDenied" gRPC 状态码作出响应(更多信息参见 SPIFFE 工作负载端点规范的错误码一节)。客户端可以(MAY)在一段退避之后,通过再次调用 FetchX509Bundles RPC 尝试重新连接。

正如流式响应一节所述,每条 X509BundlesResponse 响应都包含在该时刻客户端被授权的完整 X.509 证书包集合。因此,如果服务器在后续响应中删减了某些证书包(或删减了全部证书包,即返回 "PermissionDenied" gRPC 状态码),客户端应当(SHOULD)停止使用被删减的证书包。

6. JWT-SVID Profile ​

SPIFFE 工作负载 API 的 JWT-SVID Profile 提供了一组 gRPC 方法,工作负载可用它们来检索 JWT-SVID 及其相关的信任域证书包。本 profile 概述了这些方法的签名,以及相关的客户端与服务器行为。

6.1 Profile Definition ​

JWT-SVID Profile 的 RPC 及相关消息定义如下。完整的工作负载 API 服务定义见 workloadapi.proto。

protobuf
service SpiffeWorkloadAPI {
    /////////////////////////////////////////////////////////////////////////
    // JWT-SVID Profile
    /////////////////////////////////////////////////////////////////////////

    // 为该工作负载有权获得的所有 SPIFFE 身份、针对所请求的受众
    //(audience)获取 JWT-SVID。如果请求中附带了可选的 SPIFFE ID,
    // 则仅返回该 SPIFFE ID 对应的 JWT-SVID。
    rpc FetchJWTSVID(JWTSVIDRequest) returns (JWTSVIDResponse);

    // 获取 JWT 证书包,以 JWKS 文档格式表示,并以信任域的 SPIFFE ID
    // 为键。随着这些信息发生变化,服务器会以流的形式向下推送后续消息。
    rpc FetchJWTBundles(JWTBundlesRequest) returns (stream JWTBundlesResponse);

    // 针对所请求的受众校验 JWT-SVID。返回该 JWT-SVID 的 SPIFFE ID
    // 以及 JWT 声明(claims)。
    rpc ValidateJWTSVID(ValidateJWTSVIDRequest) returns (ValidateJWTSVIDResponse);

    // ... 其他 profile 的 RPC ...
}

message JWTSVIDRequest {
    // 必填。工作负载打算据以进行认证的受众。
    repeated string audience = 1;

    // 可选。为 JWT-SVID 所请求的 SPIFFE ID。若未设置,则返回该工作负载
    // 有权获得的所有身份的 JWT-SVID。
    string spiffe_id = 2;
}

// JWTSVIDResponse 消息用于传递 JWT-SVID。
message JWTSVIDResponse {
    // 必填。所返回的 JWT-SVID 列表。
    repeated JWTSVID svids = 1;
}

// JWTSVID 消息携带 JWT-SVID 令牌及相关元数据。
message JWTSVID {
    // 必填。该 JWT-SVID 的 SPIFFE ID。
    string spiffe_id = 1;

    // 必填。使用 JWS Compact Serialization 编码的 JWT。
    string svid = 2;

    // 可选。由运维方指定的字符串,用于在返回了多个 SVID 时,就工作负载
    // 应如何使用该身份提供指引。例如用 `internal`(内部)和 `external`
    //(外部)分别表示供内部或外部使用的 SVID。
    string hint = 3;
}

// JWTBundlesRequest 消息用于传递请求 JWT 证书包所需的参数。
// 目前尚无请求参数。
message JWTBundlesRequest { }

// JWTBundlesResponse 用于传递 JWT 证书包。
message JWTBundlesResponse {
    // 必填。以 JWK 编码的 JWT 证书包,以信任域的 SPIFFE ID 为键。
    map<string, bytes> bundles = 1;
}

// ValidateJWTSVIDRequest 消息用于传递 JWT-SVID 校验的请求参数。
message ValidateJWTSVIDRequest {
    // 必填。校验方的受众。JWT-SVID 必须包含一个 audience 声明,且其中
    // 含有此值,方能校验成功。
    string audience = 1;

    // 必填。待校验的 JWT-SVID,使用 JWS Compact Serialization 编码。
    string svid = 2;
}

// ValidateJWTSVIDResponse 消息用于传递 JWT-SVID 的校验结果。
message ValidateJWTSVIDResponse {
    // 必填。已校验的 JWT-SVID 的 SPIFFE ID。
    string spiffe_id = 1;

    // 必填。已校验的 JWT-SVID 载荷中所含的声明(claims)。其中既包括
    // SPIFFE 所要求的声明,也包括非必需的声明。
    google.protobuf.Struct claims = 2;
}

6.2 Profile RPCs ​

6.2.1 FetchJWTSVID ​

FetchJWTSVID RPC 允许客户端为特定受众请求一个或多个短期(short-lived)JWT-SVID。

JWTSVIDRequest 请求消息包含一个必填的 audience 字段,该字段必须(MUST)包含要嵌入到所返回 JWT-SVID 的 audience 声明中的值。spiffe_id 字段为可选,用于为特定的 SPIFFE ID 请求 JWT-SVID。若未指定,服务器必须(MUST)返回客户端被授权的所有身份的 JWT-SVID。

JWTSVIDResponse 响应消息由一个必填的 svids 字段构成,该字段必须(MUST)包含一个或多个 JWTSVID 消息。

JWTSVID 消息中的所有字段均为必填,唯一的例外是 hint 字段。当设置了 hint 字段(即其值非空)时,SPIFFE 工作负载 API 服务器必须(MUST)确保:在任意给定的 JWTSVIDResponse 消息中,该值在所返回的一组 SVID 里是唯一的。如果 SPIFFE 工作负载 API 客户端遇到多个 JWTSVID 消息设置了相同的 hint 值,则应当(SHOULD)选择列表中的第一条消息。

如果客户端未被授权任何身份,或未被授权通过 spiffe_id 字段请求的特定身份,则服务器应当(SHOULD)以 "PermissionDenied" gRPC 状态码作出响应(更多信息参见 SPIFFE 工作负载端点规范的错误码一节)。

6.2.2 FetchJWTBundles ​

FetchJWTBundles RPC 以流的形式返回服务器所在信任域和外部信任域两者的 JWT 证书包。这些证书包必须(MUST)仅用于认证 JWT-SVID。

JWTBundlesRequest 请求消息目前为空,是为将来扩展预留的占位符。

JWTBundlesResponse 响应消息由一个必填的 bundles 字段构成,该字段必须(MUST)至少包含服务器所在信任域的 JWT 证书包。

所返回的证书包被编码为符合 RFC 7517 定义的标准 JWK Set,其中包含该信任域的 JWT-SVID 签名密钥。这些密钥可能只代表该信任域 SPIFFE 信任域证书包中所含密钥的一个子集。服务器禁止(MUST NOT)在所返回的 JWT 证书包中包含用于其他用途的密钥。

如果客户端无权接收任何 JWT 证书包,则服务器应当(SHOULD)以 "PermissionDenied" gRPC 状态码作出响应(更多信息参见 SPIFFE 工作负载端点规范的错误码一节)。客户端可以(MAY)在一段退避之后,通过再次调用 FetchJWTBundles RPC 尝试重新连接。

正如流式响应一节所述,每条 JWTBundlesResponse 响应都包含在该时刻客户端被授权的完整 JWT 证书包集合。因此,如果服务器在后续响应中删减了某些证书包(或删减了全部证书包,即返回 "PermissionDenied" gRPC 状态码),客户端应当(SHOULD)停止使用被删减的证书包。

6.2.3 ValidateJWTSVID ​

ValidateJWTSVID RPC 代表客户端针对特定受众校验 JWT-SVID。此外,服务器必须(MUST)按照 JWT-SVID 规范中所述的规则解析并校验 JWT-SVID。JWT-SVID 载荷中所嵌入的声明应当(SHOULD)在 ValidateJWTSVIDResponse 的 claims 字段中提供;上文本规范所定义的声明是必需的,但实现可以(MAY)在将非 SPIFFE 声明返回给客户端之前将其过滤掉。SPIFFE 声明对于互操作性而言是必需的。

ValidateJWTSVIDRequest 与 ValidateJWTSVIDResponse 消息中的所有字段均为必填。

6.3 JWT-SVID Validation ​

如果客户端支持,工作负载 API 客户端应当(SHOULD)使用 ValidateJWTSVID 方法进行 JWT 校验,让 SPIFFE 工作负载 API 代表它们执行校验。这样做免去了工作负载自行实现校验逻辑的需要,而后者往往容易出错。

在与传统(legacy)JWT 校验器对接时,可以使用 FetchJWTBundles 方法获取 JWKS 证书包,用于校验 JWT-SVID 的签名。例如,如果 SPIFFE 工作负载 API 可用,但 JWT 校验软件并不知晓工作负载 API(因而无法调用 ValidateJWTSVID),实现可以改为逐个获取各证书包,并将它们提供给传统工作负载以进行校验。

FetchJWTBundles 方法返回以信任域的 SPIFFE ID 为键的证书包。在校验 JWT-SVID 时,校验方必须(MUST)使用与主体(subject)所属信任域相对应的证书包。如果不存在指定信任域的 JWT 证书包,则该令牌不被信任。

7. WIT-SVID Profile ​

稳定性:Incubating(孵化中)(仅限本节 —— 本文档的其余部分 为 Stable(稳定)。)

SPIFFE 工作负载 API 的 WIT-SVID Profile 提供了一组 gRPC 方法,工作负载可用它们来检索 WIT-SVID 及其相关的信任域证书包。本 profile 概述了这些方法的签名,以及相关的客户端与服务器行为。

与 X.509-SVID 和 JWT-SVID profile 不同,WIT-SVID profile 的实现是可选的。在服务器不支持 WIT-SVID profile 的情况下,对 WIT-SVID profile 所定义的所有 RPC 的调用都必须(MUST)返回 "Unimplemented" gRPC 状态码。如果客户端在调用某个 WIT-SVID profile RPC 时收到 "Unimplemented" 状态码,则不应(SHOULD NOT)尝试使用 WIT-SVID profile 重新连接服务器。

7.1 Profile Definition ​

WIT-SVID Profile 的 RPC 及相关消息定义如下。完整的工作负载 API 服务定义见 workloadapi.proto。

protobuf
service SpiffeWorkloadAPI {
  /////////////////////////////////////////////////////////////////////////
  // WIT-SVID Profile
  /////////////////////////////////////////////////////////////////////////

  // 为该工作负载有权获得的所有 SPIFFE 身份获取 WIT-SVID。如果工作
  // 负载有权获得的身份发生变化,或者这些身份被续订(renew),服务器
  // 会向客户端以流的形式推送后续消息。
  //
  // 在工作负载 API 端点不支持 WIT-SVID profile 时,必须返回 Unimplemented。
  rpc FetchWITSVID(WITSVIDRequest) returns (stream WITSVIDResponse);

  // 获取信任域证书包。适用于只需校验 SVID、而无需为自身获取 SVID 的
  // 客户端。随着这些信息发生变化,服务器会以流的形式向下推送后续消息。
  //
  // 在工作负载 API 端点不支持 WIT-SVID profile 时,必须返回 Unimplemented。
  rpc FetchWITBundles(WITBundlesRequest) returns (stream WITBundlesResponse);

  // ... 其他 profile 的 RPC ...
}

// WITSVIDRequest 消息用于传递请求 WIT-SVID 所需的参数。
message WITSVIDRequest {
  // 可选。为 WIT-SVID 所请求的 SPIFFE ID。若未设置,则请求该工作负载
  // 有权获得的所有 WIT-SVID。
  string spiffe_id = 1;
}

// WITSVIDResponse 消息携带 WIT-SVID。
message WITSVIDResponse {
  // 必填。
  // WITSVID 消息的列表,其中每一项都包含单个 WIT-SVID 及其私钥。
  repeated WITSVID svids = 1;
}

// WITSVID 消息携带单个 SVID 及其全部关联信息,包括该信任域的
// WIT 证书包。
message WITSVID {
  // 必填。
  // 本条目中 SVID 的 SPIFFE ID
  string spiffe_id = 1;

  // 必填。
  // 使用 compact JWS serialization 编码的 WIT SVID。
  string wit_svid = 2;

  // 必填。
  // 该 WIT SVID 的私钥,以 JWK(RFC 7517)编码。
  string wit_svid_key = 3;

  // 可选。
  // 由运维方指定的字符串,用于在返回了多个 SVID 时,就工作负载应如何
  // 使用该身份提供指引。
  // 例如用 `internal`(内部)和 `external`(外部)分别表示供内部或外部
  // 使用的 SVID。
  string hint = 4;
}

// WITBundlesRequest 消息用于传递请求 WIT 证书包所需的参数。
// 目前尚无此类参数。
message WITBundlesRequest {}

// WITBundlesResponse 消息携带一个包含工作负载应当信任的信任域证书包
// 的映射。
message WITBundlesResponse {
  // 必填。
  // 属于工作负载应当信任的信任域的 WIT 信任域证书包,以该信任域的
  // SPIFFE ID 为键。
  //
  // 证书包以 JWK set(RFC 7517)格式编码。每个 JWK 都必须指定 `kid`
  // 成员,作为该密钥的唯一标识符。
  map<string, string> bundles = 1;
}

7.2 Profile RPCs ​

7.2.1 FetchWITSVID ​

FetchWITSVID RPC 以流的形式返回 WIT-SVID。

WITSVIDRequest 请求消息只有一个可选的 spiffe_id 字段,用于为特定的 SPIFFE ID 请求 WIT-SVID。若未指定,服务器必须(MUST)返回客户端被授权的所有身份的 WIT-SVID。

WITSVIDResponse 响应由一个必填的 svids 字段构成,该字段必须(MUST)包含一个或多个 WITSVID 消息(为授予客户端的每个身份各提供一个)。

WITSVID 消息中的所有字段均为必填,唯一的例外是 hint 字段。当设置了 hint 字段(即其值非空)时,SPIFFE 工作负载 API 服务器必须(MUST)确保:在任意给定的 WITSVIDResponse 消息中,该值在所返回的一组 SVID 里是唯一的。如果 SPIFFE 工作负载 API 客户端遇到多个 WITSVID 消息设置了相同的 hint 值,则应当(SHOULD)选择列表中的第一条消息。

如果客户端无权接收任何 WIT-SVID,则服务器应当(SHOULD)以 "PermissionDenied" gRPC 状态码作出响应(更多信息参见 SPIFFE 工作负载端点规范的错误码一节)。在这种情况下,客户端可以(MAY)在一段退避之后,通过再次调用 FetchWITSVID RPC 尝试重新连接。

正如流式响应一节所述,FetchWITSVID 流上返回的每条 WITSVIDResponse 消息,都包含在该时刻客户端被授权的完整 SVID 集合。因此,如果服务器在后续响应中删减了某些 SVID(或删减了全部 SVID,即返回 "PermissionDenied" gRPC 状态码),客户端应当(SHOULD)停止使用被删减的 SVID。

7.2.2 FetchWITBundles ​

FetchWITBundles RPC 以流的形式返回服务器所在信任域和外部信任域两者的证书包。这些证书包必须(MUST)仅用于认证 WIT-SVID。

WITBundlesRequest 请求消息目前为空,是为将来扩展预留的占位符。

WITBundlesResponse 响应消息由一个必填的 bundles 字段构成,该字段必须(MUST)至少包含服务器所在信任域的信任域证书包。

证书包以 JWK Set(RFC 7517)格式编码。JWK Set 中的每个 JWK 都必须指定 kid 成员,作为签发密钥对(key-pair)的唯一标识符。

如果客户端无权接收任何 WIT 证书包,则服务器应当(SHOULD)以 "PermissionDenied" gRPC 状态码作出响应(更多信息参见 SPIFFE 工作负载端点规范的错误码一节)。客户端可以(MAY)在一段退避之后,通过再次调用 FetchWITBundles RPC 尝试重新连接。

正如流式响应一节所述,每条 WITBundleResponse 响应都包含在该时刻客户端被授权的完整信任域证书包集合。因此,如果服务器在后续响应中删减了某些证书包(或删减了全部证书包,即返回 "PermissionDenied" gRPC 状态码),客户端应当(SHOULD)停止使用被删减的证书包。

8. Default Identity ​

工作负载常常并不知道自己应当采用何种身份。何时采用何种身份是与具体站点(site)相关的问题,因此,SPIFFE 规范并不对如何做出此决定进行论述。

为支持尽可能广泛的用例,X.509-SVID 与 WIT-SVID profile 支持签发多个身份,同时也定义了一个默认身份。人们期望知晓存在多个身份的工作负载能够自行处理决策。不理解如何利用多个身份的工作负载可以使用默认身份。默认身份即 X509SVIDResponse 消息所返回的 svids 列表中的第一个。Protocol Buffers 会确保该列表的顺序得以保留。

理解如何使用多个身份的工作负载可以利用可选的 hint 字段,该字段可用于消除身份之间的歧义,并告知工作负载哪个身份应当用于何种目的。例如用 internal 和 external 分别表示供内部或外部使用的 SVID。SPIFFE 工作负载 API 实现不应(SHOULD NOT)支持长度超过 1024 字节的值。hint 字段的确切取值由运维方选择,除此之外本规范不加约束。

处理预期的 hint 缺失、或出现非预期的 hint(例如失败、告警等),是工作负载的责任。

Appendix A. Sample Implementation State Machines ​

为了让内容更清晰,作者认为纳入 SPIFFE 工作负载 API 客户端与服务器实现两者的示例状态图会有所帮助。需要注意的是,在符合本规范的前提下有许多种实现方式,而这一特定实现仅供参考。

Server State Machine ​

服务器实现示例

  1. SPIFFE 工作负载端点监听器正在启动。
  2. gRPC 服务器已随 SPIFFE 工作负载 API 处理器一同启动,现在开始接受连接。
  3. 正在校验一个传入的 FetchX509SVIDRequest。这包括检查强制性的安全标头(security header),并确保调用方有可用的身份。
  4. 工作负载 API 正在向客户端发送 FetchX509SVIDResponse。
  5. 工作负载 API 处于等待状态。要退出等待状态,需要一次中断(interrupt)或一次取消(cancellation)。中断等待状态的一个典型原因是响应中所含的信息已被更新(例如某个 SVID 已轮换,或某个 CRL 已变更)。
  6. 正在对一个待发送的响应执行校验。确保客户端仍然有权获得某个身份,且该请求尚未被取消。
  7. 服务器正在关闭流,向客户端提供与所遇情况相对应的正确错误码。
  8. 服务器遇到了致命状况,必须停止。这可能发生在监听器无法创建,或 gRPC 服务器遇到致命错误时。

Client State Machine ​

客户端实现示例

  1. 工作负载 API 客户端正在拨号(dial)连接 SPIFFE 工作负载端点。
  2. 客户端正在调用 FetchX509SVID RPC,向服务器发送请求。
  3. 客户端正阻塞等待,以接收来自服务器的 X509SVIDResponse 消息。
  4. 客户端正用服务器响应中收到的 SVID、CRL 和证书包更新其配置。此时它可以将收到的信息与当前配置进行比较,以判断是否有必要重新加载。
  5. 客户端遇到了致命状况,必须退出。
  6. 客户端正在执行指数退避(exponential backoff)。