1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-11-08 19:46:02 +01:00

Merge pull request #14275 from NixOS/s3-cleanup

libstore: Miscellaneous S3 store cleanups
This commit is contained in:
John Ericson 2025-10-16 19:36:59 +00:00 committed by GitHub
commit d87a06af7a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 94 additions and 98 deletions

View file

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

View file

@ -22,52 +22,14 @@
namespace nix { namespace nix {
AwsAuthError::AwsAuthError(int errorCode)
: Error("AWS authentication error: '%s' (%d)", aws_error_str(errorCode), errorCode)
, errorCode(errorCode)
{
}
namespace { namespace {
// Global credential provider cache using boost's concurrent map
// Key: profile name (empty string for default profile)
using CredentialProviderCache =
boost::concurrent_flat_map<std::string, std::shared_ptr<Aws::Crt::Auth::ICredentialsProvider>>;
static CredentialProviderCache credentialProviderCache;
/**
* Clear all cached credential providers.
* Called automatically by CrtWrapper destructor during static destruction.
*/
static void clearAwsCredentialsCache()
{
credentialProviderCache.clear();
}
static void initAwsCrt()
{
struct CrtWrapper
{
Aws::Crt::ApiHandle apiHandle;
CrtWrapper()
{
apiHandle.InitializeLogging(Aws::Crt::LogLevel::Warn, static_cast<FILE *>(nullptr));
}
~CrtWrapper()
{
try {
// CRITICAL: Clear credential provider cache BEFORE AWS CRT shuts down
// This ensures all providers (which hold references to ClientBootstrap)
// are destroyed while AWS CRT is still valid
clearAwsCredentialsCache();
// Now it's safe for ApiHandle destructor to run
} catch (...) {
ignoreExceptionInDestructor();
}
}
};
static CrtWrapper crt;
}
static AwsCredentials getCredentialsFromProvider(std::shared_ptr<Aws::Crt::Auth::ICredentialsProvider> provider) static AwsCredentials getCredentialsFromProvider(std::shared_ptr<Aws::Crt::Auth::ICredentialsProvider> provider)
{ {
if (!provider || !provider->IsValid()) { if (!provider || !provider->IsValid()) {
@ -79,8 +41,7 @@ static AwsCredentials getCredentialsFromProvider(std::shared_ptr<Aws::Crt::Auth:
provider->GetCredentials([prom](std::shared_ptr<Aws::Crt::Auth::Credentials> credentials, int errorCode) { provider->GetCredentials([prom](std::shared_ptr<Aws::Crt::Auth::Credentials> credentials, int errorCode) {
if (errorCode != 0 || !credentials) { if (errorCode != 0 || !credentials) {
prom->set_exception( prom->set_exception(std::make_exception_ptr(AwsAuthError(errorCode)));
std::make_exception_ptr(AwsAuthError("Failed to resolve AWS credentials: error code %d", errorCode)));
} else { } else {
auto accessKeyId = Aws::Crt::ByteCursorToStringView(credentials->GetAccessKeyId()); auto accessKeyId = Aws::Crt::ByteCursorToStringView(credentials->GetAccessKeyId());
auto secretAccessKey = Aws::Crt::ByteCursorToStringView(credentials->GetSecretAccessKey()); auto secretAccessKey = Aws::Crt::ByteCursorToStringView(credentials->GetSecretAccessKey());
@ -113,7 +74,35 @@ static AwsCredentials getCredentialsFromProvider(std::shared_ptr<Aws::Crt::Auth:
} // anonymous namespace } // anonymous namespace
AwsCredentials getAwsCredentials(const std::string & profile) class AwsCredentialProviderImpl : public AwsCredentialProvider
{
public:
AwsCredentialProviderImpl()
{
apiHandle.InitializeLogging(Aws::Crt::LogLevel::Warn, static_cast<FILE *>(nullptr));
}
AwsCredentials getCredentialsRaw(const std::string & profile);
AwsCredentials getCredentials(const ParsedS3URL & url) override
{
auto profile = url.profile.value_or("");
try {
return getCredentialsRaw(profile);
} catch (AwsAuthError & e) {
warn("AWS authentication failed for S3 request %s: %s", url.toHttpsUrl(), e.message());
credentialProviderCache.erase(profile);
throw;
}
}
private:
Aws::Crt::ApiHandle apiHandle;
boost::concurrent_flat_map<std::string, std::shared_ptr<Aws::Crt::Auth::ICredentialsProvider>>
credentialProviderCache;
};
AwsCredentials AwsCredentialProviderImpl::getCredentialsRaw(const std::string & profile)
{ {
// Get or create credential provider with caching // Get or create credential provider with caching
std::shared_ptr<Aws::Crt::Auth::ICredentialsProvider> provider; std::shared_ptr<Aws::Crt::Auth::ICredentialsProvider> provider;
@ -132,8 +121,6 @@ AwsCredentials getAwsCredentials(const std::string & profile)
profile.empty() ? "(default)" : profile.c_str()); profile.empty() ? "(default)" : profile.c_str());
try { try {
initAwsCrt();
if (profile.empty()) { if (profile.empty()) {
Aws::Crt::Auth::CredentialsProviderChainDefaultConfig config; Aws::Crt::Auth::CredentialsProviderChainDefaultConfig config;
config.Bootstrap = Aws::Crt::ApiHandle::GetOrCreateStaticDefaultClientBootstrap(); config.Bootstrap = Aws::Crt::ApiHandle::GetOrCreateStaticDefaultClientBootstrap();
@ -173,17 +160,15 @@ AwsCredentials getAwsCredentials(const std::string & profile)
return getCredentialsFromProvider(provider); return getCredentialsFromProvider(provider);
} }
void invalidateAwsCredentials(const std::string & profile) ref<AwsCredentialProvider> makeAwsCredentialsProvider()
{ {
credentialProviderCache.erase(profile); return make_ref<AwsCredentialProviderImpl>();
} }
AwsCredentials preResolveAwsCredentials(const ParsedS3URL & s3Url) ref<AwsCredentialProvider> getAwsCredentialsProvider()
{ {
std::string profile = s3Url.profile.value_or(""); static auto instance = makeAwsCredentialsProvider();
return instance;
// Get credentials (automatically cached)
return getAwsCredentials(profile);
} }
} // namespace nix } // namespace nix

View file

@ -883,22 +883,12 @@ void FileTransferRequest::setupForS3()
if (usernameAuth) { if (usernameAuth) {
debug("Using pre-resolved AWS credentials from parent process"); debug("Using pre-resolved AWS credentials from parent process");
sessionToken = preResolvedAwsSessionToken; sessionToken = preResolvedAwsSessionToken;
} else { } else if (auto creds = getAwsCredentialsProvider()->maybeGetCredentials(parsedS3)) {
std::string profile = parsedS3.profile.value_or(""); usernameAuth = UsernameAuth{
try { .username = creds->accessKeyId,
auto creds = getAwsCredentials(profile); .password = creds->secretAccessKey,
usernameAuth = UsernameAuth{ };
.username = creds.accessKeyId, sessionToken = creds->sessionToken;
.password = creds.secretAccessKey,
};
sessionToken = creds.sessionToken;
} catch (const AwsAuthError & e) {
warn("AWS authentication failed for S3 request %s: %s", uri, e.what());
// Invalidate the cached credentials so next request will retry
invalidateAwsCredentials(profile);
// Continue without authentication - might be a public bucket
return;
}
} }
if (sessionToken) if (sessionToken)
headers.emplace_back("x-amz-security-token", *sessionToken); headers.emplace_back("x-amz-security-token", *sessionToken);

View file

@ -5,6 +5,7 @@
#if NIX_WITH_AWS_AUTH #if NIX_WITH_AWS_AUTH
# include "nix/store/s3-url.hh" # include "nix/store/s3-url.hh"
# include "nix/util/ref.hh"
# include "nix/util/error.hh" # include "nix/util/error.hh"
# include <memory> # include <memory>
@ -33,35 +34,53 @@ struct AwsCredentials
} }
}; };
/** class AwsAuthError : public Error
* Exception thrown when AWS authentication fails {
*/ std::optional<int> errorCode;
MakeError(AwsAuthError, Error);
public:
using Error::Error;
AwsAuthError(int errorCode);
std::optional<int> getErrorCode() const
{
return errorCode;
}
};
class AwsCredentialProvider
{
public:
/**
* Get AWS credentials for the given URL.
*
* @param url The S3 url to get the credentials for
* @return AWS credentials
* @throws AwsAuthError if credentials cannot be resolved
*/
virtual AwsCredentials getCredentials(const ParsedS3URL & url) = 0;
std::optional<AwsCredentials> maybeGetCredentials(const ParsedS3URL & url)
{
try {
return getCredentials(url);
} catch (AwsAuthError & e) {
return std::nullopt;
}
}
virtual ~AwsCredentialProvider() {}
};
/** /**
* Get AWS credentials for the given profile. * Create a new instancee of AwsCredentialProvider.
* This function automatically caches credential providers to avoid
* creating multiple providers for the same profile.
*
* @param profile The AWS profile name (empty string for default profile)
* @return AWS credentials
* @throws AwsAuthError if credentials cannot be resolved
*/ */
AwsCredentials getAwsCredentials(const std::string & profile = ""); ref<AwsCredentialProvider> makeAwsCredentialsProvider();
/** /**
* Invalidate cached credentials for a profile (e.g., on authentication failure). * Get a reference to the global AwsCredentialProvider.
* The next request for this profile will create a new provider.
*
* @param profile The AWS profile name to invalidate
*/ */
void invalidateAwsCredentials(const std::string & profile); ref<AwsCredentialProvider> getAwsCredentialsProvider();
/**
* Pre-resolve AWS credentials for S3 URLs.
* Used to cache credentials in parent process before forking.
*/
AwsCredentials preResolveAwsCredentials(const ParsedS3URL & s3Url);
} // namespace nix } // namespace nix
#endif #endif

View file

@ -158,6 +158,8 @@ curl_s3_store_opt = get_option('curl-s3-store').require(
if curl_s3_store_opt.enabled() if curl_s3_store_opt.enabled()
deps_other += aws_crt_cpp deps_other += aws_crt_cpp
aws_c_common = cxx.find_library('aws-c-common', required : true)
deps_other += aws_c_common
endif endif
configdata_pub.set('NIX_WITH_AWS_AUTH', curl_s3_store_opt.enabled().to_int()) configdata_pub.set('NIX_WITH_AWS_AUTH', curl_s3_store_opt.enabled().to_int())

View file

@ -958,7 +958,7 @@ std::optional<AwsCredentials> DerivationBuilderImpl::preResolveAwsCredentials()
auto s3Url = ParsedS3URL::parse(parsedUrl); auto s3Url = ParsedS3URL::parse(parsedUrl);
// Use the preResolveAwsCredentials from aws-creds // Use the preResolveAwsCredentials from aws-creds
auto credentials = nix::preResolveAwsCredentials(s3Url); auto credentials = getAwsCredentialsProvider()->getCredentials(s3Url);
debug("Successfully pre-resolved AWS credentials in parent process"); debug("Successfully pre-resolved AWS credentials in parent process");
return credentials; return credentials;
} }

View file

@ -199,7 +199,7 @@ in
user-sandboxing = runNixOSTest ./user-sandboxing; user-sandboxing = runNixOSTest ./user-sandboxing;
curl-s3-binary-cache-store = runNixOSTest ./curl-s3-binary-cache-store.nix; s3-binary-cache-store = runNixOSTest ./s3-binary-cache-store.nix;
fsync = runNixOSTest ./fsync.nix; fsync = runNixOSTest ./fsync.nix;