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/io/Bootstrap.h>
// C library headers for SSO provider support
# include <aws/auth/credentials.h>
# include <boost/unordered/concurrent_flat_map.hpp>
# include <chrono>
@ -30,6 +33,44 @@ AwsAuthError::AwsAuthError(int errorCode)
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)
{
if (!provider || !provider->IsValid()) {
@ -91,6 +132,12 @@ public:
logLevel = Aws::Crt::LogLevel::Warn;
}
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);
@ -111,6 +158,7 @@ public:
private:
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>>
credentialProviderCache;
};
@ -123,18 +171,53 @@ AwsCredentialProviderImpl::createProviderForProfile(const std::string & profile)
getpid(),
profile.empty() ? "(default)" : profile.c_str());
auto bootstrap = Aws::Crt::ApiHandle::GetOrCreateStaticDefaultClientBootstrap();
// If no profile specified, use the default chain
if (profile.empty()) {
Aws::Crt::Auth::CredentialsProviderChainDefaultConfig config;
config.Bootstrap = Aws::Crt::ApiHandle::GetOrCreateStaticDefaultClientBootstrap();
config.Bootstrap = bootstrap;
return Aws::Crt::Auth::CredentialsProvider::CreateCredentialsProviderChainDefault(config);
}
Aws::Crt::Auth::CredentialsProviderProfileConfig config;
config.Bootstrap = Aws::Crt::ApiHandle::GetOrCreateStaticDefaultClientBootstrap();
// This is safe because the underlying C library will copy this string
// c.f. https://github.com/awslabs/aws-c-auth/blob/main/source/credentials_provider_profile.c#L220
config.ProfileNameOverride = Aws::Crt::ByteCursorFromCString(profile.c_str());
return Aws::Crt::Auth::CredentialsProvider::CreateCredentialsProviderProfile(config);
// For named profiles, build a custom credential chain: Environment → SSO → Profile → IMDS
// The SSO provider will gracefully fail if SSO isn't configured for this profile,
// and the chain will move on to the next provider.
Aws::Crt::Auth::CredentialsProviderChainConfig chainConfig;
auto allocator = Aws::Crt::ApiAllocator();
// 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)

View file

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