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:
commit
d87a06af7a
8 changed files with 94 additions and 98 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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())
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue