• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

maxlambrecht / rust-spiffe / 27112982280

08 Jun 2026 02:41AM UTC coverage: 83.255% (-2.8%) from 86.023%
27112982280

push

github

maxlambrecht
build(features): remove redundant workload-api-full

Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>

6165 of 7405 relevant lines covered (83.25%)

756.53 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

45.81
/spiffe/src/workload_api/client/jwt.rs
1
use crate::constants::DEFAULT_SVID;
2
use crate::workload_api::pb::workload::{
3
    JwtBundlesRequest, JwtBundlesResponse, JwtsvidRequest, JwtsvidResponse, ValidateJwtsvidRequest,
4
    ValidateJwtsvidResponse,
5
};
6
use crate::{
7
    JwtBundle, JwtBundleSet, JwtSvid, SpiffeId, TrustDomain, WorkloadApiClient, WorkloadApiError,
8
};
9
use futures::{Stream, StreamExt as _};
10
use std::str::FromStr as _;
11
use std::sync::Arc;
12

13
impl WorkloadApiClient {
14
    /// Fetches the current set of JWT bundles from the SPIFFE Workload API.
15
    ///
16
    /// This method establishes a streaming gRPC request to the Workload API
17
    /// and returns the latest JWT bundle set received from the server.
18
    ///
19
    /// # Errors
20
    ///
21
    /// Returns a [`WorkloadApiError`] if the gRPC request fails, the stream
22
    /// terminates unexpectedly, or an invalid response is received.
23
    pub async fn fetch_jwt_bundles(&self) -> Result<JwtBundleSet, WorkloadApiError> {
×
24
        let request = JwtBundlesRequest::default();
×
25

26
        let mut client = self.client.clone();
×
27

28
        let grpc_stream_response: tonic::Response<tonic::Streaming<JwtBundlesResponse>> =
×
29
            client.fetch_jwt_bundles(request).await?;
×
30

31
        let response = Self::first_message(grpc_stream_response.into_inner()).await?;
×
32
        Self::parse_jwt_bundle_set_from_grpc_response(response)
×
33
    }
×
34

35
    /// Fetches a `JwtSvid` for the given audience and optional SPIFFE ID.
36
    ///
37
    /// If `spiffe_id` is `None`, the Workload API returns the default identity.
38
    ///
39
    /// # Errors
40
    ///
41
    /// Returns a [`WorkloadApiError`] if the JWT-SVID request fails or the Workload API
42
    /// returns an invalid or empty response.
43
    pub async fn fetch_jwt_svid<I>(
26✔
44
        &self,
26✔
45
        audience: I,
26✔
46
        spiffe_id: Option<&SpiffeId>,
26✔
47
    ) -> Result<JwtSvid, WorkloadApiError>
26✔
48
    where
26✔
49
        I: IntoIterator,
26✔
50
        I::Item: AsRef<str>,
26✔
51
    {
26✔
52
        #[cfg(test)]
53
        if let Some(fetch) = &self.jwt_fetch_hook {
24✔
54
            let audience = audience
24✔
55
                .into_iter()
24✔
56
                .map(|a| a.as_ref().to_string())
24✔
57
                .collect();
24✔
58
            return fetch(audience, spiffe_id.cloned()).await;
24✔
59
        }
×
60

61
        let response = self.fetch_jwt(audience, spiffe_id).await?;
2✔
62
        let r = response
2✔
63
            .svids
2✔
64
            .get(DEFAULT_SVID)
2✔
65
            .ok_or(WorkloadApiError::EmptyResponse)?;
2✔
66

67
        let mut svid = JwtSvid::from_str(&r.svid).map_err(WorkloadApiError::JwtSvid)?;
2✔
68

69
        if !r.hint.is_empty() {
2✔
70
            svid = svid.with_hint(Arc::<str>::from(r.hint.as_str()));
2✔
71
        }
2✔
72

73
        Ok(svid)
2✔
74
    }
26✔
75

76
    /// Fetches all JWT-SVIDs for the given audience and optional SPIFFE ID.
77
    ///
78
    /// The Workload API can return more than one JWT-SVID. Each returned [`JwtSvid`] may include an
79
    /// optional **hint** (via [`JwtSvid::hint`]) that can be used to disambiguate which SVID to use.
80
    ///
81
    /// If `spiffe_id` is `None`, the Workload API returns JWT-SVIDs for the default identity.
82
    ///
83
    /// # Errors
84
    ///
85
    /// Returns a [`WorkloadApiError`] if the JWT-SVID request fails, the Workload API response is
86
    /// invalid or empty, or any returned token cannot be parsed.
87
    pub async fn fetch_all_jwt_svids<I>(
×
88
        &self,
×
89
        audience: I,
×
90
        spiffe_id: Option<&SpiffeId>,
×
91
    ) -> Result<Vec<JwtSvid>, WorkloadApiError>
×
92
    where
×
93
        I: IntoIterator,
×
94
        I::Item: AsRef<str>,
×
95
    {
×
96
        let response = self.fetch_jwt(audience, spiffe_id).await?;
×
97

98
        response
×
99
            .svids
×
100
            .into_iter()
×
101
            .map(|r| {
×
102
                let mut svid = JwtSvid::from_str(&r.svid).map_err(WorkloadApiError::JwtSvid)?;
×
103
                if !r.hint.is_empty() {
×
104
                    svid = svid.with_hint(Arc::<str>::from(r.hint.as_str()));
×
105
                }
×
106
                Ok(svid)
×
107
            })
×
108
            .collect()
×
109
    }
×
110

111
    /// Fetches the JWT-SVID whose Workload API hint matches `hint`.
112
    ///
113
    /// Wrapper around [`WorkloadApiClient::fetch_all_jwt_svids`] that selects
114
    /// a single [`JwtSvid`] by its hint.
115
    ///
116
    /// The hint is **not** part of the JWT token; it is transport metadata provided by the SPIFFE
117
    /// Workload API to help identify a specific SVID when multiple are available.
118
    ///
119
    /// If `spiffe_id` is `None`, the Workload API returns JWT-SVIDs for the default identity.
120
    ///
121
    /// # Errors
122
    ///
123
    /// Returns a [`WorkloadApiError`] if the JWT-SVID request fails, the Workload API response is
124
    /// invalid, or no JWT-SVID with the requested hint is found.
125
    pub async fn fetch_jwt_svid_by_hint<I>(
×
126
        &self,
×
127
        audience: I,
×
128
        spiffe_id: Option<&SpiffeId>,
×
129
        hint: impl AsRef<str>,
×
130
    ) -> Result<JwtSvid, WorkloadApiError>
×
131
    where
×
132
        I: IntoIterator,
×
133
        I::Item: AsRef<str>,
×
134
    {
×
135
        let hint = hint.as_ref();
×
136
        let all = self.fetch_all_jwt_svids(audience, spiffe_id).await?;
×
137
        all.into_iter()
×
138
            .find(|s| s.hint() == Some(hint))
×
139
            .ok_or_else(|| WorkloadApiError::HintNotFound(hint.to_owned()))
×
140
    }
×
141

142
    /// Fetches a JWT-SVID token string for the given audience and optional SPIFFE ID.
143
    ///
144
    /// If `spiffe_id` is `None`, the Workload API returns the default identity.
145
    ///
146
    /// # Errors
147
    ///
148
    /// Returns a [`WorkloadApiError`] if the token request fails or the Workload API
149
    /// returns an invalid or empty response.
150
    #[cfg(feature = "jwt")]
151
    pub async fn fetch_jwt_token<I>(
×
152
        &self,
×
153
        audience: I,
×
154
        spiffe_id: Option<&SpiffeId>,
×
155
    ) -> Result<String, WorkloadApiError>
×
156
    where
×
157
        I: IntoIterator,
×
158
        I::Item: AsRef<str>,
×
159
    {
×
160
        let response = self.fetch_jwt(audience, spiffe_id).await?;
×
161
        response
×
162
            .svids
×
163
            .get(DEFAULT_SVID)
×
164
            .map(|r| r.svid.clone())
×
165
            .ok_or(WorkloadApiError::EmptyResponse)
×
166
    }
×
167

168
    /// Validates a JWT-SVID token for the given audience and returns the parsed [`JwtSvid`].
169
    ///
170
    /// Validation is performed by the SPIRE agent via the Workload API. After successful
171
    /// validation, the token is parsed locally for structured access. The use of
172
    /// `parse_insecure` is safe here because the security property comes from the agent's
173
    /// validation, not from local signature verification.
174
    ///
175
    /// # Errors
176
    ///
177
    /// Returns a [`WorkloadApiError`] if validation fails or the token cannot be parsed.
178
    pub async fn validate_jwt_token(
×
179
        &self,
×
180
        audience: &str,
×
181
        jwt_token: &str,
×
182
    ) -> Result<JwtSvid, WorkloadApiError> {
×
183
        // Validate via the SPIRE agent (security property comes from agent validation)
184
        let _unused: ValidateJwtsvidResponse = self.validate_jwt(audience, jwt_token).await?;
×
185
        // Parse locally for structured access (safe because agent already validated)
186
        let jwt_svid = JwtSvid::parse_insecure(jwt_token)?;
×
187
        Ok(jwt_svid)
×
188
    }
×
189

190
    /// Streams JWT bundle set updates from the Workload API.
191
    ///
192
    /// The stream ends when the server closes the connection. This stream does not
193
    /// automatically reconnect; if you need resilience and automatic reconnection,
194
    /// use [`X509Source`] for X.509 material or handle reconnection manually.
195
    ///
196
    /// # Errors
197
    ///
198
    /// Returns a [`WorkloadApiError`] if the Workload API stream cannot be
199
    /// established or the initial request fails.
200
    pub async fn stream_jwt_bundles(
30✔
201
        &self,
30✔
202
    ) -> Result<
30✔
203
        impl Stream<Item = Result<JwtBundleSet, WorkloadApiError>> + Send + 'static + use<>,
30✔
204
        WorkloadApiError,
30✔
205
    > {
30✔
206
        let request = JwtBundlesRequest::default();
30✔
207

208
        let mut client = self.client.clone();
30✔
209

210
        let response = client.fetch_jwt_bundles(request).await?;
30✔
211
        let stream = response.into_inner().map(|message| {
29✔
212
            message
29✔
213
                .map_err(WorkloadApiError::from)
29✔
214
                .and_then(Self::parse_jwt_bundle_set_from_grpc_response)
29✔
215
        });
29✔
216
        Ok(Box::pin(stream))
29✔
217
    }
29✔
218
}
219

220
impl WorkloadApiClient {
221
    async fn fetch_jwt<I>(
2✔
222
        &self,
2✔
223
        audience: I,
2✔
224
        spiffe_id: Option<&SpiffeId>,
2✔
225
    ) -> Result<JwtsvidResponse, WorkloadApiError>
2✔
226
    where
2✔
227
        I: IntoIterator,
2✔
228
        I::Item: AsRef<str>,
2✔
229
    {
2✔
230
        let request = JwtsvidRequest {
2✔
231
            spiffe_id: spiffe_id.map(ToString::to_string).unwrap_or_default(),
2✔
232
            audience: audience
2✔
233
                .into_iter()
2✔
234
                .map(|a| a.as_ref().to_string())
2✔
235
                .collect(),
2✔
236
        };
237

238
        let mut client = self.client.clone();
2✔
239

240
        Ok(client.fetch_jwtsvid(request).await?.into_inner())
2✔
241
    }
2✔
242

243
    async fn validate_jwt(
×
244
        &self,
×
245
        audience: &str,
×
246
        jwt_svid: &str,
×
247
    ) -> Result<ValidateJwtsvidResponse, WorkloadApiError> {
×
248
        let request = ValidateJwtsvidRequest {
×
249
            audience: audience.to_string(),
×
250
            svid: jwt_svid.into(),
×
251
        };
×
252
        let mut client = self.client.clone();
×
253
        Ok(client.validate_jwtsvid(request).await?.into_inner())
×
254
    }
×
255

256
    fn parse_jwt_bundle_set_from_grpc_response(
29✔
257
        response: JwtBundlesResponse,
29✔
258
    ) -> Result<JwtBundleSet, WorkloadApiError> {
29✔
259
        let mut bundle_set = JwtBundleSet::new();
29✔
260

261
        for (td, bundle_data) in response.bundles {
58✔
262
            let trust_domain = TrustDomain::try_from(td)?;
58✔
263
            let bundle = JwtBundle::from_jwt_authorities(trust_domain, &bundle_data)
58✔
264
                .map_err(WorkloadApiError::from)?;
58✔
265

266
            bundle_set.add_bundle(bundle);
58✔
267
        }
268

269
        Ok(bundle_set)
29✔
270
    }
29✔
271
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc