diff --git a/aws/aws-credential-chain/src/main/java/software/amazon/smithy/java/aws/credentials/chain/config/CredentialProcessHandler.java b/aws/aws-credential-chain/src/main/java/software/amazon/smithy/java/aws/credentials/chain/config/CredentialProcessHandler.java index 0e4fbb73a7..d3f23b6839 100644 --- a/aws/aws-credential-chain/src/main/java/software/amazon/smithy/java/aws/credentials/chain/config/CredentialProcessHandler.java +++ b/aws/aws-credential-chain/src/main/java/software/amazon/smithy/java/aws/credentials/chain/config/CredentialProcessHandler.java @@ -37,6 +37,10 @@ *

The command string is captured at assembly time and does not change after construction. * Each call to {@code resolveIdentity()} re-executes the process to obtain fresh credentials, * so expiring credentials returned by the process are naturally refreshed without caching. + * + *

This provider registers terminally: once a profile declares {@code credential_process}, the chain commits + * to it, and a process failure is returned as an error rather than falling through to a lower-priority provider + * (per the Extensible Credentials SEP). */ public final class CredentialProcessHandler implements ChainIdentityProvider { @@ -76,7 +80,7 @@ public void setup(Class identityType, ChainSetup setup) { } for (AwsConfigCredentialSource source : profile.credentialSources()) { if (source instanceof AwsConfigCredentialSource.CredentialProcess(String commandLine)) { - setup.addResolver(new Resolver(commandLine)); + setup.addTerminalResolver(new Resolver(commandLine)); return; } } diff --git a/aws/aws-credential-chain/src/test/java/software/amazon/smithy/java/aws/credentials/chain/CredentialProcessTerminalTest.java b/aws/aws-credential-chain/src/test/java/software/amazon/smithy/java/aws/credentials/chain/CredentialProcessTerminalTest.java new file mode 100644 index 0000000000..7dbc7facbf --- /dev/null +++ b/aws/aws-credential-chain/src/test/java/software/amazon/smithy/java/aws/credentials/chain/CredentialProcessTerminalTest.java @@ -0,0 +1,38 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.aws.credentials.chain; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import software.amazon.smithy.java.aws.auth.api.identity.AwsCredentialsIdentity; +import software.amazon.smithy.java.aws.config.AwsProfileFile; +import software.amazon.smithy.java.aws.credentials.chain.config.CredentialProcessHandler; + +class CredentialProcessTerminalTest { + @Test + void claimsTerminalWhenProfileDeclaresCredentialProcess(@TempDir Path tmp) throws IOException { + Path config = tmp.resolve("config"); + Files.writeString(config, "[default]\ncredential_process = /usr/local/bin/awscreds\n"); + var file = AwsProfileFile.builder().configFile(config).credentialsFile(null).build(); + + var setup = ChainSetup.builder().build(); + setup.setProfileFile(file); + setup.setProfile(file.activeProfile(k -> null)); + + var handler = new CredentialProcessHandler(); + setup.setCurrentProvider(handler); + handler.setup(AwsCredentialsIdentity.class, setup); + + assertTrue(setup.isTerminal(), + "A profile declaring credential_process must claim the chain terminally so a process failure " + + "stops resolution instead of falling through to a lower-priority provider."); + } +}