glove/tests/control/receipt_audit_unix_server_test.cpp
trvon a8137db76b
feat(control): neutral guest channels, quarantine recovery, capability sealing, local-service proxy
Introduce the framed guest_channel_transport with shared deadlines and
std::jthread ownership, metadata-only retired-schema quarantine with
fail-closed integrity, stateless frozen-catalog validators with
capability pinned to zero, and the generic local-service proxy with
descriptor-pinned endpoints, alias-only guest sockets, audit-gated
delivery, and a Linux managed runtime capability derived only from a
registry-bound operational factory. Sage vocabulary is confined to
src/adapters/sage.
2026-08-30 20:28:41 -06:00

410 lines
14 KiB
C++

#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 <arpa/inet.h>
#include <glaze/glaze.hpp>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <unistd.h>
#include <cerrno>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <limits>
#include <optional>
#include <string>
#include <string_view>
#include <system_error>
#include <thread>
#include <utility>
#include <vector>
namespace wire_test {
struct rpc_error {
std::string code;
std::string message;
};
struct rpc_response {
std::string jsonrpc;
std::string id;
std::optional<glz::raw_json> result;
std::optional<rpc_error> error;
};
struct page_result {
std::uint8_t schema_version = 0;
std::vector<glove::container::authenticated_resource_enforcement_receipt> envelopes;
std::vector<glove::container::authenticated_refinement_evaluation_receipt> 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<const std::byte*>(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<std::size_t>(result);
}
return true;
}
auto read_exact(int descriptor, void* output, std::size_t size) -> bool {
auto* bytes = static_cast<std::byte*>(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<std::size_t>(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<const ::sockaddr*>(&address), sizeof(address)
) != 0) {
return unique_fd{};
}
return descriptor;
}
auto transact(const std::filesystem::path& socket_path, std::string_view frame)
-> std::optional<std::string> {
auto descriptor = connect_to(socket_path);
if (descriptor.get() < 0) {
return std::nullopt;
}
const auto size = htonl(static_cast<std::uint32_t>(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<std::string_view> 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> {
wire_test::rpc_response response;
constexpr glz::opts strict{.error_on_unknown_keys = true};
if (glz::read<strict>(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 = {},
.local_services = {},
.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<std::string> 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<glz::opts{.error_on_unknown_keys = true}>(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<std::string> 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();
}