Skip to content

Commit bfc98f7

Browse files
feat(auth): implement SigV4 authentication for REST catalog
1 parent 42b00d4 commit bfc98f7

15 files changed

+1084
-24
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,4 @@ GEMINI.md
4747
.kiro/
4848
my-skills/
4949
skills/
50+
docs/superpowers/

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ option(ICEBERG_BUILD_TESTS "Build tests" ON)
4545
option(ICEBERG_BUILD_BUNDLE "Build the battery included library" ON)
4646
option(ICEBERG_BUILD_REST "Build rest catalog client" ON)
4747
option(ICEBERG_BUILD_REST_INTEGRATION_TESTS "Build rest catalog integration tests" OFF)
48+
option(ICEBERG_BUILD_SIGV4 "Build SigV4 authentication support (requires AWS SDK)" OFF)
4849
option(ICEBERG_ENABLE_ASAN "Enable Address Sanitizer" OFF)
4950
option(ICEBERG_ENABLE_UBSAN "Enable Undefined Behavior Sanitizer" OFF)
5051

cmake_modules/IcebergThirdpartyToolchain.cmake

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,3 +531,21 @@ endif()
531531
if(ICEBERG_BUILD_REST)
532532
resolve_cpr_dependency()
533533
endif()
534+
535+
# ----------------------------------------------------------------------
536+
# AWS SDK for C++
537+
538+
function(resolve_aws_sdk_dependency)
539+
find_package(AWSSDK REQUIRED COMPONENTS core)
540+
list(APPEND ICEBERG_SYSTEM_DEPENDENCIES AWSSDK)
541+
set(ICEBERG_SYSTEM_DEPENDENCIES
542+
${ICEBERG_SYSTEM_DEPENDENCIES}
543+
PARENT_SCOPE)
544+
endfunction()
545+
546+
if(ICEBERG_BUILD_SIGV4)
547+
if(NOT ICEBERG_BUILD_REST)
548+
message(FATAL_ERROR "ICEBERG_BUILD_SIGV4 requires ICEBERG_BUILD_REST to be ON")
549+
endif()
550+
resolve_aws_sdk_dependency()
551+
endif()

src/iceberg/catalog/rest/CMakeLists.txt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ set(ICEBERG_REST_SOURCES
3333
rest_util.cc
3434
types.cc)
3535

36+
if(ICEBERG_BUILD_SIGV4)
37+
list(APPEND ICEBERG_REST_SOURCES auth/sigv4_auth_manager.cc)
38+
endif()
39+
3640
set(ICEBERG_REST_STATIC_BUILD_INTERFACE_LIBS)
3741
set(ICEBERG_REST_SHARED_BUILD_INTERFACE_LIBS)
3842
set(ICEBERG_REST_STATIC_INSTALL_INTERFACE_LIBS)
@@ -51,6 +55,13 @@ list(APPEND
5155
"$<IF:$<TARGET_EXISTS:iceberg::iceberg_shared>,iceberg::iceberg_shared,iceberg::iceberg_static>"
5256
"$<IF:$<BOOL:${CPR_VENDORED}>,iceberg::cpr,cpr::cpr>")
5357

58+
if(ICEBERG_BUILD_SIGV4)
59+
list(APPEND ICEBERG_REST_STATIC_BUILD_INTERFACE_LIBS aws-cpp-sdk-core)
60+
list(APPEND ICEBERG_REST_SHARED_BUILD_INTERFACE_LIBS aws-cpp-sdk-core)
61+
list(APPEND ICEBERG_REST_STATIC_INSTALL_INTERFACE_LIBS aws-cpp-sdk-core)
62+
list(APPEND ICEBERG_REST_SHARED_INSTALL_INTERFACE_LIBS aws-cpp-sdk-core)
63+
endif()
64+
5465
add_iceberg_lib(iceberg_rest
5566
SOURCES
5667
${ICEBERG_REST_SOURCES}
@@ -63,4 +74,12 @@ add_iceberg_lib(iceberg_rest
6374
SHARED_INSTALL_INTERFACE_LIBS
6475
${ICEBERG_REST_SHARED_INSTALL_INTERFACE_LIBS})
6576

77+
if(ICEBERG_BUILD_SIGV4)
78+
foreach(LIB iceberg_rest_static iceberg_rest_shared)
79+
if(TARGET ${LIB})
80+
target_compile_definitions(${LIB} PUBLIC ICEBERG_BUILD_SIGV4)
81+
endif()
82+
endforeach()
83+
endif()
84+
6685
iceberg_install_all_headers(iceberg/catalog/rest)

src/iceberg/catalog/rest/auth/auth_manager_internal.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,11 @@ Result<std::unique_ptr<AuthManager>> MakeOAuth2Manager(
4747
std::string_view name,
4848
const std::unordered_map<std::string, std::string>& properties);
4949

50+
#ifdef ICEBERG_BUILD_SIGV4
51+
/// \brief Create a SigV4 authentication manager with a delegate.
52+
Result<std::unique_ptr<AuthManager>> MakeSigV4AuthManager(
53+
std::string_view name,
54+
const std::unordered_map<std::string, std::string>& properties);
55+
#endif
56+
5057
} // namespace iceberg::rest::auth

src/iceberg/catalog/rest/auth/auth_managers.cc

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
#include <unordered_set>
2323

2424
#include "iceberg/catalog/rest/auth/auth_manager_internal.h"
25+
#ifdef ICEBERG_BUILD_SIGV4
26+
# include "iceberg/catalog/rest/auth/sigv4_auth_manager.h"
27+
#endif
2528
#include "iceberg/catalog/rest/auth/auth_properties.h"
2629
#include "iceberg/util/string_util.h"
2730

@@ -62,11 +65,15 @@ std::string InferAuthType(
6265
}
6366

6467
AuthManagerRegistry CreateDefaultRegistry() {
65-
return {
68+
AuthManagerRegistry registry = {
6669
{AuthProperties::kAuthTypeNone, MakeNoopAuthManager},
6770
{AuthProperties::kAuthTypeBasic, MakeBasicAuthManager},
6871
{AuthProperties::kAuthTypeOAuth2, MakeOAuth2Manager},
6972
};
73+
#ifdef ICEBERG_BUILD_SIGV4
74+
registry[AuthProperties::kAuthTypeSigV4] = MakeSigV4AuthManager;
75+
#endif
76+
return registry;
7077
}
7178

7279
// Get the global registry of auth manager factories.
@@ -98,4 +105,28 @@ Result<std::unique_ptr<AuthManager>> AuthManagers::Load(
98105
return it->second(name, properties);
99106
}
100107

108+
#ifdef ICEBERG_BUILD_SIGV4
109+
Result<std::unique_ptr<AuthManager>> MakeSigV4AuthManager(
110+
std::string_view name,
111+
const std::unordered_map<std::string, std::string>& properties) {
112+
// Determine the delegate auth type. Default to OAuth2 if not specified.
113+
std::string delegate_type = AuthProperties::kAuthTypeOAuth2;
114+
auto it = properties.find(AuthProperties::kSigV4DelegateAuthType);
115+
if (it != properties.end() && !it->second.empty()) {
116+
delegate_type = StringUtils::ToLower(it->second);
117+
}
118+
119+
// Prevent circular delegation (sigv4 -> sigv4 -> ...).
120+
ICEBERG_PRECHECK(delegate_type != AuthProperties::kAuthTypeSigV4,
121+
"Cannot delegate a SigV4 auth manager to another SigV4 auth manager");
122+
123+
// Load the delegate auth manager.
124+
auto delegate_props = properties;
125+
delegate_props[AuthProperties::kAuthType] = delegate_type;
126+
127+
ICEBERG_ASSIGN_OR_RAISE(auto delegate, AuthManagers::Load(name, delegate_props));
128+
return std::make_unique<SigV4AuthManager>(std::move(delegate));
129+
}
130+
#endif
131+
101132
} // namespace iceberg::rest::auth

src/iceberg/catalog/rest/auth/auth_properties.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,15 @@ class ICEBERG_REST_EXPORT AuthProperties : public ConfigBase<AuthProperties> {
5959
inline static const std::string kSigV4DelegateAuthType =
6060
"rest.auth.sigv4.delegate-auth-type";
6161

62+
// ---- SigV4 AWS credential entries ----
63+
64+
inline static const std::string kSigV4SigningRegion = "rest.signing-region";
65+
inline static const std::string kSigV4SigningName = "rest.signing-name";
66+
inline static const std::string kSigV4SigningNameDefault = "execute-api";
67+
inline static const std::string kSigV4AccessKeyId = "rest.access-key-id";
68+
inline static const std::string kSigV4SecretAccessKey = "rest.secret-access-key";
69+
inline static const std::string kSigV4SessionToken = "rest.session-token";
70+
6271
// ---- OAuth2 entries ----
6372

6473
inline static Entry<std::string> kToken{"token", ""};

src/iceberg/catalog/rest/auth/auth_session.cc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ class DefaultAuthSession : public AuthSession {
3333
explicit DefaultAuthSession(std::unordered_map<std::string, std::string> headers)
3434
: headers_(std::move(headers)) {}
3535

36-
Status Authenticate(std::unordered_map<std::string, std::string>& headers) override {
36+
Status Authenticate(
37+
std::unordered_map<std::string, std::string>& headers,
38+
[[maybe_unused]] const HTTPRequestContext& request_context) override {
3739
for (const auto& [key, value] : headers_) {
3840
headers.try_emplace(key, value);
3941
}

src/iceberg/catalog/rest/auth/auth_session.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include <string>
2424
#include <unordered_map>
2525

26+
#include "iceberg/catalog/rest/endpoint.h"
2627
#include "iceberg/catalog/rest/iceberg_rest_export.h"
2728
#include "iceberg/catalog/rest/type_fwd.h"
2829
#include "iceberg/result.h"
@@ -32,6 +33,13 @@
3233

3334
namespace iceberg::rest::auth {
3435

36+
/// \brief Context about the HTTP request being authenticated.
37+
struct ICEBERG_REST_EXPORT HTTPRequestContext {
38+
HttpMethod method = HttpMethod::kGet;
39+
std::string url;
40+
std::string body;
41+
};
42+
3543
/// \brief An authentication session that can authenticate outgoing HTTP requests.
3644
class ICEBERG_REST_EXPORT AuthSession {
3745
public:
@@ -50,7 +58,8 @@ class ICEBERG_REST_EXPORT AuthSession {
5058
/// - NotAuthorized: Not authenticated (401)
5159
/// - IOError: Network or connection errors when reaching auth server
5260
/// - RestError: HTTP errors from authentication service
53-
virtual Status Authenticate(std::unordered_map<std::string, std::string>& headers) = 0;
61+
virtual Status Authenticate(std::unordered_map<std::string, std::string>& headers,
62+
const HTTPRequestContext& request_context) = 0;
5463

5564
/// \brief Close the session and release any resources.
5665
///

0 commit comments

Comments
 (0)