diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ad69fa70..907fd20d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -119,6 +119,14 @@ jobs: fetch-depth: 0 submodules: false + - name: Cache Cargo registry and build artifacts + uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 + + - name: Install cargo-deny and just + uses: taiki-e/install-action@25435dc8dd3baed7417e0c96d3fe89013a5b2e09 # v2.81.3 + with: + tool: cargo-deny@0.19.8,just@1.51.0 + - name: Cargo metadata run: cargo metadata --locked --format-version 1 >/tmp/registry-stack-cargo-metadata.json @@ -128,8 +136,22 @@ jobs: - name: Workspace check run: cargo check --locked --workspace --all-targets - - name: Focused tests - run: cargo test --locked -p registryctl + - name: Clippy + run: cargo clippy --workspace --all-targets -- -D warnings + + - name: Workspace tests + run: cargo test --locked --workspace + + - name: Cargo deny + run: cargo deny check + + - name: Notary OpenAPI baseline + working-directory: products/notary + run: just openapi-check + + - name: Relay OpenAPI contract + working-directory: crates/registry-relay + run: just openapi-contract release-tool: name: Release tooling diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c7f79ed3..5d2a1ac7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,6 +14,10 @@ on: permissions: contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + env: CARGO_TERM_COLOR: always REGISTRY: ghcr.io diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..9309c0f1 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,9 @@ +[submodule "lab/vendor/crosswalk"] + path = lab/vendor/crosswalk + url = git@github.com:PublicSchema/crosswalk.git +[submodule "lab/vendor/registry-atlas"] + path = lab/vendor/registry-atlas + url = git@github.com:jeremi/registry-atlas.git +[submodule "lab/vendor/esignet-relay-authenticator"] + path = lab/vendor/esignet-relay-authenticator + url = git@github.com:jeremi/esignet-relay-authenticator.git diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fe5415c5..e1dac84b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -111,9 +111,17 @@ Rust workspace: cargo metadata --locked --format-version 1 cargo fmt --check cargo check --locked --workspace --all-targets -cargo test --locked -p registryctl +cargo clippy --workspace --all-targets -- -D warnings +cargo test --locked --workspace +cargo deny check bans licenses sources +(cd products/notary && just openapi-check) +(cd crates/registry-relay && just openapi-contract) ``` +The cargo-deny advisories section is not yet in the root gate (open RUSTSEC +advisories on quick-xml have no upstream fix yet); run +`cargo deny check advisories` locally. + Release and lab source checks: ```bash diff --git a/Cargo.lock b/Cargo.lock index cb3b277e..e8431a3f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -157,9 +157,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.102" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" [[package]] name = "approx" diff --git a/README.md b/README.md index 0a3418d8..6565db45 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,6 @@ flowchart LR source --> relay manifest --> relay - manifest --> notary relay --> caller relay --> notary notary --> caller diff --git a/crates/registry-config-report/Cargo.toml b/crates/registry-config-report/Cargo.toml index 3a7a9f7f..bf0b9292 100644 --- a/crates/registry-config-report/Cargo.toml +++ b/crates/registry-config-report/Cargo.toml @@ -7,6 +7,9 @@ readme = "README.md" repository.workspace = true publish = false +[lints] +workspace = true + [dependencies] serde.workspace = true serde_json.workspace = true diff --git a/crates/registry-manifest-cli/Cargo.toml b/crates/registry-manifest-cli/Cargo.toml index f8bdba76..7249faeb 100644 --- a/crates/registry-manifest-cli/Cargo.toml +++ b/crates/registry-manifest-cli/Cargo.toml @@ -10,6 +10,8 @@ keywords = ["registry", "metadata", "manifest", "cli", "publish"] categories = ["command-line-utilities", "encoding"] publish = false +# not opted into [workspace.lints]: uses unsafe (libyaml FFI in src/main.rs) + [[bin]] name = "registry-manifest" path = "src/main.rs" diff --git a/crates/registry-manifest-core/Cargo.toml b/crates/registry-manifest-core/Cargo.toml index ca2e7fba..1988c108 100644 --- a/crates/registry-manifest-core/Cargo.toml +++ b/crates/registry-manifest-core/Cargo.toml @@ -10,6 +10,9 @@ keywords = ["registry", "metadata", "manifest", "dcat", "shacl"] categories = ["data-structures", "encoding", "parser-implementations"] publish = false +[lints] +workspace = true + [dependencies] oxiri = { version = "0.2.11" } registry-platform-pdp = { workspace = true } diff --git a/crates/registry-notary-client/Cargo.toml b/crates/registry-notary-client/Cargo.toml index 1a693552..8c03a967 100644 --- a/crates/registry-notary-client/Cargo.toml +++ b/crates/registry-notary-client/Cargo.toml @@ -8,6 +8,9 @@ readme = "README.md" repository.workspace = true publish = false +[lints] +workspace = true + [features] default = ["rustls"] rustls = [] diff --git a/crates/registry-notary-core/Cargo.toml b/crates/registry-notary-core/Cargo.toml index 0ea31752..d3d0876d 100644 --- a/crates/registry-notary-core/Cargo.toml +++ b/crates/registry-notary-core/Cargo.toml @@ -8,6 +8,9 @@ readme = "README.md" repository.workspace = true publish = false +[lints] +workspace = true + [dependencies] base64.workspace = true humantime-serde.workspace = true diff --git a/crates/registry-notary-core/src/config.rs b/crates/registry-notary-core/src/config.rs index 337c43ef..e51db6c6 100644 --- a/crates/registry-notary-core/src/config.rs +++ b/crates/registry-notary-core/src/config.rs @@ -631,7 +631,7 @@ impl StandaloneRegistryNotaryConfig { config_unsigned: self .config_trust .as_ref() - .map_or(true, |trust| trust.accepted_roots.is_empty()), + .is_none_or(|trust| trust.accepted_roots.is_empty()), self_attestation_enabled: self.self_attestation.enabled, transaction_token_anchor_configured: self.auth.access_token_signing.enabled, // DPoP/mTLS proof validation for transaction tokens is not yet diff --git a/crates/registry-notary-server/Cargo.toml b/crates/registry-notary-server/Cargo.toml index 258ab206..953763f7 100644 --- a/crates/registry-notary-server/Cargo.toml +++ b/crates/registry-notary-server/Cargo.toml @@ -8,6 +8,9 @@ readme = "README.md" repository.workspace = true publish = false +# not opted into [workspace.lints]: unsafe std::env::set_var/remove_var in +# #[cfg(test)] code (src/standalone.rs, tests/) + [features] default = [] registry-notary-cel = ["dep:crosswalk-core", "dep:registry-notary-worker-harness"] diff --git a/crates/registry-notary-server/src/api.rs b/crates/registry-notary-server/src/api.rs index b5a6fc2a..6ac84e27 100644 --- a/crates/registry-notary-server/src/api.rs +++ b/crates/registry-notary-server/src/api.rs @@ -5239,20 +5239,20 @@ async fn oid4vci_token( return token_error_with_audit( &preauth, path, - Some(&configuration_id), + Some(configuration_id), SelfAttestationDenialCode::OperationDenied, TokenWireError::ServerError, ) .await; } }; - let c_nonce = match issue_c_nonce(&state, &configuration_id).await { + let c_nonce = match issue_c_nonce(&state, configuration_id).await { Some(c_nonce) => c_nonce, None => { return token_error_with_audit( &preauth, path, - Some(&configuration_id), + Some(configuration_id), SelfAttestationDenialCode::OperationDenied, TokenWireError::ServerError, ) @@ -8087,6 +8087,7 @@ fn require_self_attestation_evaluate_with_runtime_config( Ok(()) } +#[allow(clippy::too_many_arguments)] fn require_self_attestation_authorization_details( service_id: &str, config: &SelfAttestationConfig, diff --git a/crates/registry-notary-server/tests/standalone_http.rs b/crates/registry-notary-server/tests/standalone_http.rs index b3f17027..81ca30a6 100644 --- a/crates/registry-notary-server/tests/standalone_http.rs +++ b/crates/registry-notary-server/tests/standalone_http.rs @@ -932,6 +932,18 @@ fn assert_matches_posture_schema(body: &Value) { ); } +fn assert_standards_artifacts_omit_sha256(body: &Value, label: &str) { + let artifacts = body["standards_artifacts"] + .as_object() + .expect("posture standards_artifacts is object"); + for (name, artifact) in artifacts { + assert!( + artifact.get("sha256").is_none(), + "{label} standards_artifacts.{name} includes sha256, but live Notary posture no longer emits it" + ); + } +} + fn assert_matches_admin_capabilities_schema(body: &Value) { let schema: Value = serde_json::from_str(registry_platform_ops::ADMIN_CAPABILITIES_SCHEMA_V1) .expect("admin capabilities schema parses"); @@ -7502,6 +7514,92 @@ async fn admin_posture_reports_configured_instance_override() { assert!(body["instance"].get("public_base_url").is_none()); } +#[tokio::test] +async fn admin_posture_top_level_keys_match_documented_example() { + set_audit_secret(); + std::env::set_var( + "TEST_EVIDENCE_API_KEY_HASH", + "sha256:a00cf33cd46d9ef96c1eff33df1c9cca20b1a02468cd78ec6a4b2887d1640b51", + ); + std::env::set_var("TEST_EVIDENCE_SOURCE_TOKEN", "source-token"); + + let tmp = TempDir::new().expect("tempdir"); + let audit_path = tmp.path().join("audit.jsonl"); + let mut config = registry_data_api_config( + "http://127.0.0.1:1", + audit_path.to_str().expect("audit path is UTF-8"), + ); + enable_shared_admin_listener(&mut config); + add_ops_read_api_key(&mut config); + + let app = standalone_router(config).expect("standalone router builds"); + let server = TestServer::builder().http_transport().build(app); + + let default_posture = server + .get("/admin/v1/posture") + .add_header("x-api-key", "ops-token") + .await; + default_posture.assert_status_ok(); + let default_body: Value = default_posture.json(); + assert_matches_posture_schema(&default_body); + + let default_live_keys = default_body + .as_object() + .expect("posture is object") + .keys() + .cloned() + .collect::>(); + let default_example: Value = + serde_json::from_str(registry_platform_ops::NOTARY_POSTURE_EXAMPLE_V1) + .expect("notary posture example parses"); + assert_standards_artifacts_omit_sha256(&default_body, "live default posture"); + assert_standards_artifacts_omit_sha256(&default_example, "NOTARY_POSTURE_EXAMPLE_V1"); + let default_example_keys = default_example + .as_object() + .expect("example posture is object") + .keys() + .cloned() + .collect::>(); + assert_eq!( + default_example_keys, default_live_keys, + "NOTARY_POSTURE_EXAMPLE_V1 top-level keys drifted from the live default-tier posture document \ + (missing from example: {:?}, extra in example: {:?})", + default_live_keys.difference(&default_example_keys).collect::>(), + default_example_keys.difference(&default_live_keys).collect::>(), + ); + + let restricted_posture = server + .get("/admin/v1/posture?tier=restricted") + .add_header("x-api-key", "ops-token") + .await; + restricted_posture.assert_status_ok(); + let restricted_body: Value = restricted_posture.json(); + assert_matches_posture_schema(&restricted_body); + + let restricted_live_keys = restricted_body + .as_object() + .expect("posture is object") + .keys() + .cloned() + .collect::>(); + let restricted_fixture: Value = + serde_json::from_str(registry_platform_ops::RESTRICTED_POSTURE_FIXTURE_V1) + .expect("restricted posture fixture parses"); + let restricted_fixture_keys = restricted_fixture + .as_object() + .expect("restricted fixture posture is object") + .keys() + .cloned() + .collect::>(); + assert_eq!( + restricted_fixture_keys, restricted_live_keys, + "RESTRICTED_POSTURE_FIXTURE_V1 top-level keys drifted from the live restricted-tier posture document \ + (missing from fixture: {:?}, extra in fixture: {:?})", + restricted_live_keys.difference(&restricted_fixture_keys).collect::>(), + restricted_fixture_keys.difference(&restricted_live_keys).collect::>(), + ); +} + #[tokio::test] async fn admin_posture_reports_self_attestation_summary_and_redacts_signing_key_ids() { std::env::set_var("TEST_SELF_ATTESTATION_ISSUER_JWK", TEST_ISSUER_JWK); @@ -10284,14 +10382,13 @@ async fn request_body_limit_returns_413_above_threshold() { audit_path.to_str().expect("audit path is UTF-8"), )) .expect("standalone router builds"); - let server = TestServer::builder().http_transport().build(app); + let server = TestServer::new(app); - let too_large = Bytes::from(vec![b' '; 1024 * 1024 + 1]); let response = server .post("/v1/evaluations") .add_header("x-api-key", "api-token") .add_header("content-type", "application/json") - .bytes(too_large) + .add_header(header::CONTENT_LENGTH, "1048577") .await; response.assert_status(StatusCode::PAYLOAD_TOO_LARGE); @@ -14588,15 +14685,14 @@ async fn request_body_limit_413_carries_server_owned_request_id() { audit_path.to_str().expect("audit path is UTF-8"), )) .expect("standalone router builds"); - let server = TestServer::builder().http_transport().build(app); - let too_large = Bytes::from(vec![b' '; 1024 * 1024 + 1]); + let server = TestServer::new(app); let response = server .post("/v1/evaluations") .add_header("x-api-key", "api-token") .add_header("content-type", "application/json") + .add_header(header::CONTENT_LENGTH, "1048577") .add_header("x-request-id", "client-supplied-id") - .bytes(too_large) .await; response.assert_status(StatusCode::PAYLOAD_TOO_LARGE); diff --git a/crates/registry-notary-source-adapter-rhai/Cargo.toml b/crates/registry-notary-source-adapter-rhai/Cargo.toml index 993f3f51..7d95469f 100644 --- a/crates/registry-notary-source-adapter-rhai/Cargo.toml +++ b/crates/registry-notary-source-adapter-rhai/Cargo.toml @@ -8,6 +8,9 @@ readme = "README.md" repository.workspace = true publish = false +[lints] +workspace = true + [dependencies] async-trait.workspace = true # The workspace declares crosswalk-functions with its default features (text, diff --git a/crates/registry-notary-source-adapter-sidecar/Cargo.toml b/crates/registry-notary-source-adapter-sidecar/Cargo.toml index b9ea097b..2918cd74 100644 --- a/crates/registry-notary-source-adapter-sidecar/Cargo.toml +++ b/crates/registry-notary-source-adapter-sidecar/Cargo.toml @@ -8,6 +8,9 @@ readme = "README.md" repository.workspace = true publish = false +[lints] +workspace = true + [dependencies] async-trait.workspace = true axum.workspace = true diff --git a/crates/registry-notary-worker-harness/Cargo.toml b/crates/registry-notary-worker-harness/Cargo.toml index 34dcbe79..4591f20d 100644 --- a/crates/registry-notary-worker-harness/Cargo.toml +++ b/crates/registry-notary-worker-harness/Cargo.toml @@ -8,6 +8,9 @@ readme = "README.md" repository.workspace = true publish = false +# not opted into [workspace.lints]: uses unsafe (libc setrlimit, `unsafe +# extern "C"` in src/lib.rs) + [dependencies] serde.workspace = true serde_json.workspace = true diff --git a/crates/registry-notary/Cargo.toml b/crates/registry-notary/Cargo.toml index 41ef28f9..d8fe6813 100644 --- a/crates/registry-notary/Cargo.toml +++ b/crates/registry-notary/Cargo.toml @@ -8,6 +8,9 @@ readme = "README.md" repository.workspace = true publish = false +# not opted into [workspace.lints]: unsafe std::env::set_var/remove_var in +# #[cfg(test)] code (src/main.rs) + [[bin]] name = "registry-notary" path = "src/main.rs" diff --git a/crates/registry-notary/tests/doctor_cli.rs b/crates/registry-notary/tests/doctor_cli.rs index 5b29e31f..98662fd1 100644 --- a/crates/registry-notary/tests/doctor_cli.rs +++ b/crates/registry-notary/tests/doctor_cli.rs @@ -168,8 +168,7 @@ fn write_opencrvs_dci_config(tmp: &TempDir) -> PathBuf { let path = tmp.path().join("opencrvs-dci.yaml"); std::fs::write( &path, - format!( - r#" + r#" server: bind: 127.0.0.1:0 auth: @@ -240,7 +239,7 @@ evidence: field: UIN op: eq cardinality: one - fields: {{}} + fields: {} matching: policy_id: registryctl.opencrvs-dci.birth-record.lookup.v1 method: configured_lookup @@ -269,8 +268,7 @@ evidence: allowed: [predicate, redacted] formats: - application/vnd.registry-notary.claim-result+json -"# - ), +"#, ) .expect("config writes"); path diff --git a/crates/registry-platform-audit/Cargo.toml b/crates/registry-platform-audit/Cargo.toml index b2d9bdf1..ff6be868 100644 --- a/crates/registry-platform-audit/Cargo.toml +++ b/crates/registry-platform-audit/Cargo.toml @@ -7,6 +7,9 @@ readme = "README.md" repository.workspace = true publish = false +[lints] +workspace = true + [dependencies] async-trait.workspace = true hmac.workspace = true diff --git a/crates/registry-platform-authcommon/Cargo.toml b/crates/registry-platform-authcommon/Cargo.toml index 9d6a23b4..d836e29c 100644 --- a/crates/registry-platform-authcommon/Cargo.toml +++ b/crates/registry-platform-authcommon/Cargo.toml @@ -7,6 +7,9 @@ readme = "README.md" repository.workspace = true publish = false +[lints] +workspace = true + [dependencies] serde.workspace = true sha2.workspace = true diff --git a/crates/registry-platform-cache/Cargo.toml b/crates/registry-platform-cache/Cargo.toml index 2e41f825..765eb28a 100644 --- a/crates/registry-platform-cache/Cargo.toml +++ b/crates/registry-platform-cache/Cargo.toml @@ -7,6 +7,9 @@ readme = "README.md" repository.workspace = true publish = false +[lints] +workspace = true + [features] default = [] redis = ["dep:redis"] diff --git a/crates/registry-platform-config/Cargo.toml b/crates/registry-platform-config/Cargo.toml index 7208cec0..4aac9564 100644 --- a/crates/registry-platform-config/Cargo.toml +++ b/crates/registry-platform-config/Cargo.toml @@ -7,6 +7,9 @@ description = "Signed/governed runtime configuration verification contracts for repository.workspace = true publish = false +[lints] +workspace = true + [dependencies] async-trait.workspace = true aws-lc-rs = "1.17.0" diff --git a/crates/registry-platform-crypto/Cargo.toml b/crates/registry-platform-crypto/Cargo.toml index dd9eb26f..ff718e84 100644 --- a/crates/registry-platform-crypto/Cargo.toml +++ b/crates/registry-platform-crypto/Cargo.toml @@ -7,6 +7,9 @@ readme = "README.md" repository.workspace = true publish = false +[lints] +workspace = true + [dependencies] async-trait.workspace = true aws-lc-rs.workspace = true diff --git a/crates/registry-platform-httpsec/Cargo.toml b/crates/registry-platform-httpsec/Cargo.toml index ceaf08b5..43281034 100644 --- a/crates/registry-platform-httpsec/Cargo.toml +++ b/crates/registry-platform-httpsec/Cargo.toml @@ -7,6 +7,9 @@ readme = "README.md" repository.workspace = true publish = false +[lints] +workspace = true + [dependencies] axum.workspace = true http.workspace = true diff --git a/crates/registry-platform-httputil/Cargo.toml b/crates/registry-platform-httputil/Cargo.toml index ef4302bb..3dbedf9b 100644 --- a/crates/registry-platform-httputil/Cargo.toml +++ b/crates/registry-platform-httputil/Cargo.toml @@ -7,6 +7,9 @@ readme = "README.md" repository.workspace = true publish = false +[lints] +workspace = true + [features] default = ["rustls"] rustls = ["reqwest/rustls-tls"] diff --git a/crates/registry-platform-oid4vci/Cargo.toml b/crates/registry-platform-oid4vci/Cargo.toml index 3c94364b..ac8ad313 100644 --- a/crates/registry-platform-oid4vci/Cargo.toml +++ b/crates/registry-platform-oid4vci/Cargo.toml @@ -7,6 +7,9 @@ readme = "README.md" repository.workspace = true publish = false +[lints] +workspace = true + [dependencies] base64.workspace = true registry-platform-crypto = { workspace = true } diff --git a/crates/registry-platform-oidc/Cargo.toml b/crates/registry-platform-oidc/Cargo.toml index 72faa8d7..20bc1d7e 100644 --- a/crates/registry-platform-oidc/Cargo.toml +++ b/crates/registry-platform-oidc/Cargo.toml @@ -7,6 +7,9 @@ readme = "README.md" repository.workspace = true publish = false +[lints] +workspace = true + [dependencies] base64.workspace = true jsonwebtoken.workspace = true diff --git a/crates/registry-platform-ops/Cargo.toml b/crates/registry-platform-ops/Cargo.toml index 742c9cdb..fd91e669 100644 --- a/crates/registry-platform-ops/Cargo.toml +++ b/crates/registry-platform-ops/Cargo.toml @@ -7,6 +7,9 @@ readme = "README.md" repository.workspace = true publish = false +[lints] +workspace = true + [dependencies] fs2.workspace = true registry-platform-crypto = { workspace = true } diff --git a/crates/registry-platform-ops/examples/registry-notary.posture.valid.json b/crates/registry-platform-ops/examples/registry-notary.posture.valid.json index 7a4925e9..153f2fe5 100644 --- a/crates/registry-platform-ops/examples/registry-notary.posture.valid.json +++ b/crates/registry-platform-ops/examples/registry-notary.posture.valid.json @@ -18,6 +18,20 @@ "admin_enabled": true, "readiness": "ready" }, + "deployment": { + "profile": "production", + "findings": [] + }, + "audit": { + "write_policy": "fail_closed_route_families", + "redaction_mode": "redacted", + "hash_chain": "process_local", + "keyed_integrity": "hmac", + "sink_class": "file", + "retention_owner": "operator", + "checkpoints": "unsupported", + "anchoring": "none" + }, "configuration": { "source": "local_file", "dynamic_reload_supported": false, @@ -31,12 +45,10 @@ "standards_artifacts": { "evidence_service_discovery": { "media_type": "application/json", - "sha256": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "observed_status": "available" }, "jwks": { "media_type": "application/jwk-set+json", - "sha256": "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", "observed_status": "available" } }, diff --git a/crates/registry-platform-pdp/Cargo.toml b/crates/registry-platform-pdp/Cargo.toml index 63221a07..5847e5b5 100644 --- a/crates/registry-platform-pdp/Cargo.toml +++ b/crates/registry-platform-pdp/Cargo.toml @@ -7,5 +7,8 @@ readme = "README.md" repository.workspace = true publish = false +[lints] +workspace = true + [dependencies] serde.workspace = true diff --git a/crates/registry-platform-replay/Cargo.toml b/crates/registry-platform-replay/Cargo.toml index cdf3ebf1..acb63d12 100644 --- a/crates/registry-platform-replay/Cargo.toml +++ b/crates/registry-platform-replay/Cargo.toml @@ -7,6 +7,9 @@ readme = "README.md" repository.workspace = true publish = false +[lints] +workspace = true + [dependencies] async-trait.workspace = true getrandom.workspace = true diff --git a/crates/registry-platform-sdjwt/Cargo.toml b/crates/registry-platform-sdjwt/Cargo.toml index c101c0bc..e7cf70b8 100644 --- a/crates/registry-platform-sdjwt/Cargo.toml +++ b/crates/registry-platform-sdjwt/Cargo.toml @@ -7,6 +7,9 @@ readme = "README.md" repository.workspace = true publish = false +[lints] +workspace = true + [dependencies] base64.workspace = true getrandom.workspace = true diff --git a/crates/registry-platform-sts/Cargo.toml b/crates/registry-platform-sts/Cargo.toml index d987f2a4..b04fe58a 100644 --- a/crates/registry-platform-sts/Cargo.toml +++ b/crates/registry-platform-sts/Cargo.toml @@ -7,6 +7,9 @@ readme = "README.md" repository.workspace = true publish = false +[lints] +workspace = true + [dependencies] async-trait.workspace = true axum.workspace = true diff --git a/crates/registry-platform-testing/Cargo.toml b/crates/registry-platform-testing/Cargo.toml index fe978990..105669a0 100644 --- a/crates/registry-platform-testing/Cargo.toml +++ b/crates/registry-platform-testing/Cargo.toml @@ -7,6 +7,9 @@ readme = "README.md" repository.workspace = true publish = false +[lints] +workspace = true + [features] default = ["test-utils"] test-utils = [] diff --git a/crates/registry-relay/Cargo.toml b/crates/registry-relay/Cargo.toml index 818e1560..a549340c 100644 --- a/crates/registry-relay/Cargo.toml +++ b/crates/registry-relay/Cargo.toml @@ -7,6 +7,10 @@ license = "Apache-2.0" description = "Registry Relay: config-driven, read-only APIs over sensitive registry and tabular files." publish = false +# not opted into [workspace.lints]: unsafe std::env::set_var in +# src/config/test_support.rs (compiled into the lib, not just #[cfg(test)]) +# and in tests/ + [package.metadata.cargo-machete] ignored = ["humantime-serde"] diff --git a/crates/registry-relay/src/api/entity.rs b/crates/registry-relay/src/api/entity.rs index 9611e02f..362be1e4 100644 --- a/crates/registry-relay/src/api/entity.rs +++ b/crates/registry-relay/src/api/entity.rs @@ -1621,8 +1621,10 @@ struct EntityRecordQuery { } fn record_query_from_params(params: HashMap) -> Result { - let mut query = EntityRecordQuery::default(); - query.raw_params = params.clone(); + let mut query = EntityRecordQuery { + raw_params: params.clone(), + ..Default::default() + }; for (name, value) in params { match name.as_str() { "fields" => { diff --git a/crates/registry-relay/src/config/mod.rs b/crates/registry-relay/src/config/mod.rs index bf5e3ca8..8d088672 100644 --- a/crates/registry-relay/src/config/mod.rs +++ b/crates/registry-relay/src/config/mod.rs @@ -1330,18 +1330,13 @@ pub struct RequiredFilterBindingConfig { pub source: RequiredFilterBindingSource, } -#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Default, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum RequiredFilterBindingSource { + #[default] PrincipalId, } -impl Default for RequiredFilterBindingSource { - fn default() -> Self { - Self::PrincipalId - } -} - #[derive(Debug, Clone, Default, Deserialize, PartialEq)] #[serde(deny_unknown_fields)] pub struct GovernedPolicyConfig { diff --git a/crates/registry-relay/src/query/mod.rs b/crates/registry-relay/src/query/mod.rs index e3718fc3..93963727 100644 --- a/crates/registry-relay/src/query/mod.rs +++ b/crates/registry-relay/src/query/mod.rs @@ -305,6 +305,7 @@ impl EntityQueryEngine { }) } + #[allow(clippy::too_many_arguments)] pub async fn read_record( &self, dataset_id: &str, diff --git a/crates/registry-relay/tests/aggregates_entity.rs b/crates/registry-relay/tests/aggregates_entity.rs index 81c72b56..06932a77 100644 --- a/crates/registry-relay/tests/aggregates_entity.rs +++ b/crates/registry-relay/tests/aggregates_entity.rs @@ -952,7 +952,16 @@ audit:"#, "redacted aggregate dimension must not be present in observations: {observations:?}" ); assert_eq!(body["structure"]["dimensions"], json!([])); - assert_eq!(observations[0]["individual_count"], 2); + let mut counts = observations + .iter() + .map(|row| { + row["individual_count"] + .as_i64() + .expect("individual_count is an integer") + }) + .collect::>(); + counts.sort_unstable(); + assert_eq!(counts, vec![1, 2]); } #[tokio::test] diff --git a/crates/registryctl/Cargo.toml b/crates/registryctl/Cargo.toml index 730fff13..7a6e9e7c 100644 --- a/crates/registryctl/Cargo.toml +++ b/crates/registryctl/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" license = "Apache-2.0" publish = false +[lints] +workspace = true + [dependencies] anyhow = "1" base64 = "0.22" diff --git a/deny.toml b/deny.toml index e4868f21..c1cc6d06 100644 --- a/deny.toml +++ b/deny.toml @@ -22,6 +22,16 @@ ignore = [ # request-facing parsing. Review when DataFusion moves off `paste` or when # upgrading the DataFusion major version. "RUSTSEC-2024-0436", + # RUSTSEC-2026-0194: quick-xml <=0.39 has quadratic duplicate-attribute + # checks. It is currently pinned by calamine 0.35 in Relay's XLSX connector + # and by phonenumber through the pinned Crosswalk stack. Review when + # calamine supports quick-xml >=0.41, when Crosswalk/phonenumber moves off + # quick-xml <=0.38, or before accepting untrusted end-user XLSX/XML inputs. + "RUSTSEC-2026-0194", + # RUSTSEC-2026-0195: quick-xml <=0.39 can over-allocate namespace + # declarations in NsReader. Same temporary scope and review triggers as + # RUSTSEC-2026-0194. + "RUSTSEC-2026-0195", ] [licenses] diff --git a/lab/.gitmodules b/lab/.gitmodules deleted file mode 100644 index dca85c26..00000000 --- a/lab/.gitmodules +++ /dev/null @@ -1,9 +0,0 @@ -[submodule "vendor/crosswalk"] - path = vendor/crosswalk - url = git@github.com:PublicSchema/crosswalk.git -[submodule "vendor/registry-atlas"] - path = vendor/registry-atlas - url = git@github.com:jeremi/registry-atlas.git -[submodule "vendor/esignet-relay-authenticator"] - path = vendor/esignet-relay-authenticator - url = git@github.com:jeremi/esignet-relay-authenticator.git diff --git a/lab/README.md b/lab/README.md index 2d6f5c65..270eb6c4 100644 --- a/lab/README.md +++ b/lab/README.md @@ -52,16 +52,11 @@ client also has no `data/` mount. ## Quick start -Clone with submodules: - -```bash -git clone --recurse-submodules git@github.com:jeremi/registry-lab.git -cd registry-lab -``` - -For an existing checkout, or after pulling changes: +This lab lives at `lab/` inside the registry-stack monorepo. From a checkout of +the monorepo: ```bash +cd lab just setup just generate just build @@ -70,10 +65,12 @@ just smoke just client ``` -The service-first metadata path uses the `vendor/registry-manifest` submodule by -default. Override `REGISTRY_MANIFEST_REPO` when you want to test a sibling -checkout or another local path. `just generate` and `just smoke` fail early when -`registry-manifest` is missing. +`just setup` initializes the remaining `lab/vendor/` submodules: Crosswalk, +registry-atlas, and the eSignet Relay authenticator. The Registry Relay, +Notary, Platform, and Manifest source now live as crates in the monorepo root; +the lab justfile defaults to those monorepo paths, while Crosswalk, +registry-atlas, and the eSignet Relay authenticator stay pinned as vendor +submodules. `just generate` writes `.env`, fixture files, and static metadata. Run it before `just up` the first time, and run it again after pulling demo changes that add @@ -87,13 +84,20 @@ When you are done: just down ``` -For a single command that generates, builds, starts, and runs the core checks: +For a single command that generates, builds, starts, and runs the core checks, +in this monorepo use: ```bash -just quick +just release-fast ``` -For commons release validation across sibling source checkouts: +Plain `just quick` runs the same monorepo defaults for generate, build, smoke, +OpenFn, and client checks. `just release-fast` runs `scripts/release-check.sh`, +which defaults to `REGISTRY_LAB_RELEASE_SOURCE_MODE=monorepo` and also gates +the release source model. + +If you still have sibling Platform, Relay, and Notary checkouts and want to +validate against them directly, `commons-check` remains available: ```bash REGISTRY_PLATFORM_SOURCE_DIR=../registry-platform \ @@ -103,10 +107,6 @@ REGISTRY_NOTARY_SOURCE_DIR=../registry-notary \ just commons-check ``` -`commons-check` intentionally uses source dirs instead of `vendor/` pins. Update -Lab vendor or submodule pins only after Platform, Manifest, Relay, and Notary -source changes are committed. - For the first release, keep the two proof paths separate: - Source proof: run against sibling Platform, Relay, and Notary checkouts with @@ -114,10 +114,12 @@ For the first release, keep the two proof paths separate: reflected in Lab `vendor/` pins, also set `REGISTRY_LAB_ALLOW_PENDING_PINS=1`; the release source model check will print each pending pin or dirty source checkout. This is a pre-tag proof only. -- Lab pin proof: run `scripts/release-check.sh` without - `REGISTRY_LAB_RELEASE_SOURCE_MODE`. The script forces Platform, Relay, Notary, - Manifest, and Crosswalk to the committed `vendor/` submodules even when - sibling checkouts exist. This is the clean-clone/no-sibling release proof. +- Monorepo proof: run `scripts/release-check.sh` with the default + `REGISTRY_LAB_RELEASE_SOURCE_MODE=monorepo` (or explicitly). The script + resolves Platform, Relay, Notary, and Manifest against this monorepo + checkout and Crosswalk, registry-atlas, and the eSignet Relay authenticator + against the committed `vendor/` submodules. This is the primary release + proof for this monorepo. ## Demo commands @@ -311,11 +313,11 @@ removes demo volumes after a successful run. Use the individual checks above when you want to keep the current Postgres, Zitadel, or OpenFn containers running for inspection. -The `justfile` defaults `REGISTRY_RELAY_SOURCE_DIR`, -`REGISTRY_NOTARY_SOURCE_DIR`, and `REGISTRY_PLATFORM_SOURCE_DIR` to sibling -checkouts when present, otherwise to the pinned `vendor/` submodules. -`REGISTRY_OPENFN_NOTARY_SOURCE_DIR` follows `REGISTRY_NOTARY_SOURCE_DIR` by -default. Override those variables when you want to build from another local +The `justfile` defaults `REGISTRY_RELAY_SOURCE_DIR` to +`../crates/registry-relay` and `REGISTRY_NOTARY_SOURCE_DIR`, +`REGISTRY_PLATFORM_SOURCE_DIR`, and `REGISTRY_MANIFEST_REPO` to the monorepo +root. `REGISTRY_OPENFN_NOTARY_SOURCE_DIR` follows `REGISTRY_NOTARY_SOURCE_DIR` +by default. Override those variables when you want to build from another local path. ## Live Notary Redis checks @@ -624,17 +626,20 @@ the full setup sequence, which starts with `just generate` before ## Source repositories This demo keeps runtime orchestration, fixtures, static metadata config, and -walkthrough scripts in this repository. Supporting source repositories are -submodules under `vendor/`: +walkthrough scripts in this repository. Registry Platform, Registry Relay, +Registry Notary, and Registry Manifest now live as crates in the +registry-stack monorepo root rather than as `vendor/` submodules; the lab +justfile and release scripts default to the monorepo source paths. The +following remain real submodules under `lab/vendor/`: -- `vendor/registry-platform`: shared platform crates used by Relay and Notary. -- `vendor/registry-relay`: Relay source used by `Dockerfile.registry-relay`. -- `vendor/registry-notary`: Registry Notary source used by - `Dockerfile.registry-notary`. -- `vendor/registry-manifest`: static metadata publishing CLI and profiles. +- `vendor/crosswalk`: the Crosswalk mapping engine, pinned as a git dependency. +- `vendor/registry-atlas`: registry-atlas source. +- `vendor/esignet-relay-authenticator`: the eSignet Relay authenticator plugin + used by `just esignet-up`. -The Compose build uses Docker named contexts so local source checkouts can be -used without changing `compose.yaml`: +The Compose build uses Docker named contexts, and defaults to this monorepo +checkout for Platform, Manifest, and Notary plus the Relay crate path. Override +with sibling checkouts when you need to build from another local path: ```bash REGISTRY_RELAY_SOURCE_DIR=../registry-relay \ @@ -644,40 +649,39 @@ CROSSWALK_SOURCE_DIR=../crosswalk \ just build ``` -`just lab2-up` uses the same source selection model through `compose.lab2.yaml`. -That makes Lab 2 useful as a pre-pin regression pass against sibling Relay, -Notary, and Platform checkouts. `just lab2-generate` also rewrites a temporary -tool manifest when `REGISTRY_PLATFORM_SOURCE_DIR` points outside the vendored -Platform submodule, so generated governed artifacts can be checked against a -Platform source checkout. For release evidence, keep using `scripts/release-check.sh` -in `vendor` mode or pin the `vendor/` submodules before tagging. +`just lab2-up` uses the same source selection model through `compose.lab2.yaml`, +which makes Lab 2 useful as a regression pass against sibling Relay, Notary, +and Platform checkouts as well as the monorepo default. `just lab2-generate` +also rewrites a temporary tool manifest when `REGISTRY_PLATFORM_SOURCE_DIR` +points outside this monorepo, so generated governed artifacts can be checked +against a Platform source checkout. For release evidence, use +`scripts/release-check.sh` (monorepo mode by default). Use the same variables with `scripts/generate-demo-secrets.py` when you want -that script to use a sibling Relay checkout instead of the -`vendor/registry-relay` submodule. `scripts/publish-static-metadata.sh` uses -the Registry Manifest CLI from `REGISTRY_MANIFEST_REPO`, defaulting to the -`vendor/registry-manifest` submodule. For a release, pin the submodules to -commits that already include the Registry Platform, Registry Relay, and Registry -Notary behavior required by this demo. +that script to use a sibling Relay checkout instead of the monorepo default. +`scripts/publish-static-metadata.sh` uses the Registry Manifest CLI from +`REGISTRY_MANIFEST_REPO`, defaulting to this monorepo checkout. OpenFn image builds can use `REGISTRY_OPENFN_NOTARY_SOURCE_DIR` separately from the core Notary image. The lab default points OpenFn at the selected Notary -source, so local source checkouts can be tested before the lab submodule pin -moves. +source. `scripts/check-release-source-model.sh source` compares sibling Platform, -Relay, and Notary SHAs with the Lab `vendor/` pins and fails on mismatches or -dirty source checkouts. Use `REGISTRY_LAB_ALLOW_PENDING_PINS=1` only while the -final source commits are still waiting for the Lab submodule pin update. -`scripts/check-release-source-model.sh vendor` proves that the selected release -paths resolve to committed Lab pins. +Relay, and Notary SHAs with the Lab `vendor/` pins (Crosswalk, registry-atlas, +and the eSignet Relay authenticator) and fails on mismatches or dirty source +checkouts. Use `REGISTRY_LAB_ALLOW_PENDING_PINS=1` only while the final source +commits are still waiting for the Lab submodule pin update. +`scripts/check-release-source-model.sh monorepo` (the default) proves that the +selected release paths resolve to this monorepo checkout and the committed +`vendor/` pins. `just notary-client` imports the Registry Notary Python client directly from a source checkout and runs it against the default lab Notary services. It looks at -`REGISTRY_NOTARY_CLIENT_SOURCE_DIR` first, then `REGISTRY_NOTARY_SOURCE_DIR`, -then `../registry-notary`, and finally `vendor/registry-notary`. Use -`REGISTRY_NOTARY_CLIENT_SOURCE_DIR` when validating a client SDK branch before -the lab submodule pin has moved. This smoke is explicit and is not part of +`REGISTRY_NOTARY_CLIENT_SOURCE_DIR` first, then `REGISTRY_NOTARY_SOURCE_DIR` +(which the justfile and `release-check.sh` default to this monorepo checkout), +then falls back to the standalone sibling paths `../registry-notary` and +`vendor/registry-notary`. Use `REGISTRY_NOTARY_CLIENT_SOURCE_DIR` when +validating a client SDK branch. This smoke is explicit and is not part of `just quick`. ## Fixture data diff --git a/lab/justfile b/lab/justfile index 88e84073..8bb2a194 100644 --- a/lab/justfile +++ b/lab/justfile @@ -1,10 +1,11 @@ set dotenv-load := true set positional-arguments := true -default_relay_src := if path_exists("../registry-relay/Cargo.toml") == "true" { "../registry-relay" } else { if path_exists("../../registry-relay/Cargo.toml") == "true" { "../../registry-relay" } else { "./vendor/registry-relay" } } -default_notary_src := if path_exists("../registry-notary/Cargo.toml") == "true" { "../registry-notary" } else { if path_exists("../../registry-notary/Cargo.toml") == "true" { "../../registry-notary" } else { "./vendor/registry-notary" } } -default_platform_src := if path_exists("../registry-platform/Cargo.toml") == "true" { "../registry-platform" } else { if path_exists("../../registry-platform/Cargo.toml") == "true" { "../../registry-platform" } else { "./vendor/registry-platform" } } -default_manifest_src := if path_exists("../registry-manifest/Cargo.toml") == "true" { "../registry-manifest" } else { if path_exists("../../registry-manifest/Cargo.toml") == "true" { "../../registry-manifest" } else { "./vendor/registry-manifest" } } +default_stack_src := if path_exists("../Cargo.toml") == "true" { if path_exists("../crates/registry-relay/Cargo.toml") == "true" { ".." } else { "" } } else { "" } +default_relay_src := if default_stack_src != "" { "../crates/registry-relay" } else { if path_exists("../registry-relay/Cargo.toml") == "true" { "../registry-relay" } else { if path_exists("../../registry-relay/Cargo.toml") == "true" { "../../registry-relay" } else { "./vendor/registry-relay" } } } +default_notary_src := if default_stack_src != "" { default_stack_src } else { if path_exists("../registry-notary/Cargo.toml") == "true" { "../registry-notary" } else { if path_exists("../../registry-notary/Cargo.toml") == "true" { "../../registry-notary" } else { "./vendor/registry-notary" } } } +default_platform_src := if default_stack_src != "" { default_stack_src } else { if path_exists("../registry-platform/Cargo.toml") == "true" { "../registry-platform" } else { if path_exists("../../registry-platform/Cargo.toml") == "true" { "../../registry-platform" } else { "./vendor/registry-platform" } } } +default_manifest_src := if default_stack_src != "" { default_stack_src } else { if path_exists("../registry-manifest/Cargo.toml") == "true" { "../registry-manifest" } else { if path_exists("../../registry-manifest/Cargo.toml") == "true" { "../../registry-manifest" } else { "./vendor/registry-manifest" } } } default_esignet_relay_authenticator_src := "./vendor/esignet-relay-authenticator" relay_src := env_var_or_default("REGISTRY_RELAY_SOURCE_DIR", default_relay_src) @@ -40,7 +41,7 @@ default: # Initialize git submodules. setup: - git submodule update --init --recursive + git -C "$$(git rev-parse --show-toplevel)" submodule update --init --recursive -- lab/vendor/crosswalk lab/vendor/registry-atlas lab/vendor/esignet-relay-authenticator # Generate deterministic fixtures, demo secrets, and static metadata. generate: diff --git a/lab/scripts/test_esignet_relay_lab.py b/lab/scripts/test_esignet_relay_lab.py index dff5a1b3..2ff6aaac 100644 --- a/lab/scripts/test_esignet_relay_lab.py +++ b/lab/scripts/test_esignet_relay_lab.py @@ -8,12 +8,17 @@ ROOT = Path(__file__).resolve().parents[1] +REPO_ROOT = ROOT.parent def text(path: str) -> str: return (ROOT / path).read_text(encoding="utf-8") +def repo_text(path: str) -> str: + return (REPO_ROOT / path).read_text(encoding="utf-8") + + class EsignetRelayLabTest(unittest.TestCase): def test_local_compose_uses_relay_authenticator_without_mock_ida(self) -> None: compose = text("compose.esignet-live.yaml") @@ -63,9 +68,10 @@ def test_user_facing_login_text_no_longer_mentions_static_pin(self) -> None: self.assertNotIn("if eSignet asks", value) def test_plugin_submodule_is_declared(self) -> None: - modules = text(".gitmodules") + modules = repo_text(".gitmodules") - self.assertIn("[submodule \"vendor/esignet-relay-authenticator\"]", modules) + self.assertIn("[submodule \"lab/vendor/esignet-relay-authenticator\"]", modules) + self.assertIn("path = lab/vendor/esignet-relay-authenticator", modules) self.assertIn("url = git@github.com:jeremi/esignet-relay-authenticator.git", modules) def test_esignet_relay_image_rebuilds_plugin_classes_from_source(self) -> None: diff --git a/products/notary/CHANGELOG.md b/products/notary/CHANGELOG.md index 9e06f839..98501437 100644 --- a/products/notary/CHANGELOG.md +++ b/products/notary/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- Corrected the `GET /admin/v1/posture` OpenAPI example to include the + `deployment` and `audit` sections that the live default-tier posture + document already returns. + ### Changed - Renamed the binary package from `registry-notary-bin` to `registry-notary` diff --git a/products/notary/openapi/registry-notary.openapi.json b/products/notary/openapi/registry-notary.openapi.json index b9ee7e11..9671377d 100644 --- a/products/notary/openapi/registry-notary.openapi.json +++ b/products/notary/openapi/registry-notary.openapi.json @@ -1802,7 +1802,7 @@ }, "summary": "Standalone evidence evaluation, rendering, and credential issuance service.", "title": "Registry Notary API", - "version": "0.6.2" + "version": "0.8.3" }, "openapi": "3.1.0", "paths": { @@ -2741,6 +2741,16 @@ "content": { "application/json": { "example": { + "audit": { + "anchoring": "none", + "checkpoints": "unsupported", + "hash_chain": "process_local", + "keyed_integrity": "hmac", + "redaction_mode": "redacted", + "retention_owner": "operator", + "sink_class": "file", + "write_policy": "fail_closed_route_families" + }, "build": { "package": "registry-notary", "version": "0.1.0" @@ -2756,6 +2766,10 @@ "restart_required": false, "source": "local_file" }, + "deployment": { + "findings": [], + "profile": "production" + }, "instance": { "environment": "production", "id": "civil-notary-prod", @@ -2822,13 +2836,11 @@ "standards_artifacts": { "evidence_service_discovery": { "media_type": "application/json", - "observed_status": "available", - "sha256": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + "observed_status": "available" }, "jwks": { "media_type": "application/jwk-set+json", - "observed_status": "available", - "sha256": "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + "observed_status": "available" } }, "tier": "default" @@ -3640,7 +3652,7 @@ "example": { "info": { "title": "Registry Notary API", - "version": "0.6.2" + "version": "0.8.3" }, "openapi": "3.1.0", "paths": { diff --git a/products/notary/xtask/Cargo.toml b/products/notary/xtask/Cargo.toml index d7bdc1dd..e1ec1b44 100644 --- a/products/notary/xtask/Cargo.toml +++ b/products/notary/xtask/Cargo.toml @@ -6,6 +6,9 @@ license = "Apache-2.0" # Internal workspace tooling; not published. publish = false +[lints] +workspace = true + [dependencies] base64 = { version = "0.22" } registry-platform-crypto = { workspace = true }