261 lines
10 KiB
C++
261 lines
10 KiB
C++
// Profile validation: round-trip a valid profile, reject the obvious mistakes.
|
|
|
|
#include "glove/container/profile.hpp"
|
|
|
|
#include <cstdio>
|
|
#include <string>
|
|
|
|
namespace {
|
|
|
|
#define REQUIRE(cond) \
|
|
do { \
|
|
if (!(cond)) { \
|
|
std::fprintf(stderr, "REQUIRE failed: %s @ %s:%d\n", #cond, __FILE__, __LINE__); \
|
|
return 1; \
|
|
} \
|
|
} while (0)
|
|
|
|
auto run() -> int {
|
|
{
|
|
glove::container::profile p;
|
|
p.filesystem.push_back({.path = "/agent/workspace", .writable = true});
|
|
p.filesystem.push_back({.path = "/usr/lib", .writable = false});
|
|
p.home_dir = "/agent/workspace";
|
|
p.temp_dir = "/agent/workspace/tmp";
|
|
p.environment = {"PATH=/usr/bin:/bin", "XAI_API_KEY=test"};
|
|
auto out = glove::container::validate(p);
|
|
REQUIRE(out.has_value());
|
|
REQUIRE(out->filesystem.size() == 2);
|
|
REQUIRE(out->environment.size() == 2);
|
|
}
|
|
|
|
{
|
|
glove::container::profile p;
|
|
p.required_limits = glove::container::resource_limits{
|
|
.cpu_time_ms = 60'000,
|
|
.memory_bytes = 512 * 1024 * 1024,
|
|
.pids = 128,
|
|
.wall_time_ms = 120'000,
|
|
.disk_bytes = 1024 * 1024 * 1024,
|
|
.terminal_output_bytes = 16 * 1024 * 1024,
|
|
};
|
|
auto out = glove::container::validate(p);
|
|
REQUIRE(out.has_value());
|
|
auto unsupported = glove::container::require_resource_enforcement(*out, {});
|
|
REQUIRE(!unsupported.has_value());
|
|
REQUIRE(unsupported.error().find("cpu_time") != std::string::npos);
|
|
REQUIRE(unsupported.error().find("observable_receipts") != std::string::npos);
|
|
|
|
glove::container::resource_enforcement_capabilities complete{
|
|
.cpu_time = glove::container::enforcement_mechanism::cgroup_v2,
|
|
.memory = glove::container::enforcement_mechanism::cgroup_v2,
|
|
.pids = glove::container::enforcement_mechanism::cgroup_v2,
|
|
.wall_time = glove::container::enforcement_mechanism::watchdog,
|
|
.disk = glove::container::enforcement_mechanism::filesystem_quota,
|
|
.terminal_output = glove::container::enforcement_mechanism::byte_counter,
|
|
.receipt_schema_version = 1,
|
|
};
|
|
REQUIRE(complete.complete());
|
|
REQUIRE(glove::container::require_resource_enforcement(*out, complete).has_value());
|
|
|
|
const std::string profile_digest(64, 'a');
|
|
glove::container::resource_enforcement_receipt receipt{
|
|
.schema_version = 1,
|
|
.profile_digest = profile_digest,
|
|
.backend = glove::container::sandbox_backend::linux_production,
|
|
.backend_id = "linux-production:cgroup-v2",
|
|
.configured_limits = *out->required_limits,
|
|
.mechanisms = complete,
|
|
.observed =
|
|
{
|
|
.cpu_time_ms = 500,
|
|
.peak_memory_bytes = 16 * 1024 * 1024,
|
|
.peak_pids = 2,
|
|
.wall_time_ms = 750,
|
|
.disk_bytes = 4096,
|
|
.terminal_output_bytes = 1024,
|
|
},
|
|
.termination_cause = glove::container::resource_termination_cause::exited,
|
|
.exit_code = 0,
|
|
.started_at_ms = 1'000,
|
|
.finished_at_ms = 1'750,
|
|
.library_projections = {},
|
|
.retained_changes = {},
|
|
};
|
|
REQUIRE(
|
|
glove::container::validate_resource_enforcement_receipt(
|
|
receipt,
|
|
*out->required_limits,
|
|
complete,
|
|
glove::container::sandbox_backend::linux_production,
|
|
profile_digest
|
|
)
|
|
.has_value()
|
|
);
|
|
|
|
auto projected = receipt;
|
|
projected.library_projections.push_back({
|
|
.projection_id = "sage-core",
|
|
.destination_alias = "libraries",
|
|
.target_path = "/opt/sage/library-bundles/" + std::string(64U, 'd') + ".json",
|
|
.content_digest = std::string(64U, 'd'),
|
|
});
|
|
REQUIRE(
|
|
glove::container::validate_resource_enforcement_receipt(
|
|
projected,
|
|
*out->required_limits,
|
|
complete,
|
|
glove::container::sandbox_backend::linux_production,
|
|
profile_digest
|
|
)
|
|
.has_value()
|
|
);
|
|
projected.library_projections.front().target_path = "/tmp/library.json";
|
|
REQUIRE(!glove::container::validate_resource_enforcement_receipt(
|
|
projected,
|
|
*out->required_limits,
|
|
complete,
|
|
glove::container::sandbox_backend::linux_production,
|
|
profile_digest
|
|
)
|
|
.has_value());
|
|
|
|
auto mismatched_digest = receipt;
|
|
mismatched_digest.profile_digest[0] = 'b';
|
|
REQUIRE(!glove::container::validate_resource_enforcement_receipt(
|
|
mismatched_digest,
|
|
*out->required_limits,
|
|
complete,
|
|
glove::container::sandbox_backend::linux_production,
|
|
profile_digest
|
|
)
|
|
.has_value());
|
|
|
|
auto partial_mechanisms = receipt;
|
|
partial_mechanisms.mechanisms.disk = glove::container::enforcement_mechanism::unavailable;
|
|
REQUIRE(!glove::container::validate_resource_enforcement_receipt(
|
|
partial_mechanisms,
|
|
*out->required_limits,
|
|
complete,
|
|
glove::container::sandbox_backend::linux_production,
|
|
profile_digest
|
|
)
|
|
.has_value());
|
|
|
|
auto invalid_termination = receipt;
|
|
invalid_termination.termination_cause =
|
|
glove::container::resource_termination_cause::wall_time_limit;
|
|
REQUIRE(!glove::container::validate_resource_enforcement_receipt(
|
|
invalid_termination,
|
|
*out->required_limits,
|
|
complete,
|
|
glove::container::sandbox_backend::linux_production,
|
|
profile_digest
|
|
)
|
|
.has_value());
|
|
|
|
auto remote_claim = receipt;
|
|
remote_claim.backend = glove::container::sandbox_backend::remote_linux_container;
|
|
remote_claim.backend_id = "remote-linux-container:trusted-operator-v1";
|
|
remote_claim.observation_authority =
|
|
glove::container::receipt_observation_authority::trusted_remote_claim;
|
|
remote_claim.independently_verified = false;
|
|
REQUIRE(!glove::container::validate_resource_enforcement_receipt(
|
|
remote_claim,
|
|
*out->required_limits,
|
|
complete,
|
|
glove::container::sandbox_backend::remote_linux_container,
|
|
profile_digest
|
|
)
|
|
.has_value());
|
|
REQUIRE(
|
|
glove::container::validate_trusted_remote_resource_claim(
|
|
remote_claim, *out->required_limits, profile_digest
|
|
)
|
|
.has_value()
|
|
);
|
|
auto lying_authority = remote_claim;
|
|
lying_authority.observation_authority =
|
|
glove::container::receipt_observation_authority::local_enforcement;
|
|
REQUIRE(!glove::container::validate_trusted_remote_resource_claim(
|
|
lying_authority, *out->required_limits, profile_digest
|
|
)
|
|
.has_value());
|
|
auto lying_verification = remote_claim;
|
|
lying_verification.independently_verified = true;
|
|
REQUIRE(!glove::container::validate_trusted_remote_resource_claim(
|
|
lying_verification, *out->required_limits, profile_digest
|
|
)
|
|
.has_value());
|
|
auto retained_remote = remote_claim;
|
|
retained_remote.retained_changes.push_back({});
|
|
REQUIRE(!glove::container::validate_trusted_remote_resource_claim(
|
|
retained_remote, *out->required_limits, profile_digest
|
|
)
|
|
.has_value());
|
|
}
|
|
|
|
{
|
|
glove::container::profile p;
|
|
p.required_limits = glove::container::resource_limits{
|
|
.cpu_time_ms = 1,
|
|
.memory_bytes = 0,
|
|
.pids = 1,
|
|
.wall_time_ms = 1,
|
|
.disk_bytes = 1,
|
|
.terminal_output_bytes = 1,
|
|
};
|
|
auto out = glove::container::validate(p);
|
|
REQUIRE(!out.has_value());
|
|
REQUIRE(out.error().find("non-zero") != std::string::npos);
|
|
}
|
|
|
|
{
|
|
glove::container::profile p;
|
|
p.filesystem.push_back({.path = "relative/path"});
|
|
auto out = glove::container::validate(p);
|
|
REQUIRE(!out.has_value());
|
|
REQUIRE(out.error().find("absolute") != std::string::npos);
|
|
}
|
|
|
|
{
|
|
glove::container::profile p;
|
|
p.filesystem.push_back({.path = "/", .writable = true});
|
|
auto out = glove::container::validate(p);
|
|
REQUIRE(!out.has_value());
|
|
REQUIRE(out.error().find("root") != std::string::npos);
|
|
}
|
|
|
|
{
|
|
glove::container::profile p;
|
|
p.filesystem.push_back({.path = "/agent/workspace"});
|
|
p.filesystem.push_back({.path = "/agent/workspace/../workspace"});
|
|
auto out = glove::container::validate(p);
|
|
REQUIRE(!out.has_value());
|
|
REQUIRE(out.error().find("duplicate") != std::string::npos);
|
|
}
|
|
|
|
{
|
|
glove::container::profile p;
|
|
p.environment = {"TOKEN=one", "TOKEN=two"};
|
|
auto out = glove::container::validate(p);
|
|
REQUIRE(!out.has_value());
|
|
REQUIRE(out.error().find("duplicate environment") != std::string::npos);
|
|
}
|
|
|
|
{
|
|
glove::container::profile p;
|
|
p.filesystem.push_back({.path = ""});
|
|
auto out = glove::container::validate(p);
|
|
REQUIRE(!out.has_value());
|
|
REQUIRE(out.error().find("empty path") != std::string::npos);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
auto main() -> int {
|
|
return run();
|
|
}
|