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
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ const GOLDEN_JWKS = JSON.parse(
const DEFINITIONS_DOCUMENT = {
schema: 'registry.evidence-definitions/v1',
assuranceProfile: 'local',
configurationRevision: `sha256:${'0'.repeat(64)}`,
issuedBy: 'urn:example:node-test:issuer',
providedBy: 'urn:example:node-test:provider',
definitions: [
{
requirement: 'urn:example:node-test:requirement:status:v1',
configurationRevision: `sha256:${'0'.repeat(64)}`,
kind: 'criterion',
evidenceType: 'urn:example:node-test:evidence-type:status:v1',
purpose: 'example-decision',
Expand Down Expand Up @@ -71,6 +71,9 @@ test('discover reads a valid definitions document from a stub deployment', async
assert.equal(document.schema, 'registry.evidence-definitions/v1');
assert.equal(document.definitions.length, 1);
assert.equal(document.definitions[0].requirement, 'urn:example:node-test:requirement:status:v1');
// The revision a relying party pins is published per definition, so it
// reaches the caller from the requirement it belongs to.
assert.equal(document.definitions[0].configurationRevision, `sha256:${'0'.repeat(64)}`);
assert.equal(stub.requests.length, 1);
} finally {
await stub.close();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@

DEFINITIONS_DOCUMENT_BODY = (
b'{"schema": "registry.evidence-definitions/v1", "assuranceProfile": "local",'
b' "configurationRevision": "r", "issuedBy": "i", "providedBy": "p",'
b' "definitions": []}'
b' "issuedBy": "i", "providedBy": "p", "definitions": []}'
)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,41 @@
DEFINITIONS_DOCUMENT = {
"schema": "registry.evidence-definitions/v1",
"assuranceProfile": "local",
"configurationRevision": "test-revision-1",
"issuedBy": "https://issuer.example.test",
"providedBy": "https://provider.example.test",
"definitions": [],
"definitions": [
{
"requirement": "urn:example:py-test:requirement:status:v1",
# Published per definition, so a relying party pins one requirement
# without depending on the rest of the deployment.
"configurationRevision": "test-revision-1",
"kind": "criterion",
"evidenceType": "urn:example:py-test:evidence-type:status:v1",
"purpose": "example-decision",
"referenceFrameworks": ["urn:example:py-test:framework:status:v1"],
"subjects": [
{
"role": "subject",
"cardinality": "one",
"selector": {
"profile": "record-lookup-v1",
"valueOrigin": "request",
"fields": [
{
"type": "string",
"name": "record_reference",
"minimumBytes": 1,
"maximumBytes": 200,
}
],
},
}
],
"concepts": [
{"id": "urn:example:py-test:concept:status-holds", "form": "boolean"}
],
}
],
}


Expand All @@ -63,6 +94,12 @@ def test_discover_returns_the_definitions_document_as_a_dict(self):
self._serve_definitions()
document = self._client().discover()
self.assertEqual(document, DEFINITIONS_DOCUMENT)
# The revision a relying party pins reaches the caller from the
# definition it belongs to, not from the document.
self.assertNotIn("configurationRevision", document)
self.assertEqual(
document["definitions"][0]["configurationRevision"], "test-revision-1"
)

def test_the_metadata_bound_governs_discovery_and_the_response_bound_does_not(self):
"""The two bounds answer different questions.
Expand Down
2 changes: 1 addition & 1 deletion crates/registry-evidence-client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1133,7 +1133,7 @@ mod tests {
/// definitions contract permits.
fn definitions_json(schema: &str) -> String {
format!(
r#"{{"schema":"{schema}","assuranceProfile":"local","configurationRevision":"sha256:0000000000000000000000000000000000000000000000000000000000000000","issuedBy":"urn:example:client:issuer","providedBy":"urn:example:client:provider","definitions":[]}}"#
r#"{{"schema":"{schema}","assuranceProfile":"local","issuedBy":"urn:example:client:issuer","providedBy":"urn:example:client:provider","definitions":[]}}"#
)
}

Expand Down
51 changes: 49 additions & 2 deletions crates/registry-evidence-client/src/definitions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ pub const EVIDENCE_DEFINITIONS_SCHEMA_V1: &str = "registry.evidence-definitions/
pub struct EvidenceDefinitionsDocument {
pub schema: String,
pub assurance_profile: AssuranceProfile,
pub configuration_revision: String,
pub issued_by: String,
pub provided_by: String,
pub definitions: Vec<EvidenceDefinition>,
Expand Down Expand Up @@ -56,6 +55,10 @@ impl EvidenceDefinitionsDocument {
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct EvidenceDefinition {
pub requirement: String,
/// The revision an assertion for this requirement carries. It covers this
/// requirement's own configuration and artifact closure, so pinning it does
/// not couple a relying procedure to the rest of the deployment.
pub configuration_revision: String,
pub kind: DefinitionKind,
pub evidence_type: String,
pub purpose: String,
Expand Down Expand Up @@ -242,12 +245,12 @@ mod tests {
const DOCUMENT: &str = r#"{
"schema": "registry.evidence-definitions/v1",
"assuranceProfile": "local",
"configurationRevision": "sha256:0000000000000000000000000000000000000000000000000000000000000000",
"issuedBy": "urn:example:client:issuer",
"providedBy": "urn:example:client:provider",
"definitions": [
{
"requirement": "urn:example:client:requirement:status:v1",
"configurationRevision": "sha256:0000000000000000000000000000000000000000000000000000000000000000",
"kind": "criterion",
"evidenceType": "urn:example:client:evidence-type:status:v1",
"purpose": "example-decision",
Expand Down Expand Up @@ -345,6 +348,50 @@ mod tests {
.any(|definition| definition.purpose == "other-decision"));
}

#[test]
fn each_definition_carries_its_own_configuration_revision() {
// A deployment serves several requirements from one bundle and each
// publishes the revision its own assertions carry. A relying procedure
// that pinned a document-level value would break whenever an unrelated
// requirement's configuration changed, so the field lives here.
let mut document = document();
let mut other_requirement = document.definitions[0].clone();
other_requirement.requirement = "urn:example:client:requirement:other:v1".to_owned();
other_requirement.configuration_revision = format!("sha256:{}", "1".repeat(64));
document.definitions.push(other_requirement);
let round_tripped: EvidenceDefinitionsDocument = serde_json::from_str(
&serde_json::to_string(&document).expect("the two requirement document serializes"),
)
.expect("the two requirement document parses");
assert_eq!(
round_tripped
.definition("urn:example:client:requirement:status:v1")
.expect("the first requirement is present")
.configuration_revision,
format!("sha256:{}", "0".repeat(64))
);
assert_eq!(
round_tripped
.definition("urn:example:client:requirement:other:v1")
.expect("the second requirement is present")
.configuration_revision,
format!("sha256:{}", "1".repeat(64))
);
}

#[test]
fn a_document_level_configuration_revision_is_refused() {
// The revision moved from the document to each definition. A deployment
// still publishing it at the document level would have a relying party
// pin a value no assertion carries, so the closed type refuses it
// rather than ignoring it.
let document_level = DOCUMENT.replace(
r#""assuranceProfile": "local","#,
r#""assuranceProfile": "local", "configurationRevision": "sha256:0000000000000000000000000000000000000000000000000000000000000000","#,
);
assert!(serde_json::from_str::<EvidenceDefinitionsDocument>(&document_level).is_err());
}

#[test]
fn an_undeclared_member_is_refused() {
let extended = DOCUMENT.replace(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,12 +232,15 @@ async fn discovery_publishes_shapes_this_client_parses_exactly() {

assert_eq!(definitions.schema, EVIDENCE_DEFINITIONS_SCHEMA_V1);
assert_eq!(definitions.assurance_profile, AssuranceProfile::Local);
assert!(definitions.configuration_revision.starts_with("sha256:"));
assert_eq!(definitions.definitions.len(), 1);

let definition = definitions
.definition(REQUIREMENT)
.expect("the requester is entitled to the fixture requirement");
// The revision is published per definition, because that is the scope an
// assertion for one requirement carries.
assert!(definition.configuration_revision.starts_with("sha256:"));
assert_eq!(definition.configuration_revision.len(), 71);
assert_eq!(definition.kind, DefinitionKind::Criterion);
assert_eq!(definition.purpose, "fixture-eligibility");
assert_eq!(definition.subjects.len(), 1);
Expand Down Expand Up @@ -678,7 +681,7 @@ fn spec(
evidence_type: definition.evidence_type.clone(),
issued_by: definitions.issued_by.clone(),
provided_by: definitions.provided_by.clone(),
configuration_revision: definitions.configuration_revision.clone(),
configuration_revision: definition.configuration_revision.clone(),
expected_assurance_profile: definitions.assurance_profile,
subjects: definition
.subjects
Expand Down
Loading
Loading