diff options
author | Divy Srivastava <dj.srivastava23@gmail.com> | 2024-02-28 07:58:02 +0530 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-02-28 07:58:02 +0530 |
commit | 9b5d2f8c1bae498d78400c8e9263bcae6e521adf (patch) | |
tree | 69453f9be9fc65774f3087bb986409aadee5acb4 /tests | |
parent | e9fe71acb53c8856754ef892c463253cb96087ce (diff) |
feat(publish): provenance attestation (#22573)
Supply chain security for JSR.
```
$ deno publish --provenance
Successfully published @divy/test_provenance@0.0.3
Provenance transparency log available at https://search.sigstore.dev/?logIndex=73657418
```
0. Package has been published.
1. Fetches the version manifest and verifies it's matching with uploaded
files and exports.
2. Builds the attestation SLSA payload using Github actions env.
3. Creates an ephemeral key pair for signing the github token
(aud=sigstore) and DSSE pre authentication tag.
4. Requests a X.509 signing certificate from Fulcio using the challenge
and ephemeral public key PEM.
5. Prepares a DSSE envelop for Rekor to witness. Posts an intoto entry
to Rekor and gets back the transparency log index.
6. Builds the provenance bundle and posts it to JSR.
Diffstat (limited to 'tests')
-rw-r--r-- | tests/integration/publish_tests.rs | 9 | ||||
-rw-r--r-- | tests/testdata/publish/successful_provenance.out | 7 | ||||
-rw-r--r-- | tests/util/server/src/lib.rs | 56 | ||||
-rw-r--r-- | tests/util/server/src/servers/mod.rs | 4 | ||||
-rw-r--r-- | tests/util/server/src/servers/registry.rs | 73 |
5 files changed, 149 insertions, 0 deletions
diff --git a/tests/integration/publish_tests.rs b/tests/integration/publish_tests.rs index ae7a332c4..fafc018f9 100644 --- a/tests/integration/publish_tests.rs +++ b/tests/integration/publish_tests.rs @@ -4,6 +4,7 @@ use deno_core::serde_json::json; use test_util::assert_contains; use test_util::assert_not_contains; use test_util::env_vars_for_jsr_npm_tests; +use test_util::env_vars_for_jsr_provenance_tests; use test_util::env_vars_for_jsr_tests; use test_util::env_vars_for_npm_tests; use test_util::itest; @@ -164,6 +165,14 @@ itest!(successful { http_server: true, }); +itest!(provenance { + args: "publish --provenance", + output: "publish/successful_provenance.out", + cwd: Some("publish/successful"), + envs: env_vars_for_jsr_provenance_tests(), + http_server: true, +}); + itest!(no_check { args: "publish --token 'sadfasdf' --no-check", // still type checks the slow types output though diff --git a/tests/testdata/publish/successful_provenance.out b/tests/testdata/publish/successful_provenance.out new file mode 100644 index 000000000..512efcff0 --- /dev/null +++ b/tests/testdata/publish/successful_provenance.out @@ -0,0 +1,7 @@ +Check file:///[WILDCARD]/publish/successful/mod.ts +Checking for slow types in the public API... +Check file:///[WILDCARD]/publish/successful/mod.ts +Publishing @foo/bar@1.0.0 ... +Successfully published @foo/bar@1.0.0 +Provenance transparency log available at https://search.sigstore.dev/?logIndex=42069 +Visit http://127.0.0.1:4250/@foo/bar@1.0.0 for details diff --git a/tests/util/server/src/lib.rs b/tests/util/server/src/lib.rs index 9a0323433..c65526ca3 100644 --- a/tests/util/server/src/lib.rs +++ b/tests/util/server/src/lib.rs @@ -64,6 +64,50 @@ pub fn env_vars_for_jsr_tests() -> Vec<(String, String)> { ] } +pub fn env_vars_for_jsr_provenance_tests() -> Vec<(String, String)> { + let mut envs = env_vars_for_jsr_tests(); + envs.extend(vec![ + ("REKOR_URL".to_string(), rekor_url()), + ("FULCIO_URL".to_string(), fulcio_url()), + ( + "DISABLE_JSR_MANIFEST_VERIFICATION_FOR_TESTING".to_string(), + "true".to_string(), + ), + ]); + // set GHA variable for attestation. + envs.extend([ + ("CI".to_string(), "true".to_string()), + ("GITHUB_ACTIONS".to_string(), "true".to_string()), + ("ACTIONS_ID_TOKEN_REQUEST_URL".to_string(), gha_token_url()), + ( + "ACTIONS_ID_TOKEN_REQUEST_TOKEN".to_string(), + "dummy".to_string(), + ), + ( + "GITHUB_REPOSITORY".to_string(), + "littledivy/deno_sdl2".to_string(), + ), + ( + "GITHUB_SERVER_URL".to_string(), + "https://github.com".to_string(), + ), + ("GITHUB_REF".to_string(), "refs/tags/sdl2@0.0.1".to_string()), + ("GITHUB_SHA".to_string(), "lol".to_string()), + ("GITHUB_RUN_ID".to_string(), "1".to_string()), + ("GITHUB_RUN_ATTEMPT".to_string(), "1".to_string()), + ( + "RUNNER_ENVIRONMENT".to_string(), + "github-hosted".to_string(), + ), + ( + "GITHUB_WORKFLOW_REF".to_string(), + "littledivy/deno_sdl2@refs/tags/sdl2@0.0.1".to_string(), + ), + ]); + + envs +} + pub fn env_vars_for_jsr_npm_tests() -> Vec<(String, String)> { vec![ ("NPM_CONFIG_REGISTRY".to_string(), npm_registry_url()), @@ -125,6 +169,18 @@ pub fn jsr_registry_url() -> String { "http://127.0.0.1:4250/".to_string() } +pub fn rekor_url() -> String { + "http://127.0.0.1:4251".to_string() +} + +pub fn fulcio_url() -> String { + "http://127.0.0.1:4251".to_string() +} + +pub fn gha_token_url() -> String { + "http://127.0.0.1:4251/gha_oidc?test=true".to_string() +} + pub fn jsr_registry_unset_url() -> String { "http://JSR_URL.is.unset".to_string() } diff --git a/tests/util/server/src/servers/mod.rs b/tests/util/server/src/servers/mod.rs index f828f1bd4..b57e9dd25 100644 --- a/tests/util/server/src/servers/mod.rs +++ b/tests/util/server/src/servers/mod.rs @@ -84,6 +84,7 @@ const WS_PING_PORT: u16 = 4245; const H2_GRPC_PORT: u16 = 4246; const H2S_GRPC_PORT: u16 = 4247; const REGISTRY_SERVER_PORT: u16 = 4250; +const PROVENANCE_MOCK_SERVER_PORT: u16 = 4251; // Use the single-threaded scheduler. The hyper server is used as a point of // comparison for the (single-threaded!) benchmarks in cli/bench. We're not @@ -127,6 +128,8 @@ pub async fn run_all_servers() { let h2_grpc_server_fut = grpc::h2_grpc_server(H2_GRPC_PORT, H2S_GRPC_PORT); let registry_server_fut = registry::registry_server(REGISTRY_SERVER_PORT); + let provenance_mock_server_fut = + registry::provenance_mock_server(PROVENANCE_MOCK_SERVER_PORT); let server_fut = async { futures::join!( @@ -154,6 +157,7 @@ pub async fn run_all_servers() { h2_only_server_fut, h2_grpc_server_fut, registry_server_fut, + provenance_mock_server_fut, ) } .boxed_local(); diff --git a/tests/util/server/src/servers/registry.rs b/tests/util/server/src/servers/registry.rs index 0efe06217..1a0caff1f 100644 --- a/tests/util/server/src/servers/registry.rs +++ b/tests/util/server/src/servers/registry.rs @@ -5,6 +5,8 @@ use crate::testdata_path; use super::run_server; use super::ServerKind; use super::ServerOptions; +use base64::engine::general_purpose::STANDARD_NO_PAD; +use base64::Engine as _; use bytes::Bytes; use http_body_util::combinators::UnsyncBoxBody; use http_body_util::Empty; @@ -36,6 +38,77 @@ pub async fn registry_server(port: u16) { .await } +pub async fn provenance_mock_server(port: u16) { + let addr = SocketAddr::from(([127, 0, 0, 1], port)); + + run_server( + ServerOptions { + addr, + error_msg: "Provenance mock server error", + kind: ServerKind::Auto, + }, + provenance_mock_server_handler, + ) + .await +} + +async fn provenance_mock_server_handler( + req: Request<Incoming>, +) -> Result<Response<UnsyncBoxBody<Bytes, Infallible>>, anyhow::Error> { + let path = req.uri().path(); + + // OIDC request + if path.starts_with("/gha_oidc") { + let jwt_claim = json!({ + "sub": "divy", + "email": "divy@deno.com", + "iss": "https://github.com", + }); + let token = format!( + "AAA.{}.", + STANDARD_NO_PAD.encode(serde_json::to_string(&jwt_claim).unwrap()) + ); + let body = serde_json::to_string_pretty(&json!({ + "value": token, + })); + let res = Response::new(UnsyncBoxBody::new(Full::from(body.unwrap()))); + return Ok(res); + } + + // Fulcio + if path.starts_with("/api/v2/signingCert") { + let body = serde_json::to_string_pretty(&json!({ + "signedCertificateEmbeddedSct": { + "chain": { + "certificates": [ + "fake_certificate" + ] + } + } + })); + let res = Response::new(UnsyncBoxBody::new(Full::from(body.unwrap()))); + return Ok(res); + } + + // Rekor + if path.starts_with("/api/v1/log/entries") { + let body = serde_json::to_string_pretty(&json!({ + "transparency_log_1": { + "logID": "test_log_id", + "logIndex": 42069, + } + })); + let res = Response::new(UnsyncBoxBody::new(Full::from(body.unwrap()))); + return Ok(res); + } + + let empty_body = UnsyncBoxBody::new(Empty::new()); + let res = Response::builder() + .status(StatusCode::NOT_FOUND) + .body(empty_body)?; + Ok(res) +} + async fn registry_server_handler( req: Request<Incoming>, ) -> Result<Response<UnsyncBoxBody<Bytes, Infallible>>, anyhow::Error> { |