From 3e3fed8edb5a07115548a4040c411ecac4df153e Mon Sep 17 00:00:00 2001 From: Sreesh Maheshwar Date: Sun, 6 Sep 2026 22:59:50 +0100 Subject: [PATCH] feat(rest): request vended storage credentials --- .../catalog/rest/catalog_properties.cc | 16 +++++++++- src/iceberg/catalog/rest/constant.h | 5 ++++ src/iceberg/test/rest_util_test.cc | 30 +++++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/iceberg/catalog/rest/catalog_properties.cc b/src/iceberg/catalog/rest/catalog_properties.cc index 0e417e6c3..58e35c1f2 100644 --- a/src/iceberg/catalog/rest/catalog_properties.cc +++ b/src/iceberg/catalog/rest/catalog_properties.cc @@ -23,6 +23,9 @@ #include #include +#include "iceberg/catalog/rest/constant.h" +#include "iceberg/util/string_util.h" + namespace iceberg::rest { RestCatalogProperties RestCatalogProperties::default_properties() { @@ -38,7 +41,18 @@ RestCatalogProperties RestCatalogProperties::FromMap( std::unordered_map RestCatalogProperties::ExtractHeaders() const { - return Extract(kHeaderPrefix); + auto headers = Extract(kHeaderPrefix); + // Request vended credentials unless the caller set the access-delegation header. + // Header names are case-insensitive. + const std::string canonical = StringUtils::ToUpper(kHeaderAccessDelegation); + const bool set_by_caller = + std::ranges::any_of(headers, [&canonical](const auto& header) { + return StringUtils::ToUpper(header.first) == canonical; + }); + if (!set_by_caller) { + headers.emplace(kHeaderAccessDelegation, kAccessDelegationVendedCredentials); + } + return headers; } Result RestCatalogProperties::Uri() const { diff --git a/src/iceberg/catalog/rest/constant.h b/src/iceberg/catalog/rest/constant.h index 0a6e8d0c0..ba957e8eb 100644 --- a/src/iceberg/catalog/rest/constant.h +++ b/src/iceberg/catalog/rest/constant.h @@ -32,12 +32,17 @@ inline const std::string kHeaderContentType = "Content-Type"; inline const std::string kHeaderAccept = "Accept"; inline const std::string kHeaderXClientVersion = "X-Client-Version"; inline const std::string kHeaderUserAgent = "User-Agent"; +inline const std::string kHeaderAccessDelegation = "X-Iceberg-Access-Delegation"; inline const std::string kMimeTypeApplicationJson = "application/json"; inline const std::string kMimeTypeFormUrlEncoded = "application/x-www-form-urlencoded"; inline const std::string kUserAgentPrefix = "iceberg-cpp/"; inline const std::string kUserAgent = "iceberg-cpp/" ICEBERG_VERSION_STRING; +// Value for the access-delegation header requesting that the server vend +// short-lived storage credentials in load responses. +inline const std::string kAccessDelegationVendedCredentials = "vended-credentials"; + inline const std::string kQueryParamParent = "parent"; inline const std::string kQueryParamPageToken = "page_token"; diff --git a/src/iceberg/test/rest_util_test.cc b/src/iceberg/test/rest_util_test.cc index 0035afca0..49de7e1d1 100644 --- a/src/iceberg/test/rest_util_test.cc +++ b/src/iceberg/test/rest_util_test.cc @@ -232,4 +232,34 @@ TEST(RestCatalogPropertiesTest, SnapshotLoadingModeDefaultIsAll) { EXPECT_EQ(result.value(), SnapshotMode::kAll); } +TEST(RestCatalogPropertiesTest, ExtractHeadersStripsHeaderPrefix) { + auto config = RestCatalogProperties::FromMap({{"header.Authorization", "Bearer token"}, + {"header.X-Custom", "abc"}, + {"uri", "https://localhost"}}); + auto headers = config.ExtractHeaders(); + EXPECT_EQ(headers.at("Authorization"), "Bearer token"); + EXPECT_EQ(headers.at("X-Custom"), "abc"); +} + +TEST(RestCatalogPropertiesTest, ExtractHeadersRequestsVendedCredentialsByDefault) { + auto config = RestCatalogProperties::FromMap({{"uri", "https://localhost"}}); + EXPECT_EQ(config.ExtractHeaders().at("X-Iceberg-Access-Delegation"), + "vended-credentials"); +} + +TEST(RestCatalogPropertiesTest, ExplicitAccessDelegationHeaderOverridesDefault) { + auto config = RestCatalogProperties::FromMap( + {{"header.X-Iceberg-Access-Delegation", "remote-signing"}}); + EXPECT_EQ(config.ExtractHeaders().at("X-Iceberg-Access-Delegation"), "remote-signing"); +} + +TEST(RestCatalogPropertiesTest, ExplicitAccessDelegationHeaderOverrideIsCaseInsensitive) { + auto config = RestCatalogProperties::FromMap( + {{"header.x-iceberg-access-delegation", "remote-signing"}}); + auto headers = config.ExtractHeaders(); + // The caller's header wins and no duplicate canonical header is added. + EXPECT_EQ(headers.at("x-iceberg-access-delegation"), "remote-signing"); + EXPECT_FALSE(headers.contains("X-Iceberg-Access-Delegation")); +} + } // namespace iceberg::rest