本文为 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。
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。
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。
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

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

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