Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion src/iceberg/catalog/rest/catalog_properties.cc
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
#include <string>
#include <string_view>

#include "iceberg/catalog/rest/constant.h"
#include "iceberg/util/string_util.h"

namespace iceberg::rest {

RestCatalogProperties RestCatalogProperties::default_properties() {
Expand All @@ -38,7 +41,18 @@ RestCatalogProperties RestCatalogProperties::FromMap(

std::unordered_map<std::string, std::string> 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);

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sending this by default mirrors PyIceberg, which sets the access-delegation header on the session for every request and only ever uses vended-credentials:

The Java REST client does not send this header; engines like Trino set it via a header.* catalog property.

}
return headers;
}

Result<std::string_view> RestCatalogProperties::Uri() const {
Expand Down
5 changes: 5 additions & 0 deletions src/iceberg/catalog/rest/constant.h
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down
30 changes: 30 additions & 0 deletions src/iceberg/test/rest_util_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading