1010# include < aws/crt/auth/Credentials.h>
1111# include < aws/crt/io/Bootstrap.h>
1212
13- // C library headers for SSO provider support
13+ // C library headers for SSO, STS WebIdentity, and ECS credential providers
1414# include < aws/auth/credentials.h>
1515
1616// C library headers for custom logging
@@ -170,6 +170,53 @@ static std::shared_ptr<Aws::Crt::Auth::ICredentialsProvider> createSSOProvider(
170170 return createWrappedProvider (aws_credentials_provider_new_sso (allocator, &options), allocator);
171171}
172172
173+ /* *
174+ * Create an STS WebIdentity credentials provider using the C library directly.
175+ * This reads AWS_WEB_IDENTITY_TOKEN_FILE, AWS_ROLE_ARN, AWS_ROLE_SESSION_NAME,
176+ * and AWS_REGION from the environment (falling back to the profile config).
177+ * Used by EKS IRSA, GitHub Actions OIDC, and other sts:AssumeRoleWithWebIdentity flows.
178+ * Returns nullptr if the required parameters can't be resolved.
179+ */
180+ static std::shared_ptr<Aws::Crt::Auth::ICredentialsProvider> createSTSWebIdentityProvider (
181+ const std::string & profileName,
182+ Aws::Crt::Io::ClientBootstrap * bootstrap,
183+ Aws::Crt::Io::TlsContext * tlsContext,
184+ Aws::Crt::Allocator * allocator = Aws::Crt::ApiAllocator())
185+ {
186+ aws_credentials_provider_sts_web_identity_options options;
187+ AWS_ZERO_STRUCT (options);
188+
189+ options.bootstrap = bootstrap->GetUnderlyingHandle ();
190+ options.tls_ctx = tlsContext ? tlsContext->GetUnderlyingHandle () : nullptr ;
191+ if (!profileName.empty ()) {
192+ options.profile_name_override = aws_byte_cursor_from_c_str (profileName.c_str ());
193+ }
194+
195+ return createWrappedProvider (aws_credentials_provider_new_sts_web_identity (allocator, &options), allocator);
196+ }
197+
198+ /* *
199+ * Create an ECS container credentials provider using the C library directly.
200+ * This reads AWS_CONTAINER_CREDENTIALS_RELATIVE_URI or
201+ * AWS_CONTAINER_CREDENTIALS_FULL_URI (plus the optional
202+ * AWS_CONTAINER_AUTHORIZATION_TOKEN / _TOKEN_FILE) from the environment.
203+ * Used by ECS tasks and EKS Pod Identity.
204+ * Returns nullptr if neither URI env var is set.
205+ */
206+ static std::shared_ptr<Aws::Crt::Auth::ICredentialsProvider> createECSProvider (
207+ Aws::Crt::Io::ClientBootstrap * bootstrap,
208+ Aws::Crt::Io::TlsContext * tlsContext,
209+ Aws::Crt::Allocator * allocator = Aws::Crt::ApiAllocator())
210+ {
211+ aws_credentials_provider_ecs_environment_options options;
212+ AWS_ZERO_STRUCT (options);
213+
214+ options.bootstrap = bootstrap->GetUnderlyingHandle ();
215+ options.tls_ctx = tlsContext ? tlsContext->GetUnderlyingHandle () : nullptr ;
216+
217+ return createWrappedProvider (aws_credentials_provider_new_ecs_from_environment (allocator, &options), allocator);
218+ }
219+
173220static AwsCredentials getCredentialsFromProvider (std::shared_ptr<Aws::Crt::Auth::ICredentialsProvider> provider)
174221{
175222 if (!provider || !provider->IsValid ()) {
@@ -223,13 +270,14 @@ class AwsCredentialProviderImpl : public AwsCredentialProvider
223270 // This ensures AWS logs respect Nix's verbosity settings and are formatted consistently.
224271 initialiseAwsLogger ();
225272
226- // Create a shared TLS context for SSO (required for HTTPS connections )
273+ // Create a shared TLS context for SSO, STS WebIdentity, and ECS providers (required for HTTPS)
227274 auto allocator = Aws::Crt::ApiAllocator ();
228275 auto tlsCtxOptions = Aws::Crt::Io::TlsContextOptions::InitDefaultClient (allocator);
229276 tlsContext =
230277 std::make_shared<Aws::Crt::Io::TlsContext>(tlsCtxOptions, Aws::Crt::Io::TlsMode::CLIENT, allocator);
231278 if (!tlsContext || !*tlsContext) {
232- warn (" failed to create TLS context for AWS SSO; SSO authentication will be unavailable" );
279+ warn (
280+ " failed to create TLS context for AWS credential providers; SSO, STS WebIdentity, and ECS container authentication will be unavailable" );
233281 tlsContext = nullptr ;
234282 }
235283
@@ -273,19 +321,20 @@ AwsCredentialProviderImpl::createProviderForProfile(const std::string & profile)
273321
274322 debug (" [pid=%d] creating new AWS credential provider for profile '%s'" , getpid (), profileDisplayName);
275323
276- // Build a custom credential chain: Environment → SSO → Profile → IMDS
324+ // Build a custom credential chain: Environment → SSO → Profile → STS WebIdentity → ECS → IMDS
277325 // This works for both default and named profiles, ensuring consistent behavior
278326 // including SSO support and proper TLS context for STS-based role assumption.
279327 Aws::Crt::Auth::CredentialsProviderChainConfig chainConfig;
280328 auto allocator = Aws::Crt::ApiAllocator ();
281329
282- auto addProviderToChain = [&](std::string_view name, auto createProvider) {
330+ auto addProviderToChain = [&](std::string_view name, auto createProvider) -> bool {
283331 if (auto provider = createProvider ()) {
284332 chainConfig.Providers .push_back (provider);
285333 debug (" Added AWS %s Credential Provider to chain for profile '%s'" , name, profileDisplayName);
286- } else {
287- debug (" Skipped AWS %s Credential Provider for profile '%s'" , name, profileDisplayName);
334+ return true ;
288335 }
336+ debug (" Skipped AWS %s Credential Provider for profile '%s'" , name, profileDisplayName);
337+ return false ;
289338 };
290339
291340 // 1. Environment variables (highest priority)
@@ -311,12 +360,37 @@ AwsCredentialProviderImpl::createProviderForProfile(const std::string & profile)
311360 return Aws::Crt::Auth::CredentialsProvider::CreateCredentialsProviderProfile (profileConfig, allocator);
312361 });
313362
314- // 4. IMDS provider (for EC2 instances, lowest priority)
315- addProviderToChain (" IMDS" , [&]() {
316- Aws::Crt::Auth::CredentialsProviderImdsConfig imdsConfig;
317- imdsConfig.Bootstrap = bootstrap;
318- return Aws::Crt::Auth::CredentialsProvider::CreateCredentialsProviderImds (imdsConfig, allocator);
319- });
363+ // 4. STS WebIdentity (AWS_WEB_IDENTITY_TOKEN_FILE + AWS_ROLE_ARN — EKS IRSA, GitHub Actions OIDC)
364+ // 5. ECS container metadata (AWS_CONTAINER_CREDENTIALS_RELATIVE_URI — ECS tasks, EKS Pod Identity)
365+ // ECS and IMDS are mutually exclusive per both the aws-c-auth default chain and the
366+ // pre-2.33 aws-sdk-cpp DefaultAWSCredentialsProviderChain: when container credential
367+ // env vars are set, IMDS is skipped so a transient ECS endpoint failure can't silently
368+ // fall through to the (typically broader) EC2 instance profile.
369+ bool ecsAdded = false ;
370+ if (tlsContext) {
371+ addProviderToChain (" STS WebIdentity" , [&]() {
372+ return createSTSWebIdentityProvider (profile, bootstrap, tlsContext.get (), allocator);
373+ });
374+ ecsAdded =
375+ addProviderToChain (" ECS" , [&]() { return createECSProvider (bootstrap, tlsContext.get (), allocator); });
376+ } else {
377+ debug (
378+ " Skipped AWS STS WebIdentity and ECS Credential Providers for profile '%s': TLS context unavailable" ,
379+ profileDisplayName);
380+ }
381+
382+ // 6. IMDS provider (for EC2 instances, lowest priority) — only if ECS didn't claim the slot
383+ if (!ecsAdded) {
384+ addProviderToChain (" IMDS" , [&]() {
385+ Aws::Crt::Auth::CredentialsProviderImdsConfig imdsConfig;
386+ imdsConfig.Bootstrap = bootstrap;
387+ return Aws::Crt::Auth::CredentialsProvider::CreateCredentialsProviderImds (imdsConfig, allocator);
388+ });
389+ } else {
390+ debug (
391+ " Skipped AWS IMDS Credential Provider for profile '%s': ECS provider is active (mutually exclusive)" ,
392+ profileDisplayName);
393+ }
320394
321395 return Aws::Crt::Auth::CredentialsProvider::CreateCredentialsProviderChain (chainConfig, allocator);
322396}
0 commit comments