glove/tests/supervisor/path_alias_test.cpp
2026-07-19 21:06:11 -04:00

281 lines
11 KiB
C++

#include "glove/supervisor/path_alias.hpp"
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <cstdio>
#include <filesystem>
#include <fstream>
#include <string>
#include <utility>
#include <vector>
namespace {
#define REQUIRE(cond) \
do { \
if (!(cond)) { \
std::fprintf(stderr, "REQUIRE failed: %s @ %s:%d\n", #cond, __FILE__, __LINE__); \
return 1; \
} \
} while (0)
class temporary_tree {
public:
temporary_tree() {
std::string pattern = "/tmp/glove-path-alias-test-XXXXXX";
char* created = ::mkdtemp(pattern.data());
if (created != nullptr) {
root_ = std::filesystem::canonical(created);
}
}
temporary_tree(const temporary_tree&) = delete;
auto operator=(const temporary_tree&) -> temporary_tree& = delete;
~temporary_tree() {
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_;
};
auto read_mode() -> glove::supervisor::path_access_policy {
return {
.access = glove::supervisor::path_access::read,
.materialization = glove::supervisor::path_materialization::bind,
.create_policy = glove::supervisor::path_create_policy::never,
.cleanup_policy = glove::supervisor::path_cleanup_policy::retain,
.max_bytes = 0,
};
}
auto ephemeral_mode() -> glove::supervisor::path_access_policy {
return {
.access = glove::supervisor::path_access::ephemeral_write,
.materialization = glove::supervisor::path_materialization::copy,
.create_policy = glove::supervisor::path_create_policy::empty_directory,
.cleanup_policy = glove::supervisor::path_cleanup_policy::remove,
.max_bytes = 1024U * 1024U,
};
}
auto direct_mode() -> glove::supervisor::path_access_policy {
return {
.access = glove::supervisor::path_access::direct_write,
.materialization = glove::supervisor::path_materialization::bind,
.create_policy = glove::supervisor::path_create_policy::never,
.cleanup_policy = glove::supervisor::path_cleanup_policy::retain,
.max_bytes = 0,
};
}
auto policy_for(const std::filesystem::path& host_path) -> glove::supervisor::path_alias_policy {
return {
.alias = "sage_protocol",
.host_path = host_path.string(),
.target_path = "/workspace/sage-protocol",
.max_ttl_secs = 3600,
.access = {read_mode(), ephemeral_mode(), direct_mode()},
};
}
auto run() -> int {
temporary_tree tree;
REQUIRE(!tree.root().empty());
const auto source = tree.root() / "source";
const auto replacement = tree.root() / "replacement";
REQUIRE(std::filesystem::create_directory(source));
REQUIRE(std::filesystem::create_directory(replacement));
{
std::ofstream marker{source / "marker"};
marker << "original";
}
auto registry = glove::supervisor::path_alias_registry::build({policy_for(source)});
REQUIRE(registry.has_value());
const glove::supervisor::path_grant_request read_request{
.alias = "sage_protocol",
.access = glove::supervisor::path_access::read,
.ttl_secs = 60,
.max_bytes = 0,
};
auto read_grant = registry->resolve(read_request);
REQUIRE(read_grant.has_value());
REQUIRE(read_grant->descriptor_fd() >= 0);
REQUIRE(read_grant->alias() == "sage_protocol");
REQUIRE(read_grant->target_path() == "/workspace/sage-protocol");
REQUIRE(read_grant->access() == glove::supervisor::path_access::read);
REQUIRE(read_grant->materialization() == glove::supervisor::path_materialization::bind);
REQUIRE(read_grant->ttl_secs() == 60);
REQUIRE(read_grant->max_bytes() == 0);
REQUIRE(read_grant->identity().inode != 0);
REQUIRE(read_grant->verify_identity().has_value());
auto unknown = registry->resolve({
.alias = "not_configured",
.access = glove::supervisor::path_access::read,
.ttl_secs = 60,
});
REQUIRE(!unknown.has_value());
auto raw_path = registry->resolve({
.alias = source.string(),
.access = glove::supervisor::path_access::read,
.ttl_secs = 60,
});
REQUIRE(!raw_path.has_value());
auto excessive_ttl = registry->resolve({
.alias = "sage_protocol",
.access = glove::supervisor::path_access::read,
.ttl_secs = 3601,
});
REQUIRE(!excessive_ttl.has_value());
auto ephemeral = registry->resolve({
.alias = "sage_protocol",
.access = glove::supervisor::path_access::ephemeral_write,
.ttl_secs = 120,
.max_bytes = 512U * 1024U,
});
REQUIRE(ephemeral.has_value());
REQUIRE(ephemeral->materialization() == glove::supervisor::path_materialization::copy);
REQUIRE(ephemeral->cleanup_policy() == glove::supervisor::path_cleanup_policy::remove);
REQUIRE(ephemeral->max_bytes() == 512U * 1024U);
auto unbounded = registry->resolve({
.alias = "sage_protocol",
.access = glove::supervisor::path_access::ephemeral_write,
.ttl_secs = 120,
.max_bytes = 0,
});
REQUIRE(!unbounded.has_value());
auto excessive_quota = registry->resolve({
.alias = "sage_protocol",
.access = glove::supervisor::path_access::ephemeral_write,
.ttl_secs = 120,
.max_bytes = 2U * 1024U * 1024U,
});
REQUIRE(!excessive_quota.has_value());
auto direct = registry->resolve({
.alias = "sage_protocol",
.access = glove::supervisor::path_access::direct_write,
.ttl_secs = 120,
});
REQUIRE(!direct.has_value());
REQUIRE(direct.error().find("authenticated local approval") != std::string::npos);
REQUIRE(registry
->validate_plan({
.grant =
{
.alias = "sage_protocol",
.access = glove::supervisor::path_access::direct_write,
.ttl_secs = 120,
},
.materialization = glove::supervisor::path_materialization::bind,
.cleanup_policy = glove::supervisor::path_cleanup_policy::retain,
})
.has_value());
REQUIRE(!registry
->validate_plan({
.grant =
{
.alias = "sage_protocol",
.access = glove::supervisor::path_access::direct_write,
.ttl_secs = 120,
},
.materialization = glove::supervisor::path_materialization::copy,
.cleanup_policy = glove::supervisor::path_cleanup_policy::retain,
})
.has_value());
auto duplicate_alias =
glove::supervisor::path_alias_registry::build({policy_for(source), policy_for(source)});
REQUIRE(!duplicate_alias.has_value());
const auto nested_source = source / "nested";
REQUIRE(std::filesystem::create_directory(nested_source));
auto nested_policy = policy_for(nested_source);
nested_policy.alias = "nested";
nested_policy.target_path = "/workspace/nested";
REQUIRE(!glove::supervisor::path_alias_registry::build({policy_for(source),
std::move(nested_policy)})
.has_value());
auto overlapping_target = policy_for(replacement);
overlapping_target.alias = "replacement";
overlapping_target.target_path = "/workspace/sage-protocol/nested";
REQUIRE(!glove::supervisor::path_alias_registry::build({policy_for(source),
std::move(overlapping_target)})
.has_value());
auto duplicate_access_policy = policy_for(source);
duplicate_access_policy.access.push_back(read_mode());
REQUIRE(!glove::supervisor::path_alias_registry::build({std::move(duplicate_access_policy)})
.has_value());
auto traversal_policy = policy_for(source);
traversal_policy.alias = "../escape";
REQUIRE(
!glove::supervisor::path_alias_registry::build({std::move(traversal_policy)}).has_value()
);
auto target_traversal = policy_for(source);
target_traversal.target_path = "/workspace/../etc";
REQUIRE(
!glove::supervisor::path_alias_registry::build({std::move(target_traversal)}).has_value()
);
auto reserved = policy_for(source);
reserved.target_path = "/proc/agent";
REQUIRE(!glove::supervisor::path_alias_registry::build({std::move(reserved)}).has_value());
auto host_root = policy_for("/");
REQUIRE(!glove::supervisor::path_alias_registry::build({std::move(host_root)}).has_value());
auto host_home = policy_for("/home/example");
REQUIRE(!glove::supervisor::path_alias_registry::build({std::move(host_home)}).has_value());
auto host_proc = policy_for("/proc/version");
REQUIRE(!glove::supervisor::path_alias_registry::build({std::move(host_proc)}).has_value());
auto host_secret = policy_for("/srv/.ssh/keys");
REQUIRE(!glove::supervisor::path_alias_registry::build({std::move(host_secret)}).has_value());
const auto fifo_source = tree.root() / "fifo-source";
REQUIRE(::mkfifo(fifo_source.c_str(), 0600) == 0);
REQUIRE(!glove::supervisor::path_alias_registry::build({policy_for(fifo_source)}).has_value());
const auto file_source = tree.root() / "file-source";
const auto hardlink_source = tree.root() / "hardlink-source";
{
std::ofstream file{file_source};
file << "same inode";
}
REQUIRE(::link(file_source.c_str(), hardlink_source.c_str()) == 0);
auto hardlink_policy = policy_for(hardlink_source);
hardlink_policy.alias = "hardlink";
hardlink_policy.target_path = "/workspace/hardlink";
REQUIRE(!glove::supervisor::path_alias_registry::build({policy_for(file_source),
std::move(hardlink_policy)})
.has_value());
const auto symlink_source = tree.root() / "symlink-source";
REQUIRE(::symlink(source.c_str(), symlink_source.c_str()) == 0);
auto symlink_registry =
glove::supervisor::path_alias_registry::build({policy_for(symlink_source)});
REQUIRE(!symlink_registry.has_value());
const auto original_location = tree.root() / "source-original";
std::filesystem::rename(source, original_location);
std::filesystem::rename(replacement, source);
REQUIRE(!read_grant->verify_identity().has_value());
auto replacement_grant = registry->resolve(read_request);
REQUIRE(replacement_grant.has_value());
REQUIRE(replacement_grant->identity() != read_grant->identity());
std::filesystem::remove_all(source);
REQUIRE(::symlink(original_location.c_str(), source.c_str()) == 0);
REQUIRE(!registry->resolve(read_request).has_value());
return 0;
}
} // namespace
auto main() -> int {
return run();
}