diff --git a/src/libstore/aws-creds.cc b/src/libstore/aws-creds.cc index dfdd81abb..cb7c4fae9 100644 --- a/src/libstore/aws-creds.cc +++ b/src/libstore/aws-creds.cc @@ -13,6 +13,9 @@ # include # include +// C library headers for SSO provider support +# include + # include # include @@ -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 createWrappedProvider( + aws_credentials_provider * rawProvider, Aws::Crt::Allocator * allocator = Aws::Crt::ApiAllocator()) +{ + if (rawProvider == nullptr) { + return nullptr; + } + + auto provider = Aws::Crt::MakeShared(allocator, rawProvider, allocator); + return std::static_pointer_cast(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 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 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(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 tlsContext; boost::concurrent_flat_map> 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) diff --git a/src/libstore/meson.build b/src/libstore/meson.build index d8927c3a6..088d8bcbb 100644 --- a/src/libstore/meson.build +++ b/src/libstore/meson.build @@ -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())