Skip to main content

lenso_service/
workload_identity.rs

1use crate::{ContextClaimProof, ServicePrincipal};
2use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
3use jsonwebtoken::{
4    Algorithm, DecodingKey, EncodingKey, Header, Validation, decode, decode_header, encode,
5    errors::ErrorKind,
6};
7use serde::{Deserialize, Serialize};
8use sha2::{Digest, Sha256};
9use spiffe::{
10    JwtSource, JwtSvid, JwtSvidError, SpiffeId, TrustDomain, X509Source, X509Svid,
11    x509_source::SvidPicker,
12};
13use std::{
14    collections::BTreeMap,
15    future::Future,
16    pin::Pin,
17    sync::{
18        RwLock,
19        atomic::{AtomicU64, Ordering},
20    },
21};
22
23const DEVELOPMENT_ISSUER: &str = "lenso-system-sandbox-development-only";
24const CREDENTIAL_KIND: &str = "lenso.workload-identity.v1";
25const MAX_SANDBOX_CREDENTIAL_TTL_MS: u64 = 5 * 60 * 1_000;
26
27/// Proof supplied by a transport adapter after it authenticates the connection.
28/// It is deliberately separate from endpoint, hostname, IP, replica, and region metadata.
29#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
30pub struct AuthenticatedTransportBinding(pub String);
31
32impl AuthenticatedTransportBinding {
33    #[must_use]
34    pub fn new(proof: impl Into<String>) -> Self {
35        Self(proof.into())
36    }
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
40#[serde(rename_all = "snake_case")]
41pub enum WorkloadIdentityErrorCode {
42    DevelopmentProviderForbidden,
43    InvalidRequest,
44    InvalidProof,
45    IssuerMismatch,
46    AudienceMismatch,
47    CredentialExpired,
48    TransportBindingMismatch,
49    StaleCredential,
50    RotationFailed,
51    ProviderUnavailable,
52}
53
54#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
55#[serde(rename_all = "camelCase")]
56pub struct WorkloadIdentityEvidence {
57    pub outcome: String,
58    pub service_principal: Option<String>,
59    pub credential_id: Option<String>,
60    pub key_id: Option<String>,
61}
62
63#[derive(Debug, Clone, PartialEq, Eq)]
64pub struct WorkloadIdentityError {
65    pub code: WorkloadIdentityErrorCode,
66    pub message: String,
67    pub evidence: WorkloadIdentityEvidence,
68}
69
70impl std::fmt::Display for WorkloadIdentityError {
71    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72        formatter.write_str(&self.message)
73    }
74}
75
76impl std::error::Error for WorkloadIdentityError {}
77
78#[derive(Debug, Clone, PartialEq, Eq)]
79pub struct WorkloadCredentialRequest {
80    pub service_principal: String,
81    pub audience: String,
82    pub authenticated_transport_binding: String,
83    pub issued_at_unix_ms: u64,
84    pub ttl_ms: u64,
85}
86
87impl WorkloadCredentialRequest {
88    #[must_use]
89    pub fn new(
90        service_principal: impl Into<String>,
91        audience: impl Into<String>,
92        authenticated_transport_binding: impl Into<String>,
93        issued_at_unix_ms: u64,
94        ttl_ms: u64,
95    ) -> Self {
96        Self {
97            service_principal: service_principal.into(),
98            audience: audience.into(),
99            authenticated_transport_binding: authenticated_transport_binding.into(),
100            issued_at_unix_ms,
101            ttl_ms,
102        }
103    }
104}
105
106#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
107#[serde(rename_all = "camelCase")]
108pub struct WorkloadCredential {
109    pub token: String,
110    pub issuer: String,
111    pub service_principal: String,
112    pub audience: String,
113    pub expires_at_unix_ms: u64,
114    pub credential_id: String,
115    pub key_id: String,
116    pub algorithm: String,
117}
118
119impl WorkloadCredential {
120    #[must_use]
121    pub fn service_principal_context(&self) -> ServicePrincipal {
122        ServicePrincipal {
123            issuer: self.issuer.clone(),
124            subject: self.service_principal.clone(),
125            audiences: vec![self.audience.clone()],
126            expires_at_unix_ms: self.expires_at_unix_ms,
127            credential_id: self.credential_id.clone(),
128            proof: ContextClaimProof {
129                verification_method: self.key_id.clone(),
130                algorithm: self.algorithm.clone(),
131                signature: self.token.clone(),
132            },
133        }
134    }
135}
136
137#[derive(Debug, Clone, PartialEq, Eq)]
138pub struct WorkloadIdentityVerification {
139    pub audience: String,
140    pub authenticated_transport_binding: String,
141    pub now_unix_ms: u64,
142}
143
144impl WorkloadIdentityVerification {
145    #[must_use]
146    pub fn new(
147        audience: impl Into<String>,
148        authenticated_transport_binding: impl Into<String>,
149        now_unix_ms: u64,
150    ) -> Self {
151        Self {
152            audience: audience.into(),
153            authenticated_transport_binding: authenticated_transport_binding.into(),
154            now_unix_ms,
155        }
156    }
157}
158
159#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
160#[serde(rename_all = "camelCase")]
161pub struct AuthenticatedServicePrincipal {
162    pub service_principal: String,
163    pub credential_id: String,
164    pub issuer: String,
165    pub audience: String,
166    pub expires_at_unix_ms: u64,
167    pub key_id: String,
168    pub algorithm: String,
169    pub evidence: WorkloadIdentityEvidence,
170}
171
172#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
173#[serde(rename_all = "camelCase")]
174pub struct WorkloadIdentityRotationEvidence {
175    pub outcome: String,
176    pub previous_key_id: String,
177    pub active_key_id: String,
178}
179
180pub trait WorkloadIdentityProvider: std::fmt::Debug + Send + Sync {
181    fn issue(
182        &self,
183        request: WorkloadCredentialRequest,
184    ) -> Result<WorkloadCredential, WorkloadIdentityError>;
185
186    /// Asynchronous credential issuance for providers backed by networked or
187    /// local-agent protocols. Existing synchronous providers inherit a
188    /// source-compatible adapter around `issue`.
189    fn issue_async(
190        &self,
191        request: WorkloadCredentialRequest,
192    ) -> Pin<Box<dyn Future<Output = Result<WorkloadCredential, WorkloadIdentityError>> + Send + '_>>
193    {
194        Box::pin(async move { self.issue(request) })
195    }
196
197    fn verify(
198        &self,
199        token: &str,
200        verification: &WorkloadIdentityVerification,
201    ) -> Result<AuthenticatedServicePrincipal, WorkloadIdentityError>;
202}
203
204/// Operator-owned SPIFFE composition for one stable Lenso Service Principal.
205/// The Workload API endpoint supplies both JWT-SVIDs and rotating X.509-SVIDs;
206/// Lenso never receives certificate-authority signing material.
207#[derive(Debug, Clone, PartialEq, Eq)]
208pub struct SpiffeWorkloadIdentityConfig {
209    endpoint: String,
210    trust_domain: TrustDomain,
211    service_principal: String,
212    spiffe_id: SpiffeId,
213}
214
215impl SpiffeWorkloadIdentityConfig {
216    pub fn new(
217        endpoint: impl Into<String>,
218        trust_domain: impl AsRef<str>,
219        service_principal: impl Into<String>,
220    ) -> Result<Self, WorkloadIdentityError> {
221        let endpoint = endpoint.into();
222        let service_principal = service_principal.into();
223        if endpoint.trim().is_empty() {
224            return Err(identity_error(
225                WorkloadIdentityErrorCode::InvalidRequest,
226                "invalid_spiffe_endpoint",
227                "SPIFFE Workload Identity requires a Workload API endpoint",
228                Some(service_principal),
229                None,
230                None,
231            ));
232        }
233        let Some(service_id) = service_principal.strip_prefix("service:") else {
234            return Err(invalid_service_principal());
235        };
236        if !valid_service_principal(&service_principal) {
237            return Err(invalid_service_principal());
238        }
239        let trust_domain = TrustDomain::new(trust_domain.as_ref()).map_err(|_| {
240            identity_error(
241                WorkloadIdentityErrorCode::InvalidRequest,
242                "invalid_spiffe_trust_domain",
243                "SPIFFE Workload Identity requires a valid trust domain",
244                Some(service_principal.clone()),
245                None,
246                None,
247            )
248        })?;
249        let spiffe_id = SpiffeId::from_segments(trust_domain.clone(), &["service", service_id])
250            .map_err(|_| invalid_service_principal())?;
251        Ok(Self {
252            endpoint,
253            trust_domain,
254            service_principal,
255            spiffe_id,
256        })
257    }
258
259    #[must_use]
260    pub fn endpoint(&self) -> &str {
261        &self.endpoint
262    }
263
264    #[must_use]
265    pub const fn trust_domain(&self) -> &TrustDomain {
266        &self.trust_domain
267    }
268
269    #[must_use]
270    pub fn service_principal(&self) -> &str {
271        &self.service_principal
272    }
273
274    #[must_use]
275    pub const fn spiffe_id(&self) -> &SpiffeId {
276        &self.spiffe_id
277    }
278}
279
280#[derive(Debug)]
281struct ExactSpiffeIdPicker(SpiffeId);
282
283impl SvidPicker for ExactSpiffeIdPicker {
284    fn pick_svid(&self, svids: &[std::sync::Arc<X509Svid>]) -> Option<usize> {
285        svids.iter().position(|svid| svid.spiffe_id() == &self.0)
286    }
287}
288
289/// Production Workload Identity provider backed by a SPIFFE Workload API.
290/// JWT-SVIDs authenticate the application request and are bound to the peer
291/// SPIFFE ID authenticated by the live X.509-SVID mTLS connection.
292#[derive(Debug)]
293pub struct SpiffeWorkloadIdentityProvider {
294    config: SpiffeWorkloadIdentityConfig,
295    jwt_source: JwtSource,
296    x509_source: X509Source,
297}
298
299impl SpiffeWorkloadIdentityProvider {
300    pub async fn connect(
301        config: SpiffeWorkloadIdentityConfig,
302    ) -> Result<Self, WorkloadIdentityError> {
303        let x509_source = X509Source::builder()
304            .endpoint(config.endpoint())
305            .picker(ExactSpiffeIdPicker(config.spiffe_id().clone()))
306            .build()
307            .await
308            .map_err(|_| provider_unavailable("spiffe_x509_source_unavailable"))?;
309        let selected = x509_source
310            .svid()
311            .map_err(|_| provider_unavailable("spiffe_x509_source_unavailable"))?;
312        if selected.spiffe_id() != config.spiffe_id() {
313            x509_source.shutdown().await;
314            return Err(identity_error(
315                WorkloadIdentityErrorCode::IssuerMismatch,
316                "spiffe_identity_mismatch",
317                "The SPIFFE Workload API did not provide the configured Service identity",
318                Some(config.service_principal().to_owned()),
319                None,
320                None,
321            ));
322        }
323        let jwt_source = match JwtSource::builder()
324            .endpoint(config.endpoint())
325            .build()
326            .await
327        {
328            Ok(source) => source,
329            Err(_) => {
330                x509_source.shutdown().await;
331                return Err(provider_unavailable("spiffe_jwt_source_unavailable"));
332            }
333        };
334        Ok(Self {
335            config,
336            jwt_source,
337            x509_source,
338        })
339    }
340
341    #[must_use]
342    pub fn config(&self) -> &SpiffeWorkloadIdentityConfig {
343        &self.config
344    }
345
346    /// Live X.509 material for `spiffe-rustls` mTLS composition. New handshakes
347    /// consume rotated SVIDs without rebuilding this provider.
348    #[must_use]
349    pub fn x509_source(&self) -> X509Source {
350        self.x509_source.clone()
351    }
352
353    #[must_use]
354    pub fn authenticated_transport_binding(
355        peer_spiffe_id: &SpiffeId,
356    ) -> AuthenticatedTransportBinding {
357        AuthenticatedTransportBinding::new(peer_spiffe_id.to_string())
358    }
359
360    pub async fn shutdown(&self) {
361        self.jwt_source.shutdown().await;
362        self.x509_source.shutdown().await;
363    }
364}
365
366impl WorkloadIdentityProvider for SpiffeWorkloadIdentityProvider {
367    fn issue(
368        &self,
369        request: WorkloadCredentialRequest,
370    ) -> Result<WorkloadCredential, WorkloadIdentityError> {
371        Err(identity_error(
372            WorkloadIdentityErrorCode::ProviderUnavailable,
373            "spiffe_async_issuance_required",
374            "SPIFFE credential issuance requires the asynchronous provider method",
375            Some(request.service_principal),
376            None,
377            None,
378        ))
379    }
380
381    fn issue_async(
382        &self,
383        request: WorkloadCredentialRequest,
384    ) -> Pin<Box<dyn Future<Output = Result<WorkloadCredential, WorkloadIdentityError>> + Send + '_>>
385    {
386        Box::pin(async move {
387            if request.service_principal != self.config.service_principal
388                || request.audience.trim().is_empty()
389                || request.authenticated_transport_binding != self.config.spiffe_id.to_string()
390                || request.ttl_ms == 0
391            {
392                return Err(identity_error(
393                    WorkloadIdentityErrorCode::InvalidRequest,
394                    "invalid_spiffe_credential_request",
395                    "SPIFFE credentials require the configured Service Principal, audience, mTLS peer identity, and positive lifetime",
396                    Some(request.service_principal),
397                    None,
398                    None,
399                ));
400            }
401            let svid = self
402                .jwt_source
403                .fetch_jwt_svid_with_id([&request.audience], Some(&self.config.spiffe_id))
404                .await
405                .map_err(|_| provider_unavailable("spiffe_credential_issuance_failed"))?;
406            if svid.spiffe_id() != &self.config.spiffe_id {
407                return Err(identity_error(
408                    WorkloadIdentityErrorCode::IssuerMismatch,
409                    "spiffe_identity_mismatch",
410                    "The SPIFFE Workload API issued an unexpected Service identity",
411                    Some(request.service_principal),
412                    None,
413                    Some(svid.key_id().to_owned()),
414                ));
415            }
416            let expires_at_unix_ms = expiry_unix_ms(&svid)?;
417            let maximum_expiry = request
418                .issued_at_unix_ms
419                .checked_add(request.ttl_ms)
420                .and_then(|value| value.checked_add(999))
421                .ok_or_else(|| {
422                    identity_error(
423                        WorkloadIdentityErrorCode::InvalidRequest,
424                        "invalid_credential_lifetime",
425                        "Workload credential lifetime exceeds the supported timestamp range",
426                        Some(request.service_principal.clone()),
427                        None,
428                        Some(svid.key_id().to_owned()),
429                    )
430                })?;
431            if expires_at_unix_ms <= request.issued_at_unix_ms
432                || expires_at_unix_ms > maximum_expiry
433            {
434                return Err(identity_error(
435                    WorkloadIdentityErrorCode::InvalidRequest,
436                    "credential_lifetime_out_of_policy",
437                    "The SPIFFE issuer credential lifetime exceeds the requested maximum",
438                    Some(request.service_principal),
439                    None,
440                    Some(svid.key_id().to_owned()),
441                ));
442            }
443            let token = svid.token().to_owned();
444            let algorithm = jwt_algorithm(&token)?;
445            Ok(WorkloadCredential {
446                credential_id: credential_id(&token),
447                token,
448                issuer: format!("spiffe://{}", self.config.trust_domain),
449                service_principal: self.config.service_principal.clone(),
450                audience: request.audience,
451                expires_at_unix_ms,
452                key_id: svid.key_id().to_owned(),
453                algorithm,
454            })
455        })
456    }
457
458    fn verify(
459        &self,
460        token: &str,
461        verification: &WorkloadIdentityVerification,
462    ) -> Result<AuthenticatedServicePrincipal, WorkloadIdentityError> {
463        let svid = JwtSvid::parse_and_validate(token, &self.jwt_source, &[&verification.audience])
464            .map_err(spiffe_verification_error)?;
465        if svid.spiffe_id().trust_domain() != &self.config.trust_domain {
466            return Err(identity_error(
467                WorkloadIdentityErrorCode::IssuerMismatch,
468                "issuer_mismatch",
469                "SPIFFE Workload Identity belongs to an untrusted domain",
470                None,
471                Some(credential_id(token)),
472                Some(svid.key_id().to_owned()),
473            ));
474        }
475        let service_principal =
476            service_principal_from_spiffe_id(svid.spiffe_id()).ok_or_else(invalid_proof)?;
477        let expires_at_unix_ms = expiry_unix_ms(&svid)?;
478        if verification.now_unix_ms >= expires_at_unix_ms {
479            return Err(identity_error(
480                WorkloadIdentityErrorCode::CredentialExpired,
481                "credential_expired",
482                "Workload Identity credential has expired",
483                Some(service_principal),
484                Some(credential_id(token)),
485                Some(svid.key_id().to_owned()),
486            ));
487        }
488        if verification.authenticated_transport_binding != svid.spiffe_id().to_string() {
489            return Err(identity_error(
490                WorkloadIdentityErrorCode::TransportBindingMismatch,
491                "transport_binding_mismatch",
492                "JWT-SVID identity is not bound to the authenticated X.509-SVID mTLS peer",
493                Some(service_principal),
494                Some(credential_id(token)),
495                Some(svid.key_id().to_owned()),
496            ));
497        }
498        let credential_id = credential_id(token);
499        let key_id = svid.key_id().to_owned();
500        Ok(AuthenticatedServicePrincipal {
501            service_principal: service_principal.clone(),
502            credential_id: credential_id.clone(),
503            issuer: format!("spiffe://{}", self.config.trust_domain),
504            audience: verification.audience.clone(),
505            expires_at_unix_ms,
506            key_id: key_id.clone(),
507            algorithm: jwt_algorithm(token)?,
508            evidence: WorkloadIdentityEvidence {
509                outcome: "authenticated".to_owned(),
510                service_principal: Some(service_principal),
511                credential_id: Some(credential_id),
512                key_id: Some(key_id),
513            },
514        })
515    }
516}
517
518#[derive(Debug)]
519pub struct SystemSandboxWorkloadIdentityProvider {
520    state: RwLock<SystemSandboxProviderState>,
521    next_credential: AtomicU64,
522}
523
524#[derive(Debug)]
525struct SystemSandboxProviderState {
526    secret: String,
527    generation: u64,
528    retired_secrets: BTreeMap<String, String>,
529}
530
531#[derive(Debug, Clone, Serialize, Deserialize)]
532struct WorkloadClaims {
533    iss: String,
534    sub: String,
535    aud: Vec<String>,
536    exp: u64,
537    iat: u64,
538    jti: String,
539    credential_kind: String,
540    authenticated_transport_binding: String,
541    expires_at_unix_ms: u64,
542}
543
544impl SystemSandboxWorkloadIdentityProvider {
545    pub fn new(
546        environment: &str,
547        secret: impl Into<String>,
548    ) -> Result<Self, WorkloadIdentityError> {
549        if !matches!(
550            environment.trim().to_ascii_lowercase().as_str(),
551            "local" | "dev" | "development" | "test"
552        ) {
553            return Err(identity_error(
554                WorkloadIdentityErrorCode::DevelopmentProviderForbidden,
555                "development_provider_forbidden",
556                "The System Sandbox Workload Identity provider is development-only",
557                None,
558                None,
559                None,
560            ));
561        }
562        let secret = secret.into();
563        if secret.is_empty() {
564            return Err(identity_error(
565                WorkloadIdentityErrorCode::InvalidRequest,
566                "invalid_provider_secret",
567                "The System Sandbox signing secret must not be empty",
568                None,
569                None,
570                None,
571            ));
572        }
573        Ok(Self {
574            state: RwLock::new(SystemSandboxProviderState {
575                secret,
576                generation: 1,
577                retired_secrets: BTreeMap::new(),
578            }),
579            next_credential: AtomicU64::new(1),
580        })
581    }
582
583    pub fn rotate(
584        &self,
585        new_secret: impl Into<String>,
586    ) -> Result<WorkloadIdentityRotationEvidence, WorkloadIdentityError> {
587        let new_secret = new_secret.into();
588        let mut state = self.state.write().expect("identity provider lock poisoned");
589        let active_key_id = key_id(state.generation);
590        if new_secret.is_empty() || new_secret == state.secret {
591            return Err(identity_error(
592                WorkloadIdentityErrorCode::RotationFailed,
593                "rotation_failed",
594                "The System Sandbox credential rotation did not provide a new signing secret",
595                None,
596                None,
597                Some(active_key_id),
598            ));
599        }
600        let previous_key_id = active_key_id;
601        let previous_secret = std::mem::replace(&mut state.secret, new_secret);
602        state
603            .retired_secrets
604            .insert(previous_key_id.clone(), previous_secret);
605        state.generation += 1;
606        Ok(WorkloadIdentityRotationEvidence {
607            outcome: "rotated".to_owned(),
608            previous_key_id,
609            active_key_id: key_id(state.generation),
610        })
611    }
612}
613
614impl WorkloadIdentityProvider for SystemSandboxWorkloadIdentityProvider {
615    fn issue(
616        &self,
617        request: WorkloadCredentialRequest,
618    ) -> Result<WorkloadCredential, WorkloadIdentityError> {
619        if !valid_service_principal(&request.service_principal)
620            || request.audience.trim().is_empty()
621            || request.authenticated_transport_binding.trim().is_empty()
622            || request.ttl_ms == 0
623            || request.ttl_ms > MAX_SANDBOX_CREDENTIAL_TTL_MS
624        {
625            return Err(identity_error(
626                WorkloadIdentityErrorCode::InvalidRequest,
627                "invalid_credential_request",
628                "Workload credentials require a Service Principal, audience, authenticated transport binding, and positive lifetime",
629                None,
630                None,
631                None,
632            ));
633        }
634        let expires_at_unix_ms = request
635            .issued_at_unix_ms
636            .checked_add(request.ttl_ms)
637            .ok_or_else(|| {
638                identity_error(
639                    WorkloadIdentityErrorCode::InvalidRequest,
640                    "invalid_credential_lifetime",
641                    "Workload credential lifetime exceeds the supported timestamp range",
642                    Some(request.service_principal.clone()),
643                    None,
644                    None,
645                )
646            })?;
647        let sequence = self.next_credential.fetch_add(1, Ordering::SeqCst);
648        let state = self.state.read().expect("identity provider lock poisoned");
649        let active_key_id = key_id(state.generation);
650        let credential_id = format!(
651            "sandbox:{}:{}:{sequence}",
652            state.generation, request.service_principal
653        );
654        let claims = WorkloadClaims {
655            iss: DEVELOPMENT_ISSUER.to_owned(),
656            sub: request.service_principal.clone(),
657            aud: vec![request.audience.clone()],
658            exp: expires_at_unix_ms.div_ceil(1_000),
659            iat: request.issued_at_unix_ms / 1_000,
660            jti: credential_id.clone(),
661            credential_kind: CREDENTIAL_KIND.to_owned(),
662            authenticated_transport_binding: request.authenticated_transport_binding,
663            expires_at_unix_ms,
664        };
665        let mut header = Header::new(Algorithm::HS256);
666        header.kid = Some(active_key_id.clone());
667        let token = encode(
668            &header,
669            &claims,
670            &EncodingKey::from_secret(state.secret.as_bytes()),
671        )
672        .map_err(|_| {
673            identity_error(
674                WorkloadIdentityErrorCode::InvalidProof,
675                "credential_signing_failed",
676                "The System Sandbox provider could not sign the Workload Identity credential",
677                Some(request.service_principal.clone()),
678                Some(credential_id.clone()),
679                Some(active_key_id.clone()),
680            )
681        })?;
682        Ok(WorkloadCredential {
683            token,
684            issuer: DEVELOPMENT_ISSUER.to_owned(),
685            service_principal: request.service_principal,
686            audience: request.audience,
687            expires_at_unix_ms,
688            credential_id,
689            key_id: active_key_id,
690            algorithm: "HS256-development-only".to_owned(),
691        })
692    }
693
694    fn verify(
695        &self,
696        token: &str,
697        verification: &WorkloadIdentityVerification,
698    ) -> Result<AuthenticatedServicePrincipal, WorkloadIdentityError> {
699        let header = decode_header(token).map_err(|_| invalid_proof())?;
700        let state = self.state.read().expect("identity provider lock poisoned");
701        let active_key_id = key_id(state.generation);
702        let credential_key_id = header.kid.ok_or_else(invalid_proof)?;
703        let (secret, stale) = if credential_key_id == active_key_id {
704            (state.secret.as_str(), false)
705        } else if let Some(secret) = state.retired_secrets.get(&credential_key_id) {
706            (secret.as_str(), true)
707        } else {
708            return Err(invalid_proof());
709        };
710        let mut validation = Validation::new(Algorithm::HS256);
711        validation.validate_exp = false;
712        validation.validate_aud = false;
713        validation.required_spec_claims.clear();
714        let claims = decode::<WorkloadClaims>(
715            token,
716            &DecodingKey::from_secret(secret.as_bytes()),
717            &validation,
718        )
719        .map_err(|_| invalid_proof())?
720        .claims;
721        let evidence = |outcome: &str| WorkloadIdentityEvidence {
722            outcome: outcome.to_owned(),
723            service_principal: Some(claims.sub.clone()),
724            credential_id: Some(claims.jti.clone()),
725            key_id: Some(credential_key_id.clone()),
726        };
727        if claims.credential_kind != CREDENTIAL_KIND {
728            return Err(WorkloadIdentityError {
729                code: WorkloadIdentityErrorCode::InvalidProof,
730                message: "Provider host tokens cannot be used as Workload Identity credentials"
731                    .to_owned(),
732                evidence: evidence("credential_kind_mismatch"),
733            });
734        }
735        if claims.iss != DEVELOPMENT_ISSUER {
736            return Err(WorkloadIdentityError {
737                code: WorkloadIdentityErrorCode::IssuerMismatch,
738                message: "Workload Identity issuer does not match the configured provider"
739                    .to_owned(),
740                evidence: evidence("issuer_mismatch"),
741            });
742        }
743        if stale {
744            return Err(WorkloadIdentityError {
745                code: WorkloadIdentityErrorCode::StaleCredential,
746                message:
747                    "The Workload Identity credential was issued by a superseded System Sandbox key"
748                        .to_owned(),
749                evidence: evidence("stale_credential"),
750            });
751        }
752        if !claims
753            .aud
754            .iter()
755            .any(|audience| audience == &verification.audience)
756        {
757            return Err(WorkloadIdentityError {
758                code: WorkloadIdentityErrorCode::AudienceMismatch,
759                message: "Workload Identity credential is not intended for this receiver"
760                    .to_owned(),
761                evidence: evidence("audience_mismatch"),
762            });
763        }
764        if verification.now_unix_ms >= claims.expires_at_unix_ms {
765            return Err(WorkloadIdentityError {
766                code: WorkloadIdentityErrorCode::CredentialExpired,
767                message: "Workload Identity credential has expired".to_owned(),
768                evidence: evidence("credential_expired"),
769            });
770        }
771        if claims.authenticated_transport_binding != verification.authenticated_transport_binding {
772            return Err(WorkloadIdentityError {
773                code: WorkloadIdentityErrorCode::TransportBindingMismatch,
774                message: "Workload Identity proof is not bound to the authenticated transport"
775                    .to_owned(),
776                evidence: evidence("transport_binding_mismatch"),
777            });
778        }
779        let authenticated_evidence = evidence("authenticated");
780        Ok(AuthenticatedServicePrincipal {
781            service_principal: claims.sub,
782            credential_id: claims.jti,
783            issuer: claims.iss,
784            audience: verification.audience.clone(),
785            expires_at_unix_ms: claims.expires_at_unix_ms,
786            key_id: credential_key_id,
787            algorithm: "HS256-development-only".to_owned(),
788            evidence: authenticated_evidence,
789        })
790    }
791}
792
793fn key_id(generation: u64) -> String {
794    format!("system-sandbox-key-{generation}")
795}
796
797fn valid_service_principal(value: &str) -> bool {
798    value.strip_prefix("service:").is_some_and(|service_id| {
799        !service_id.is_empty()
800            && service_id
801                .bytes()
802                .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_'))
803    })
804}
805
806fn invalid_service_principal() -> WorkloadIdentityError {
807    identity_error(
808        WorkloadIdentityErrorCode::InvalidRequest,
809        "invalid_service_principal",
810        "Workload Identity requires a stable Service Principal",
811        None,
812        None,
813        None,
814    )
815}
816
817fn service_principal_from_spiffe_id(spiffe_id: &SpiffeId) -> Option<String> {
818    let service_id = spiffe_id.path().strip_prefix("/service/")?;
819    let service_principal = format!("service:{service_id}");
820    (spiffe_id.path().split('/').count() == 3 && valid_service_principal(&service_principal))
821        .then_some(service_principal)
822}
823
824fn expiry_unix_ms(svid: &JwtSvid) -> Result<u64, WorkloadIdentityError> {
825    u64::try_from(svid.expiry().unix_timestamp())
826        .ok()
827        .and_then(|seconds| seconds.checked_mul(1_000))
828        .ok_or_else(|| {
829            identity_error(
830                WorkloadIdentityErrorCode::InvalidProof,
831                "invalid_credential_expiry",
832                "SPIFFE credential expiration is outside the supported timestamp range",
833                None,
834                None,
835                Some(svid.key_id().to_owned()),
836            )
837        })
838}
839
840fn credential_id(token: &str) -> String {
841    let digest = Sha256::digest(token.as_bytes());
842    format!("spiffe-jwt:{}", URL_SAFE_NO_PAD.encode(digest))
843}
844
845fn jwt_algorithm(token: &str) -> Result<String, WorkloadIdentityError> {
846    decode_header(token)
847        .map(|header| format!("{:?}", header.alg))
848        .map_err(|_| invalid_proof())
849}
850
851fn spiffe_verification_error(error: JwtSvidError) -> WorkloadIdentityError {
852    let (code, outcome, message) = match &error {
853        JwtSvidError::InvalidAudience(_, _) => (
854            WorkloadIdentityErrorCode::AudienceMismatch,
855            "audience_mismatch",
856            "Workload Identity credential is not intended for this receiver",
857        ),
858        JwtSvidError::InvalidToken(error) if matches!(error.kind(), ErrorKind::InvalidAudience) => {
859            (
860                WorkloadIdentityErrorCode::AudienceMismatch,
861                "audience_mismatch",
862                "Workload Identity credential is not intended for this receiver",
863            )
864        }
865        JwtSvidError::InvalidToken(error)
866            if matches!(error.kind(), ErrorKind::ExpiredSignature) =>
867        {
868            (
869                WorkloadIdentityErrorCode::CredentialExpired,
870                "credential_expired",
871                "Workload Identity credential has expired",
872            )
873        }
874        _ => (
875            WorkloadIdentityErrorCode::InvalidProof,
876            "invalid_proof",
877            "SPIFFE Workload Identity proof could not be verified",
878        ),
879    };
880    identity_error(code, outcome, message, None, None, None)
881}
882
883fn provider_unavailable(outcome: &str) -> WorkloadIdentityError {
884    identity_error(
885        WorkloadIdentityErrorCode::ProviderUnavailable,
886        outcome,
887        "SPIFFE Workload Identity material is unavailable",
888        None,
889        None,
890        None,
891    )
892}
893
894fn invalid_proof() -> WorkloadIdentityError {
895    identity_error(
896        WorkloadIdentityErrorCode::InvalidProof,
897        "invalid_proof",
898        "Workload Identity proof could not be verified",
899        None,
900        None,
901        None,
902    )
903}
904
905fn identity_error(
906    code: WorkloadIdentityErrorCode,
907    outcome: &str,
908    message: &str,
909    service_principal: Option<String>,
910    credential_id: Option<String>,
911    key_id: Option<String>,
912) -> WorkloadIdentityError {
913    WorkloadIdentityError {
914        code,
915        message: message.to_owned(),
916        evidence: WorkloadIdentityEvidence {
917            outcome: outcome.to_owned(),
918            service_principal,
919            credential_id,
920            key_id,
921        },
922    }
923}