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
26 changes: 24 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -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
10 changes: 9 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions Cargo.lock

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

1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ flowchart LR

source --> relay
manifest --> relay
manifest --> notary
relay --> caller
relay --> notary
notary --> caller
Expand Down
3 changes: 3 additions & 0 deletions crates/registry-config-report/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ readme = "README.md"
repository.workspace = true
publish = false

[lints]
workspace = true

[dependencies]
serde.workspace = true
serde_json.workspace = true
Expand Down
2 changes: 2 additions & 0 deletions crates/registry-manifest-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
3 changes: 3 additions & 0 deletions crates/registry-manifest-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
3 changes: 3 additions & 0 deletions crates/registry-notary-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ readme = "README.md"
repository.workspace = true
publish = false

[lints]
workspace = true

[features]
default = ["rustls"]
rustls = []
Expand Down
3 changes: 3 additions & 0 deletions crates/registry-notary-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ readme = "README.md"
repository.workspace = true
publish = false

[lints]
workspace = true

[dependencies]
base64.workspace = true
humantime-serde.workspace = true
Expand Down
2 changes: 1 addition & 1 deletion crates/registry-notary-core/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions crates/registry-notary-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
7 changes: 4 additions & 3 deletions crates/registry-notary-server/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Expand Down Expand Up @@ -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,
Expand Down
108 changes: 102 additions & 6 deletions crates/registry-notary-server/tests/standalone_http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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::<BTreeSet<_>>();
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::<BTreeSet<_>>();
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::<Vec<_>>(),
default_example_keys.difference(&default_live_keys).collect::<Vec<_>>(),
);

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::<BTreeSet<_>>();
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::<BTreeSet<_>>();
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::<Vec<_>>(),
restricted_fixture_keys.difference(&restricted_live_keys).collect::<Vec<_>>(),
);
}

#[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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
3 changes: 3 additions & 0 deletions crates/registry-notary-source-adapter-rhai/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
3 changes: 3 additions & 0 deletions crates/registry-notary-source-adapter-sidecar/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ readme = "README.md"
repository.workspace = true
publish = false

[lints]
workspace = true

[dependencies]
async-trait.workspace = true
axum.workspace = true
Expand Down
3 changes: 3 additions & 0 deletions crates/registry-notary-worker-harness/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions crates/registry-notary/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Loading
Loading