Skip to content

Commit f60c77e

Browse files
committed
feat(provider): add support for oidc token authentication
1 parent 2359e53 commit f60c77e

8 files changed

Lines changed: 125 additions & 1 deletion

File tree

Cargo.lock

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ serde = { version = "1.0.192", features = ["derive"] }
3030
serde_json = { version = "1.0.108", features = ["preserve_order"] }
3131
url = "2.4.1"
3232
sha256 = "1.4.0"
33-
tokio = { version = "1", features = ["macros", "rt"] }
33+
tokio = { version = "1", features = ["macros", "rt", "process"] }
3434
tokio-tar = "0.3.1"
3535
tokio-util = "0.7.16"
3636
md5 = "0.7.0"

src/run/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,13 @@ pub async fn run(
195195
}
196196
debug!("Using the token from the CodSpeed configuration file");
197197
config.set_token(codspeed_config.auth.token.clone());
198+
} else {
199+
let oidc_token = provider.get_oidc_token().await;
200+
if oidc_token.is_some() {
201+
debug!("Using OIDC token for authentication");
202+
203+
config.set_token(oidc_token);
204+
}
198205
}
199206

200207
let system_info = SystemInfo::new()?;

src/run/run_environment/buildkite/provider.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use std::env;
22

3+
use async_trait::async_trait;
34
use simplelog::SharedLogger;
5+
use tokio::process::Command;
46

57
use crate::prelude::*;
68
use crate::run::helpers::{GitRemote, parse_git_remote};
@@ -119,6 +121,7 @@ impl RunEnvironmentDetector for BuildkiteProvider {
119121
}
120122
}
121123

124+
#[async_trait(?Send)]
122125
impl RunEnvironmentProvider for BuildkiteProvider {
123126
fn get_repository_provider(&self) -> RepositoryProvider {
124127
RepositoryProvider::GitHub
@@ -151,6 +154,39 @@ impl RunEnvironmentProvider for BuildkiteProvider {
151154
fn get_run_provider_run_part(&self) -> Option<RunPart> {
152155
None
153156
}
157+
158+
/// Return an OIDC token obtained from Buildkite, if available.
159+
///
160+
/// This uses the `buildkite-agent oidc request-token` command to obtain
161+
/// a token with the audience set to "codspeed".
162+
///
163+
/// Docs:
164+
/// - https://buildkite.com/docs/agent/v3/cli-oidc
165+
/// - https://buildkite.com/docs/pipelines/security/oidc
166+
async fn get_oidc_token(&self) -> Option<String> {
167+
let output = Command::new("buildkite-agent")
168+
.args(["oidc", "request-token", "--audience", "codspeed"])
169+
.output()
170+
.await
171+
.ok()?;
172+
173+
if !output.status.success() {
174+
warn!(
175+
"Failed to request OIDC token from Buildkite: {}",
176+
String::from_utf8_lossy(&output.stderr)
177+
);
178+
return None;
179+
}
180+
181+
let token = String::from_utf8(output.stdout).ok()?;
182+
let token = token.trim();
183+
184+
if token.is_empty() {
185+
return None;
186+
}
187+
188+
Some(token.to_string())
189+
}
154190
}
155191

156192
#[cfg(test)]

src/run/run_environment/github_actions/provider.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
use async_trait::async_trait;
12
use git2::Repository;
23
use lazy_static::lazy_static;
34
use regex::Regex;
5+
use reqwest::Client;
6+
use serde::Deserialize;
47
use serde_json::Value;
58
use simplelog::SharedLogger;
69
use std::collections::BTreeMap;
@@ -42,6 +45,11 @@ impl GitHubActionsProvider {
4245
}
4346
}
4447

48+
#[derive(Deserialize)]
49+
struct OIDCResponse {
50+
value: Option<String>,
51+
}
52+
4553
lazy_static! {
4654
static ref PR_REF_REGEX: Regex = Regex::new(r"^refs/pull/(?P<pr_number>\d+)/merge$").unwrap();
4755
}
@@ -129,6 +137,7 @@ impl RunEnvironmentDetector for GitHubActionsProvider {
129137
}
130138
}
131139

140+
#[async_trait(?Send)]
132141
impl RunEnvironmentProvider for GitHubActionsProvider {
133142
fn get_repository_provider(&self) -> RepositoryProvider {
134143
RepositoryProvider::GitHub
@@ -236,6 +245,46 @@ impl RunEnvironmentProvider for GitHubActionsProvider {
236245
.to_string();
237246
Ok(commit_hash)
238247
}
248+
249+
/// Get the OIDC token for GitHub Actions.
250+
///
251+
/// This requires that the workflow has the `id-token` permission enabled.
252+
///
253+
/// Docs:
254+
/// - https://docs.github.com/en/actions/how-tos/secure-your-work/security-harden-deployments/oidc-with-reusable-workflows
255+
/// - https://docs.github.com/en/actions/concepts/security/openid-connect
256+
/// - https://docs.github.com/en/actions/reference/security/oidc#methods-for-requesting-the-oidc-token
257+
async fn get_oidc_token(&self) -> Option<String> {
258+
// The `ACTIONS_ID_TOKEN_REQUEST_TOKEN` environment variable is set when the `id-token` permission is granted.
259+
// This is necessary to authenticate with OIDC, but not strictly set just for OIDC.
260+
let request_token = get_env_variable("ACTIONS_ID_TOKEN_REQUEST_TOKEN").ok();
261+
let request_url = get_env_variable("ACTIONS_ID_TOKEN_REQUEST_URL").ok();
262+
263+
if request_token.is_none() || request_url.is_none() {
264+
warn!(
265+
"The Workflow is missing the 'id-token: write' permission required to request an OIDC token. \
266+
Please refer to https://docs.github.com/en/actions/how-to-guides/security-hardening-your-workflows/about-security-hardening-with-openid-connect for more information."
267+
);
268+
269+
return None;
270+
}
271+
272+
let request_url = request_url.unwrap();
273+
let request_url = format!("{request_url}&audience=codspeed");
274+
let request_token = request_token.unwrap();
275+
276+
Client::new()
277+
.get(request_url)
278+
.header("Accept", "application/json")
279+
.header("Authorization", format!("Bearer {request_token}"))
280+
.send()
281+
.await
282+
.ok()?
283+
.json::<OIDCResponse>()
284+
.await
285+
.ok()?
286+
.value
287+
}
239288
}
240289

241290
#[cfg(test)]

src/run/run_environment/gitlab_ci/provider.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use async_trait::async_trait;
12
use simplelog::SharedLogger;
23
use std::collections::BTreeMap;
34
use std::env;
@@ -139,6 +140,7 @@ impl RunEnvironmentDetector for GitLabCIProvider {
139140
}
140141
}
141142

143+
#[async_trait(?Send)]
142144
impl RunEnvironmentProvider for GitLabCIProvider {
143145
fn get_logger(&self) -> Box<dyn SharedLogger> {
144146
Box::new(GitLabCILogger::new())
@@ -175,6 +177,15 @@ impl RunEnvironmentProvider for GitLabCIProvider {
175177
metadata: BTreeMap::new(),
176178
})
177179
}
180+
181+
/// For GitLab CI, OIDC tokens must be passed via env variable.
182+
///
183+
/// See:
184+
/// - https://docs.gitlab.com/integration/openid_connect_provider/
185+
/// - https://docs.gitlab.com/ci/secrets/id_token_authentication/
186+
async fn get_oidc_token(&self) -> Option<String> {
187+
None
188+
}
178189
}
179190

180191
#[cfg(test)]

src/run/run_environment/local/provider.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use async_trait::async_trait;
12
use git2::Repository;
23
use simplelog::SharedLogger;
34

@@ -117,6 +118,7 @@ impl RunEnvironmentDetector for LocalProvider {
117118
}
118119
}
119120

121+
#[async_trait(?Send)]
120122
impl RunEnvironmentProvider for LocalProvider {
121123
fn get_repository_provider(&self) -> RepositoryProvider {
122124
self.repository_provider.clone()
@@ -178,6 +180,10 @@ impl RunEnvironmentProvider for LocalProvider {
178180
fn get_run_provider_run_part(&self) -> Option<RunPart> {
179181
None
180182
}
183+
184+
async fn get_oidc_token(&self) -> Option<String> {
185+
unimplemented!("LocalProvider does not support OIDC tokens")
186+
}
181187
}
182188

183189
fn extract_provider_owner_and_repository_from_remote_url(

src/run/run_environment/provider.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use async_trait::async_trait;
12
use git2::Repository;
23
use simplelog::SharedLogger;
34

@@ -18,6 +19,7 @@ pub trait RunEnvironmentDetector {
1819

1920
/// `RunEnvironmentProvider` is a trait that defines the necessary methods
2021
/// for a continuous integration provider.
22+
#[async_trait(?Send)]
2123
pub trait RunEnvironmentProvider {
2224
/// Returns the logger for the RunEnvironment.
2325
fn get_logger(&self) -> Box<dyn SharedLogger>;
@@ -34,6 +36,9 @@ pub trait RunEnvironmentProvider {
3436
/// Return the metadata necessary to identify the `RunPart`
3537
fn get_run_provider_run_part(&self) -> Option<RunPart>;
3638

39+
/// Get an OIDC token for the current run environment, if supported.
40+
async fn get_oidc_token(&self) -> Option<String>;
41+
3742
/// Returns the metadata necessary for uploading results to CodSpeed.
3843
///
3944
/// # Arguments

0 commit comments

Comments
 (0)