Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove sgx dependency in code update test #6448

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
5 changes: 2 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1163,9 +1163,8 @@ if(BUILD_TESTS)

add_e2e_test(
NAME code_update_test
PYTHON_SCRIPT ${CMAKE_SOURCE_DIR}/tests/code_update.py
ADDITIONAL_ARGS --oe-binary ${OE_BINDIR} --js-app-bundle
${CMAKE_SOURCE_DIR}/samples/apps/logging/js
PYTHON_SCRIPT ${CMAKE_SOURCE_DIR}/tests/code_update.py --js-app-bundle
${CMAKE_SOURCE_DIR}/samples/apps/logging/js
)

if(BUILD_TPCC)
Expand Down
15 changes: 13 additions & 2 deletions include/ccf/pal/attestation.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "ccf/ds/quote_info.h"
#include "ccf/pal/measurement.h"
#include "ccf/pal/snp_ioctl.h"
#include "ds/files.h"

#include <fcntl.h>
#include <functional>
Expand Down Expand Up @@ -200,9 +201,19 @@ namespace ccf::pal
RetrieveEndorsementCallback endorsement_cb,
const snp::EndorsementsServers& endorsements_servers = {})
{
// Once we have merged cchost + .so, we can take the
// digest of argv[0] instead of using a files.
std::vector<uint8_t> so_digest;
if (files::exists("VIRTUAL_ENCLAVE_DIGEST"))
{
so_digest = files::slurp("VIRTUAL_ENCLAVE_DIGEST");
}
std::string so_digest_str = std::string(so_digest.begin(), so_digest.end());

endorsement_cb(
{
.format = QuoteFormat::insecure_virtual,
.quote = ds::from_hex(so_digest_str),
},
{});
}
Expand Down Expand Up @@ -246,8 +257,8 @@ namespace ccf::pal
throw std::logic_error(
"Cannot verify virtual attestation report if node is SEV-SNP");
}
// For now, virtual resembles SGX (mostly for historical reasons)
measurement = SgxAttestationMeasurement();
// For historical reasons, virtual resembles SGX
measurement = SgxAttestationMeasurement(quote_info.quote);
report_data = SgxAttestationReportData();
}
else if (quote_info.format == QuoteFormat::amd_sev_snp_v1)
Expand Down
16 changes: 8 additions & 8 deletions src/ds/files.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ namespace files
* @param file file to check
* @return true if the file exists.
*/
bool exists(const std::string& file)
static inline bool exists(const std::string& file)
{
std::ifstream f(file.c_str());
return f.good();
Expand All @@ -40,7 +40,7 @@ namespace files
* exist. If true, an empty vector is returned. If false, the process exits
* @return vector<uint8_t> the file contents as bytes.
*/
std::vector<uint8_t> slurp(const std::string& file, bool optional = false)
static inline std::vector<uint8_t> slurp(const std::string& file, bool optional = false)
{
std::ifstream f(file, std::ios::binary | std::ios::ate);

Expand Down Expand Up @@ -79,13 +79,13 @@ namespace files
* exist. If true, an empty vector is returned. If false, the process exits
* @return std::string the file contents as a string.
*/
std::string slurp_string(const std::string& file, bool optional = false)
static inline std::string slurp_string(const std::string& file, bool optional = false)
{
auto v = slurp(file, optional);
return {v.begin(), v.end()};
}

std::optional<std::string> try_slurp_string(const std::string& file)
static inline std::optional<std::string> try_slurp_string(const std::string& file)
{
if (!fs::exists(file))
{
Expand All @@ -103,7 +103,7 @@ namespace files
* exits
* @return nlohmann::json JSON object containing the parsed file
*/
nlohmann::json slurp_json(const std::string& file, bool optional = false)
static inline nlohmann::json slurp_json(const std::string& file, bool optional = false)
{
auto v = slurp(file, optional);
if (v.size() == 0)
Expand All @@ -118,7 +118,7 @@ namespace files
* @param data vector to write
* @param file the path
*/
void dump(const std::vector<uint8_t>& data, const std::string& file)
static inline void dump(const std::vector<uint8_t>& data, const std::string& file)
{
using namespace std;
ofstream f(file, ios::binary | ios::trunc);
Expand All @@ -133,12 +133,12 @@ namespace files
* @param data string to write
* @param file the path
*/
void dump(const std::string& data, const std::string& file)
static inline void dump(const std::string& data, const std::string& file)
{
return dump(std::vector<uint8_t>(data.begin(), data.end()), file);
}

void rename(const fs::path& src, const fs::path& dst)
static inline void rename(const fs::path& src, const fs::path& dst)
{
std::error_code ec;
fs::rename(src, dst, ec);
Expand Down
60 changes: 9 additions & 51 deletions tests/code_update.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,38 +20,6 @@
VIRTUAL_CODE_ID = "0" * 64


@reqs.description("Verify node evidence")
def test_verify_quotes(network, args):
if args.enclave_platform == "virtual":
LOG.warning("Skipping quote test with virtual enclave")
return network
elif snp.IS_SNP:
LOG.warning(
"Skipping quote test until there is a separate utility to verify SNP quotes"
)
return network

LOG.info("Check the network is stable")
primary, _ = network.find_primary()
check_can_progress(primary)

for node in network.get_joined_nodes():
LOG.info(f"Verifying quote for node {node.node_id}")
cafile = os.path.join(network.common_dir, "service_cert.pem")
assert (
infra.proc.ccall(
"verify_quote.sh",
f"https://{node.get_public_rpc_host()}:{node.get_public_rpc_port()}",
"--cacert",
f"{cafile}",
log_output=True,
).returncode
== 0
), f"Quote verification for node {node.node_id} failed"

return network


@reqs.description("Test the SNP measurements table")
@reqs.snp_only()
def test_snp_measurements_tables(network, args):
Expand Down Expand Up @@ -331,18 +299,14 @@ def test_add_node_with_no_uvm_endorsements(network, args):

@reqs.description("Node with bad code fails to join")
def test_add_node_with_bad_code(network, args):
if args.enclave_platform != "sgx":
LOG.warning("Skipping test_add_node_with_bad_code with non-sgx enclave")
return network

replacement_package = (
"samples/apps/logging/liblogging"
if args.package == "libjs_generic"
else "libjs_generic"
)

new_code_id = infra.utils.get_code_id(
args.enclave_type, args.enclave_platform, args.oe_binary, replacement_package
args.enclave_type, args.enclave_platform, replacement_package
)

LOG.info(f"Adding a node with unsupported code id {new_code_id}")
Expand Down Expand Up @@ -378,10 +342,10 @@ def test_update_all_nodes(network, args):
primary, _ = network.find_nodes()

first_code_id = infra.utils.get_code_id(
args.enclave_type, args.enclave_platform, args.oe_binary, args.package
args.enclave_type, args.enclave_platform, args.package
)
new_code_id = infra.utils.get_code_id(
args.enclave_type, args.enclave_platform, args.oe_binary, replacement_package
args.enclave_type, args.enclave_platform, replacement_package
)

if args.enclave_platform == "virtual":
Expand Down Expand Up @@ -433,8 +397,6 @@ def test_update_all_nodes(network, args):
if node.node_id == primary.node_id:
new_primary, _ = network.wait_for_new_primary(primary)
primary = new_primary
# See https://github.com/microsoft/CCF/issues/1713
check_can_progress(new_primary)
node.stop()

LOG.info("Check the network is still functional")
Expand All @@ -458,7 +420,6 @@ def test_proposal_invalidation(network, args):
temp_code_id = infra.utils.get_code_id(
args.enclave_type,
args.enclave_platform,
args.oe_binary,
get_replacement_package(args),
)
network.consortium.add_new_code(primary, temp_code_id)
Expand All @@ -484,7 +445,11 @@ def run(args):
) as network:
network.start_and_open(args)

test_verify_quotes(network, args)
test_add_node_with_bad_code(network, args)
return
# NB: Assumes the current nodes are still using args.package, so must run before test_proposal_invalidation
test_proposal_invalidation(network, args)

if snp.IS_SNP:
test_snp_measurements_tables(network, args)
test_add_node_with_no_uvm_endorsements(network, args)
Expand All @@ -493,16 +458,9 @@ def run(args):
test_add_node_remove_trusted_security_policy(network, args)
test_start_node_with_mismatched_host_data(network, args)
test_add_node_with_bad_host_data(network, args)
test_add_node_with_bad_code(network, args)
# NB: Assumes the current nodes are still using args.package, so must run before test_proposal_invalidation
test_proposal_invalidation(network, args)

if not snp.IS_SNP:
else:
test_update_all_nodes(network, args)

# Run again at the end to confirm current nodes are acceptable
test_verify_quotes(network, args)


if __name__ == "__main__":
args = infra.e2e_args.cli_args()
Expand Down
11 changes: 11 additions & 0 deletions tests/infra/remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
# Licensed under the Apache 2.0 License.
import os
import time
import glob
import hashlib
from enum import Enum, auto
import paramiko
import subprocess
Expand Down Expand Up @@ -423,6 +425,15 @@ def _setup_files(self, use_links: bool):
dst_path = os.path.join(self.root, os.path.basename(path))
self.cp(path, dst_path)

vencs = glob.glob(os.path.join(self.root, "*.virtual.so"))
for venc in vencs:
with open(venc, "rb") as f:
digest = hashlib.sha256(f.read()).hexdigest()
with open(
os.path.join(self.root, "VIRTUAL_ENCLAVE_DIGEST"), "w"
) as f:
f.write(digest)

def get(
self,
src_path,
Expand Down
23 changes: 2 additions & 21 deletions tests/infra/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,11 @@
# Licensed under the Apache 2.0 License.
import infra.path
import hashlib
import os
import subprocess


def get_code_id(
enclave_type, enclave_platform, oe_binary_dir, package, library_dir="."
):
def get_code_id(enclave_type, enclave_platform, package, library_dir="."):
lib_path = infra.path.build_lib_path(
package, enclave_type, enclave_platform, library_dir
)

if enclave_platform == "sgx":
res = subprocess.run(
[os.path.join(oe_binary_dir, "oesign"), "dump", "-e", lib_path],
capture_output=True,
check=True,
)
lines = [
line
for line in res.stdout.decode().split(os.linesep)
if line.startswith("mrenclave=")
]

return lines[0].split("=")[1]
else:
# Virtual and SNP
return hashlib.sha256(lib_path.encode()).hexdigest()
return hashlib.sha256(lib_path.encode()).hexdigest()
2 changes: 0 additions & 2 deletions tests/lts_compatibility.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,6 @@ def run_code_upgrade_from(
new_code_id = infra.utils.get_code_id(
args.enclave_type,
args.enclave_platform,
args.oe_binary,
args.package,
library_dir=to_library_dir,
)
Expand Down Expand Up @@ -293,7 +292,6 @@ def run_code_upgrade_from(
old_code_id = infra.utils.get_code_id(
args.enclave_type,
args.enclave_platform,
args.oe_binary,
args.package,
library_dir=from_library_dir,
)
Expand Down
Loading