Skip to content
Open
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: 14 additions & 2 deletions .github/workflows/cpp-linter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,26 @@ jobs:
uses: actions/checkout@v6
- name: Install dependencies
shell: bash
run: sudo apt-get update && sudo apt-get install -y libcurl4-openssl-dev
run: sudo apt-get update && sudo apt-get install -y libcurl4-openssl-dev ninja-build
- name: Cache vcpkg packages
uses: actions/cache@v4
id: vcpkg-cache
with:
path: /usr/local/share/vcpkg/installed
key: vcpkg-x64-linux-aws-sdk-cpp-core-${{ hashFiles('.github/workflows/cpp-linter.yml') }}
- name: Install AWS SDK via vcpkg
if: steps.vcpkg-cache.outputs.cache-hit != 'true'
shell: bash
run: vcpkg install aws-sdk-cpp[core]:x64-linux
- name: Run build
env:
CC: gcc-14
CXX: g++-14
run: |
mkdir build && cd build
cmake .. -G Ninja -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
cmake .. -G Ninja -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-DICEBERG_BUILD_SIGV4=ON \
-DCMAKE_TOOLCHAIN_FILE=/usr/local/share/vcpkg/scripts/buildsystems/vcpkg.cmake
cmake --build .
- uses: cpp-linter/cpp-linter-action@0f6d1b8d7e38b584cbee606eb23d850c217d54f8
id: linter
Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ option(ICEBERG_BUILD_TESTS "Build tests" ON)
option(ICEBERG_BUILD_BUNDLE "Build the battery included library" ON)
option(ICEBERG_BUILD_REST "Build rest catalog client" ON)
option(ICEBERG_BUILD_REST_INTEGRATION_TESTS "Build rest catalog integration tests" OFF)
option(ICEBERG_BUILD_SIGV4 "Build SigV4 authentication support (requires AWS SDK)" OFF)
option(ICEBERG_ENABLE_ASAN "Enable Address Sanitizer" OFF)
option(ICEBERG_ENABLE_UBSAN "Enable Undefined Behavior Sanitizer" OFF)

Expand Down
18 changes: 18 additions & 0 deletions cmake_modules/IcebergThirdpartyToolchain.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -531,3 +531,21 @@ endif()
if(ICEBERG_BUILD_REST)
resolve_cpr_dependency()
endif()

# ----------------------------------------------------------------------
# AWS SDK for C++

function(resolve_aws_sdk_dependency)
find_package(AWSSDK REQUIRED COMPONENTS core)
list(APPEND ICEBERG_SYSTEM_DEPENDENCIES AWSSDK)
set(ICEBERG_SYSTEM_DEPENDENCIES
${ICEBERG_SYSTEM_DEPENDENCIES}
PARENT_SCOPE)
endfunction()

if(ICEBERG_BUILD_SIGV4)
if(NOT ICEBERG_BUILD_REST)
message(FATAL_ERROR "ICEBERG_BUILD_SIGV4 requires ICEBERG_BUILD_REST to be ON")
endif()
resolve_aws_sdk_dependency()
endif()
19 changes: 19 additions & 0 deletions src/iceberg/catalog/rest/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ set(ICEBERG_REST_SOURCES
rest_util.cc
types.cc)

if(ICEBERG_BUILD_SIGV4)
list(APPEND ICEBERG_REST_SOURCES auth/sigv4_auth_manager.cc)
endif()

set(ICEBERG_REST_STATIC_BUILD_INTERFACE_LIBS)
set(ICEBERG_REST_SHARED_BUILD_INTERFACE_LIBS)
set(ICEBERG_REST_STATIC_INSTALL_INTERFACE_LIBS)
Expand All @@ -51,6 +55,13 @@ list(APPEND
"$<IF:$<TARGET_EXISTS:iceberg::iceberg_shared>,iceberg::iceberg_shared,iceberg::iceberg_static>"
"$<IF:$<BOOL:${CPR_VENDORED}>,iceberg::cpr,cpr::cpr>")

if(ICEBERG_BUILD_SIGV4)
list(APPEND ICEBERG_REST_STATIC_BUILD_INTERFACE_LIBS aws-cpp-sdk-core)
list(APPEND ICEBERG_REST_SHARED_BUILD_INTERFACE_LIBS aws-cpp-sdk-core)
list(APPEND ICEBERG_REST_STATIC_INSTALL_INTERFACE_LIBS aws-cpp-sdk-core)
list(APPEND ICEBERG_REST_SHARED_INSTALL_INTERFACE_LIBS aws-cpp-sdk-core)
endif()

add_iceberg_lib(iceberg_rest
SOURCES
${ICEBERG_REST_SOURCES}
Expand All @@ -63,4 +74,12 @@ add_iceberg_lib(iceberg_rest
SHARED_INSTALL_INTERFACE_LIBS
${ICEBERG_REST_SHARED_INSTALL_INTERFACE_LIBS})

if(ICEBERG_BUILD_SIGV4)
foreach(LIB iceberg_rest_static iceberg_rest_shared)
if(TARGET ${LIB})
target_compile_definitions(${LIB} PUBLIC ICEBERG_BUILD_SIGV4)
endif()
endforeach()
endif()

iceberg_install_all_headers(iceberg/catalog/rest)
7 changes: 7 additions & 0 deletions src/iceberg/catalog/rest/auth/auth_manager_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,11 @@ Result<std::unique_ptr<AuthManager>> MakeOAuth2Manager(
std::string_view name,
const std::unordered_map<std::string, std::string>& properties);

#ifdef ICEBERG_BUILD_SIGV4
/// \brief Create a SigV4 authentication manager with a delegate.
Result<std::unique_ptr<AuthManager>> MakeSigV4AuthManager(
std::string_view name,
const std::unordered_map<std::string, std::string>& properties);
#endif

} // namespace iceberg::rest::auth
33 changes: 32 additions & 1 deletion src/iceberg/catalog/rest/auth/auth_managers.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
#include <unordered_set>

#include "iceberg/catalog/rest/auth/auth_manager_internal.h"
#ifdef ICEBERG_BUILD_SIGV4
# include "iceberg/catalog/rest/auth/sigv4_auth_manager.h"
#endif
#include "iceberg/catalog/rest/auth/auth_properties.h"
#include "iceberg/util/string_util.h"

Expand Down Expand Up @@ -62,11 +65,15 @@ std::string InferAuthType(
}

AuthManagerRegistry CreateDefaultRegistry() {
return {
AuthManagerRegistry registry = {
{AuthProperties::kAuthTypeNone, MakeNoopAuthManager},
{AuthProperties::kAuthTypeBasic, MakeBasicAuthManager},
{AuthProperties::kAuthTypeOAuth2, MakeOAuth2Manager},
};
#ifdef ICEBERG_BUILD_SIGV4
registry[AuthProperties::kAuthTypeSigV4] = MakeSigV4AuthManager;
#endif
return registry;
}

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

#ifdef ICEBERG_BUILD_SIGV4
Result<std::unique_ptr<AuthManager>> MakeSigV4AuthManager(
std::string_view name,
const std::unordered_map<std::string, std::string>& properties) {
// Determine the delegate auth type. Default to OAuth2 if not specified.
std::string delegate_type = AuthProperties::kAuthTypeOAuth2;
auto it = properties.find(AuthProperties::kSigV4DelegateAuthType);
if (it != properties.end() && !it->second.empty()) {
delegate_type = StringUtils::ToLower(it->second);
}

// Prevent circular delegation (sigv4 -> sigv4 -> ...).
ICEBERG_PRECHECK(delegate_type != AuthProperties::kAuthTypeSigV4,
"Cannot delegate a SigV4 auth manager to another SigV4 auth manager");

// Load the delegate auth manager.
auto delegate_props = properties;
delegate_props[AuthProperties::kAuthType] = delegate_type;

ICEBERG_ASSIGN_OR_RAISE(auto delegate, AuthManagers::Load(name, delegate_props));
return std::make_unique<SigV4AuthManager>(std::move(delegate));
}
#endif

} // namespace iceberg::rest::auth
14 changes: 14 additions & 0 deletions src/iceberg/catalog/rest/auth/auth_properties.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,20 @@ class ICEBERG_REST_EXPORT AuthProperties : public ConfigBase<AuthProperties> {
inline static const std::string kSigV4DelegateAuthType =
"rest.auth.sigv4.delegate-auth-type";

// ---- SigV4 AWS credential entries ----

/// AWS region for SigV4 signing.
inline static const std::string kSigV4SigningRegion = "rest.signing-region";
/// AWS service name for SigV4 signing.
inline static const std::string kSigV4SigningName = "rest.signing-name";
inline static const std::string kSigV4SigningNameDefault = "execute-api";
/// Static access key ID for SigV4 signing.
inline static const std::string kSigV4AccessKeyId = "rest.access-key-id";
/// Static secret access key for SigV4 signing.
inline static const std::string kSigV4SecretAccessKey = "rest.secret-access-key";
/// Optional session token for SigV4 signing.
inline static const std::string kSigV4SessionToken = "rest.session-token";

// ---- OAuth2 entries ----

inline static Entry<std::string> kToken{"token", ""};
Expand Down
4 changes: 3 additions & 1 deletion src/iceberg/catalog/rest/auth/auth_session.cc
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ class DefaultAuthSession : public AuthSession {
explicit DefaultAuthSession(std::unordered_map<std::string, std::string> headers)
: headers_(std::move(headers)) {}

Status Authenticate(std::unordered_map<std::string, std::string>& headers) override {
Status Authenticate(
std::unordered_map<std::string, std::string>& headers,
[[maybe_unused]] const HTTPRequestContext& request_context) override {
for (const auto& [key, value] : headers_) {
headers.try_emplace(key, value);
}
Expand Down
11 changes: 10 additions & 1 deletion src/iceberg/catalog/rest/auth/auth_session.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <string>
#include <unordered_map>

#include "iceberg/catalog/rest/endpoint.h"
#include "iceberg/catalog/rest/iceberg_rest_export.h"
#include "iceberg/catalog/rest/type_fwd.h"
#include "iceberg/result.h"
Expand All @@ -32,6 +33,13 @@

namespace iceberg::rest::auth {

/// \brief Context about the HTTP request being authenticated.
struct ICEBERG_REST_EXPORT HTTPRequestContext {
HttpMethod method = HttpMethod::kGet;
std::string url;
std::string body;
};

/// \brief An authentication session that can authenticate outgoing HTTP requests.
class ICEBERG_REST_EXPORT AuthSession {
public:
Expand All @@ -50,7 +58,8 @@ class ICEBERG_REST_EXPORT AuthSession {
/// - NotAuthorized: Not authenticated (401)
/// - IOError: Network or connection errors when reaching auth server
/// - RestError: HTTP errors from authentication service
virtual Status Authenticate(std::unordered_map<std::string, std::string>& headers) = 0;
virtual Status Authenticate(std::unordered_map<std::string, std::string>& headers,
const HTTPRequestContext& request_context) = 0;

/// \brief Close the session and release any resources.
///
Expand Down
Loading
Loading