Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/trust-root-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,13 @@ jobs:
./rhtas/tuf-repo-init.sh --export-keys file:///tmp/exported-keys \
--fulcio-cert ./rhtas/test/fulcio-cert \
--fulcio-uri "https://fulcio.rhtas" \
--oidc-uri "https://oauth2.rhtas/auth" \
--tsa-cert ./rhtas/test/tsa-chain \
--tsa-uri "https://tsa.rhtas" \
--ctlog-key ./rhtas/test/ctfe-pubkey \
--fulcio-uri "https://ctlog.rhtas" \
--ctlog-uri "https://ctlog.rhtas" \
--rekor-key ./rhtas/test/rekor-pubkey \
--fulcio-uri "https://rekor.rhtas" \
--rekor-uri "https://rekor.rhtas" \
/tmp/testrepo
- run: curl -O -L "https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64" && mv cosign-linux-amd64 ${HOME}/.local/bin/cosign && sudo chmod +x ${HOME}/.local/bin/cosign
- run: cosign -d initialize --mirror=file:///tmp/testrepo --root=/tmp/testrepo/root.json
Expand Down
10 changes: 10 additions & 0 deletions rhtas/tuf-repo-init.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ Options:
--fulcio-uri
Fulcio base URI

--oidc-uri
OIDC provider URI (used with Fulcio for authentication)

--tsa-cert
TSA certificate chain file

Expand Down Expand Up @@ -51,6 +54,7 @@ export TSA_CERT=""
export CTLOG_KEY=""
export REKOR_KEY=""
export FULCIO_URI=""
export OIDC_URI=""
export TSA_URI=""
export CTLOG_URI=""
export REKOR_URI=""
Expand Down Expand Up @@ -78,6 +82,11 @@ while [[ $# -gt 0 ]]; do
shift
shift
;;
--oidc-uri)
OIDC_URI="$2"
shift
shift
;;
--tsa-cert)
TSA_CERT="$2"
shift
Expand Down Expand Up @@ -206,6 +215,7 @@ if [ -n "${FULCIO_CERT}" ]; then
--key "${KEYDIR}/timestamp.pem" \
--set-fulcio-target "${FULCIO_CERT}" \
--fulcio-uri "${FULCIO_URI}" \
--oidc-uri "${OIDC_URI}" \
--targets-expires "${METADATA_EXPIRATION}" \
--targets-version 1 \
--snapshot-expires "${METADATA_EXPIRATION}" \
Expand Down
30 changes: 28 additions & 2 deletions tuftool/src/rhtas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ pub(crate) struct RhtasArgs {
#[arg(long)]
fulcio_uri: Option<String>,

/// URI for the OIDC provider (used with Fulcio).
/// Example: <https://oauth2.sigstore.dev/auth>
#[arg(long)]
oidc_uri: Option<String>,

/// Path to the new Ctlog target file
#[arg(long = "set-ctlog-target")]
ctlog_target: Option<PathBuf>,
Expand Down Expand Up @@ -677,6 +682,11 @@ impl RhtasArgs {
start = None;
}

let valid_for = Some(TimeRange {
start: start.clone(),
end: end.clone(),
});

let mut certificates: Vec<X509Certificate> = Vec::new();
for item in certificate_raw_bytes_vec {
certificates.push(X509Certificate { raw_bytes: item });
Expand All @@ -689,7 +699,7 @@ impl RhtasArgs {
}),
uri: self.fulcio_uri.clone().unwrap(),
cert_chain: Some(X509CertificateChain { certificates }),
valid_for: Some(TimeRange { start, end }),
valid_for: valid_for.clone(),
operator: String::new(),
};

Expand All @@ -701,6 +711,16 @@ impl RhtasArgs {
eprintln!("Failed to set target: {e:?} in trust_bundle");
}
}

if let Some(ref oidc_uri) = self.oidc_uri {
if let Err(e) = trust_bundle.add_oidc_url_to_signing_config(
oidc_uri.clone(),
valid_for,
"sigstore.dev".to_string(),
) {
eprintln!("Failed to add OIDC URL to signing_config: {e:?}");
}
}
}
Ok(())
}
Expand Down Expand Up @@ -1089,14 +1109,15 @@ impl RhtasArgs {
|| self.tsa_status.is_some())
{
return error::InvalidArgumentCombinationSnafu {
msg: "--set-fulcio-target only accepts --fulcio-uri and --fulcio-status."
msg: "--set-fulcio-target only accepts --fulcio-uri, --fulcio-status, and --oidc-uri."
.to_string(),
}
.fail();
}

if self.ctlog_target.is_some()
&& (self.fulcio_uri.is_some()
|| self.oidc_uri.is_some()
|| self.rekor_uri.is_some()
|| self.tsa_uri.is_some()
|| self.fulcio_status.is_some()
Expand All @@ -1111,6 +1132,7 @@ impl RhtasArgs {

if self.rekor_target.is_some()
&& (self.fulcio_uri.is_some()
|| self.oidc_uri.is_some()
|| self.ctlog_uri.is_some()
|| self.tsa_uri.is_some()
|| self.fulcio_status.is_some()
Expand All @@ -1125,6 +1147,7 @@ impl RhtasArgs {

if self.tsa_target.is_some()
&& (self.fulcio_uri.is_some()
|| self.oidc_uri.is_some()
|| self.ctlog_uri.is_some()
|| self.rekor_uri.is_some()
|| self.fulcio_status.is_some()
Expand All @@ -1144,6 +1167,9 @@ impl RhtasArgs {
if self.fulcio_status.is_none() {
self.fulcio_status = Some(String::from("Active"));
}
if self.oidc_uri.is_none() {
self.oidc_uri = Some(String::from("https://oauth2.sigstore.dev/auth"));
}
}
if self.ctlog_target.is_some() {
if self.ctlog_uri.is_none() {
Expand Down
33 changes: 33 additions & 0 deletions tuftool/src/sigstore_trust/trust/sigstore/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,38 @@ impl SigstoreTrustBundle {
std::fs::write(file_path, json).map_err(SigstoreError::from)
}

/// Add an OIDC URL to the `SigningConfig` (used with Fulcio).
/// The `SigningConfig` must already exist (it is created when adding the Fulcio CA target).
pub fn add_oidc_url_to_signing_config(
&mut self,
url: String,
valid_for: Option<TimeRange>,
operator: String,
) -> Result<()> {
let Some(signing_config) = &mut self.signing_config else {
return Err(SigstoreError::UnexpectedError(
"Cannot add OIDC URL: signing_config does not exist (add Fulcio target first)"
.to_string(),
));
};

let operator = if operator.is_empty() {
"sigstore.dev".to_string()
} else {
operator
};
signing_config
.oidc_urls
.retain(|service| service.url != url);
signing_config.oidc_urls.push(Service {
url,
major_api_version: 1,
valid_for,
operator,
});
Ok(())
}

/// Save the signing config to a file
pub fn save_signing_config_to_file(&self, file_path: &Path) -> Result<()> {
if let Some(signing_config) = &self.signing_config {
Expand Down Expand Up @@ -712,6 +744,7 @@ impl SigstoreTrustBundle {
match target_type {
Target::CertificateAuthority => {
signing_config.ca_urls.retain(|svc| svc.url != uri);
signing_config.oidc_urls.retain(|svc| svc.url != uri);
}
Target::Tlog => {
signing_config.rekor_tlog_urls.retain(|svc| svc.url != uri);
Expand Down
102 changes: 102 additions & 0 deletions tuftool/tests/rhtas_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,27 @@ async fn rhtas_command_argument_validation() {
])
.assert()
.failure();

// oidc-uri with ctlog-target should also fail (oidc is only valid with fulcio-target)
Command::cargo_bin("tuftool")
.unwrap()
.args([
"rhtas",
"-o",
repo_dir.to_str().unwrap(),
"-k",
root_key.to_str().unwrap(),
"--root",
root_json.to_str().unwrap(),
"--set-ctlog-target",
new_targets_input_dir.to_str().unwrap(),
"--oidc-uri",
"https://oauth2.sigstore.dev/auth",
"--metadata-url",
metadata_base_url.as_str(),
])
.assert()
.failure();
}

#[tokio::test]
Expand Down Expand Up @@ -480,3 +501,84 @@ async fn rhtas_command_force_metadata_version() {
assert_eq!(repo.timestamp().signed.expires, new_timestamp_expiration);
assert_eq!(repo.timestamp().signed.version.get(), new_timestamp_version);
}

#[tokio::test]
#[serial]
async fn rhtas_command_fulcio_oidc_signing_config() {
let root_json = test_utils::test_data().join("simple-rsa").join("root.json");
let root_key = test_utils::test_data().join("snakeoil.pem");
let repo_dir = test_utils::test_data().join("rhtas_tmp");

let _cleanup = TestRepoCleanup::new(repo_dir.clone());

create_repo(repo_dir.clone());

let mut fulcio_cert = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
fulcio_cert.pop();
fulcio_cert.push("rhtas/test/fulcio-cert");
let metadata_base_url = &dir_url(&repo_dir);

// Add Fulcio target with OIDC URI - this should populate signing_config with oidcUrls
Command::cargo_bin("tuftool")
.unwrap()
.args([
"rhtas",
"-o",
repo_dir.to_str().unwrap(),
"-k",
root_key.to_str().unwrap(),
"--root",
root_json.to_str().unwrap(),
"--set-fulcio-target",
fulcio_cert.to_str().unwrap(),
"--fulcio-uri",
"https://fulcio.test.example",
"--oidc-uri",
"https://oauth2.test.example/auth",
"--metadata-url",
metadata_base_url.as_str(),
])
.assert()
.success();

// Load the repo and verify signing_config contains oidcUrls
let repo = RepositoryLoader::new(
&tokio::fs::read(root_json.clone()).await.unwrap(),
dir_url(&repo_dir),
dir_url(repo_dir.join("targets")),
)
.load()
.await
.unwrap();

// Find signing_config target (may be hashed or direct)
let signing_config_name = repo
.targets()
.signed
.targets
.keys()
.find(|k| k.raw().contains("signing_config"))
.expect("signing_config target should exist");

let signing_config_data = test_utils::read_to_end(
repo.read_target(signing_config_name)
.await
.unwrap()
.unwrap(),
)
.await;
let signing_config: serde_json::Value =
serde_json::from_slice(&signing_config_data).expect("signing_config should be valid JSON");

let oidc_urls = signing_config
.get("oidcUrls")
.expect("signing_config should contain oidcUrls");
let oidc_urls = oidc_urls.as_array().expect("oidcUrls should be an array");
assert!(!oidc_urls.is_empty(), "oidcUrls should not be empty");
let first_oidc = &oidc_urls[0];
assert_eq!(
first_oidc.get("url").and_then(|v| v.as_str()),
Some("https://oauth2.test.example/auth"),
"OIDC URL should match"
);
}