Skip to content

Commit c317a02

Browse files
committed
fix(provider): request OIDC token just before upload
1 parent 1007a63 commit c317a02

3 files changed

Lines changed: 86 additions & 35 deletions

File tree

src/run/mod.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ pub async fn run(
191191
) -> Result<()> {
192192
let output_json = args.message_format == Some(MessageFormat::Json);
193193
let mut config = Config::try_from(args)?;
194-
let provider = run_environment::get_provider(&config)?;
194+
let mut provider = run_environment::get_provider(&config)?;
195195
let logger = Logger::new(&provider)?;
196196

197197
#[allow(deprecated)]
@@ -214,8 +214,7 @@ pub async fn run(
214214
debug!("Using the token from the CodSpeed configuration file");
215215
config.set_token(codspeed_config.auth.token.clone());
216216
} else {
217-
// If relevant, set the OIDC token for authentication
218-
provider.set_oidc_token(&mut config).await?;
217+
provider.check_oidc_configuration(&config)?;
219218
}
220219

221220
let system_info = SystemInfo::new()?;
@@ -266,6 +265,12 @@ pub async fn run(
266265
};
267266

268267
if !config.skip_upload {
268+
if provider.get_run_environment() != RunEnvironment::Local {
269+
// If relevant, set the OIDC token for authentication
270+
// Note: OIDC tokens can expire quickly, so we set it just before the upload
271+
provider.set_oidc_token(&mut config).await?;
272+
}
273+
269274
start_group!("Uploading performance data");
270275
let upload_result =
271276
uploader::upload(&config, &system_info, &provider, &run_data, executor.name()).await?;

src/run/run_environment/github_actions/provider.rs

Lines changed: 70 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ use crate::run::{
2222

2323
use super::logger::GithubActionLogger;
2424

25-
#[derive(Debug)]
2625
pub struct GitHubActionsProvider {
2726
pub owner: String,
2827
pub repository: String,
@@ -39,6 +38,16 @@ pub struct GitHubActionsProvider {
3938

4039
/// Indicates whether the repository is private.
4140
is_repository_private: bool,
41+
42+
/// OIDC configuration data necessary to request an OIDC token.
43+
///
44+
/// If None, OIDC is not configured for this run.
45+
oidc_config: Option<OIDCTokenRequestData>,
46+
}
47+
48+
struct OIDCTokenRequestData {
49+
request_url: String,
50+
request_token: String,
4251
}
4352

4453
impl GitHubActionsProvider {
@@ -139,6 +148,7 @@ impl TryFrom<&Config> for GitHubActionsProvider {
139148
repository_root_path,
140149
is_head_repo_fork,
141150
is_repository_private,
151+
oidc_config: None,
142152
})
143153
}
144154
}
@@ -259,26 +269,27 @@ impl RunEnvironmentProvider for GitHubActionsProvider {
259269
Ok(commit_hash)
260270
}
261271

262-
/// Set the OIDC token for GitHub Actions if necessary
272+
/// Validate that the environment is correctly configured for OIDC usage.
263273
///
264274
/// ## Logic
265-
/// - If the user has explicitly set a token in the configuration (i.e. "static token"), do not override it, but display an info message.
275+
/// - If the user has explicitly set a token in the configuration (i.e. "static token"), inform the user that OIDC is available but do nothing.
266276
/// - Otherwise, check if the necessary environment variables are set to use OIDC.
267-
/// - Then attempt to request an OIDC token.
268277
///
278+
/// For Github Actions, there are two necessary environment variables:
279+
/// - `ACTIONS_ID_TOKEN_REQUEST_TOKEN`
280+
/// - `ACTIONS_ID_TOKEN_REQUEST_URL`
269281
/// If environment variables are not set, this could be because:
270282
/// - The user has misconfigured the workflow (missing `id-token` permission)
271283
/// - The run is from a public fork, in which case GitHub Actions does not provide these environment variables for security reasons.
272284
///
273-
///
274285
/// ## Notes
275286
/// Retrieving the token requires that the workflow has the `id-token` permission enabled.
276287
///
277288
/// Docs:
278289
/// - https://docs.github.com/en/actions/how-tos/secure-your-work/security-harden-deployments/oidc-with-reusable-workflows
279290
/// - https://docs.github.com/en/actions/concepts/security/openid-connect
280291
/// - https://docs.github.com/en/actions/reference/security/oidc#methods-for-requesting-the-oidc-token
281-
async fn set_oidc_token(&self, config: &mut Config) -> Result<()> {
292+
fn check_oidc_configuration(&mut self, config: &Config) -> Result<()> {
282293
// Check if a static token is already set
283294
if config.token.is_some() {
284295
info!(
@@ -303,8 +314,8 @@ impl RunEnvironmentProvider for GitHubActionsProvider {
303314

304315
if self.is_repository_private {
305316
bail!(
306-
"Unable to retrieve OIDC token for authentication. \n\
307-
Make sure your workflow has the `id-token: write` permission set. \n\
317+
"Unable to retrieve OIDC token for authentication.\n\
318+
Make sure your workflow has the `id-token: write` permission set.\n\
308319
See https://codspeed.io/docs/integrations/ci/github-actions/configuration#oidc-recommended"
309320
)
310321
}
@@ -319,34 +330,57 @@ impl RunEnvironmentProvider for GitHubActionsProvider {
319330
}
320331

321332
let request_url = request_url.unwrap();
322-
let request_url = format!("{request_url}&audience={}", self.get_oidc_audience());
323333
let request_token = request_token.unwrap();
324334

325-
let token = match OIDC_CLIENT
326-
.get(request_url)
327-
.header("Accept", "application/json")
328-
.header("Authorization", format!("Bearer {request_token}"))
329-
.send()
330-
.await
331-
{
332-
Ok(response) => match response.json::<OIDCResponse>().await {
333-
Ok(oidc_response) => oidc_response.value,
335+
self.oidc_config = Some(OIDCTokenRequestData {
336+
request_url,
337+
request_token,
338+
});
339+
340+
Ok(())
341+
}
342+
343+
/// Request the OIDC token from GitHub Actions if necessary.
344+
///
345+
/// All the validation has already been performed in `check_oidc_configuration`.
346+
/// So if the oidc_config is None, we simply return.
347+
async fn set_oidc_token(&self, config: &mut Config) -> Result<()> {
348+
if let Some(oidc_config) = &self.oidc_config {
349+
let request_url = format!(
350+
"{}&audience={}",
351+
oidc_config.request_url,
352+
self.get_oidc_audience()
353+
);
354+
355+
let token = match OIDC_CLIENT
356+
.get(request_url)
357+
.header("Accept", "application/json")
358+
.header(
359+
"Authorization",
360+
format!("Bearer {}", oidc_config.request_token),
361+
)
362+
.send()
363+
.await
364+
{
365+
Ok(response) => match response.json::<OIDCResponse>().await {
366+
Ok(oidc_response) => oidc_response.value,
367+
Err(_) => None,
368+
},
334369
Err(_) => None,
335-
},
336-
Err(_) => None,
337-
};
370+
};
338371

339-
if token.is_some() {
340-
debug!("Successfully retrieved OIDC token for authentication.");
341-
config.set_token(token);
342-
} else if self.is_repository_private {
343-
bail!(
344-
"Unable to retrieve OIDC token for authentication. \n\
345-
Make sure your workflow has the `id-token: write` permission set. \n\
346-
See https://codspeed.io/docs/integrations/ci/github-actions/configuration#oidc-recommended"
347-
)
348-
} else {
349-
warn!("Failed to retrieve OIDC token for authentication.");
372+
if token.is_some() {
373+
debug!("Successfully retrieved OIDC token for authentication.");
374+
config.set_token(token);
375+
} else if self.is_repository_private {
376+
bail!(
377+
"Unable to retrieve OIDC token for authentication. \n\
378+
Make sure your workflow has the `id-token: write` permission set. \n\
379+
See https://codspeed.io/docs/integrations/ci/github-actions/configuration#oidc-recommended"
380+
)
381+
} else {
382+
warn!("Failed to retrieve OIDC token for authentication.");
383+
}
350384
}
351385

352386
Ok(())
@@ -641,6 +675,7 @@ mod tests {
641675
repository_root_path: "/home/work/my-repo".into(),
642676
is_head_repo_fork: false,
643677
is_repository_private: false,
678+
oidc_config: None,
644679
};
645680

646681
let run_part = github_actions_provider.get_run_provider_run_part().unwrap();
@@ -685,6 +720,7 @@ mod tests {
685720
repository_root_path: "/home/work/my-repo".into(),
686721
is_head_repo_fork: false,
687722
is_repository_private: false,
723+
oidc_config: None,
688724
};
689725

690726
let run_part = github_actions_provider.get_run_provider_run_part().unwrap();
@@ -738,6 +774,7 @@ mod tests {
738774
repository_root_path: "/home/work/my-repo".into(),
739775
is_head_repo_fork: false,
740776
is_repository_private: false,
777+
oidc_config: None,
741778
};
742779

743780
let run_part = github_actions_provider.get_run_provider_run_part().unwrap();
@@ -789,6 +826,7 @@ mod tests {
789826
repository_root_path: "/home/work/my-repo".into(),
790827
is_head_repo_fork: false,
791828
is_repository_private: false,
829+
oidc_config: None,
792830
};
793831

794832
let run_part = github_actions_provider.get_run_provider_run_part().unwrap();

src/run/run_environment/provider.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,20 @@ pub trait RunEnvironmentProvider {
5050
OIDC_AUDIENCE
5151
}
5252

53+
/// Check the OIDC configuration for the current run environment, if supported.
54+
fn check_oidc_configuration(&mut self, _config: &Config) -> Result<()> {
55+
Ok(())
56+
}
57+
5358
/// Handle an OIDC token for the current run environment, if supported.
5459
///
5560
/// Updates the config if necessary.
5661
///
5762
/// Depending on the provider, this may involve requesting the token,
5863
/// warning the user about potential misconfigurations, or other necessary steps.
64+
///
65+
/// Warning: OIDC tokens are typically short-lived. This method must be called
66+
/// just before the upload step to ensure the token is valid during the upload.
5967
async fn set_oidc_token(&self, _config: &mut Config) -> Result<()> {
6068
Ok(())
6169
}

0 commit comments

Comments
 (0)