• 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

39.68
/spiffe/src/workload_api/client/mod.rs
1
//! Workload API client for fetching SPIFFE X.509 and JWT material.
2
//!
3
//! `WorkloadApiClient` provides one-shot RPCs (fetch SVIDs/bundles) and streaming RPCs for
4
//! receiving updates as material rotates. Higher-level types like [`crate::X509Source`] handle
5
//! reconnection and provide an always-up-to-date view of the X.509 context.
6
//!
7
//! A single workload may be issued **multiple SVIDs** by the SPIFFE Workload API. When this
8
//! happens, the agent may attach an optional **hint** to each SVID to help distinguish identities.
9
//! Hints are **not part of the cryptographic material** and have no security meaning.
10

11
#[cfg(feature = "x509")]
12
mod x509;
13

14
mod header;
15
#[cfg(feature = "jwt")]
16
mod jwt;
17

18
use crate::transport::connect;
19
use crate::transport::Endpoint;
20
use crate::workload_api::client::header::MetadataAdder;
21
use crate::workload_api::error::WorkloadApiError;
22
use crate::workload_api::pb::workload::spiffe_workload_api_client::SpiffeWorkloadApiClient;
23

24
#[cfg(all(test, feature = "jwt"))]
25
use crate::{JwtSvid, SpiffeId};
26
#[cfg(all(test, feature = "jwt"))]
27
use std::{future::Future, pin::Pin, sync::Arc};
28

29
pub use header::InterceptorFn;
30

31
#[cfg(all(test, feature = "jwt"))]
32
pub(crate) type JwtFetchTestHook = Arc<
33
    dyn Fn(
34
            Vec<String>,
35
            Option<SpiffeId>,
36
        ) -> Pin<Box<dyn Future<Output = Result<JwtSvid, WorkloadApiError>> + Send>>
37
        + Send
38
        + Sync
39
        + 'static,
40
>;
41

42
/// Client for the SPIFFE Workload API.
43
///
44
/// Provides one-shot calls and streaming updates for X.509 and JWT SVIDs and bundles.
45
/// For an always-up-to-date, shareable source of X.509 material with automatic reconnection,
46
/// see [`crate::X509Source`].
47
#[cfg_attr(not(all(test, feature = "jwt")), derive(Debug))]
48
#[derive(Clone)]
49
pub struct WorkloadApiClient {
50
    endpoint: Endpoint,
51
    client: SpiffeWorkloadApiClient<
52
        tonic::service::interceptor::InterceptedService<tonic::transport::Channel, MetadataAdder>,
53
    >,
54
    #[cfg(all(test, feature = "jwt"))]
55
    jwt_fetch_hook: Option<JwtFetchTestHook>,
56
}
57

58
#[cfg(all(test, feature = "jwt"))]
59
impl std::fmt::Debug for WorkloadApiClient {
60
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
×
61
        f.debug_struct("WorkloadApiClient")
×
62
            .field("endpoint", &self.endpoint)
×
63
            .field("client", &self.client)
×
64
            .finish_non_exhaustive()
×
65
    }
×
66
}
67

68
impl WorkloadApiClient {
69
    /// Returns the configured Workload API endpoint.
70
    pub const fn endpoint(&self) -> &Endpoint {
×
71
        &self.endpoint
×
72
    }
×
73

74
    /// Connects to the Workload API using a parsed [`Endpoint`].
75
    ///
76
    /// # Errors
77
    ///
78
    /// Returns a [`WorkloadApiError`] if the endpoint cannot be reached or the gRPC
79
    /// connection fails.
80
    pub async fn connect(endpoint: Endpoint) -> Result<Self, WorkloadApiError> {
74✔
81
        let channel = connect(&endpoint).await?;
74✔
82
        Ok(Self {
74✔
83
            endpoint,
74✔
84
            client: SpiffeWorkloadApiClient::with_interceptor(channel, MetadataAdder::new(None)),
74✔
85
            #[cfg(all(test, feature = "jwt"))]
74✔
86
            jwt_fetch_hook: None,
74✔
87
        })
74✔
88
    }
74✔
89

90
    /// Connects to the Workload API using the given endpoint string.
91
    ///
92
    /// Examples:
93
    /// - `unix:/tmp/spire-agent/public/api.sock` or `unix:///tmp/spire-agent/public/api.sock`
94
    /// - `tcp:127.0.0.1:8081` or `tcp://127.0.0.1:8081`
95
    ///
96
    /// # Errors
97
    ///
98
    /// Returns a [`WorkloadApiError`] if the endpoint string is invalid, the endpoint
99
    /// cannot be reached, or the gRPC connection fails.
100
    pub async fn connect_to(endpoint: impl AsRef<str>) -> Result<Self, WorkloadApiError> {
6✔
101
        let endpoint = Endpoint::parse(endpoint.as_ref())?;
6✔
102
        Self::connect(endpoint).await
6✔
103
    }
6✔
104

105
    /// Connects to the Workload API using `SPIFFE_ENDPOINT_SOCKET`.
106
    ///
107
    /// # Errors
108
    ///
109
    /// Returns a [`WorkloadApiError`] if the endpoint socket is not set, cannot be
110
    /// reached, or the gRPC connection fails.
111
    pub async fn connect_env() -> Result<Self, WorkloadApiError> {
68✔
112
        let endpoint = crate::workload_api::endpoint::from_env()?;
68✔
113
        Self::connect(endpoint).await
68✔
114
    }
68✔
115

116
    /// Creates a client from an existing gRPC channel.
117
    ///
118
    /// This is primarily intended for tests or advanced transport customization (e.g., custom TLS
119
    /// configuration, load balancing, or connection pooling). The provided channel must be
120
    /// configured to connect to the actual SPIFFE endpoint.
121
    ///
122
    /// For normal usage, use [`WorkloadApiClient::connect`] or [`WorkloadApiClient::connect_env`].
123
    ///
124
    /// # Example (TCP endpoint)
125
    ///
126
    /// ```no_run
127
    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
128
    /// # use spiffe::{WorkloadApiClient, transport::Endpoint};
129
    /// # use tonic::transport::Channel;
130
    /// let endpoint = "tcp://127.0.0.1:8080".parse::<Endpoint>()?;
131
    /// let channel = Channel::from_shared("http://127.0.0.1:8080")?
132
    ///     .connect()
133
    ///     .await?;
134
    /// let client = WorkloadApiClient::new_with_channel(endpoint, channel);
135
    /// # Ok(())
136
    /// # }
137
    /// ```
138
    pub fn new_with_channel(endpoint: Endpoint, channel: tonic::transport::Channel) -> Self {
×
139
        Self {
×
140
            endpoint,
×
141
            client: SpiffeWorkloadApiClient::with_interceptor(channel, MetadataAdder::new(None)),
×
142
            #[cfg(all(test, feature = "jwt"))]
×
143
            jwt_fetch_hook: None,
×
144
        }
×
145
    }
×
146

147
    /// Creates a client from an existing gRPC channel with a custom
148
    /// per-RPC metadata interceptor.
149
    ///
150
    /// The `interceptor` is called on every RPC to inject custom metadata
151
    /// (e.g. K8s service account tokens for identity-server authentication).
152
    /// The standard `workload.spiffe.io: true` header is always added
153
    /// automatically.
154
    ///
155
    /// This is the primary constructor for identity-server, which requires
156
    /// per-RPC authentication metadata over TLS+TCP (unlike standard SPIRE
157
    /// which uses Unix domain sockets with implicit OS-level auth).
158
    pub fn new_with_channel_and_interceptor(
×
159
        endpoint: Endpoint,
×
160
        channel: tonic::transport::Channel,
×
161
        interceptor: InterceptorFn,
×
162
    ) -> Self {
×
163
        Self {
×
164
            endpoint,
×
165
            client: SpiffeWorkloadApiClient::with_interceptor(
×
166
                channel,
×
167
                MetadataAdder::new(Some(interceptor)),
×
168
            ),
×
169
            #[cfg(all(test, feature = "jwt"))]
×
170
            jwt_fetch_hook: None,
×
171
        }
×
172
    }
×
173

174
    #[cfg(all(test, feature = "jwt-source"))]
175
    pub(crate) fn new_with_jwt_fetch_hook(endpoint: Endpoint, hook: JwtFetchTestHook) -> Self {
24✔
176
        let channel = tonic::transport::Endpoint::from_static("http://127.0.0.1:1").connect_lazy();
24✔
177

178
        Self {
24✔
179
            endpoint,
24✔
180
            client: SpiffeWorkloadApiClient::with_interceptor(channel, MetadataAdder::new(None)),
24✔
181
            jwt_fetch_hook: Some(hook),
24✔
182
        }
24✔
183
    }
24✔
184
}
185

186
impl WorkloadApiClient {
187
    /// Extracts the first message from a streaming gRPC response.
188
    ///
189
    /// Returns `WorkloadApiError::EmptyResponse` if the stream ends without yielding a message.
190
    async fn first_message<T>(mut stream: tonic::Streaming<T>) -> Result<T, WorkloadApiError> {
×
191
        stream
×
192
            .message()
×
193
            .await?
×
194
            .ok_or(WorkloadApiError::EmptyResponse)
×
195
    }
×
196
}
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