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
71 changes: 71 additions & 0 deletions .github/workflows/sigv4_test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

# SigV4 build + unit tests (Linux only; aws-cpp-sdk-core via vcpkg).
name: SigV4 Tests

on:
push:
branches:
- '**'
- '!dependabot/**'
tags:
- '**'
pull_request:

concurrency:
group: ${{ github.repository }}-${{ github.head_ref || github.sha }}-${{ github.workflow }}
cancel-in-progress: true

permissions:
contents: read

env:
ICEBERG_HOME: /tmp/iceberg

jobs:
sigv4:
name: SigV4 (AMD64 Ubuntu 24.04)
runs-on: ubuntu-24.04
timeout-minutes: 35
env:
CC: gcc-14
CXX: g++-14
AWS_EC2_METADATA_DISABLED: "TRUE"
steps:
- name: Checkout iceberg-cpp
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Install dependencies
shell: bash
run: sudo apt-get update && sudo apt-get install -y libcurl4-openssl-dev
- name: Cache vcpkg packages
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
id: vcpkg-cache
with:
path: /usr/local/share/vcpkg/installed
key: vcpkg-x64-linux-aws-sdk-cpp-core-${{ hashFiles('.github/workflows/sigv4_test.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: Build and test Iceberg with SigV4
shell: bash
env:
CMAKE_TOOLCHAIN_FILE: /usr/local/share/vcpkg/scripts/buildsystems/vcpkg.cmake
run: ci/scripts/build_iceberg.sh "$(pwd)" OFF OFF OFF ON
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ 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_S3 "Build with S3 support" OFF)
option(ICEBERG_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
12 changes: 11 additions & 1 deletion ci/scripts/build_iceberg.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
# specific language governing permissions and limitations
# under the License.
#
# Usage: build_iceberg.sh <source_dir> [rest_integration_tests=OFF] [sccache=OFF] [s3=OFF]
# Usage: build_iceberg.sh <source_dir> [rest_integration_tests=OFF] [sccache=OFF] [s3=OFF] [sigv4=OFF]

set -eux

Expand All @@ -26,6 +26,7 @@ build_dir=${1}/build
build_rest_integration_test=${2:-OFF}
build_enable_sccache=${3:-OFF}
build_enable_s3=${4:-OFF}
build_enable_sigv4=${5:-OFF}

mkdir ${build_dir}
pushd ${build_dir}
Expand All @@ -48,11 +49,20 @@ else
CMAKE_ARGS+=("-DICEBERG_S3=OFF")
fi

if [[ "${build_enable_sigv4}" == "ON" ]]; then
CMAKE_ARGS+=("-DICEBERG_SIGV4=ON")
else
CMAKE_ARGS+=("-DICEBERG_SIGV4=OFF")
fi

if is_windows; then
CMAKE_ARGS+=("-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake")
CMAKE_ARGS+=("-DCMAKE_BUILD_TYPE=Release")
else
CMAKE_ARGS+=("-DCMAKE_BUILD_TYPE=Debug")
if [[ -n "${CMAKE_TOOLCHAIN_FILE:-}" ]]; then
CMAKE_ARGS+=("-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}")
fi
fi

if [[ "${build_enable_sccache}" == "ON" ]]; then
Expand Down
18 changes: 18 additions & 0 deletions cmake_modules/IcebergThirdpartyToolchain.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -539,3 +539,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)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Here it records only AWSSDK for installed-package dependency discovery, while src/iceberg/catalog/rest/CMakeLists.txt exports aws-cpp-sdk-core in the REST install interface. The generated iceberg-config.cmake can only call find_dependency(AWSSDK) without COMPONENTS core, but AWS SDK’s CMake config loads component packages from AWSSDK_FIND_COMPONENTS. A downstream installed SigV4 build can therefore fail to find/link AWS core unless it happens to be on the default linker path.

I'd suggest to special-case find_dependency(AWSSDK COMPONENTS core) in the iceberg-config.cmake.in or otherwise export the AWS SDK dependency component-aware.

set(ICEBERG_SYSTEM_DEPENDENCIES
${ICEBERG_SYSTEM_DEPENDENCIES}
PARENT_SCOPE)
endfunction()

if(ICEBERG_SIGV4)
if(NOT ICEBERG_BUILD_REST)
message(FATAL_ERROR "ICEBERG_SIGV4 requires ICEBERG_BUILD_REST to be ON")
endif()
resolve_aws_sdk_dependency()
endif()
7 changes: 7 additions & 0 deletions meson.options
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,11 @@ option(
value: 'disabled',
)

option(
'sigv4',
type: 'feature',
description: 'Build AWS SigV4 authentication support for rest catalog',
value: 'disabled',
)

option('tests', type: 'feature', description: 'Build tests', value: 'enabled')
17 changes: 17 additions & 0 deletions src/iceberg/catalog/rest/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ set(ICEBERG_REST_SOURCES
rest_util.cc
types.cc)

list(APPEND ICEBERG_REST_SOURCES auth/sigv4_auth_manager.cc)

set(ICEBERG_REST_STATIC_BUILD_INTERFACE_LIBS)
set(ICEBERG_REST_SHARED_BUILD_INTERFACE_LIBS)
set(ICEBERG_REST_STATIC_INSTALL_INTERFACE_LIBS)
Expand All @@ -52,6 +54,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_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 @@ -64,4 +73,12 @@ add_iceberg_lib(iceberg_rest
SHARED_INSTALL_INTERFACE_LIBS
${ICEBERG_REST_SHARED_INSTALL_INTERFACE_LIBS})

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

iceberg_install_all_headers(iceberg/catalog/rest)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Currently sigv4_auth_manager.h is installed regardless of the ICEBERG_BUILD_SIGV4 option value.

6 changes: 6 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,10 @@ Result<std::unique_ptr<AuthManager>> MakeOAuth2Manager(
std::string_view name,
const std::unordered_map<std::string, std::string>& properties);

/// \brief Create a SigV4 authentication manager with a delegate. Returns
/// NotSupported when the library was built without ICEBERG_SIGV4.
Result<std::unique_ptr<AuthManager>> MakeSigV4AuthManager(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Where is the definition? BTW, we don't need to use macro ICEBERG_BUILD_SIGV4 everywhere. We can return Unsupported from MakeSigV4AuthManager function internally depending on this macro.

std::string_view name,
const std::unordered_map<std::string, std::string>& properties);

} // namespace iceberg::rest::auth
4 changes: 3 additions & 1 deletion src/iceberg/catalog/rest/auth/auth_managers.cc
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,13 @@ std::string InferAuthType(
}

AuthManagerRegistry CreateDefaultRegistry() {
return {
AuthManagerRegistry registry = {
{AuthProperties::kAuthTypeNone, MakeNoopAuthManager},
{AuthProperties::kAuthTypeBasic, MakeBasicAuthManager},
{AuthProperties::kAuthTypeOAuth2, MakeOAuth2Manager},
{AuthProperties::kAuthTypeSigV4, MakeSigV4AuthManager},
};
return registry;
}

// Get the global registry of auth manager factories.
Expand Down
8 changes: 6 additions & 2 deletions src/iceberg/catalog/rest/auth/auth_properties.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,14 @@ class ICEBERG_REST_EXPORT AuthProperties : public ConfigBase<AuthProperties> {

// ---- SigV4 entries ----

inline static const std::string kSigV4Region = "rest.auth.sigv4.region";
inline static const std::string kSigV4Service = "rest.auth.sigv4.service";
inline static const std::string kSigV4DelegateAuthType =
"rest.auth.sigv4.delegate-auth-type";
inline static const std::string kSigV4SigningRegion = "rest.signing-region";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We can remove the legacy key kSigV4Region/kSigV4Service

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done.

inline static const std::string kSigV4SigningName = "rest.signing-name";
inline static const std::string kSigV4SigningNameDefault = "execute-api";
inline static const std::string kSigV4AccessKeyId = "rest.access-key-id";
inline static const std::string kSigV4SecretAccessKey = "rest.secret-access-key";
inline static const std::string kSigV4SessionToken = "rest.session-token";

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

Expand Down
7 changes: 4 additions & 3 deletions src/iceberg/catalog/rest/auth/auth_session.cc
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,12 @@ 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 {
Result<HttpRequest> Authenticate(const HttpRequest& request) override {
HttpRequest authenticated = request;
for (const auto& [key, value] : headers_) {
headers.try_emplace(key, value);
authenticated.headers.try_emplace(key, value);
}
return {};
return authenticated;
}

private:
Expand Down
14 changes: 8 additions & 6 deletions 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/http_request.h"
#include "iceberg/catalog/rest/iceberg_rest_export.h"
#include "iceberg/catalog/rest/type_fwd.h"
#include "iceberg/result.h"
Expand All @@ -37,20 +38,21 @@ class ICEBERG_REST_EXPORT AuthSession {
public:
virtual ~AuthSession() = default;

/// \brief Authenticate the given request headers.
/// \brief Authenticate an outgoing HTTP request.
///
/// This method adds authentication information (e.g., Authorization header)
/// to the provided headers map. The implementation should be idempotent.
/// Returns a new request with authentication information (e.g., an
/// Authorization header) added. Implementations must be idempotent and must
/// not mutate the input request.
///
/// \param[in,out] headers The headers map to add authentication information to.
/// \return Status indicating success or one of the following errors:
/// \param request The request to authenticate.
/// \return The authenticated request on success, or one of:
/// - AuthenticationFailed: General authentication failure (invalid credentials,
/// etc.)
/// - TokenExpired: Authentication token has expired and needs refresh
/// - 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 Result<HttpRequest> Authenticate(const HttpRequest& request) = 0;

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