diff --git a/.snpcc_canary b/.snpcc_canary index 71aea15f7fc8..64409b594064 100644 --- a/.snpcc_canary +++ b/.snpcc_canary @@ -2,4 +2,4 @@ (. =) Y (0 0) (x X) Y O \ o | / /-xXx--//-----x=x--/-xXx--/---x---->>>--/ -... \ No newline at end of file +...... \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index bfb143167cbe..2b195b5b7232 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. [5.0.0-dev10]: https://github.com/microsoft/CCF/releases/tag/ccf-5.0.0-dev10 - The `url` field in `snp_endorsements_servers` can now contain environment variables that will be resolved at startup, such as "$Fabric_NodeIPOrFQDN:2377" (#5862). -- Add a `new snp_security_policy_file` configuration value under `attestation`, superseding the lookup from `$UVM_SECURITY_CONTEXT_DIR`. The value can contain environment variables, for example: `"snp_security_policy_file": "$UVM_SECURITY_CONTEXT_DIR/security-policy-base64"`. +- Add a new `snp_security_policy_file` configuration value under `attestation`, superseding the lookup from `$UVM_SECURITY_CONTEXT_DIR`. The value can contain environment variables, for example: `"snp_security_policy_file": "$UVM_SECURITY_CONTEXT_DIR/security-policy-base64"`. +- Add a new `snp_uvm_endorsements_file` configuration value under `attestation`, superseding the lookup from `$UVM_SECURITY_CONTEXT_DIR`. The value can contain environment variables, for example: `"snp_uvm_endorsements_file": "$UVM_SECURITY_CONTEXT_DIR/reference-info-base64"`. This value can come from an untrusted location, like `snp_security_policy_file` and AMD endorsements (fetched from `snp_endorsements_servers`), because the CCF code contains pre-defined roots of trust. ## [5.0.0-dev9] diff --git a/doc/host_config_schema/cchost_config.json b/doc/host_config_schema/cchost_config.json index d28c8f98ec54..b3466ec2bab8 100644 --- a/doc/host_config_schema/cchost_config.json +++ b/doc/host_config_schema/cchost_config.json @@ -460,6 +460,10 @@ "type": ["string", "null"], "description": "Path to file containing the security policy (SEV-SNP only), can contain environment variables, such as $UVM_SECURITY_CONTEXT_DIR" }, + "snp_uvm_endorsements_file": { + "type": ["string", "null"], + "description": "Path to file containing UVM endorsements as a base64-encoded COSE Sign1 (SEV-SNP only). Can contain environment variables, such as $UVM_SECURITY_CONTEXT_DIR" + }, "snp_endorsements_servers": { "type": "array", "items": { diff --git a/doc/operations/platforms/snp.rst b/doc/operations/platforms/snp.rst index 43c2a67933b0..620d37e6d4e9 100644 --- a/doc/operations/platforms/snp.rst +++ b/doc/operations/platforms/snp.rst @@ -61,6 +61,7 @@ For non-Azure deployments, the certificate chain for VCEK will need to be retrie } ], "snp_security_policy_file": "/path/to/security-policy-base64", + "snp_uvm_endorsements_file": "/path/to/reference-info-base64" } .. tip:: See :ccf_repo:`samples/config/start_config_amd_sev_snp.json` for a sample node configuration for non-Azure deployments. diff --git a/include/ccf/node/startup_config.h b/include/ccf/node/startup_config.h index 743df6f4338c..33b88b112613 100644 --- a/include/ccf/node/startup_config.h +++ b/include/ccf/node/startup_config.h @@ -57,6 +57,7 @@ struct CCFConfig { ccf::pal::snp::EndorsementsServers snp_endorsements_servers = {}; std::optional snp_security_policy_file = std::nullopt; + std::optional snp_uvm_endorsements_file = std::nullopt; struct Environment { diff --git a/samples/config/start_config_aci_sev_snp.json b/samples/config/start_config_aci_sev_snp.json index a8b5905770a0..63854909263e 100644 --- a/samples/config/start_config_aci_sev_snp.json +++ b/samples/config/start_config_aci_sev_snp.json @@ -43,6 +43,7 @@ "url": "169.254.169.254" } ], - "snp_security_policy_file": "$UVM_SECURITY_CONTEXT_DIR/security-policy-base64" + "snp_security_policy_file": "$UVM_SECURITY_CONTEXT_DIR/security-policy-base64", + "snp_uvm_endorsements_file": "$UVM_SECURITY_CONTEXT_DIR/reference-info-base64" } } diff --git a/samples/config/start_config_aks_sev_snp.json b/samples/config/start_config_aks_sev_snp.json index 6a884ef4eadd..5cfb4a0ed19f 100644 --- a/samples/config/start_config_aks_sev_snp.json +++ b/samples/config/start_config_aks_sev_snp.json @@ -34,8 +34,13 @@ } }, "attestation": { - "environment": { - "security_context_directory": "UVM_SECURITY_CONTEXT_DIR" - } + "snp_endorsements_servers": [ + { + "type": "THIM", + "url": "169.254.169.254" + } + ], + "snp_security_policy_file": "/path/to/security-policy-base64", + "snp_uvm_endorsements_file": "/opt/confidential-containers/share/kata-containers/reference-info-base64" } } diff --git a/samples/config/start_config_amd_sev_snp.json b/samples/config/start_config_amd_sev_snp.json index 20548c3c3e67..5a9ea714ffd3 100644 --- a/samples/config/start_config_amd_sev_snp.json +++ b/samples/config/start_config_amd_sev_snp.json @@ -40,6 +40,7 @@ "url": "kdsintf.amd.com" } ], - "snp_security_policy_file": "/path/to/security-policy-base64" + "snp_security_policy_file": "/path/to/security-policy-base64", + "snp_uvm_endorsements_file": "/path/to/reference-info-base64" } } diff --git a/src/common/configuration.h b/src/common/configuration.h index c97997cfc1c0..25a4918e7294 100644 --- a/src/common/configuration.h +++ b/src/common/configuration.h @@ -83,7 +83,8 @@ DECLARE_JSON_OPTIONAL_FIELDS( CCFConfig::Attestation, snp_endorsements_servers, environment, - snp_security_policy_file); + snp_security_policy_file, + snp_uvm_endorsements_file); DECLARE_JSON_TYPE_WITH_OPTIONAL_FIELDS(CCFConfig); DECLARE_JSON_REQUIRED_FIELDS(CCFConfig, network); diff --git a/src/host/main.cpp b/src/host/main.cpp index 3d5aac5607a7..04c763099810 100644 --- a/src/host/main.cpp +++ b/src/host/main.cpp @@ -559,6 +559,21 @@ int main(int argc, char** argv) files::try_slurp_string(security_policy_file); } + if (startup_config.attestation.snp_uvm_endorsements_file.has_value()) + { + auto snp_uvm_endorsements_file = + startup_config.attestation.snp_uvm_endorsements_file.value(); + LOG_DEBUG_FMT( + "Resolving snp_uvm_endorsements_file: {}", snp_uvm_endorsements_file); + snp_uvm_endorsements_file = + nonstd::expand_envvars_in_path(snp_uvm_endorsements_file); + LOG_DEBUG_FMT( + "Resolved snp_uvm_endorsements_file: {}", snp_uvm_endorsements_file); + + startup_config.attestation.environment.uvm_endorsements = + files::try_slurp_string(snp_uvm_endorsements_file); + } + for (auto endorsement_servers_it = startup_config.attestation.snp_endorsements_servers.begin(); endorsement_servers_it != diff --git a/src/node/uvm_endorsements.h b/src/node/uvm_endorsements.h index 0d6649d55495..7cd23077bd52 100644 --- a/src/node/uvm_endorsements.h +++ b/src/node/uvm_endorsements.h @@ -51,6 +51,34 @@ namespace ccf std::string feed; }; + // Roots of trust for UVM endorsements/measurement in AMD SEV-SNP attestations + static std::vector uvm_roots_of_trust = { + // Confidential Azure Kubertnetes Service (AKS) + {"did:x509:0:sha256:I__iuL25oXEVFdTP_aBLx_eT1RPHbCQ_ECBQfYZpt9s::eku:1.3.6." + "1.4.1.311.76.59.1.2", + "ContainerPlat-AMD-UVM", + "0"}, + // Confidential Azure Container Instances (ACI) + {"did:x509:0:sha256:I__iuL25oXEVFdTP_aBLx_eT1RPHbCQ_ECBQfYZpt9s::eku:1.3.6." + "1.4.1.311.76.59.1.5", + "ConfAKS-AMD-UVM", + "0"}}; + + bool inline matches_uvm_roots_of_trust(const UVMEndorsements& endorsements) + { + for (const auto& uvm_root_of_trust : uvm_roots_of_trust) + { + if ( + uvm_root_of_trust.did == endorsements.did && + uvm_root_of_trust.feed == endorsements.feed && + uvm_root_of_trust.svn <= endorsements.svn) + { + return true; + } + } + return false; + } + namespace cose { static constexpr auto HEADER_PARAM_ISSUER = "iss"; @@ -311,6 +339,18 @@ namespace ccf phdr.feed, payload.sevsnpvm_guest_svn); - return {did, phdr.feed, payload.sevsnpvm_guest_svn}; + UVMEndorsements end{did, phdr.feed, payload.sevsnpvm_guest_svn}; + + if (!matches_uvm_roots_of_trust(end)) + { + throw std::logic_error(fmt::format( + "UVM endorsements did {}, feed {}, svn {} " + "do not match any of the known UVM roots of trust", + end.did, + end.feed, + end.svn)); + } + + return end; } } \ No newline at end of file diff --git a/tests/config.jinja b/tests/config.jinja index 849f6f7acfb8..710a199f247b 100644 --- a/tests/config.jinja +++ b/tests/config.jinja @@ -21,7 +21,8 @@ "attestation": { "snp_endorsements_servers": {{ snp_endorsements_servers|tojson }}, - "snp_security_policy_file": "{{ snp_security_policy_file }}" + "snp_security_policy_file": "{{ snp_security_policy_file }}", + "snp_uvm_endorsements_file": "{{ snp_uvm_endorsements_file }}" }, "service_data_json_file": {{ service_data_json_file|tojson }}, "command": { diff --git a/tests/infra/network.py b/tests/infra/network.py index 13081775a27f..f3c4d2b9c595 100644 --- a/tests/infra/network.py +++ b/tests/infra/network.py @@ -197,6 +197,7 @@ class Network: "tick_ms", "max_msg_size_bytes", "snp_security_policy_file", + "snp_uvm_endorsements_file", ] # Maximum delay (seconds) for updates to propagate from the primary to backups diff --git a/tests/infra/remote.py b/tests/infra/remote.py index 5d1c18914448..46e199f63ddd 100644 --- a/tests/infra/remote.py +++ b/tests/infra/remote.py @@ -620,6 +620,7 @@ def __init__( follow_redirect=True, max_uncommitted_tx_count=0, snp_security_policy_file=None, + snp_uvm_endorsements_file=None, **kwargs, ): """ @@ -742,6 +743,12 @@ def __init__( "$UVM_SECURITY_CONTEXT_DIR/security-policy-base64" ) + # Default snp_uvm_endorsements_file if not set + if snp_uvm_endorsements_file is None: + snp_uvm_endorsements_file = ( + "$UVM_SECURITY_CONTEXT_DIR/reference-info-base64" + ) + # Validate consensus timers if ( election_timeout_ms is not None @@ -803,6 +810,7 @@ def __init__( follow_redirect=follow_redirect, max_uncommitted_tx_count=max_uncommitted_tx_count, snp_security_policy_file=snp_security_policy_file, + snp_uvm_endorsements_file=snp_uvm_endorsements_file, **kwargs, )