diff --git a/src/iceberg/catalog/rest/CMakeLists.txt b/src/iceberg/catalog/rest/CMakeLists.txt index 12d77e599..3c58d9600 100644 --- a/src/iceberg/catalog/rest/CMakeLists.txt +++ b/src/iceberg/catalog/rest/CMakeLists.txt @@ -15,6 +15,8 @@ # specific language governing permissions and limitations # under the License. +add_subdirectory(auth) + set(ICEBERG_REST_SOURCES catalog_properties.cc endpoint.cc @@ -24,7 +26,10 @@ set(ICEBERG_REST_SOURCES resource_paths.cc rest_catalog.cc rest_util.cc - types.cc) + types.cc + auth/auth_manager.cc + auth/auth_managers.cc + auth/auth_session.cc) set(ICEBERG_REST_STATIC_BUILD_INTERFACE_LIBS) set(ICEBERG_REST_SHARED_BUILD_INTERFACE_LIBS) diff --git a/src/iceberg/catalog/rest/auth/CMakeLists.txt b/src/iceberg/catalog/rest/auth/CMakeLists.txt new file mode 100644 index 000000000..d44743efb --- /dev/null +++ b/src/iceberg/catalog/rest/auth/CMakeLists.txt @@ -0,0 +1,18 @@ +# 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. + +iceberg_install_all_headers(iceberg/catalog/rest/auth) diff --git a/src/iceberg/catalog/rest/auth/auth_manager.cc b/src/iceberg/catalog/rest/auth/auth_manager.cc new file mode 100644 index 000000000..df26c3cca --- /dev/null +++ b/src/iceberg/catalog/rest/auth/auth_manager.cc @@ -0,0 +1,41 @@ +/* + * 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. + */ + +#include "iceberg/catalog/rest/auth/auth_manager.h" + +#include "iceberg/catalog/rest/auth/auth_session.h" + +namespace iceberg::rest::auth { + +Result> AuthManager::InitSession( + HttpClient& init_client, + const std::unordered_map& properties) { + // By default, use the catalog session for initialization + return CatalogSession(init_client, properties); +} + +Result> AuthManager::TableSession( + [[maybe_unused]] const TableIdentifier& table, + [[maybe_unused]] const std::unordered_map& properties, + [[maybe_unused]] const AuthSession& parent) { + // By default, return nullptr to indicate the parent session should be reused. + return nullptr; +} + +} // namespace iceberg::rest::auth diff --git a/src/iceberg/catalog/rest/auth/auth_manager.h b/src/iceberg/catalog/rest/auth/auth_manager.h new file mode 100644 index 000000000..7388c7c54 --- /dev/null +++ b/src/iceberg/catalog/rest/auth/auth_manager.h @@ -0,0 +1,95 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include + +#include "iceberg/catalog/rest/iceberg_rest_export.h" +#include "iceberg/catalog/rest/type_fwd.h" +#include "iceberg/result.h" +#include "iceberg/table_identifier.h" + +/// \file iceberg/catalog/rest/auth/auth_manager.h +/// \brief Authentication manager interface for REST catalog. + +namespace iceberg::rest::auth { + +/// \brief Produces authentication sessions for catalog and table requests. +/// +/// AuthManager is responsible for creating authentication sessions at different scopes: +/// - InitSession: Short-lived session for catalog initialization (optional) +/// - CatalogSession: Long-lived session for catalog-level operations (required) +/// - TableSession: Optional table-specific session or reuse of catalog session +/// +/// Implementations are registered via AuthManagers::Register() and loaded by auth type. +class ICEBERG_REST_EXPORT AuthManager { + public: + virtual ~AuthManager() = default; + + /// \brief Create a short-lived session used to contact the configuration endpoint. + /// + /// This session is used only during catalog initialization to fetch server + /// configuration and perform initial authentication. It is typically discarded after + /// initialization. + /// + /// \param init_client HTTP client used for initialization requests. + /// \param properties Client configuration supplied by the catalog. + /// \return Session for initialization or an error if credentials cannot be acquired. + virtual Result> InitSession( + HttpClient& init_client, + const std::unordered_map& properties); + + /// \brief Create the long-lived catalog session that acts as the parent session. + /// + /// This session is used for all catalog-level operations (list namespaces, list tables, + /// etc.) and serves as the parent session for table-specific operations. It is owned + /// by the catalog and reused throughout the catalog's lifetime. + /// + /// \param shared_client HTTP client owned by the catalog and reused for auth calls. + /// \param properties Catalog properties (client config + server defaults). + /// \return Session for catalog operations or an error if authentication cannot be set + /// up. + virtual Result> CatalogSession( + HttpClient& shared_client, + const std::unordered_map& properties) = 0; + + /// \brief Create or reuse a session scoped to a single table/view. + /// + /// This method can return a new table-specific session or indicate that the parent + /// catalog session should be reused by returning nullptr. + /// + /// \param table Target table identifier. + /// \param properties Table-specific auth properties returned by the server. + /// \param parent Catalog session to inherit from or extract information from. + /// \return A new session for the table, nullptr to reuse parent session, or an error. + virtual Result> TableSession( + const TableIdentifier& table, + const std::unordered_map& properties, + const AuthSession& parent); + + /// \brief Release resources held by the manager. + /// + /// \return Status of the close operation. + virtual Status Close() { return {}; } +}; + +} // namespace iceberg::rest::auth diff --git a/src/iceberg/catalog/rest/auth/auth_managers.cc b/src/iceberg/catalog/rest/auth/auth_managers.cc new file mode 100644 index 000000000..877a48cb3 --- /dev/null +++ b/src/iceberg/catalog/rest/auth/auth_managers.cc @@ -0,0 +1,85 @@ +/* + * 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. + */ + +#include "iceberg/catalog/rest/auth/auth_managers.h" + +#include +#include + +#include "iceberg/catalog/rest/auth/auth_properties.h" +#include "iceberg/util/string_util.h" + +namespace iceberg::rest::auth { + +namespace { + +/// \brief Infer the authentication type from properties. +/// +/// If no explicit auth type is set, this function tries to infer it from +/// other properties. If "credential" or "token" is present, it implies +/// OAuth2 authentication. Otherwise, defaults to no authentication. +/// +/// This behavior is consistent with Java Iceberg's AuthManagers. +std::string InferAuthType( + const std::unordered_map& properties) { + // Check for explicit auth type first + auto it = properties.find(std::string(AuthProperties::kAuthType)); + if (it != properties.end() && !it->second.empty()) { + return StringUtils::ToLower(it->second); + } + + // Infer from OAuth2 properties (credential or token) + bool has_credential = + properties.contains(std::string(AuthProperties::kOAuth2Credential)); + bool has_token = properties.contains(std::string(AuthProperties::kOAuth2Token)); + if (has_credential || has_token) { + return std::string(AuthProperties::kAuthTypeOAuth2); + } + + // Default to no authentication + return std::string(AuthProperties::kAuthTypeNone); +} + +/// \brief Get the global registry of auth manager factories. +AuthManagerRegistry& GetRegistry() { + static AuthManagerRegistry registry; + return registry; +} + +} // namespace + +void AuthManagers::Register(std::string_view auth_type, AuthManagerFactory factory) { + GetRegistry()[StringUtils::ToLower(std::string(auth_type))] = std::move(factory); +} + +Result> AuthManagers::Load( + std::string_view name, + const std::unordered_map& properties) { + std::string auth_type = InferAuthType(properties); + + auto& registry = GetRegistry(); + auto it = registry.find(auth_type); + if (it == registry.end()) { + return NotImplemented("Authentication type '{}' is not supported", auth_type); + } + + return it->second(name, properties); +} + +} // namespace iceberg::rest::auth diff --git a/src/iceberg/catalog/rest/auth/auth_managers.h b/src/iceberg/catalog/rest/auth/auth_managers.h new file mode 100644 index 000000000..ffd0bd61d --- /dev/null +++ b/src/iceberg/catalog/rest/auth/auth_managers.h @@ -0,0 +1,70 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include +#include +#include + +#include "iceberg/catalog/rest/auth/auth_manager.h" +#include "iceberg/catalog/rest/iceberg_rest_export.h" +#include "iceberg/result.h" +#include "iceberg/util/string_util.h" + +/// \file iceberg/catalog/rest/auth/auth_managers.h +/// \brief Factory for creating authentication managers. + +namespace iceberg::rest::auth { + +/// \brief Function that builds an AuthManager for a given catalog. +/// +/// \param name Catalog name passed to the manager. +/// \param properties Consolidated catalog configuration. +/// \return Newly created manager instance. +using AuthManagerFactory = std::function( + std::string_view name, + const std::unordered_map& properties)>; + +/// \brief Registry type for AuthManager factories with heterogeneous lookup support. +using AuthManagerRegistry = + std::unordered_map; + +/// \brief Registry-backed factory for AuthManager implementations. +class ICEBERG_REST_EXPORT AuthManagers { + public: + /// \brief Load a manager by consulting the "rest.auth.type" configuration. + /// + /// \param name Catalog name passed to the manager. + /// \param properties Catalog properties used to determine auth type. + /// \return Manager instance or an error if no factory matches. + static Result> Load( + std::string_view name, + const std::unordered_map& properties); + + /// \brief Register or override the factory for a given auth type. + /// + /// \param auth_type Case-insensitive type identifier (e.g., "basic"). + /// \param factory Factory function that produces the manager. + static void Register(std::string_view auth_type, AuthManagerFactory factory); +}; + +} // namespace iceberg::rest::auth diff --git a/src/iceberg/catalog/rest/auth/auth_properties.h b/src/iceberg/catalog/rest/auth/auth_properties.h new file mode 100644 index 000000000..64eb46948 --- /dev/null +++ b/src/iceberg/catalog/rest/auth/auth_properties.h @@ -0,0 +1,69 @@ +/* + * 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. + */ + +#pragma once + +#include + +/// \file iceberg/catalog/rest/auth/auth_properties.h +/// \brief Property keys and constants for REST catalog authentication. + +namespace iceberg::rest::auth { + +/// \brief Property keys and constants for authentication configuration. +/// +/// This struct defines all the property keys used to configure authentication +/// for the REST catalog. It follows the same naming conventions as Java Iceberg. +struct AuthProperties { + /// \brief Property key for specifying the authentication type. + static constexpr std::string_view kAuthType = "rest.auth.type"; + /// \brief Authentication type: no authentication. + static constexpr std::string_view kAuthTypeNone = "none"; + /// \brief Authentication type: HTTP Basic authentication. + static constexpr std::string_view kAuthTypeBasic = "basic"; + /// \brief Authentication type: OAuth2 authentication. + static constexpr std::string_view kAuthTypeOAuth2 = "oauth2"; + /// \brief Authentication type: AWS SigV4 authentication. + static constexpr std::string_view kAuthTypeSigV4 = "sigv4"; + /// \brief Property key for Basic auth username. + static constexpr std::string_view kBasicUsername = "rest.auth.basic.username"; + /// \brief Property key for Basic auth password. + static constexpr std::string_view kBasicPassword = "rest.auth.basic.password"; + /// \brief Property key for OAuth2 token (bearer token). + static constexpr std::string_view kOAuth2Token = "token"; + /// \brief Property key for OAuth2 credential (client_id:client_secret). + static constexpr std::string_view kOAuth2Credential = "credential"; + /// \brief Property key for OAuth2 scope. + static constexpr std::string_view kOAuth2Scope = "scope"; + /// \brief Property key for OAuth2 server URI. + static constexpr std::string_view kOAuth2ServerUri = "oauth2-server-uri"; + /// \brief Property key for enabling token refresh. + static constexpr std::string_view kOAuth2TokenRefreshEnabled = "token-refresh-enabled"; + /// \brief Default OAuth2 scope for catalog operations. + static constexpr std::string_view kOAuth2DefaultScope = "catalog"; + /// \brief Property key for SigV4 region. + static constexpr std::string_view kSigV4Region = "rest.auth.sigv4.region"; + /// \brief Property key for SigV4 service name. + static constexpr std::string_view kSigV4Service = "rest.auth.sigv4.service"; + /// \brief Property key for SigV4 delegate auth type. + static constexpr std::string_view kSigV4DelegateAuthType = + "rest.auth.sigv4.delegate-auth-type"; +}; + +} // namespace iceberg::rest::auth diff --git a/src/iceberg/catalog/rest/auth/auth_session.cc b/src/iceberg/catalog/rest/auth/auth_session.cc new file mode 100644 index 000000000..6f17ff526 --- /dev/null +++ b/src/iceberg/catalog/rest/auth/auth_session.cc @@ -0,0 +1,43 @@ +/* + * 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. + */ + +#include "iceberg/catalog/rest/auth/auth_session.h" + +#include + +namespace iceberg::rest::auth { + +DefaultAuthSession::DefaultAuthSession( + std::unordered_map headers) + : headers_(std::move(headers)) {} + +Status DefaultAuthSession::Authenticate( + std::unordered_map& headers) { + for (const auto& [key, value] : headers_) { + headers.try_emplace(key, value); + } + return {}; +} + +std::unique_ptr DefaultAuthSession::Make( + std::unordered_map headers) { + return std::make_unique(std::move(headers)); +} + +} // namespace iceberg::rest::auth diff --git a/src/iceberg/catalog/rest/auth/auth_session.h b/src/iceberg/catalog/rest/auth/auth_session.h new file mode 100644 index 000000000..11bfca02c --- /dev/null +++ b/src/iceberg/catalog/rest/auth/auth_session.h @@ -0,0 +1,94 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include + +#include "iceberg/catalog/rest/iceberg_rest_export.h" +#include "iceberg/result.h" + +/// \file iceberg/catalog/rest/auth/auth_session.h +/// \brief Authentication session interface for REST catalog. + +namespace iceberg::rest::auth { + +/// \brief An authentication session that can authenticate outgoing HTTP requests. +/// +/// Authentication sessions are typically immutable, but may hold resources that need +/// to be released when the session is no longer needed (e.g., token refresh threads). +/// Implementations should override Close() to release any such resources. +/// +class ICEBERG_REST_EXPORT AuthSession { + public: + virtual ~AuthSession() = default; + + /// \brief Authenticate the given request headers. + /// + /// This method adds authentication information (e.g., Authorization header) + /// to the provided headers map. The implementation should be idempotent. + /// + /// \param[in,out] headers The headers map to add authentication information to. + /// \return Status indicating success or failure of authentication. + /// - Success: Returns Status::OK + /// - Failure: Returns one of the following errors: + /// - 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& headers) = 0; + + /// \brief Close the session and release any resources. + /// + /// This method is called when the session is no longer needed. For stateful + /// sessions (e.g., OAuth2 with token refresh), this should stop any background + /// threads and release resources. + /// + /// \return Status indicating success or failure of closing the session. + virtual Status Close() { return {}; } +}; + +/// \brief A default authentication session that adds static headers to requests. +/// +/// This implementation authenticates requests by adding a fixed set of headers. +/// It is suitable for authentication methods that use static credentials, +/// such as Basic auth or static bearer tokens. +class ICEBERG_REST_EXPORT DefaultAuthSession : public AuthSession { + public: + /// \brief Construct a DefaultAuthSession with the given headers. + /// + /// \param headers The headers to add to each request for authentication. + explicit DefaultAuthSession(std::unordered_map headers); + + ~DefaultAuthSession() override = default; + + Status Authenticate(std::unordered_map& headers) override; + + static std::unique_ptr Make( + std::unordered_map headers); + + private: + std::unordered_map headers_; +}; + +} // namespace iceberg::rest::auth diff --git a/src/iceberg/catalog/rest/meson.build b/src/iceberg/catalog/rest/meson.build index a914c7e2d..aaff255e4 100644 --- a/src/iceberg/catalog/rest/meson.build +++ b/src/iceberg/catalog/rest/meson.build @@ -16,6 +16,9 @@ # under the License. iceberg_rest_sources = files( + 'auth/auth_manager.cc', + 'auth/auth_managers.cc', + 'auth/auth_session.cc', 'catalog_properties.cc', 'endpoint.cc', 'error_handlers.cc', @@ -72,3 +75,13 @@ install_headers( ], subdir: 'iceberg/catalog/rest', ) + +install_headers( + [ + 'auth/auth_manager.h', + 'auth/auth_managers.h', + 'auth/auth_properties.h', + 'auth/auth_session.h', + ], + subdir: 'iceberg/catalog/rest/auth', +) diff --git a/src/iceberg/catalog/rest/type_fwd.h b/src/iceberg/catalog/rest/type_fwd.h index e7fddb91a..daebad6d7 100644 --- a/src/iceberg/catalog/rest/type_fwd.h +++ b/src/iceberg/catalog/rest/type_fwd.h @@ -34,3 +34,11 @@ class RestCatalog; class RestCatalogProperties; } // namespace iceberg::rest + +namespace iceberg::rest::auth { + +class AuthManager; +class AuthSession; +class DefaultAuthSession; + +} // namespace iceberg::rest::auth diff --git a/src/iceberg/result.h b/src/iceberg/result.h index ddc428a23..223352f61 100644 --- a/src/iceberg/result.h +++ b/src/iceberg/result.h @@ -30,6 +30,7 @@ namespace iceberg { /// \brief Error types for iceberg. enum class ErrorKind { kAlreadyExists, + kAuthenticationFailed, kBadRequest, kCommitFailed, kCommitStateUnknown, @@ -56,6 +57,7 @@ enum class ErrorKind { kNotSupported, kRestError, kServiceUnavailable, + kTokenExpired, kUnknownError, kValidationFailed, }; @@ -91,6 +93,7 @@ using Status = Result; } DEFINE_ERROR_FUNCTION(AlreadyExists) +DEFINE_ERROR_FUNCTION(AuthenticationFailed) DEFINE_ERROR_FUNCTION(BadRequest) DEFINE_ERROR_FUNCTION(CommitFailed) DEFINE_ERROR_FUNCTION(CommitStateUnknown) @@ -117,6 +120,7 @@ DEFINE_ERROR_FUNCTION(NotImplemented) DEFINE_ERROR_FUNCTION(NotSupported) DEFINE_ERROR_FUNCTION(RestError) DEFINE_ERROR_FUNCTION(ServiceUnavailable) +DEFINE_ERROR_FUNCTION(TokenExpired) DEFINE_ERROR_FUNCTION(UnknownError) DEFINE_ERROR_FUNCTION(ValidationFailed)