diff --git a/Cargo.lock b/Cargo.lock index 9213828..5052266 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "aho-corasick" version = "1.1.4" @@ -11,6 +17,20 @@ dependencies = [ "memchr", ] +[[package]] +name = "ambient-id" +version = "0.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8cad022ed72ad2176498be1c097bb46e598193e92f3491ea0766980edeee168" +dependencies = [ + "reqwest", + "reqwest-middleware", + "secrecy", + "serde", + "serde_json", + "thiserror 2.0.18", +] + [[package]] name = "android_system_properties" version = "0.1.5" @@ -20,6 +40,25 @@ dependencies = [ "libc", ] +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "async-compression" +version = "0.4.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98ec5f6c2f8bc326c994cb9e241cc257ddaba9afa8555a43cffbb5dd86efaa37" +dependencies = [ + "compression-codecs", + "compression-core", + "futures-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "async-recursion" version = "1.1.1" @@ -197,12 +236,39 @@ dependencies = [ "x509-cert", ] +[[package]] +name = "compression-codecs" +version = "0.4.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0f7ac3e5b97fdce45e8922fb05cae2c37f7bbd63d30dd94821dacfd8f3f2bf2" +dependencies = [ + "compression-core", + "flate2", + "memchr", +] + +[[package]] +name = "compression-core" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" + [[package]] name = "const-oid" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -218,6 +284,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + [[package]] name = "crmf" version = "0.2.0" @@ -318,6 +393,12 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + [[package]] name = "errno" version = "0.3.14" @@ -325,7 +406,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -346,6 +427,22 @@ version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7ac824320a75a52197e8f2d787f6a38b6718bb6897a35142d749af3c0e8f4fe" +[[package]] +name = "flate2" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -506,6 +603,31 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "h2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + [[package]] name = "heck" version = "0.5.0" @@ -567,6 +689,7 @@ dependencies = [ "bytes", "futures-channel", "futures-core", + "h2", "http", "http-body", "httparse", @@ -588,6 +711,7 @@ dependencies = [ "hyper", "hyper-util", "rustls", + "rustls-native-certs", "rustls-pki-types", "tokio", "tokio-rustls", @@ -745,6 +869,16 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "ipnet" version = "2.11.0" @@ -842,6 +976,16 @@ version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "mio" version = "1.1.0" @@ -879,6 +1023,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "openssl-probe" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f50d9b3dabb09ecd771ad0aa242ca6894994c130308ca3d7684634df8037391" + [[package]] name = "option-ext" version = "0.2.0" @@ -1006,7 +1156,7 @@ dependencies = [ "rustc-hash", "rustls", "socket2", - "thiserror", + "thiserror 2.0.18", "tokio", "tracing", "web-time", @@ -1027,7 +1177,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror", + "thiserror 2.0.18", "tinyvec", "tracing", "web-time", @@ -1114,7 +1264,7 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.16", "libredox", - "thiserror", + "thiserror 2.0.18", ] [[package]] @@ -1162,6 +1312,7 @@ dependencies = [ "bytes", "futures-core", "futures-util", + "h2", "http", "http-body", "http-body-util", @@ -1174,6 +1325,7 @@ dependencies = [ "pin-project-lite", "quinn", "rustls", + "rustls-native-certs", "rustls-pki-types", "serde", "serde_json", @@ -1193,6 +1345,21 @@ dependencies = [ "webpki-roots", ] +[[package]] +name = "reqwest-middleware" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57f17d28a6e6acfe1733fe24bcd30774d13bffa4b8a22535b4c8c98423088d4e" +dependencies = [ + "anyhow", + "async-trait", + "http", + "reqwest", + "serde", + "thiserror 1.0.69", + "tower-service", +] + [[package]] name = "ring" version = "0.17.14" @@ -1258,7 +1425,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -1277,6 +1444,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pki-types" version = "1.14.0" @@ -1326,12 +1505,53 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "secrecy" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" +dependencies = [ + "zeroize", +] + +[[package]] +name = "security-framework" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.27" @@ -1472,7 +1692,7 @@ dependencies = [ "sigstore-rekor", "sigstore-tsa", "sigstore-types", - "thiserror", + "thiserror 2.0.18", ] [[package]] @@ -1483,7 +1703,7 @@ dependencies = [ "directories", "serde", "serde_json", - "thiserror", + "thiserror 2.0.18", "tokio", ] @@ -1524,7 +1744,7 @@ dependencies = [ "signature", "sigstore-types", "spki", - "thiserror", + "thiserror 2.0.18", "tracing", "x509-cert", ] @@ -1542,7 +1762,7 @@ dependencies = [ "sigstore-crypto", "sigstore-oidc", "sigstore-types", - "thiserror", + "thiserror 2.0.18", "tokio", "url", ] @@ -1558,13 +1778,14 @@ dependencies = [ "serde_json", "sigstore-crypto", "sigstore-types", - "thiserror", + "thiserror 2.0.18", ] [[package]] name = "sigstore-oidc" version = "0.6.0" dependencies = [ + "ambient-id", "base64", "rand", "reqwest", @@ -1572,7 +1793,7 @@ dependencies = [ "serde_json", "sigstore-crypto", "sigstore-types", - "thiserror", + "thiserror 2.0.18", "tokio", "url", ] @@ -1590,7 +1811,7 @@ dependencies = [ "sigstore-crypto", "sigstore-merkle", "sigstore-types", - "thiserror", + "thiserror 2.0.18", "tokio", "url", ] @@ -1612,7 +1833,7 @@ dependencies = [ "sigstore-trust-root", "sigstore-tsa", "sigstore-types", - "thiserror", + "thiserror 2.0.18", "tokio", "x509-cert", ] @@ -1631,7 +1852,7 @@ dependencies = [ "serde_json", "sigstore-crypto", "sigstore-types", - "thiserror", + "thiserror 2.0.18", "tokio", "tough", "tracing", @@ -1658,7 +1879,7 @@ dependencies = [ "serde_json", "sigstore-crypto", "sigstore-types", - "thiserror", + "thiserror 2.0.18", "tokio", "tracing", "x509-cert", @@ -1675,7 +1896,7 @@ dependencies = [ "pem", "serde", "serde_json", - "thiserror", + "thiserror 2.0.18", ] [[package]] @@ -1702,13 +1923,19 @@ dependencies = [ "sigstore-trust-root", "sigstore-tsa", "sigstore-types", - "thiserror", + "thiserror 2.0.18", "tls_codec", "tokio", "tracing", "x509-cert", ] +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + [[package]] name = "slab" version = "0.4.11" @@ -1817,7 +2044,16 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.60.2", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", ] [[package]] @@ -1826,7 +2062,18 @@ version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -1993,13 +2240,18 @@ version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ + "async-compression", "bitflags", "bytes", + "futures-core", "futures-util", "http", "http-body", + "http-body-util", "iri-string", "pin-project-lite", + "tokio", + "tokio-util", "tower", "tower-layer", "tower-service", @@ -2257,7 +2509,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] diff --git a/crates/sigstore-oidc/Cargo.toml b/crates/sigstore-oidc/Cargo.toml index d4c0f06..e2f3468 100644 --- a/crates/sigstore-oidc/Cargo.toml +++ b/crates/sigstore-oidc/Cargo.toml @@ -15,6 +15,7 @@ serde_json = { workspace = true } reqwest = { workspace = true, features = ["rustls-tls"] } url = { workspace = true } tokio = { workspace = true } +ambient-id = "0.0.7" thiserror = { workspace = true } base64 = { workspace = true } rand = { workspace = true } diff --git a/crates/sigstore-oidc/src/ambient.rs b/crates/sigstore-oidc/src/ambient.rs deleted file mode 100644 index cb5dc12..0000000 --- a/crates/sigstore-oidc/src/ambient.rs +++ /dev/null @@ -1,121 +0,0 @@ -//! Ambient credential detection for CI/CD environments -//! -//! This module provides detection and retrieval of OIDC tokens from -//! various CI/CD environments like GitHub Actions, GitLab CI, etc. - -use crate::error::{Error, Result}; -use crate::token::IdentityToken; - -/// Detected CI/CD environment -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum CiEnvironment { - /// GitHub Actions - GitHubActions, - /// GitLab CI - GitLabCi, - /// Google Cloud Build - GoogleCloudBuild, - /// Buildkite - Buildkite, - /// CircleCI - CircleCi, -} - -/// Detect the current CI/CD environment -pub fn detect_environment() -> Option { - if std::env::var("GITHUB_ACTIONS").is_ok() { - Some(CiEnvironment::GitHubActions) - } else if std::env::var("GITLAB_CI").is_ok() { - Some(CiEnvironment::GitLabCi) - } else if std::env::var("BUILDER_OUTPUT").is_ok() { - Some(CiEnvironment::GoogleCloudBuild) - } else if std::env::var("BUILDKITE").is_ok() { - Some(CiEnvironment::Buildkite) - } else if std::env::var("CIRCLECI").is_ok() { - Some(CiEnvironment::CircleCi) - } else { - None - } -} - -/// Get an ambient identity token from the current environment -pub async fn get_ambient_token() -> Result { - match detect_environment() { - Some(CiEnvironment::GitHubActions) => get_github_actions_token().await, - Some(CiEnvironment::GitLabCi) => get_gitlab_ci_token().await, - Some(env) => Err(Error::Token(format!( - "ambient token retrieval not implemented for {:?}", - env - ))), - None => Err(Error::Token("no CI/CD environment detected".to_string())), - } -} - -/// Get OIDC token from GitHub Actions -async fn get_github_actions_token() -> Result { - // GitHub Actions provides OIDC tokens through the ACTIONS_ID_TOKEN_REQUEST_URL - let request_url = std::env::var("ACTIONS_ID_TOKEN_REQUEST_URL") - .map_err(|_| Error::Token("ACTIONS_ID_TOKEN_REQUEST_URL not set".to_string()))?; - - let request_token = std::env::var("ACTIONS_ID_TOKEN_REQUEST_TOKEN") - .map_err(|_| Error::Token("ACTIONS_ID_TOKEN_REQUEST_TOKEN not set".to_string()))?; - - // Request the token with sigstore audience - let url = format!("{}&audience=sigstore", request_url); - - let client = reqwest::Client::new(); - let response = client - .get(&url) - .header("Authorization", format!("bearer {}", request_token)) - .send() - .await - .map_err(|e| Error::Http(e.to_string()))?; - - if !response.status().is_success() { - return Err(Error::Token(format!( - "GitHub Actions returned status {}", - response.status() - ))); - } - - #[derive(serde::Deserialize)] - struct TokenResponse { - value: String, - } - - let token_response: TokenResponse = response - .json() - .await - .map_err(|e| Error::Token(format!("failed to parse token response: {}", e)))?; - - IdentityToken::from_jwt(&token_response.value) -} - -/// Get OIDC token from GitLab CI -async fn get_gitlab_ci_token() -> Result { - // GitLab CI provides the token in CI_JOB_JWT_V2 or CI_JOB_JWT - let token = std::env::var("CI_JOB_JWT_V2") - .or_else(|_| std::env::var("CI_JOB_JWT")) - .map_err(|_| Error::Token("CI_JOB_JWT_V2 or CI_JOB_JWT not set".to_string()))?; - - IdentityToken::from_jwt(&token) -} - -/// Check if we're running in a supported CI/CD environment -pub fn is_ci_environment() -> bool { - detect_environment().is_some() -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_detect_environment_none() { - // In a test environment without CI vars, should return None - // This test is environment-dependent - let env = detect_environment(); - // Just verify it doesn't panic - let _ = env; - } -} diff --git a/crates/sigstore-oidc/src/lib.rs b/crates/sigstore-oidc/src/lib.rs index 3ee4886..a60bc14 100644 --- a/crates/sigstore-oidc/src/lib.rs +++ b/crates/sigstore-oidc/src/lib.rs @@ -3,12 +3,10 @@ //! This crate handles identity token acquisition through various OIDC flows //! including interactive browser-based OAuth and ambient credential detection. -pub mod ambient; pub mod error; pub mod oauth; pub mod token; -pub use ambient::{detect_environment, get_ambient_token, is_ci_environment, CiEnvironment}; pub use error::{Error, Result}; pub use oauth::{get_identity_token, DeviceCodeResponse, OAuthClient, OAuthConfig}; pub use token::{issuers, Audience, FederatedClaims, IdentityToken, TokenClaims}; diff --git a/crates/sigstore-oidc/src/token.rs b/crates/sigstore-oidc/src/token.rs index 082df08..1d4f171 100644 --- a/crates/sigstore-oidc/src/token.rs +++ b/crates/sigstore-oidc/src/token.rs @@ -1,6 +1,7 @@ //! Identity token handling use crate::error::{Error, Result}; +use ambient_id::Detector; use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine}; use serde::{Deserialize, Serialize}; @@ -95,6 +96,21 @@ impl IdentityToken { }) } + /// Detect and retrieve an ambient identity token from the current environment + /// + /// This attempts to find OIDC credentials in environments like GitHub Actions, + /// GitLab CI, Buildkite, etc. using the `ambient-id` crate. + pub async fn detect_ambient() -> Result> { + match Detector::new().detect("sigstore").await { + Ok(Some(token)) => Self::from_jwt(token.reveal()).map(Some), + Ok(None) => Ok(None), + Err(e) => Err(Error::Token(format!( + "failed to detect ambient credentials: {}", + e + ))), + } + } + /// Create from raw token string without parsing pub fn new(token: impl Into) -> Self { let raw = token.into(); diff --git a/crates/sigstore-sign/examples/sign_attestation.rs b/crates/sigstore-sign/examples/sign_attestation.rs index 5a0ce00..f3de583 100644 --- a/crates/sigstore-sign/examples/sign_attestation.rs +++ b/crates/sigstore-sign/examples/sign_attestation.rs @@ -39,7 +39,7 @@ //! crates/sigstore-verify/test_data/bundles/signed-package-2.1.0-hb0f4dca_0.conda //! ``` -use sigstore_oidc::{get_ambient_token, get_identity_token, is_ci_environment, IdentityToken}; +use sigstore_oidc::{get_identity_token, IdentityToken}; use sigstore_sign::{Attestation, SigningConfig, SigningContext}; use std::env; @@ -245,11 +245,13 @@ async fn get_token(explicit_token: Option) -> Result) -> Result