Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(publish): provenance attestation #22573

Merged
merged 26 commits into from
Feb 28, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ fancy-regex = "=0.10.0"
# If you disable the default __vendored_zlib_ng feature above, you _must_ be able to link against `-lz`.
flate2.workspace = true
fs3.workspace = true
p256.workspace = true
spki = { version = "0.7", features = ["pem"] }
glob = "0.3.1"
hex.workspace = true
ignore = "0.4"
Expand All @@ -126,6 +128,7 @@ pin-project.workspace = true
quick-junit = "^0.3.5"
rand = { workspace = true, features = ["small_rng"] }
regex.workspace = true
reqwest.workspace = true
ring.workspace = true
rustyline.workspace = true
rustyline-derive = "=0.7.0"
Expand Down
8 changes: 8 additions & 0 deletions cli/args/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ pub struct PublishFlags {
pub token: Option<String>,
pub dry_run: bool,
pub allow_slow_types: bool,
pub provenance: bool,
}

#[derive(Clone, Debug, Eq, PartialEq)]
Expand Down Expand Up @@ -2395,6 +2396,12 @@ fn publish_subcommand() -> Command {
.help("Allow publishing with slow types")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("provenance")
.long("provenance")
.help("From CI/CD system, publicly links the package to where it was built and published from.")
.action(ArgAction::SetTrue)
)
Comment on lines +2399 to +2404
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can also do this in a follow up, but can we enable provenance by default when we are using GH OIDC for auth?

And then add a --no-provenance option to force it off, and --provenance option to force it on.

We can then also use --provenance to do enable provenance for local publishes at some later date.

.arg(check_arg(/* type checks by default */ true))
.arg(no_check_arg())
})
Expand Down Expand Up @@ -3835,6 +3842,7 @@ fn publish_parse(flags: &mut Flags, matches: &mut ArgMatches) {
token: matches.remove_one("token"),
dry_run: matches.get_flag("dry-run"),
allow_slow_types: matches.get_flag("allow-slow-types"),
provenance: matches.get_flag("provenance"),
});
}

Expand Down
8 changes: 8 additions & 0 deletions cli/tools/registry/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ pub struct OidcConfig {
pub token: String,
}

pub(crate) fn is_gha() -> bool {
std::env::var("GITHUB_ACTIONS").unwrap_or_default() == "true"
}

pub(crate) fn gha_oidc_token() -> Option<String> {
std::env::var("ACTIONS_ID_TOKEN_REQUEST_TOKEN").ok()
}

fn get_gh_oidc_env_vars() -> Option<Result<(String, String), AnyError>> {
if std::env::var("GITHUB_ACTIONS").unwrap_or_default() == "true" {
let url = std::env::var("ACTIONS_ID_TOKEN_REQUEST_URL");
Expand Down
54 changes: 50 additions & 4 deletions cli/tools/registry/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ mod auth;
mod diagnostics;
mod graph;
mod paths;
mod provenance;
mod publish_order;
mod tar;

Expand Down Expand Up @@ -444,6 +445,7 @@ async fn perform_publish(
mut publish_order_graph: PublishOrderGraph,
mut prepared_package_by_name: HashMap<String, Rc<PreparedPublishPackage>>,
auth_method: AuthMethod,
provenance: bool,
) -> Result<(), AnyError> {
let client = http_client.client()?;
let registry_api_url = jsr_api_url().to_string();
Expand Down Expand Up @@ -504,6 +506,7 @@ async fn perform_publish(
&registry_api_url,
&registry_url,
&authorization,
provenance,
)
.await
.with_context(|| format!("Failed to publish {}", display_name))?;
Expand All @@ -530,6 +533,7 @@ async fn publish_package(
registry_api_url: &str,
registry_url: &str,
authorization: &str,
provenance: bool,
) -> Result<(), AnyError> {
let client = http_client.client()?;
println!(
Expand Down Expand Up @@ -635,6 +639,46 @@ async fn publish_package(
package.package,
package.version
);

if provenance {
// Get the version manifest from JSR
let meta_url = jsr_url().join(&format!(
"@{}/{}/{}_meta.json",
package.scope, package.package, package.version
))?;

let meta_bytes = client.get(meta_url).send().await?.bytes().await?;

// TODO: Verify manifest.

let subject = provenance::Subject {
name: format!("{}/{}", package.scope, package.package),
littledivy marked this conversation as resolved.
Show resolved Hide resolved
digest: provenance::SubjectDigest {
sha512: hex::encode(sha2::Sha512::digest(&meta_bytes)),
littledivy marked this conversation as resolved.
Show resolved Hide resolved
},
};
let transparency_log = provenance::generate_provenance(subject).await?;

println!("{}",
colors::green(format!(
"Provenance transparency log available at https://search.sigstore.dev/?logIndex={}",
transparency_log.rekor_log_index
))
);

// Submit transparency log ID to JSR
let provenance_url = format!(
"{}scopes/{}/packages/{}/versions/{}/provenance",
registry_api_url, package.scope, package.package, package.version
);
client
.post(provenance_url)
.header(reqwest::header::AUTHORIZATION, authorization)
.json(&json!({ "rekorlogId": transparency_log.rekor_log_index }))
.send()
.await?;
}

println!(
"{}",
colors::gray(format!(
Expand Down Expand Up @@ -807,13 +851,12 @@ pub async fn publish(
Arc::new(ImportMap::new(Url::parse("file:///dev/null").unwrap()))
});

let directory_path = cli_factory.cli_options().initial_cwd();

let mapped_resolver = Arc::new(MappedSpecifierResolver::new(
Some(import_map),
cli_factory.package_json_deps_provider().clone(),
));

let directory_path = cli_factory.cli_options().initial_cwd();

let cli_options = cli_factory.cli_options();
let Some(config_file) = cli_options.maybe_config_file() else {
bail!(
Expand Down Expand Up @@ -859,6 +902,9 @@ pub async fn publish(
prepared_data.publish_order_graph,
prepared_data.package_by_name,
auth_method,
publish_flags.provenance,
)
.await
.await?;

Ok(())
}