Skip to content

Commit d87a06a

Browse files
authored
Merge pull request NixOS#14275 from NixOS/s3-cleanup
libstore: Miscellaneous S3 store cleanups
2 parents 2dc9f2a + e7047fd commit d87a06a

File tree

8 files changed

+91
-95
lines changed

8 files changed

+91
-95
lines changed

ci/gha/tests/default.nix

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ rec {
222222
};
223223

224224
vmTests = {
225-
inherit (nixosTests) curl-s3-binary-cache-store;
225+
inherit (nixosTests) s3-binary-cache-store;
226226
}
227227
// lib.optionalAttrs (!withSanitizers && !withCoverage) {
228228
# evalNixpkgs uses non-instrumented components from hydraJobs, so only run it

src/libstore/aws-creds.cc

Lines changed: 39 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -22,51 +22,13 @@
2222

2323
namespace nix {
2424

25-
namespace {
26-
27-
// Global credential provider cache using boost's concurrent map
28-
// Key: profile name (empty string for default profile)
29-
using CredentialProviderCache =
30-
boost::concurrent_flat_map<std::string, std::shared_ptr<Aws::Crt::Auth::ICredentialsProvider>>;
31-
32-
static CredentialProviderCache credentialProviderCache;
33-
34-
/**
35-
* Clear all cached credential providers.
36-
* Called automatically by CrtWrapper destructor during static destruction.
37-
*/
38-
static void clearAwsCredentialsCache()
25+
AwsAuthError::AwsAuthError(int errorCode)
26+
: Error("AWS authentication error: '%s' (%d)", aws_error_str(errorCode), errorCode)
27+
, errorCode(errorCode)
3928
{
40-
credentialProviderCache.clear();
4129
}
4230

43-
static void initAwsCrt()
44-
{
45-
struct CrtWrapper
46-
{
47-
Aws::Crt::ApiHandle apiHandle;
48-
49-
CrtWrapper()
50-
{
51-
apiHandle.InitializeLogging(Aws::Crt::LogLevel::Warn, static_cast<FILE *>(nullptr));
52-
}
53-
54-
~CrtWrapper()
55-
{
56-
try {
57-
// CRITICAL: Clear credential provider cache BEFORE AWS CRT shuts down
58-
// This ensures all providers (which hold references to ClientBootstrap)
59-
// are destroyed while AWS CRT is still valid
60-
clearAwsCredentialsCache();
61-
// Now it's safe for ApiHandle destructor to run
62-
} catch (...) {
63-
ignoreExceptionInDestructor();
64-
}
65-
}
66-
};
67-
68-
static CrtWrapper crt;
69-
}
31+
namespace {
7032

7133
static AwsCredentials getCredentialsFromProvider(std::shared_ptr<Aws::Crt::Auth::ICredentialsProvider> provider)
7234
{
@@ -79,8 +41,7 @@ static AwsCredentials getCredentialsFromProvider(std::shared_ptr<Aws::Crt::Auth:
7941

8042
provider->GetCredentials([prom](std::shared_ptr<Aws::Crt::Auth::Credentials> credentials, int errorCode) {
8143
if (errorCode != 0 || !credentials) {
82-
prom->set_exception(
83-
std::make_exception_ptr(AwsAuthError("Failed to resolve AWS credentials: error code %d", errorCode)));
44+
prom->set_exception(std::make_exception_ptr(AwsAuthError(errorCode)));
8445
} else {
8546
auto accessKeyId = Aws::Crt::ByteCursorToStringView(credentials->GetAccessKeyId());
8647
auto secretAccessKey = Aws::Crt::ByteCursorToStringView(credentials->GetSecretAccessKey());
@@ -113,7 +74,35 @@ static AwsCredentials getCredentialsFromProvider(std::shared_ptr<Aws::Crt::Auth:
11374

11475
} // anonymous namespace
11576

116-
AwsCredentials getAwsCredentials(const std::string & profile)
77+
class AwsCredentialProviderImpl : public AwsCredentialProvider
78+
{
79+
public:
80+
AwsCredentialProviderImpl()
81+
{
82+
apiHandle.InitializeLogging(Aws::Crt::LogLevel::Warn, static_cast<FILE *>(nullptr));
83+
}
84+
85+
AwsCredentials getCredentialsRaw(const std::string & profile);
86+
87+
AwsCredentials getCredentials(const ParsedS3URL & url) override
88+
{
89+
auto profile = url.profile.value_or("");
90+
try {
91+
return getCredentialsRaw(profile);
92+
} catch (AwsAuthError & e) {
93+
warn("AWS authentication failed for S3 request %s: %s", url.toHttpsUrl(), e.message());
94+
credentialProviderCache.erase(profile);
95+
throw;
96+
}
97+
}
98+
99+
private:
100+
Aws::Crt::ApiHandle apiHandle;
101+
boost::concurrent_flat_map<std::string, std::shared_ptr<Aws::Crt::Auth::ICredentialsProvider>>
102+
credentialProviderCache;
103+
};
104+
105+
AwsCredentials AwsCredentialProviderImpl::getCredentialsRaw(const std::string & profile)
117106
{
118107
// Get or create credential provider with caching
119108
std::shared_ptr<Aws::Crt::Auth::ICredentialsProvider> provider;
@@ -132,8 +121,6 @@ AwsCredentials getAwsCredentials(const std::string & profile)
132121
profile.empty() ? "(default)" : profile.c_str());
133122

134123
try {
135-
initAwsCrt();
136-
137124
if (profile.empty()) {
138125
Aws::Crt::Auth::CredentialsProviderChainDefaultConfig config;
139126
config.Bootstrap = Aws::Crt::ApiHandle::GetOrCreateStaticDefaultClientBootstrap();
@@ -173,17 +160,15 @@ AwsCredentials getAwsCredentials(const std::string & profile)
173160
return getCredentialsFromProvider(provider);
174161
}
175162

176-
void invalidateAwsCredentials(const std::string & profile)
163+
ref<AwsCredentialProvider> makeAwsCredentialsProvider()
177164
{
178-
credentialProviderCache.erase(profile);
165+
return make_ref<AwsCredentialProviderImpl>();
179166
}
180167

181-
AwsCredentials preResolveAwsCredentials(const ParsedS3URL & s3Url)
168+
ref<AwsCredentialProvider> getAwsCredentialsProvider()
182169
{
183-
std::string profile = s3Url.profile.value_or("");
184-
185-
// Get credentials (automatically cached)
186-
return getAwsCredentials(profile);
170+
static auto instance = makeAwsCredentialsProvider();
171+
return instance;
187172
}
188173

189174
} // namespace nix

src/libstore/filetransfer.cc

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -883,22 +883,12 @@ void FileTransferRequest::setupForS3()
883883
if (usernameAuth) {
884884
debug("Using pre-resolved AWS credentials from parent process");
885885
sessionToken = preResolvedAwsSessionToken;
886-
} else {
887-
std::string profile = parsedS3.profile.value_or("");
888-
try {
889-
auto creds = getAwsCredentials(profile);
890-
usernameAuth = UsernameAuth{
891-
.username = creds.accessKeyId,
892-
.password = creds.secretAccessKey,
893-
};
894-
sessionToken = creds.sessionToken;
895-
} catch (const AwsAuthError & e) {
896-
warn("AWS authentication failed for S3 request %s: %s", uri, e.what());
897-
// Invalidate the cached credentials so next request will retry
898-
invalidateAwsCredentials(profile);
899-
// Continue without authentication - might be a public bucket
900-
return;
901-
}
886+
} else if (auto creds = getAwsCredentialsProvider()->maybeGetCredentials(parsedS3)) {
887+
usernameAuth = UsernameAuth{
888+
.username = creds->accessKeyId,
889+
.password = creds->secretAccessKey,
890+
};
891+
sessionToken = creds->sessionToken;
902892
}
903893
if (sessionToken)
904894
headers.emplace_back("x-amz-security-token", *sessionToken);

src/libstore/include/nix/store/aws-creds.hh

Lines changed: 41 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#if NIX_WITH_AWS_AUTH
66

77
# include "nix/store/s3-url.hh"
8+
# include "nix/util/ref.hh"
89
# include "nix/util/error.hh"
910

1011
# include <memory>
@@ -33,35 +34,53 @@ struct AwsCredentials
3334
}
3435
};
3536

36-
/**
37-
* Exception thrown when AWS authentication fails
38-
*/
39-
MakeError(AwsAuthError, Error);
37+
class AwsAuthError : public Error
38+
{
39+
std::optional<int> errorCode;
4040

41-
/**
42-
* Get AWS credentials for the given profile.
43-
* This function automatically caches credential providers to avoid
44-
* creating multiple providers for the same profile.
45-
*
46-
* @param profile The AWS profile name (empty string for default profile)
47-
* @return AWS credentials
48-
* @throws AwsAuthError if credentials cannot be resolved
49-
*/
50-
AwsCredentials getAwsCredentials(const std::string & profile = "");
41+
public:
42+
using Error::Error;
43+
AwsAuthError(int errorCode);
44+
45+
std::optional<int> getErrorCode() const
46+
{
47+
return errorCode;
48+
}
49+
};
50+
51+
class AwsCredentialProvider
52+
{
53+
public:
54+
/**
55+
* Get AWS credentials for the given URL.
56+
*
57+
* @param url The S3 url to get the credentials for
58+
* @return AWS credentials
59+
* @throws AwsAuthError if credentials cannot be resolved
60+
*/
61+
virtual AwsCredentials getCredentials(const ParsedS3URL & url) = 0;
62+
63+
std::optional<AwsCredentials> maybeGetCredentials(const ParsedS3URL & url)
64+
{
65+
try {
66+
return getCredentials(url);
67+
} catch (AwsAuthError & e) {
68+
return std::nullopt;
69+
}
70+
}
71+
72+
virtual ~AwsCredentialProvider() {}
73+
};
5174

5275
/**
53-
* Invalidate cached credentials for a profile (e.g., on authentication failure).
54-
* The next request for this profile will create a new provider.
55-
*
56-
* @param profile The AWS profile name to invalidate
76+
* Create a new instancee of AwsCredentialProvider.
5777
*/
58-
void invalidateAwsCredentials(const std::string & profile);
78+
ref<AwsCredentialProvider> makeAwsCredentialsProvider();
5979

6080
/**
61-
* Pre-resolve AWS credentials for S3 URLs.
62-
* Used to cache credentials in parent process before forking.
81+
* Get a reference to the global AwsCredentialProvider.
6382
*/
64-
AwsCredentials preResolveAwsCredentials(const ParsedS3URL & s3Url);
83+
ref<AwsCredentialProvider> getAwsCredentialsProvider();
6584

6685
} // namespace nix
6786
#endif

src/libstore/meson.build

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,8 @@ curl_s3_store_opt = get_option('curl-s3-store').require(
158158

159159
if curl_s3_store_opt.enabled()
160160
deps_other += aws_crt_cpp
161+
aws_c_common = cxx.find_library('aws-c-common', required : true)
162+
deps_other += aws_c_common
161163
endif
162164

163165
configdata_pub.set('NIX_WITH_AWS_AUTH', curl_s3_store_opt.enabled().to_int())

src/libstore/unix/build/derivation-builder.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -958,7 +958,7 @@ std::optional<AwsCredentials> DerivationBuilderImpl::preResolveAwsCredentials()
958958
auto s3Url = ParsedS3URL::parse(parsedUrl);
959959

960960
// Use the preResolveAwsCredentials from aws-creds
961-
auto credentials = nix::preResolveAwsCredentials(s3Url);
961+
auto credentials = getAwsCredentialsProvider()->getCredentials(s3Url);
962962
debug("Successfully pre-resolved AWS credentials in parent process");
963963
return credentials;
964964
}

tests/nixos/default.nix

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ in
199199

200200
user-sandboxing = runNixOSTest ./user-sandboxing;
201201

202-
curl-s3-binary-cache-store = runNixOSTest ./curl-s3-binary-cache-store.nix;
202+
s3-binary-cache-store = runNixOSTest ./s3-binary-cache-store.nix;
203203

204204
fsync = runNixOSTest ./fsync.nix;
205205

File renamed without changes.

0 commit comments

Comments
 (0)