#include "glove/container/receipt_producer.hpp" #include "glove/control/receipt_audit_protocol.hpp" #include "glove/control/receipt_audit_unix_server.hpp" #include "receipt_audit_unix_server_detail.hpp" #include "receipt_audit_wire.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace wire_test { struct rpc_error { std::string code; std::string message; }; struct rpc_response { std::string jsonrpc; std::string id; std::optional result; std::optional error; }; struct page_result { std::uint8_t schema_version = 0; std::vector envelopes; std::vector refinement_envelopes; bool has_more = false; glove::container::receipt_audit_anchor local_anchor; }; } // namespace wire_test namespace { #define REQUIRE(cond) \ do { \ if (!(cond)) { \ std::fprintf(stderr, "REQUIRE failed: %s @ %s:%d\n", #cond, __FILE__, __LINE__); \ return 1; \ } \ } while (0) constexpr std::string_view audit_key = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"; constexpr std::string_view audit_key_id = "b97d6f8d2ae381761ea00f360c230cf75e8de5fdc6a8d25624a5c36b97f0d475"; constexpr std::string_view bootstrap_secret = "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"; constexpr std::string_view plan_digest = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; class temporary_directory { public: temporary_directory() { std::string pattern = "/tmp/glove-receipt-server-test-XXXXXX"; if (char* created = ::mkdtemp(pattern.data()); created != nullptr) { root_ = created; } } temporary_directory(const temporary_directory&) = delete; auto operator=(const temporary_directory&) -> temporary_directory& = delete; ~temporary_directory() { std::error_code error; std::filesystem::remove_all(root_, error); } [[nodiscard]] auto root() const -> const std::filesystem::path& { return root_; } private: std::filesystem::path root_; }; class unique_fd { public: explicit unique_fd(int value = -1) noexcept : value_{value} {} unique_fd(const unique_fd&) = delete; auto operator=(const unique_fd&) -> unique_fd& = delete; unique_fd(unique_fd&& other) noexcept : value_{std::exchange(other.value_, -1)} {} auto operator=(unique_fd&&) -> unique_fd& = delete; ~unique_fd() { if (value_ >= 0) { ::close(value_); } } [[nodiscard]] auto get() const noexcept -> int { return value_; } private: int value_; }; auto write_owner_only(const std::filesystem::path& path, std::string_view value) -> bool { std::ofstream output{path, std::ios::binary | std::ios::trunc}; output << value << '\n'; output.flush(); return output.good() && ::chmod(path.c_str(), 0600) == 0; } auto receipt() -> glove::container::resource_enforcement_receipt { using namespace glove::container; return { .schema_version = 1, .profile_digest = std::string(64, 'c'), .backend = sandbox_backend::linux_production, .backend_id = "linux-production:cgroup-v2-v1", .configured_limits = { .cpu_time_ms = 60'000, .memory_bytes = std::uint64_t{512} * 1024U * 1024U, .pids = 128, .wall_time_ms = 120'000, .disk_bytes = std::uint64_t{1024} * 1024U * 1024U, .terminal_output_bytes = std::uint64_t{16} * 1024U * 1024U, }, .mechanisms = { .cpu_time = enforcement_mechanism::cgroup_v2, .memory = enforcement_mechanism::cgroup_v2, .pids = enforcement_mechanism::cgroup_v2, .wall_time = enforcement_mechanism::watchdog, .disk = enforcement_mechanism::filesystem_quota, .terminal_output = enforcement_mechanism::byte_counter, .receipt_schema_version = 1, }, .observed = { .cpu_time_ms = 500, .peak_memory_bytes = std::uint64_t{16} * 1024U * 1024U, .peak_pids = 2, .wall_time_ms = 750, .disk_bytes = 4096, .terminal_output_bytes = 1024, }, .termination_cause = resource_termination_cause::exited, .exit_code = 0, .started_at_ms = 1'000, .finished_at_ms = 1'750, .library_projections = {}, .retained_changes = {}, }; } auto write_exact(int descriptor, const void* input, std::size_t size) -> bool { const auto* bytes = static_cast(input); std::size_t written = 0; while (written < size) { const auto result = ::write(descriptor, bytes + written, size - written); if (result < 0 && errno == EINTR) { continue; } if (result <= 0) { return false; } written += static_cast(result); } return true; } auto read_exact(int descriptor, void* output, std::size_t size) -> bool { auto* bytes = static_cast(output); std::size_t consumed = 0; while (consumed < size) { const auto result = ::read(descriptor, bytes + consumed, size - consumed); if (result < 0 && errno == EINTR) { continue; } if (result <= 0) { return false; } consumed += static_cast(result); } return true; } auto connect_to(const std::filesystem::path& socket_path) -> unique_fd { unique_fd descriptor{::socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0)}; if (descriptor.get() < 0) { return unique_fd{}; } ::sockaddr_un address{}; address.sun_family = AF_UNIX; const auto value = socket_path.string(); if (value.size() >= sizeof(address.sun_path)) { return unique_fd{}; } std::memcpy(address.sun_path, value.c_str(), value.size() + 1U); if (::connect( descriptor.get(), reinterpret_cast(&address), sizeof(address) ) != 0) { return unique_fd{}; } return descriptor; } auto transact(const std::filesystem::path& socket_path, std::string_view frame) -> std::optional { auto descriptor = connect_to(socket_path); if (descriptor.get() < 0) { return std::nullopt; } const auto size = htonl(static_cast(frame.size())); if (!write_exact(descriptor.get(), &size, sizeof(size)) || !write_exact(descriptor.get(), frame.data(), frame.size())) { return std::nullopt; } std::uint32_t response_size = 0; if (!read_exact(descriptor.get(), &response_size, sizeof(response_size))) { return std::nullopt; } const auto decoded_size = ntohl(response_size); if (decoded_size == 0 || decoded_size > glove::control::max_control_frame_bytes) { return std::nullopt; } std::string response(decoded_size, '\0'); if (!read_exact(descriptor.get(), response.data(), response.size())) { return std::nullopt; } return response; } auto make_request( std::string_view id, std::string_view method, std::string_view payload, std::optional idempotency_key = std::nullopt ) -> std::string { std::string request = "{\"jsonrpc\":\"2.0\",\"id\":\"" + std::string{id} + "\",\"method\":\"" + std::string{method} + "\",\"params\":{\"schema_version\":1,\"bootstrap_secret\":\"" + std::string{bootstrap_secret} + "\",\"deadline_ms\":4102444800000"; if (idempotency_key) { request += ",\"idempotency_key\":\"" + std::string{*idempotency_key} + "\""; } request += ",\"payload\":" + std::string{payload} + "}}"; return request; } auto decode_response(std::string_view frame) -> std::optional { wire_test::rpc_response response; constexpr glz::opts strict{.error_on_unknown_keys = true}; if (glz::read(response, frame)) { return std::nullopt; } return response; } auto verify_peer_credential_contract() -> int { int descriptors[2] = {-1, -1}; REQUIRE(::socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, descriptors) == 0); const unique_fd local{descriptors[0]}; const unique_fd peer{descriptors[1]}; const auto owner = ::geteuid(); REQUIRE(glove::control::detail::verify_peer_owner(local.get(), owner).has_value()); const auto wrong_owner = owner == std::numeric_limits<::uid_t>::max() ? owner - 1U : owner + 1U; REQUIRE(!glove::control::detail::verify_peer_owner(local.get(), wrong_owner).has_value()); return 0; } auto run() -> int { REQUIRE(verify_peer_credential_contract() == 0); temporary_directory temp; REQUIRE(!temp.root().empty()); const auto socket_path = temp.root() / "gloved.sock"; const auto secret_path = temp.root() / "bootstrap-secret"; const auto audit_key_path = temp.root() / "audit.key"; const auto journal_path = temp.root() / "receipts.journal"; REQUIRE(write_owner_only(secret_path, bootstrap_secret)); REQUIRE(write_owner_only(audit_key_path, audit_key)); const glove::container::receipt_audit_anchor genesis{ .key_id = std::string{audit_key_id}, .sequence = 0, .head_hmac = std::string(64, '0'), }; const glove::container::receipt_audit_producer_config producer_config{ .key_path = audit_key_path, .journal_path = journal_path, }; auto producer = glove::container::receipt_audit_producer::initialize(producer_config); REQUIRE(producer.has_value()); REQUIRE((*producer)->anchor() == genesis); REQUIRE((*producer)->acknowledge_bootstrap(genesis).has_value()); auto reservation = (*producer)->reserve_terminal(); REQUIRE(reservation.has_value()); auto terminal = (*producer)->commit_terminal(std::move(*reservation), "session-1", plan_digest, receipt()); REQUIRE(terminal.has_value()); const auto terminal_anchor = (*producer)->anchor(); producer->reset(); const glove::control::receipt_audit_unix_server_config server_config{ .socket_path = socket_path, .bootstrap_secret_path = secret_path, .producer = producer_config, .plan_validator = {}, .sessions = {}, .session_runtime = {}, .path_exposures = {}, .materialization_root = {}, .io_timeout_ms = 100, }; auto server = glove::control::receipt_audit_unix_server::create(server_config); REQUIRE(server.has_value()); struct stat socket_status{}; REQUIRE(::lstat(socket_path.c_str(), &socket_status) == 0); REQUIRE(S_ISSOCK(socket_status.st_mode)); REQUIRE(socket_status.st_uid == ::geteuid()); REQUIRE((socket_status.st_mode & 0777U) == 0600U); std::optional server_error; std::thread server_thread{[&] { for (std::size_t request = 0; request < 2; ++request) { if (auto served = (*server)->serve_one(); !served) { server_error = served.error(); return; } } }}; const auto genesis_json = glz::write_json(genesis); REQUIRE(genesis_json.has_value()); const auto page_payload = "{\"sage_anchor\":" + *genesis_json + ",\"limit\":1000}"; auto page_frame = transact(socket_path, make_request("page-1", "verify_audit_chain", page_payload)); REQUIRE(page_frame.has_value()); auto page_response = decode_response(*page_frame); REQUIRE(page_response.has_value()); REQUIRE(page_response->result.has_value()); wire_test::page_result page; REQUIRE(!glz::read(page, page_response->result->str)); REQUIRE(page.envelopes.size() == 1); REQUIRE(page.envelopes.front() == *terminal); REQUIRE(page.local_anchor == terminal_anchor); const auto terminal_anchor_json = glz::write_json(terminal_anchor); REQUIRE(terminal_anchor_json.has_value()); const auto ack_payload = "{\"anchor\":" + *terminal_anchor_json + "}"; auto ack_frame = transact( socket_path, make_request("ack-1", "acknowledge_audit_chain", ack_payload, "receipt-audit-ack-1") ); REQUIRE(ack_frame.has_value()); auto ack_response = decode_response(*ack_frame); REQUIRE(ack_response.has_value()); REQUIRE(ack_response->result.has_value()); server_thread.join(); REQUIRE(!server_error.has_value()); server->reset(); REQUIRE(::access(socket_path.c_str(), F_OK) != 0); REQUIRE(::chmod(secret_path.c_str(), 0644) == 0); REQUIRE(!glove::control::receipt_audit_unix_server::create(server_config).has_value()); REQUIRE(::chmod(secret_path.c_str(), 0600) == 0); REQUIRE(::chmod(temp.root().c_str(), 0755) == 0); REQUIRE(!glove::control::receipt_audit_unix_server::create(server_config).has_value()); REQUIRE(::chmod(temp.root().c_str(), 0700) == 0); auto timeout_server = glove::control::receipt_audit_unix_server::create(server_config); REQUIRE(timeout_server.has_value()); std::optional timeout_error; std::thread timeout_thread{[&] { auto served = (*timeout_server)->serve_one(); if (!served) { timeout_error = served.error(); } }}; auto stalled = connect_to(socket_path); REQUIRE(stalled.get() >= 0); timeout_thread.join(); REQUIRE(timeout_error.has_value()); REQUIRE(timeout_error->find("timed out") != std::string::npos); return 0; } } // namespace auto main() -> int { return run(); }