auths_cli/commands/artifact/
oidc.rs1use std::path::Path;
12use std::sync::Arc;
13
14use anyhow::{Context, Result, bail};
15use chrono::{DateTime, Utc};
16
17use auths_infra_http::{HttpJwksClient, HttpJwtValidator, PinnedJwksClient};
18use auths_sdk::domains::signing::ci_env::CiPlatform;
19use auths_sdk::workflows::ci::machine_identity::{
20 OidcMachineIdentityConfig, create_machine_identity_from_oidc_token,
21};
22use auths_verifier::core::OidcBinding;
23
24pub const DEFAULT_OIDC_ISSUER: &str = "https://token.actions.githubusercontent.com";
27
28fn oidc_platform(platform: &CiPlatform) -> Result<&'static str> {
33 match platform {
34 CiPlatform::GithubActions => Ok("github"),
35 CiPlatform::GitlabCi => Ok("gitlab"),
36 CiPlatform::CircleCi => Ok("circleci"),
37 CiPlatform::Generic | CiPlatform::Local => bail!(
38 "--oidc-token needs a CI platform with a known OIDC claim shape \
39 (GitHub Actions, GitLab CI, or CircleCI) — detected {:?}",
40 platform
41 ),
42 }
43}
44
45pub fn resolve_oidc_binding(
57 token_path: &Path,
58 issuer: &str,
59 audience: &str,
60 jwks_path: Option<&Path>,
61 platform: &CiPlatform,
62 now: DateTime<Utc>,
63) -> Result<OidcBinding> {
64 let oidc_platform = oidc_platform(platform)?;
65
66 let token = std::fs::read_to_string(token_path)
67 .with_context(|| format!("Failed to read OIDC token from {token_path:?}"))?;
68 let token = token.trim();
69 if token.is_empty() {
70 bail!("OIDC token file {token_path:?} is empty");
71 }
72
73 let validator = match jwks_path {
74 Some(path) => {
75 let jwks_raw = std::fs::read_to_string(path)
76 .with_context(|| format!("Failed to read pinned JWKS from {path:?}"))?;
77 let jwks: serde_json::Value = serde_json::from_str(&jwks_raw)
78 .with_context(|| format!("Pinned JWKS {path:?} is not valid JSON"))?;
79 HttpJwtValidator::new(Arc::new(PinnedJwksClient::new(jwks)))
80 }
81 None => HttpJwtValidator::new(Arc::new(HttpJwksClient::with_default_ttl())),
82 };
83
84 let config = OidcMachineIdentityConfig {
85 issuer: issuer.to_string(),
86 audience: audience.to_string(),
87 platform: oidc_platform.to_string(),
88 };
89
90 let rt = tokio::runtime::Runtime::new().context("Failed to create async runtime")?;
91 let identity = rt
92 .block_on(create_machine_identity_from_oidc_token(
93 token,
94 config,
95 Arc::new(validator),
96 now,
97 ))
98 .map_err(|e| anyhow::anyhow!("OIDC token validation failed: {e}"))?;
99
100 Ok(OidcBinding::from(&identity))
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 #[test]
108 fn local_platform_cannot_present_a_token() {
109 let err = oidc_platform(&CiPlatform::Local).expect_err("local must be rejected");
110 assert!(err.to_string().contains("known OIDC claim shape"));
111 }
112
113 #[test]
114 fn github_maps_to_github_claim_shape() {
115 assert_eq!(oidc_platform(&CiPlatform::GithubActions).unwrap(), "github");
116 }
117}