Skip to content

Commit ae3d3b8

Browse files
committed
Initial commit
1 parent 637294e commit ae3d3b8

13 files changed

Lines changed: 1144 additions & 221 deletions

cpp/CMakeLists.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,8 +181,9 @@ set(SOURCES
181181
)
182182

183183
if(KvikIO_REMOTE_SUPPORT)
184-
list(APPEND SOURCES "src/hdfs.cpp" "src/remote_handle.cpp" "src/detail/remote_handle.cpp"
185-
"src/detail/tls.cpp" "src/detail/url.cpp" "src/shim/libcurl.cpp"
184+
list(
185+
APPEND SOURCES "src/aws_credential_provider.cpp" "src/hdfs.cpp" "src/remote_handle.cpp"
186+
"src/detail/remote_handle.cpp" "src/detail/tls.cpp" "src/detail/url.cpp" "src/shim/libcurl.cpp"
186187
)
187188
endif()
188189

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
#pragma once
6+
7+
#ifndef KVIKIO_LIBCURL_FOUND
8+
#error \
9+
"cannot include the remote IO API, please build KvikIO with libcurl (-DKvikIO_REMOTE_SUPPORT=ON)"
10+
#endif
11+
12+
#include <memory>
13+
#include <optional>
14+
#include <string>
15+
16+
struct curl_slist;
17+
18+
namespace kvikio {
19+
20+
/**
21+
* @brief Immutable AWS SigV4 user/password and optional session-token header for libcurl.
22+
*
23+
* `token_header_list` must outlive `curl_easy_perform`; callers hold `shared_ptr` to this object
24+
* until the transfer completes.
25+
*/
26+
class AwsAuthMaterial {
27+
public:
28+
std::string userpwd;
29+
::curl_slist* token_header_list{};
30+
31+
AwsAuthMaterial();
32+
~AwsAuthMaterial();
33+
AwsAuthMaterial(AwsAuthMaterial const&) = delete;
34+
AwsAuthMaterial& operator=(AwsAuthMaterial const&) = delete;
35+
AwsAuthMaterial(AwsAuthMaterial&&) = delete;
36+
AwsAuthMaterial& operator=(AwsAuthMaterial&&) = delete;
37+
38+
static std::shared_ptr<AwsAuthMaterial const> create(std::string access_key_id,
39+
std::string secret_access_key,
40+
std::optional<std::string> session_token);
41+
};
42+
43+
/**
44+
* @brief How Python / Cython select the AWS credential source for S3.
45+
*/
46+
enum class AwsCredentialKind : std::uint8_t {
47+
Default = 0, ///< Environment keys if set, else IAM role via metadata (IMDSv2)
48+
Environment = 1, ///< `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` / optional token only
49+
Static = 2, ///< Explicit access key, secret, optional session token
50+
IamRole = 3, ///< IAM role credentials from the compute metadata service (IMDSv2) only
51+
Legacy = 4, ///< Optional args plus environment (pre-credential S3 API semantics)
52+
};
53+
54+
class AwsCredentialProvider {
55+
public:
56+
virtual ~AwsCredentialProvider() = default;
57+
58+
/**
59+
* @brief Return auth material for one HTTP request; implementations cache and refresh as needed.
60+
*/
61+
virtual std::shared_ptr<AwsAuthMaterial const> get_auth_material() = 0;
62+
};
63+
64+
/**
65+
* @brief Build a credential provider for the given kind (used by Cython).
66+
*
67+
* @param kind Credential selection mode
68+
* @param aws_access_key Required when kind == Static; optional when kind == Legacy (env fallback)
69+
* @param aws_secret_access_key Required when kind == Static
70+
* @param aws_session_token Optional; required when access key begins with "ASIA" (Static / Legacy)
71+
* @param imds_endpoint_override Optional base URL (e.g. http://127.0.0.1:1234) for tests; if
72+
* nullopt, uses `AWS_EC2_METADATA_SERVICE_ENDPOINT` or the default EC2 link-local address.
73+
*/
74+
std::shared_ptr<AwsCredentialProvider> make_aws_credential_provider(
75+
AwsCredentialKind kind,
76+
std::optional<std::string> aws_access_key = std::nullopt,
77+
std::optional<std::string> aws_secret_access_key = std::nullopt,
78+
std::optional<std::string> aws_session_token = std::nullopt,
79+
std::optional<std::string> imds_endpoint_override = std::nullopt);
80+
81+
/**
82+
* @brief Provider matching legacy S3Endpoint optional arguments plus environment variables.
83+
*/
84+
std::shared_ptr<AwsCredentialProvider> make_legacy_env_and_args_credential_provider(
85+
std::optional<std::string> aws_access_key,
86+
std::optional<std::string> aws_secret_access_key,
87+
std::optional<std::string> aws_session_token);
88+
89+
/**
90+
* @brief Default chain: static env keys if both `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`
91+
* are set and non-empty, otherwise IAM role credentials via the metadata service (IMDSv2).
92+
*/
93+
std::shared_ptr<AwsCredentialProvider> make_default_aws_credential_provider(
94+
std::optional<std::string> imds_endpoint_override = std::nullopt);
95+
96+
} // namespace kvikio

cpp/include/kvikio/remote_handle.hpp

Lines changed: 31 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: Copyright (c) 2024-2025, NVIDIA CORPORATION.
2+
* SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION.
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55
#pragma once
@@ -16,11 +16,11 @@
1616
#include <kvikio/threadpool_wrapper.hpp>
1717
#include <kvikio/utils.hpp>
1818

19-
struct curl_slist;
20-
2119
namespace kvikio {
2220

2321
class CurlHandle; // Prototype
22+
class AwsAuthMaterial;
23+
class AwsCredentialProvider;
2424

2525
/**
2626
* @brief Types of remote file endpoints supported by KvikIO.
@@ -135,14 +135,14 @@ class HttpEndpoint : public RemoteEndpoint {
135135
* @brief A remote endpoint for AWS S3 storage requiring credentials
136136
*
137137
* This endpoint is for accessing private S3 objects using AWS credentials (access key, secret key,
138-
* region and optional session token).
138+
* region and optional session token), optionally sourced from the environment, explicit parameters,
139+
* or IAM role credentials via the compute metadata service (IMDSv2).
139140
*/
140141
class S3Endpoint : public RemoteEndpoint {
141142
private:
142143
std::string _url;
143144
std::string _aws_sigv4;
144-
std::string _aws_userpwd;
145-
curl_slist* _curl_header_list{};
145+
std::shared_ptr<AwsCredentialProvider> _credential_provider;
146146

147147
public:
148148
/**
@@ -176,19 +176,21 @@ class S3Endpoint : public RemoteEndpoint {
176176
[[nodiscard]] static std::pair<std::string, std::string> parse_s3_url(std::string const& s3_url);
177177

178178
/**
179-
* @brief Create a S3 endpoint from a url.
179+
* @brief Create a S3 endpoint from a url and credential provider.
180180
*
181181
* @param url The full http url to the S3 file. NB: this should be an url starting with
182182
* "http://" or "https://". If you have an S3 url of the form "s3://<bucket>/<object>", please
183183
* use `S3Endpoint::parse_s3_url()` and `S3Endpoint::url_from_bucket_and_object() to convert it.
184184
* @param aws_region The AWS region, such as "us-east-1", to use. If nullopt, the value of the
185185
* `AWS_DEFAULT_REGION` environment variable is used.
186-
* @param aws_access_key The AWS access key to use. If nullopt, the value of the
187-
* `AWS_ACCESS_KEY_ID` environment variable is used.
188-
* @param aws_secret_access_key The AWS secret access key to use. If nullopt, the value of the
189-
* `AWS_SECRET_ACCESS_KEY` environment variable is used.
190-
* @param aws_session_token The AWS session token to use. If nullopt, the value of the
191-
* `AWS_SESSION_TOKEN` environment variable is used.
186+
* @param credential_provider Source for AWS access key, secret, and optional session token.
187+
*/
188+
S3Endpoint(std::string url,
189+
std::optional<std::string> aws_region,
190+
std::shared_ptr<AwsCredentialProvider> credential_provider);
191+
192+
/**
193+
* @brief Create a S3 endpoint from a url (legacy optional arguments and environment variables).
192194
*/
193195
S3Endpoint(std::string url,
194196
std::optional<std::string> aws_region = std::nullopt,
@@ -197,21 +199,15 @@ class S3Endpoint : public RemoteEndpoint {
197199
std::optional<std::string> aws_session_token = std::nullopt);
198200

199201
/**
200-
* @brief Create a S3 endpoint from a bucket and object name.
201-
*
202-
* @param bucket_and_object_names The bucket and object names of the S3 bucket.
203-
* @param aws_region The AWS region, such as "us-east-1", to use. If nullopt, the value of the
204-
* `AWS_DEFAULT_REGION` environment variable is used.
205-
* @param aws_access_key The AWS access key to use. If nullopt, the value of the
206-
* `AWS_ACCESS_KEY_ID` environment variable is used.
207-
* @param aws_secret_access_key The AWS secret access key to use. If nullopt, the value of the
208-
* `AWS_SECRET_ACCESS_KEY` environment variable is used.
209-
* @param aws_endpoint_url Overwrite the endpoint url (including the protocol part) by using
210-
* the scheme: "<aws_endpoint_url>/<bucket_name>/<object_name>". If nullopt, the value of the
211-
* `AWS_ENDPOINT_URL` environment variable is used. If this is also not set, the regular AWS
212-
* url scheme is used: "https://<bucket_name>.s3.<region>.amazonaws.com/<object_name>".
213-
* @param aws_session_token The AWS session token to use. If nullopt, the value of the
214-
* `AWS_SESSION_TOKEN` environment variable is used.
202+
* @brief Create a S3 endpoint from a bucket and object name and credential provider.
203+
*/
204+
S3Endpoint(std::pair<std::string, std::string> bucket_and_object_names,
205+
std::optional<std::string> aws_region,
206+
std::optional<std::string> aws_endpoint_url,
207+
std::shared_ptr<AwsCredentialProvider> credential_provider);
208+
209+
/**
210+
* @brief Create a S3 endpoint from a bucket and object name (legacy optional arguments).
215211
*/
216212
S3Endpoint(std::pair<std::string, std::string> bucket_and_object_names,
217213
std::optional<std::string> aws_region = std::nullopt,
@@ -222,6 +218,13 @@ class S3Endpoint : public RemoteEndpoint {
222218

223219
~S3Endpoint() override;
224220
void setopt(CurlHandle& curl) override;
221+
/**
222+
* @brief Apply SigV4 user/password and session token headers from `material` to `curl`.
223+
*
224+
* Call after `setopt()` on the same handle. Hold `material` alive until `curl.perform()` returns.
225+
*/
226+
void apply_auth_to_curl(CurlHandle& curl, AwsAuthMaterial const& material) const;
227+
[[nodiscard]] std::shared_ptr<AwsAuthMaterial const> get_auth_material();
225228
std::string str() const override;
226229
std::size_t get_file_size() override;
227230
void setup_range_request(CurlHandle& curl, std::size_t file_offset, std::size_t size) override;

0 commit comments

Comments
 (0)