139 lines
5.2 KiB
C++
139 lines
5.2 KiB
C++
#include "glove/supervisor/change_apply_authorization.hpp"
|
|
|
|
#include <cstdio>
|
|
#include <span>
|
|
#include <string>
|
|
#include <string_view>
|
|
|
|
namespace {
|
|
|
|
#define REQUIRE(condition) \
|
|
do { \
|
|
if (!(condition)) { \
|
|
std::fprintf(stderr, "REQUIRE failed: %s @ %s:%d\n", #condition, __FILE__, __LINE__); \
|
|
return 1; \
|
|
} \
|
|
} while (false)
|
|
|
|
class exact_verifier final : public glove::supervisor::change_apply_signature_verifier {
|
|
public:
|
|
exact_verifier(std::string payload, bool accept)
|
|
: payload_{std::move(payload)}, accept_{accept} {}
|
|
|
|
auto verify(
|
|
const glove::supervisor::change_apply_authorization_claims& claims,
|
|
std::span<const unsigned char> canonical_claims,
|
|
std::string_view signature,
|
|
std::uint64_t now_ms
|
|
) const -> glove::supervisor::result<void> override {
|
|
const std::string_view payload{
|
|
reinterpret_cast<const char*>(canonical_claims.data()), canonical_claims.size()
|
|
};
|
|
if (!accept_ || claims.key_id != "admin-key-1" || signature != signature_value ||
|
|
payload != payload_ || now_ms != 2'000) {
|
|
return std::unexpected(std::string{"signature mismatch"});
|
|
}
|
|
return {};
|
|
}
|
|
|
|
static constexpr std::string_view signature_value = "dGVzdC1vbmx5LXNpZ25hdHVyZS12YWx1ZQ==";
|
|
|
|
private:
|
|
std::string payload_;
|
|
bool accept_ = false;
|
|
};
|
|
|
|
auto claims() -> glove::supervisor::change_apply_authorization_claims {
|
|
return {
|
|
.schema_version = 1,
|
|
.audience = "gloved",
|
|
.key_id = "admin-key-1",
|
|
.grant_id = "grant-1",
|
|
.executor_node_id = "node-1",
|
|
.session_id = "session-1",
|
|
.controller_plan_digest = std::string(64, 'a'),
|
|
.plan_content_digest = std::string(64, 'b'),
|
|
.exposure_id = "workspace",
|
|
.generation = 7,
|
|
.scope_digest = std::string(64, 'c'),
|
|
.manifest_digest = std::string(64, 'd'),
|
|
.policy_revision = 9,
|
|
.issued_at_ms = 1'000,
|
|
.expires_at_ms = 61'000,
|
|
};
|
|
}
|
|
|
|
auto context(const glove::supervisor::change_apply_authorization_claims& value)
|
|
-> glove::supervisor::change_apply_authorization_context {
|
|
return {
|
|
.executor_node_id = value.executor_node_id,
|
|
.session_id = value.session_id,
|
|
.controller_plan_digest = value.controller_plan_digest,
|
|
.plan_content_digest = value.plan_content_digest,
|
|
.exposure_id = value.exposure_id,
|
|
.generation = value.generation,
|
|
.scope_digest = value.scope_digest,
|
|
.manifest_digest = value.manifest_digest,
|
|
.policy_revision = value.policy_revision,
|
|
};
|
|
}
|
|
|
|
auto run() -> int {
|
|
using namespace glove::supervisor;
|
|
|
|
const auto value = claims();
|
|
auto payload = change_apply_authorization_signing_payload(value);
|
|
REQUIRE(payload.has_value());
|
|
auto encoded =
|
|
encode_change_apply_authorization(value, std::string{exact_verifier::signature_value});
|
|
REQUIRE(encoded.has_value());
|
|
REQUIRE(encoded->authorization_digest.size() == 64U);
|
|
REQUIRE(encoded->canonical_json.find("host_path") == std::string::npos);
|
|
auto repeated =
|
|
encode_change_apply_authorization(value, std::string{exact_verifier::signature_value});
|
|
REQUIRE(repeated.has_value());
|
|
REQUIRE(repeated->canonical_json == encoded->canonical_json);
|
|
|
|
exact_verifier verifier{*payload, true};
|
|
auto verified =
|
|
verify_change_apply_authorization(encoded->canonical_json, context(value), 2'000, verifier);
|
|
REQUIRE(verified.has_value());
|
|
REQUIRE(verified->claims == value);
|
|
REQUIRE(verified->authorization_digest == encoded->authorization_digest);
|
|
|
|
auto wrong_context = context(value);
|
|
wrong_context.generation += 1;
|
|
REQUIRE(
|
|
!verify_change_apply_authorization(encoded->canonical_json, wrong_context, 2'000, verifier)
|
|
.has_value()
|
|
);
|
|
REQUIRE(!verify_change_apply_authorization(
|
|
encoded->canonical_json, context(value), value.expires_at_ms, verifier
|
|
)
|
|
.has_value());
|
|
exact_verifier rejecting{*payload, false};
|
|
REQUIRE(!verify_change_apply_authorization(
|
|
encoded->canonical_json, context(value), 2'000, rejecting
|
|
)
|
|
.has_value());
|
|
|
|
auto tampered = encoded->canonical_json;
|
|
const auto digest_position = tampered.find(std::string(64, 'd'));
|
|
REQUIRE(digest_position != std::string::npos);
|
|
tampered[digest_position] = 'e';
|
|
REQUIRE(
|
|
!verify_change_apply_authorization(tampered, context(value), 2'000, verifier).has_value()
|
|
);
|
|
|
|
auto long_lived = value;
|
|
long_lived.expires_at_ms =
|
|
long_lived.issued_at_ms + maximum_change_apply_authorization_lifetime_ms + 1U;
|
|
REQUIRE(!change_apply_authorization_signing_payload(long_lived).has_value());
|
|
return 0;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
auto main() -> int {
|
|
return run();
|
|
}
|