Skip to content

Commit a72aade

Browse files
committed
Add SSO credentials provider support and related tests.
1 parent 040e854 commit a72aade

File tree

21 files changed

+1502
-68
lines changed

21 files changed

+1502
-68
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"category": "AWS Single Sign-on",
3+
"contributor": "",
4+
"type": "feature",
5+
"description": "Added support for retrieving SSO credentials: https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sso.html."
6+
}

core/annotations/src/main/java/software/amazon/awssdk/annotations/SdkInternalApi.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import java.lang.annotation.Target;
2020

2121
/**
22-
* Marker interface for 'internal' APIs that should not be used outside the core module. Breaking
22+
* Marker interface for 'internal' APIs that should not be used outside the same module. Breaking
2323
* changes can and will be introduced to elements marked as {@link SdkInternalApi}. Users of the SDK
2424
* and the generated clients themselves should not depend on any packages, types, fields,
2525
* constructors, or methods with this annotation.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.auth.credentials;
17+
18+
import software.amazon.awssdk.annotations.SdkProtectedApi;
19+
import software.amazon.awssdk.profiles.Profile;
20+
21+
/**
22+
* A factory for {@link AwsCredentialsProvider}s, which can be used to create different credentials providers with
23+
* different Profile properties.
24+
*/
25+
@FunctionalInterface
26+
@SdkProtectedApi
27+
public interface ProfileCredentialsProviderFactory {
28+
AwsCredentialsProvider create(Profile profile);
29+
}

core/auth/src/main/java/software/amazon/awssdk/auth/credentials/internal/ProfileCredentialsUtils.java

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import software.amazon.awssdk.auth.credentials.EnvironmentVariableCredentialsProvider;
3636
import software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider;
3737
import software.amazon.awssdk.auth.credentials.ProcessCredentialsProvider;
38+
import software.amazon.awssdk.auth.credentials.ProfileCredentialsProviderFactory;
3839
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
3940
import software.amazon.awssdk.auth.credentials.SystemPropertyCredentialsProvider;
4041
import software.amazon.awssdk.core.internal.util.ClassLoaderHelper;
@@ -50,6 +51,8 @@
5051
public final class ProfileCredentialsUtils {
5152
private static final String STS_PROFILE_CREDENTIALS_PROVIDER_FACTORY =
5253
"software.amazon.awssdk.services.sts.internal.StsProfileCredentialsProviderFactory";
54+
private static final String SSO_PROFILE_CREDENTIALS_PROVIDER_FACTORY =
55+
"software.amazon.awssdk.services.sso.auth.SsoProfileCredentialsProviderFactory";
5356

5457
private final Profile profile;
5558

@@ -95,19 +98,22 @@ public Optional<AwsCredentialsProvider> credentialsProvider() {
9598
* @param children The child profiles that source credentials from this profile.
9699
*/
97100
private Optional<AwsCredentialsProvider> credentialsProvider(Set<String> children) {
101+
if (properties.containsKey(ProfileProperty.ROLE_ARN) && properties.containsKey(ProfileProperty.WEB_IDENTITY_TOKEN_FILE)) {
102+
return Optional.ofNullable(roleAndWebIdentityTokenProfileCredentialsProvider());
103+
}
104+
105+
if (properties.containsKey(ProfileProperty.SSO_ROLE_NAME) || properties.containsKey(ProfileProperty.SSO_ACCOUNT_ID)
106+
|| properties.containsKey(ProfileProperty.SSO_REGION) || properties.containsKey(ProfileProperty.SSO_START_URL)) {
107+
return Optional.ofNullable(ssoProfileCredentialsProvider());
108+
}
109+
98110
if (properties.containsKey(ProfileProperty.ROLE_ARN)) {
99111
boolean hasSourceProfile = properties.containsKey(ProfileProperty.SOURCE_PROFILE);
100112
boolean hasCredentialSource = properties.containsKey(ProfileProperty.CREDENTIAL_SOURCE);
101-
boolean hasWebIdentityTokenFile = properties.containsKey(ProfileProperty.WEB_IDENTITY_TOKEN_FILE);
102-
boolean hasRoleArn = properties.containsKey(ProfileProperty.ROLE_ARN);
103113
Validate.validState(!(hasSourceProfile && hasCredentialSource),
104114
"Invalid profile file: profile has both %s and %s.",
105115
ProfileProperty.SOURCE_PROFILE, ProfileProperty.CREDENTIAL_SOURCE);
106116

107-
if (hasWebIdentityTokenFile && hasRoleArn) {
108-
return Optional.ofNullable(roleAndWebIdentityTokenProfileCredentialsProvider());
109-
}
110-
111117
if (hasSourceProfile) {
112118
return Optional.ofNullable(roleAndSourceProfileBasedProfileCredentialsProvider(children));
113119
}
@@ -164,6 +170,17 @@ private AwsCredentialsProvider credentialProcessCredentialsProvider() {
164170
.build();
165171
}
166172

173+
/**
174+
* Create the SSO credentials provider based on the related profile properties.
175+
*/
176+
private AwsCredentialsProvider ssoProfileCredentialsProvider() {
177+
requireProperties(ProfileProperty.SSO_ACCOUNT_ID,
178+
ProfileProperty.SSO_REGION,
179+
ProfileProperty.SSO_ROLE_NAME,
180+
ProfileProperty.SSO_START_URL);
181+
return ssoCredentialsProviderFactory().create(profile);
182+
}
183+
167184
private AwsCredentialsProvider roleAndWebIdentityTokenProfileCredentialsProvider() {
168185
requireProperties(ProfileProperty.ROLE_ARN, ProfileProperty.WEB_IDENTITY_TOKEN_FILE);
169186

@@ -263,4 +280,20 @@ private ChildProfileCredentialsProviderFactory stsCredentialsProviderFactory() {
263280
throw new IllegalStateException("Failed to create the '" + name + "' profile credentials provider.", e);
264281
}
265282
}
283+
284+
/**
285+
* Load the factory that can be used to create the SSO credentials provider, assuming it is on the classpath.
286+
*/
287+
private ProfileCredentialsProviderFactory ssoCredentialsProviderFactory() {
288+
try {
289+
Class<?> ssoProfileCredentialsProviderFactory = ClassLoaderHelper.loadClass(SSO_PROFILE_CREDENTIALS_PROVIDER_FACTORY,
290+
getClass());
291+
return (ProfileCredentialsProviderFactory) ssoProfileCredentialsProviderFactory.getConstructor().newInstance();
292+
} catch (ClassNotFoundException e) {
293+
throw new IllegalStateException("To use Sso related properties in the '" + name + "' profile, the 'sso' service "
294+
+ "module must be on the class path.", e);
295+
} catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
296+
throw new IllegalStateException("Failed to create the '" + name + "' profile credentials provider.", e);
297+
}
298+
}
266299
}

core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileFileLocation.java

Lines changed: 3 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,15 @@
1515

1616
package software.amazon.awssdk.profiles;
1717

18+
import static software.amazon.awssdk.utils.UserHomeDirectoryUtils.userHomeDirectory;
19+
1820
import java.nio.file.FileSystems;
1921
import java.nio.file.Files;
2022
import java.nio.file.Path;
2123
import java.nio.file.Paths;
2224
import java.util.Optional;
2325
import java.util.regex.Pattern;
24-
import software.amazon.awssdk.annotations.SdkInternalApi;
2526
import software.amazon.awssdk.annotations.SdkPublicApi;
26-
import software.amazon.awssdk.utils.JavaSystemSetting;
27-
import software.amazon.awssdk.utils.StringUtils;
2827

2928
/**
3029
* A collection of static methods for loading the location for configuration and credentials files.
@@ -46,7 +45,7 @@ private ProfileFileLocation() {
4645
public static Path configurationFilePath() {
4746
return resolveProfileFilePath(
4847
ProfileFileSystemSetting.AWS_CONFIG_FILE.getStringValue()
49-
.orElse(Paths.get(ProfileFileLocation.userHomeDirectory(),
48+
.orElse(Paths.get(userHomeDirectory(),
5049
".aws", "config").toString()));
5150
}
5251

@@ -78,43 +77,6 @@ public static Optional<Path> credentialsFileLocation() {
7877
return resolveIfExists(credentialsFilePath());
7978
}
8079

81-
/**
82-
* Load the home directory that should be used for the profile file. This will check the same environment variables as the CLI
83-
* to identify the location of home, before falling back to java-specific resolution.
84-
*/
85-
@SdkInternalApi
86-
static String userHomeDirectory() {
87-
boolean isWindows = JavaSystemSetting.OS_NAME.getStringValue()
88-
.map(s -> StringUtils.lowerCase(s).startsWith("windows"))
89-
.orElse(false);
90-
91-
// To match the logic of the CLI we have to consult environment variables directly.
92-
// CHECKSTYLE:OFF
93-
String home = System.getenv("HOME");
94-
95-
if (home != null) {
96-
return home;
97-
}
98-
99-
if (isWindows) {
100-
String userProfile = System.getenv("USERPROFILE");
101-
102-
if (userProfile != null) {
103-
return userProfile;
104-
}
105-
106-
String homeDrive = System.getenv("HOMEDRIVE");
107-
String homePath = System.getenv("HOMEPATH");
108-
109-
if (homeDrive != null && homePath != null) {
110-
return homeDrive + homePath;
111-
}
112-
}
113-
114-
return JavaSystemSetting.USER_HOME.getStringValueOrThrow();
115-
// CHECKSTYLE:ON
116-
}
117-
11880
private static Path resolveProfileFilePath(String path) {
11981
// Resolve ~ using the CLI's logic, not whatever Java decides to do with it.
12082
if (HOME_DIRECTORY_PATTERN.matcher(path).matches()) {

core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileProperty.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,28 @@ public final class ProfileProperty {
9999
*/
100100
public static final String RETRY_MODE = "retry_mode";
101101

102+
/**
103+
* Aws region where the SSO directory for the given 'sso_start_url' is hosted. This is independent of the general 'region'.
104+
*/
105+
public static final String SSO_REGION = "sso_region";
106+
107+
/**
108+
* The corresponding IAM role in the AWS account that temporary AWS credentials will be resolved for.
109+
*/
110+
public static final String SSO_ROLE_NAME = "sso_role_name";
111+
112+
/**
113+
* AWS account ID that temporary AWS credentials will be resolved for.
114+
*/
115+
public static final String SSO_ACCOUNT_ID = "sso_account_id";
116+
117+
/**
118+
* Start url provided by the SSO service via the console. It's the main URL used for login to the SSO directory.
119+
* This is also referred to as the "User Portal URL" and can also be used to login to the SSO web interface for AWS
120+
* console access.
121+
*/
122+
public static final String SSO_START_URL = "sso_start_url";
123+
102124
private ProfileProperty() {
103125
}
104126
}

services/sso/pom.xml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,5 +56,23 @@
5656
<artifactId>aws-json-protocol</artifactId>
5757
<version>${awsjavasdk.version}</version>
5858
</dependency>
59+
<dependency>
60+
<groupId>software.amazon.awssdk</groupId>
61+
<artifactId>profiles</artifactId>
62+
<version>${awsjavasdk.version}</version>
63+
<scope>compile</scope>
64+
</dependency>
65+
66+
<!-- Test Dependencies -->
67+
<dependency>
68+
<groupId>com.google.jimfs</groupId>
69+
<artifactId>jimfs</artifactId>
70+
<scope>test</scope>
71+
</dependency>
72+
<dependency>
73+
<groupId>com.google.guava</groupId>
74+
<artifactId>guava</artifactId>
75+
<scope>test</scope>
76+
</dependency>
5977
</dependencies>
6078
</project>
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.services.sso.auth;
17+
18+
import java.util.Arrays;
19+
import java.util.Collections;
20+
import java.util.List;
21+
import software.amazon.awssdk.annotations.SdkPublicApi;
22+
import software.amazon.awssdk.core.SdkField;
23+
import software.amazon.awssdk.core.SdkPojo;
24+
import software.amazon.awssdk.core.exception.SdkClientException;
25+
26+
/**
27+
* <p>
28+
* The session token that was passed is expired or is not valid.
29+
* </p>
30+
*/
31+
@SdkPublicApi
32+
public final class ExpiredTokenException extends SdkClientException {
33+
34+
private static final List<SdkField<?>> SDK_FIELDS = Collections.unmodifiableList(Arrays.asList());
35+
36+
private ExpiredTokenException(Builder b) {
37+
super(b);
38+
}
39+
40+
@Override
41+
public Builder toBuilder() {
42+
return new BuilderImpl(this);
43+
}
44+
45+
public static Builder builder() {
46+
return new BuilderImpl();
47+
}
48+
49+
public interface Builder extends SdkPojo, SdkClientException.Builder {
50+
@Override
51+
Builder message(String message);
52+
53+
@Override
54+
Builder cause(Throwable cause);
55+
56+
@Override
57+
ExpiredTokenException build();
58+
}
59+
60+
static final class BuilderImpl extends SdkClientException.BuilderImpl implements Builder {
61+
private BuilderImpl() {
62+
}
63+
64+
private BuilderImpl(ExpiredTokenException model) {
65+
super(model);
66+
}
67+
68+
@Override
69+
public BuilderImpl message(String message) {
70+
this.message = message;
71+
return this;
72+
}
73+
74+
@Override
75+
public BuilderImpl cause(Throwable cause) {
76+
this.cause = cause;
77+
return this;
78+
}
79+
80+
@Override
81+
public ExpiredTokenException build() {
82+
return new ExpiredTokenException(this);
83+
}
84+
85+
@Override
86+
public List<SdkField<?>> sdkFields() {
87+
return SDK_FIELDS;
88+
}
89+
}
90+
}

0 commit comments

Comments
 (0)