I've been reading through documentation for some time. I can see examples for the JavaScript and Go SDK that show how to load the config file by setting the AWS_SDK_LOAD_CONFIG
environment variable to a truthy value. The documentation is here and here respectively.
However, for my requirements, I must use Java. I can't find an equivalent reference in the Java SDK. Which leads me to assume three things.
ack -i "AWS_SDK_LOAD_CONFIG"
shows that neither project use this variable.For clarity the profile I need to load is sbx
, which lives in my config, but has no adjacent value in the credentials file. Here is my ~/.aws/config file:
[profile shared]
output = json
region = us-west-2
adfs_config.ssl_verification = True
adfs_config.role_arn = ....
adfs_config.adfs_host = ....
adfs_config.adfs_user = ....
[profile sbx]
role_arn = ... (this is different from the adfs_config.role_arn above)
source_profile = shared
region = us-west-2
and ~/.aws/credentials file: (this file is automatically populated with the aws-adfs
command.
[shared]
aws_access_key_id = ....
aws_secret_access_key = ....
aws_session_token = ....
aws_security_token = ....
I figured out the answer. The problem to this issue is that the credentials file is loaded by default, but it doesn't always have all the information available from the config file. We need both to be loaded and flattened.
AWS already provides ProfileAssumeRoleCredentialsProvider
which allows us to assume a Role from a profile. Once we provide it all the information it needs, it can assume the Role without issue (assuming your token is current)
/**
* @author Paul Nelson Baker
* @see <a href="https://github.com/paul-nelson-baker/">GitHub</a>
* @see <a href="https://www.linkedin.com/in/paul-n-baker/">LinkedIn</a>
* @since 2018-11
*/
public class CredentialsChain {
public static final AWSCredentialsProviderChain CREDENTIALS_PROVIDER_CHAIN;
static {
AllProfiles allProfiles = flattenConfigurationFiles(
DEFAULT_CONFIG_LOCATION_PROVIDER.getLocation(), // ~/.aws/config
DEFAULT_CREDENTIALS_LOCATION_PROVIDER.getLocation() // ~/.aws/credentials
);
String currentProfileName = AwsProfileNameLoader.INSTANCE.loadProfileName();
BasicProfile currentProfile = allProfiles.getProfile(currentProfileName);
STSProfileCredentialsService profileCredentialsService = new STSProfileCredentialsService();
// We stick our merged profile provider first, but we still want the default behavior to apply
// so create a new chain with the default chain as the tail provider.
CREDENTIALS_PROVIDER_CHAIN = new AWSCredentialsProviderChain(
new ProfileAssumeRoleCredentialsProvider(profileCredentialsService, allProfiles, currentProfile),
new DefaultAWSCredentialsProviderChain()
);
}
private static AllProfiles flattenConfigurationFiles(File firstFile, File... additionalFiles) {
// Utilize the AWS SDK to load the actual profile objects
List<ProfilesConfigFile> allProfileConfigFiles = Stream.concat(Stream.of(firstFile), Arrays.stream(additionalFiles))
.map(ProfilesConfigFile::new).collect(Collectors.toList());
// Process each file one by one, look at their profiles, and place their values into a single map
// Duplicate profiles will now have the single key/value pairs.
Map<String, Map<String, String>> buildingMap = new LinkedHashMap<>();
for (ProfilesConfigFile currentConfigFile : allProfileConfigFiles) {
for (Entry<String, BasicProfile> currentProfile : currentConfigFile.getAllBasicProfiles().entrySet()) {
// Some profiles are prefixed with "profile " so we want to cull it so we're actually merging the correct data
String currentProfileName = currentProfile.getKey().replaceAll("^profile\\s+", "");
if (!buildingMap.containsKey(currentProfileName)) {
buildingMap.put(currentProfileName, new LinkedHashMap<>());
}
Map<String, String> profileKeyValuePairs = buildingMap.get(currentProfileName);
for (Entry<String, String> overridingEntry : currentProfile.getValue().getProperties().entrySet()) {
profileKeyValuePairs.put(overridingEntry.getKey(), overridingEntry.getValue());
}
}
}
// Take the results, and convert them to AWS SDK Types
Map<String, BasicProfile> finalResult = new LinkedHashMap<>();
for (Entry<String, Map<String, String>> currentFinalProfile : buildingMap.entrySet()) {
String currentProfileName = currentFinalProfile.getKey();
finalResult.put(currentProfileName, new BasicProfile(currentProfileName, currentFinalProfile.getValue()));
}
return new AllProfiles(finalResult);
}
private CredentialsChain() {
}
}