1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-12-22 17:01:08 +01:00

libstore: add AWS SSO support for S3 authentication

This enables seamless AWS SSO authentication for S3 binary caches
without requiring users to manually export credentials.

This adds SSO support by calling aws_credentials_provider_new_sso() from
the C library directly. It builds a custom credential chain: Env → SSO →
Profile → IMDS

The SSO provider requires a TLS context for HTTPS connections to SSO
endpoints, which is created once and shared across all providers.
This commit is contained in:
Jörg Thalheim 2025-11-24 21:06:59 +01:00
parent a6eb2e91b7
commit ec91479076
2 changed files with 92 additions and 7 deletions

View file

@ -13,6 +13,9 @@
# include <aws/crt/auth/Credentials.h> # include <aws/crt/auth/Credentials.h>
# include <aws/crt/io/Bootstrap.h> # include <aws/crt/io/Bootstrap.h>
// C library headers for SSO provider support
# include <aws/auth/credentials.h>
# include <boost/unordered/concurrent_flat_map.hpp> # include <boost/unordered/concurrent_flat_map.hpp>
# include <chrono> # include <chrono>
@ -30,6 +33,44 @@ AwsAuthError::AwsAuthError(int errorCode)
namespace { namespace {
/**
* Helper function to wrap a C credentials provider in the C++ interface.
* This replicates the static s_CreateWrappedProvider from aws-crt-cpp.
*/
static std::shared_ptr<Aws::Crt::Auth::ICredentialsProvider> createWrappedProvider(
aws_credentials_provider * rawProvider, Aws::Crt::Allocator * allocator = Aws::Crt::ApiAllocator())
{
if (rawProvider == nullptr) {
return nullptr;
}
auto provider = Aws::Crt::MakeShared<Aws::Crt::Auth::CredentialsProvider>(allocator, rawProvider, allocator);
return std::static_pointer_cast<Aws::Crt::Auth::ICredentialsProvider>(provider);
}
/**
* Create an SSO credentials provider using the C library directly.
* The C++ wrapper doesn't expose SSO, so we call the C library and wrap the result.
* Returns nullptr if SSO provider creation fails (e.g., profile doesn't have SSO config).
*/
static std::shared_ptr<Aws::Crt::Auth::ICredentialsProvider> createSSOProvider(
const std::string & profileName,
Aws::Crt::Io::ClientBootstrap * bootstrap,
Aws::Crt::Io::TlsContext * tlsContext,
Aws::Crt::Allocator * allocator = Aws::Crt::ApiAllocator())
{
aws_credentials_provider_sso_options options;
AWS_ZERO_STRUCT(options);
options.bootstrap = bootstrap->GetUnderlyingHandle();
options.tls_ctx = tlsContext ? tlsContext->GetUnderlyingHandle() : nullptr;
options.profile_name_override = aws_byte_cursor_from_c_str(profileName.c_str());
// Create the SSO provider - will return nullptr if SSO isn't configured for this profile
// createWrappedProvider handles nullptr gracefully
return createWrappedProvider(aws_credentials_provider_new_sso(allocator, &options), allocator);
}
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()) {
@ -91,6 +132,12 @@ public:
logLevel = Aws::Crt::LogLevel::Warn; logLevel = Aws::Crt::LogLevel::Warn;
} }
apiHandle.InitializeLogging(logLevel, stderr); apiHandle.InitializeLogging(logLevel, stderr);
// Create a shared TLS context for SSO (required for HTTPS connections)
auto allocator = Aws::Crt::ApiAllocator();
auto tlsCtxOptions = Aws::Crt::Io::TlsContextOptions::InitDefaultClient(allocator);
tlsContext =
std::make_shared<Aws::Crt::Io::TlsContext>(tlsCtxOptions, Aws::Crt::Io::TlsMode::CLIENT, allocator);
} }
AwsCredentials getCredentialsRaw(const std::string & profile); AwsCredentials getCredentialsRaw(const std::string & profile);
@ -111,6 +158,7 @@ public:
private: private:
Aws::Crt::ApiHandle apiHandle; Aws::Crt::ApiHandle apiHandle;
std::shared_ptr<Aws::Crt::Io::TlsContext> tlsContext;
boost::concurrent_flat_map<std::string, std::shared_ptr<Aws::Crt::Auth::ICredentialsProvider>> boost::concurrent_flat_map<std::string, std::shared_ptr<Aws::Crt::Auth::ICredentialsProvider>>
credentialProviderCache; credentialProviderCache;
}; };
@ -123,18 +171,53 @@ AwsCredentialProviderImpl::createProviderForProfile(const std::string & profile)
getpid(), getpid(),
profile.empty() ? "(default)" : profile.c_str()); profile.empty() ? "(default)" : profile.c_str());
auto bootstrap = Aws::Crt::ApiHandle::GetOrCreateStaticDefaultClientBootstrap();
// If no profile specified, use the default chain
if (profile.empty()) { if (profile.empty()) {
Aws::Crt::Auth::CredentialsProviderChainDefaultConfig config; Aws::Crt::Auth::CredentialsProviderChainDefaultConfig config;
config.Bootstrap = Aws::Crt::ApiHandle::GetOrCreateStaticDefaultClientBootstrap(); config.Bootstrap = bootstrap;
return Aws::Crt::Auth::CredentialsProvider::CreateCredentialsProviderChainDefault(config); return Aws::Crt::Auth::CredentialsProvider::CreateCredentialsProviderChainDefault(config);
} }
Aws::Crt::Auth::CredentialsProviderProfileConfig config; // For named profiles, build a custom credential chain: Environment → SSO → Profile → IMDS
config.Bootstrap = Aws::Crt::ApiHandle::GetOrCreateStaticDefaultClientBootstrap(); // The SSO provider will gracefully fail if SSO isn't configured for this profile,
// This is safe because the underlying C library will copy this string // and the chain will move on to the next provider.
// c.f. https://github.com/awslabs/aws-c-auth/blob/main/source/credentials_provider_profile.c#L220 Aws::Crt::Auth::CredentialsProviderChainConfig chainConfig;
config.ProfileNameOverride = Aws::Crt::ByteCursorFromCString(profile.c_str()); auto allocator = Aws::Crt::ApiAllocator();
return Aws::Crt::Auth::CredentialsProvider::CreateCredentialsProviderProfile(config);
// 1. Environment variables (highest priority)
auto envProvider = Aws::Crt::Auth::CredentialsProvider::CreateCredentialsProviderEnvironment(allocator);
if (envProvider) {
chainConfig.Providers.push_back(envProvider);
}
// 2. SSO provider (try it, will fail gracefully if not configured)
auto ssoProvider = createSSOProvider(profile, bootstrap, tlsContext.get(), allocator);
if (ssoProvider) {
debug("[pid=%d] added SSO provider to credential chain for profile '%s'", getpid(), profile.c_str());
chainConfig.Providers.push_back(ssoProvider);
}
// 3. Profile provider (for static credentials)
Aws::Crt::Auth::CredentialsProviderProfileConfig profileConfig;
profileConfig.Bootstrap = bootstrap;
profileConfig.ProfileNameOverride = Aws::Crt::ByteCursorFromCString(profile.c_str());
auto profileProvider =
Aws::Crt::Auth::CredentialsProvider::CreateCredentialsProviderProfile(profileConfig, allocator);
if (profileProvider) {
chainConfig.Providers.push_back(profileProvider);
}
// 4. IMDS provider (for EC2 instances, lowest priority)
Aws::Crt::Auth::CredentialsProviderImdsConfig imdsConfig;
imdsConfig.Bootstrap = bootstrap;
auto imdsProvider = Aws::Crt::Auth::CredentialsProvider::CreateCredentialsProviderImds(imdsConfig, allocator);
if (imdsProvider) {
chainConfig.Providers.push_back(imdsProvider);
}
return Aws::Crt::Auth::CredentialsProvider::CreateCredentialsProviderChain(chainConfig, allocator);
} }
AwsCredentials AwsCredentialProviderImpl::getCredentialsRaw(const std::string & profile) AwsCredentials AwsCredentialProviderImpl::getCredentialsRaw(const std::string & profile)

View file

@ -160,6 +160,8 @@ if s3_aws_auth.enabled()
deps_other += aws_crt_cpp deps_other += aws_crt_cpp
aws_c_common = cxx.find_library('aws-c-common', required : true) aws_c_common = cxx.find_library('aws-c-common', required : true)
deps_other += aws_c_common deps_other += aws_c_common
aws_c_auth = cxx.find_library('aws-c-auth', required : true)
deps_other += aws_c_auth
endif endif
configdata_pub.set('NIX_WITH_AWS_AUTH', s3_aws_auth.enabled().to_int()) configdata_pub.set('NIX_WITH_AWS_AUTH', s3_aws_auth.enabled().to_int())