auths_infra_http/
oidc_platforms.rs1#[allow(clippy::disallowed_methods)] pub async fn github_actions_oidc_token() -> Result<String, String> {
10 let actions_id_token_url = std::env::var("ACTIONS_ID_TOKEN_REQUEST_URL").map_err(|_| {
11 "ACTIONS_ID_TOKEN_REQUEST_URL not set (not running in GitHub Actions)".to_string()
12 })?;
13
14 let actions_id_token_request_token =
15 std::env::var("ACTIONS_ID_TOKEN_REQUEST_TOKEN").map_err(|_| {
16 "ACTIONS_ID_TOKEN_REQUEST_TOKEN not set (not running in GitHub Actions)".to_string()
17 })?;
18
19 let client = crate::default_http_client();
20
21 let response = client
22 .get(&actions_id_token_url)
23 .bearer_auth(&actions_id_token_request_token)
24 .send()
25 .await
26 .map_err(|e| format!("failed to acquire GitHub Actions OIDC token: {}", e))?;
27
28 let json: serde_json::Value = response
29 .json()
30 .await
31 .map_err(|e| format!("failed to parse GitHub Actions token response: {}", e))?;
32
33 json.get("token")
34 .and_then(|t| t.as_str())
35 .map(|s| s.to_string())
36 .ok_or_else(|| "GitHub Actions token response missing 'token' field".to_string())
37}
38
39#[allow(clippy::disallowed_methods)] pub async fn gitlab_ci_oidc_token() -> Result<String, String> {
48 let ci_job_jwt_v2 = std::env::var("CI_JOB_JWT_V2")
49 .map_err(|_| "CI_JOB_JWT_V2 not set (not running in GitLab CI)".to_string())?;
50
51 Ok(ci_job_jwt_v2)
52}
53
54#[allow(clippy::disallowed_methods)] pub async fn circleci_oidc_token() -> Result<String, String> {
63 let circle_oidc_token = std::env::var("CIRCLE_OIDC_TOKEN")
64 .map_err(|_| "CIRCLE_OIDC_TOKEN not set (not running in CircleCI)".to_string())?;
65
66 Ok(circle_oidc_token)
67}
68
69pub fn normalize_workload_claims(
77 platform: &str,
78 claims: serde_json::Value,
79) -> Result<serde_json::Map<String, serde_json::Value>, String> {
80 let mut normalized = serde_json::Map::new();
81
82 match platform {
83 "github" => {
84 if let Some(repo) = claims.get("repository").and_then(|v| v.as_str()) {
86 normalized.insert(
87 "repository".to_string(),
88 serde_json::Value::String(repo.to_string()),
89 );
90 }
91 if let Some(actor) = claims.get("actor").and_then(|v| v.as_str()) {
92 normalized.insert(
93 "actor".to_string(),
94 serde_json::Value::String(actor.to_string()),
95 );
96 }
97 if let Some(workflow) = claims.get("workflow").and_then(|v| v.as_str()) {
98 normalized.insert(
99 "workflow".to_string(),
100 serde_json::Value::String(workflow.to_string()),
101 );
102 }
103 if let Some(job_workflow_ref) = claims.get("job_workflow_ref").and_then(|v| v.as_str())
104 {
105 normalized.insert(
106 "job_workflow_ref".to_string(),
107 serde_json::Value::String(job_workflow_ref.to_string()),
108 );
109 }
110 if let Some(run_id) = claims.get("run_id").and_then(|v| v.as_str()) {
111 normalized.insert(
112 "run_id".to_string(),
113 serde_json::Value::String(run_id.to_string()),
114 );
115 }
116 if let Some(run_number) = claims.get("run_number").and_then(|v| v.as_str()) {
117 normalized.insert(
118 "run_number".to_string(),
119 serde_json::Value::String(run_number.to_string()),
120 );
121 }
122 Ok(normalized)
123 }
124 "gitlab" => {
125 if let Some(project_id) = claims.get("project_id").and_then(|v| v.as_i64()) {
127 normalized.insert(
128 "project_id".to_string(),
129 serde_json::Value::Number(project_id.into()),
130 );
131 }
132 if let Some(project_path) = claims.get("project_path").and_then(|v| v.as_str()) {
133 normalized.insert(
134 "project_path".to_string(),
135 serde_json::Value::String(project_path.to_string()),
136 );
137 }
138 if let Some(user_id) = claims.get("user_id").and_then(|v| v.as_i64()) {
139 normalized.insert(
140 "user_id".to_string(),
141 serde_json::Value::Number(user_id.into()),
142 );
143 }
144 if let Some(user_login) = claims.get("user_login").and_then(|v| v.as_str()) {
145 normalized.insert(
146 "user_login".to_string(),
147 serde_json::Value::String(user_login.to_string()),
148 );
149 }
150 if let Some(pipeline_id) = claims.get("pipeline_id").and_then(|v| v.as_i64()) {
151 normalized.insert(
152 "pipeline_id".to_string(),
153 serde_json::Value::Number(pipeline_id.into()),
154 );
155 }
156 if let Some(job_id) = claims.get("job_id").and_then(|v| v.as_i64()) {
157 normalized.insert(
158 "job_id".to_string(),
159 serde_json::Value::Number(job_id.into()),
160 );
161 }
162 Ok(normalized)
163 }
164 "circleci" => {
165 if let Some(project_id) = claims.get("project_id").and_then(|v| v.as_str()) {
167 normalized.insert(
168 "project_id".to_string(),
169 serde_json::Value::String(project_id.to_string()),
170 );
171 }
172 if let Some(project_name) = claims.get("project_name").and_then(|v| v.as_str()) {
173 normalized.insert(
174 "project_name".to_string(),
175 serde_json::Value::String(project_name.to_string()),
176 );
177 }
178 if let Some(workflow_id) = claims.get("workflow_id").and_then(|v| v.as_str()) {
179 normalized.insert(
180 "workflow_id".to_string(),
181 serde_json::Value::String(workflow_id.to_string()),
182 );
183 }
184 if let Some(job_number) = claims.get("job_number").and_then(|v| v.as_str()) {
185 normalized.insert(
186 "job_number".to_string(),
187 serde_json::Value::String(job_number.to_string()),
188 );
189 }
190 if let Some(org_id) = claims.get("org_id").and_then(|v| v.as_str()) {
191 normalized.insert(
192 "org_id".to_string(),
193 serde_json::Value::String(org_id.to_string()),
194 );
195 }
196 Ok(normalized)
197 }
198 _ => Err(format!("unknown OIDC platform: {}", platform)),
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205
206 #[test]
207 fn test_normalize_github_claims() {
208 let claims = serde_json::json!({
209 "repository": "owner/repo",
210 "actor": "github-user",
211 "workflow": "test.yml",
212 "job_workflow_ref": "owner/repo/.github/workflows/test.yml@main",
213 "run_id": "12345",
214 "run_number": "1"
215 });
216
217 let result = normalize_workload_claims("github", claims);
218 assert!(result.is_ok());
219 let normalized = result.unwrap();
220 assert_eq!(
221 normalized.get("repository").and_then(|v| v.as_str()),
222 Some("owner/repo")
223 );
224 assert_eq!(
225 normalized.get("actor").and_then(|v| v.as_str()),
226 Some("github-user")
227 );
228 }
229
230 #[test]
231 fn test_normalize_gitlab_claims() {
232 let claims = serde_json::json!({
233 "project_id": 123,
234 "project_path": "group/project",
235 "user_id": 456,
236 "user_login": "gitlab-user",
237 "pipeline_id": 789,
238 "job_id": 999
239 });
240
241 let result = normalize_workload_claims("gitlab", claims);
242 assert!(result.is_ok());
243 let normalized = result.unwrap();
244 assert_eq!(
245 normalized.get("project_path").and_then(|v| v.as_str()),
246 Some("group/project")
247 );
248 }
249
250 #[test]
251 fn test_normalize_circleci_claims() {
252 let claims = serde_json::json!({
253 "project_id": "abc123",
254 "project_name": "my-project",
255 "workflow_id": "def456",
256 "job_number": "1",
257 "org_id": "ghi789"
258 });
259
260 let result = normalize_workload_claims("circleci", claims);
261 assert!(result.is_ok());
262 let normalized = result.unwrap();
263 assert_eq!(
264 normalized.get("project_name").and_then(|v| v.as_str()),
265 Some("my-project")
266 );
267 }
268
269 #[test]
270 fn test_unknown_platform() {
271 let claims = serde_json::json!({});
272 let result = normalize_workload_claims("unknown", claims);
273 assert!(result.is_err());
274 }
275}