Skip to content

Commit dc03c6a

Browse files
committed
libstore: Put all the AWS credentials logic behind interface class AwsCredentialProvider
This makes it so we don't need to rely on global variables and hacky destructors to clean up another global variable. Just putting it in the correct order in the class is more than enough.
1 parent b1d067c commit dc03c6a

File tree

4 files changed

+69
-89
lines changed

4 files changed

+69
-89
lines changed

src/libstore/aws-creds.cc

Lines changed: 34 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -24,50 +24,6 @@ namespace nix {
2424

2525
namespace {
2626

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()
39-
{
40-
credentialProviderCache.clear();
41-
}
42-
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-
}
70-
7127
static AwsCredentials getCredentialsFromProvider(std::shared_ptr<Aws::Crt::Auth::ICredentialsProvider> provider)
7228
{
7329
if (!provider || !provider->IsValid()) {
@@ -113,7 +69,35 @@ static AwsCredentials getCredentialsFromProvider(std::shared_ptr<Aws::Crt::Auth:
11369

11470
} // anonymous namespace
11571

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

134118
try {
135-
initAwsCrt();
136-
137119
if (profile.empty()) {
138120
Aws::Crt::Auth::CredentialsProviderChainDefaultConfig config;
139121
config.Bootstrap = Aws::Crt::ApiHandle::GetOrCreateStaticDefaultClientBootstrap();
@@ -173,17 +155,15 @@ AwsCredentials getAwsCredentials(const std::string & profile)
173155
return getCredentialsFromProvider(provider);
174156
}
175157

176-
void invalidateAwsCredentials(const std::string & profile)
158+
ref<AwsCredentialProvider> makeAwsCredentialsProvider()
177159
{
178-
credentialProviderCache.erase(profile);
160+
return make_ref<AwsCredentialProviderImpl>();
179161
}
180162

181-
AwsCredentials preResolveAwsCredentials(const ParsedS3URL & s3Url)
163+
ref<AwsCredentialProvider> getAwsCredentialsProvider()
182164
{
183-
std::string profile = s3Url.profile.value_or("");
184-
185-
// Get credentials (automatically cached)
186-
return getAwsCredentials(profile);
165+
static auto instance = makeAwsCredentialsProvider();
166+
return instance;
187167
}
188168

189169
} // 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: 28 additions & 18 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>
@@ -38,30 +39,39 @@ struct AwsCredentials
3839
*/
3940
MakeError(AwsAuthError, Error);
4041

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 = "");
42+
class AwsCredentialProvider
43+
{
44+
public:
45+
/**
46+
* Get AWS credentials for the given URL.
47+
*
48+
* @param url The S3 url to get the credentials for
49+
* @return AWS credentials
50+
* @throws AwsAuthError if credentials cannot be resolved
51+
*/
52+
virtual AwsCredentials getCredentials(const ParsedS3URL & url) = 0;
53+
54+
std::optional<AwsCredentials> maybeGetCredentials(const ParsedS3URL & url)
55+
{
56+
try {
57+
return getCredentials(url);
58+
} catch (AwsAuthError & e) {
59+
return std::nullopt;
60+
}
61+
}
62+
63+
virtual ~AwsCredentialProvider() {}
64+
};
5165

5266
/**
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
67+
* Create a new instancee of AwsCredentialProvider.
5768
*/
58-
void invalidateAwsCredentials(const std::string & profile);
69+
ref<AwsCredentialProvider> makeAwsCredentialsProvider();
5970

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

6676
} // namespace nix
6777
#endif

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
}

0 commit comments

Comments
 (0)