Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ private ChainSetup(Builder builder) {
this.profileNameOverride = builder.profileNameOverride;
this.properties = Context.create();
this.envFn = builder.envFn;
this.profileFile = builder.profileFile;
}

/**
Expand Down Expand Up @@ -214,6 +215,7 @@ public static final class Builder {
private ScheduledExecutorService executor;
private String profileNameOverride;
private Function<String, String> envFn = System::getenv;
private AwsProfileFile profileFile;

private Builder() {}

Expand Down Expand Up @@ -252,6 +254,19 @@ public Builder env(Function<String, String> envFn) {
return this;
}

/**
* Supplies an already-parsed AWS config/credentials file. When set, the {@code SHARED_CONFIG} provider
* uses this file instead of reading {@code ~/.aws/config} and {@code ~/.aws/credentials} from disk. Use
* when the caller has already loaded the profile file, or to point the chain at a non-default location.
*
* @param profileFile the parsed profile file.
* @return this builder.
*/
public Builder profileFile(AwsProfileFile profileFile) {
this.profileFile = profileFile;
return this;
}

/**
* Builds the {@link ChainSetup}.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import software.amazon.smithy.java.auth.api.identity.Identity;
import software.amazon.smithy.java.auth.api.identity.IdentityResolver;
import software.amazon.smithy.java.auth.api.identity.IdentityResult;
import software.amazon.smithy.java.aws.config.AwsProfileFile;
import software.amazon.smithy.java.client.core.CallContext;
import software.amazon.smithy.java.context.Context;
import software.amazon.smithy.java.logging.InternalLogger;
Expand Down Expand Up @@ -81,11 +82,34 @@ public static <I extends Identity> CredentialChain<I> create(Class<I> identityTy
* @throws IllegalStateException if two providers claim the same standard slot.
*/
public static <I extends Identity> CredentialChain<I> create(Class<I> identityType, ScheduledExecutorService ex) {
return create(identityType, ex, null);
}

/**
* Create a credential chain by discovering providers via ServiceLoader, using a caller-supplied AWS
* config/credentials file.
*
* <p>When {@code profileFile} is non-null, the {@code SHARED_CONFIG} provider uses it instead of reading
* {@code ~/.aws/config} and {@code ~/.aws/credentials} from disk. Use this when the file has already been
* loaded, or to point the chain at a non-default location.
*
* @param identityType Identity type to resolve.
* @param ex Executor used for background resolution.
* @param profileFile Already-parsed profile file to use, or {@code null} to load from the default locations.
* @return the assembled chain.
* @throws IllegalStateException if two providers claim the same standard slot.
*/
public static <I extends Identity> CredentialChain<I> create(
Class<I> identityType,
ScheduledExecutorService ex,
AwsProfileFile profileFile
) {
List<ChainIdentityProvider> registrations = new ArrayList<>();
for (ChainIdentityProvider r : ServiceLoader.load(ChainIdentityProvider.class)) {
registrations.add(r);
}
return assemble(identityType, registrations, ex);
ChainSetup setup = ChainSetup.builder().executor(ex).profileFile(profileFile).build();
return assemble(identityType, registrations, ex, setup);
}

static <I extends Identity> CredentialChain<I> assemble(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,13 @@ public OrderingConstraint ordering() {

@Override
public void setup(Class<? extends Identity> identityType, ChainSetup setup) {
AwsProfileFile profileFile = AwsProfileFile.loadSilently();
// Defer to a profile file already supplied on the setup (e.g., injected by the client builder or an
// upstream provider) instead of reading from disk. Only fall back to loading the shared config/credentials
// files when none was provided.
AwsProfileFile profileFile = setup.profileFile();
if (profileFile == null) {
profileFile = AwsProfileFile.loadSilently();
}
if (profileFile != null) {
setup.setProfileFile(profileFile);
String name = setup.profileNameOverride();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* 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.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame;

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.config.AwsProfileFile;
import software.amazon.smithy.java.aws.credentials.chain.config.SharedConfigProvider;

/**
* Verifies that an already-parsed {@link AwsProfileFile} supplied on the {@link ChainSetup} is used as-is and that
* {@link SharedConfigProvider} does not overwrite it by reading from disk.
*/
class InjectedProfileFileTest {

@Test
void sharedConfigProviderUsesInjectedProfileFile(@TempDir Path tmp) throws IOException {
Path config = tmp.resolve("config");
Files.writeString(config, """
[default]
aws_access_key_id = injected_ak
aws_secret_access_key = injected_sk
""");
var injected = AwsProfileFile.builder().configFile(config).credentialsFile(null).build();

// Empty environment so the active profile resolves to "default" deterministically, regardless of any
// AWS_PROFILE set in the ambient test environment.
var setup = ChainSetup.builder().profileFile(injected).env(k -> null).build();
var provider = new SharedConfigProvider();
setup.setCurrentProvider(provider);
provider.setup(software.amazon.smithy.java.aws.auth.api.identity.AwsCredentialsIdentity.class, setup);

// The injected instance survives: SharedConfigProvider did not replace it with a disk load.
assertSame(injected,
setup.profileFile(),
"SharedConfigProvider should defer to the profile file already set on the chain setup.");
// And it resolved the active profile from that injected file.
assertNotNull(setup.profile());
}
}
Loading