Skip to content

Commit a14f449

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

6 files changed

Lines changed: 91 additions & 0 deletions

File tree

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: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::env;
22

3+
use async_trait::async_trait;
34
use simplelog::SharedLogger;
45

56
use crate::prelude::*;
@@ -119,6 +120,7 @@ impl RunEnvironmentDetector for BuildkiteProvider {
119120
}
120121
}
121122

123+
#[async_trait(?Send)]
122124
impl RunEnvironmentProvider for BuildkiteProvider {
123125
fn get_repository_provider(&self) -> RepositoryProvider {
124126
RepositoryProvider::GitHub
@@ -151,6 +153,17 @@ impl RunEnvironmentProvider for BuildkiteProvider {
151153
fn get_run_provider_run_part(&self) -> Option<RunPart> {
152154
None
153155
}
156+
157+
/// For now, we do not support OIDC tokens for Buildkite
158+
///
159+
/// If we want to in the future, we can implement it using the Buildkite Agent CLI.
160+
///
161+
/// Docs:
162+
/// - https://buildkite.com/docs/agent/v3/cli-oidc
163+
/// - https://buildkite.com/docs/pipelines/security/oidc
164+
async fn get_oidc_token(&self) -> Option<String> {
165+
None
166+
}
154167
}
155168

156169
#[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)