Skip to content

Commit a526192

Browse files
committed
aws: better credentials provider fix #1145
1 parent cf97835 commit a526192

File tree

7 files changed

+400
-271
lines changed

7 files changed

+400
-271
lines changed

doc/doc/aws/aws.md

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,9 @@
22

33
Small utility module that exports ```AmazonWebServiceClient``` services.
44

5-
It also give you access to aws credentials (access and secret keys).
6-
75
## exports
86

9-
* One ore more ```AmazonWebServiceClient```, like ```AmazonS3Client```, ```AmazonSimpleEmailServiceClient```, etc...
7+
* One ore more ```amazon services```, like ```AmazonS3Client```, ```AmazonSimpleEmailServiceClient```, etc...
108

119
## dependency
1210

@@ -30,8 +28,8 @@ aws.secretKey = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
3028
```java
3129
{
3230
use(new Aws()
33-
.with(creds -> new AmazonS3Client(creds))
34-
.with(creds -> new AmazonSimpleEmailServiceClient(creds))
31+
.with(creds -> AmazonS3Client.builder().withCredentials(creds).build())
32+
.with(creds -> AmazonSimpleEmailServiceClient.builder.withCredentials(creds).build())
3533
);
3634

3735
get("/", req -> {
@@ -49,8 +47,8 @@ This module is small and simple. All it does is bind ```AmazonWebServiceClient``
4947
```java
5048
{
5149
use(new Aws()
52-
.with(creds -> new AmazonS3Client(creds))
53-
.doWith((AmazonS3Client s3) -> new TransferManager(s3))
50+
.with(creds -> AmazonS3Client.builder().withCredentials(creds).build())
51+
.doWith((AmazonS3Client s3) -> TransferManagerBuilder.standard().withS3Client(s3).build())
5452
);
5553

5654
post("/", req -> {
@@ -74,10 +72,17 @@ Keys are defined in ```.conf``` file. It is possible to use global or per servic
7472
```
7573
{
7674
use(new Aws()
77-
.with(creds -> new AmazonS3Client(creds)) // use aws.s3 keys
78-
.with(creds -> new AmazonSimpleEmailServiceClient(creds)) // use global keys
75+
// use aws.s3 keys
76+
.with(creds -> AmazonS3Client.builder().withCredentials(creds).build())
77+
// use global keys
78+
.with(creds -> AmazonSimpleEmailServiceClient.builder.withCredentials(creds).build())
7979
);
8080
```
8181

82-
It uses the ```AmazonWebServiceClient#getServiceName()``` method in order to find per service
83-
keys.
82+
The module also install the defaults aws credentials provider. Provider precedence is as follows:
83+
84+
- application *.conf file (application.conf, application.prod.conf, etc...)
85+
- environment (EnvironmentVariableCredentialsProvider)
86+
- jvm system properties (SystemPropertiesCredentialsProvider)
87+
- aws configuration profile (ProfileCredentialsProvider)
88+
- ec2 (EC2ContainerCredentialsProviderWrapper)

modules/jooby-aws/src/main/java/org/jooby/aws/Aws.java

Lines changed: 45 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -203,26 +203,25 @@
203203
*/
204204
package org.jooby.aws;
205205

206+
import com.amazonaws.AmazonWebServiceClient;
207+
import com.amazonaws.auth.AWSCredentialsProvider;
208+
import com.google.common.collect.ImmutableList;
209+
import com.google.common.collect.ImmutableList.Builder;
210+
import com.google.inject.Binder;
211+
import com.typesafe.config.Config;
206212
import static java.util.Objects.requireNonNull;
207-
208-
import java.util.ArrayList;
209-
import java.util.List;
210-
import java.util.function.BiFunction;
211-
import java.util.function.Function;
212-
213213
import org.jooby.Env;
214214
import org.jooby.Jooby;
215215
import org.jooby.internal.aws.AwsShutdownSupport;
216-
import org.jooby.internal.aws.ConfigCredentialsProvider;
216+
import org.jooby.internal.aws.CredentialsFactory;
217+
import org.jooby.internal.aws.ForwardingCredentialsProvider;
217218
import org.slf4j.Logger;
218219
import org.slf4j.LoggerFactory;
219220

220-
import com.amazonaws.AmazonWebServiceClient;
221-
import com.amazonaws.auth.AWSCredentialsProvider;
222-
import com.google.common.collect.ImmutableList;
223-
import com.google.common.collect.ImmutableList.Builder;
224-
import com.google.inject.Binder;
225-
import com.typesafe.config.Config;
221+
import java.util.ArrayList;
222+
import java.util.List;
223+
import java.util.function.BiFunction;
224+
import java.util.function.Function;
226225

227226
/**
228227
* <h1>aws module</h1>
@@ -264,7 +263,7 @@
264263
* </p>
265264
*
266265
* <p>
267-
* This module is small and simple. All it does is bind {@link AmazonWebServiceClient} instances in
266+
* This module is small and simple. All it does is bind <code>amazon service</code> instances in
268267
* Guice. It also helps to bind utility classes like <code>TransferManager</code>.
269268
* </p>
270269
*
@@ -306,19 +305,29 @@
306305
* );
307306
* </pre>
308307
*
309-
* It uses the {@link AmazonWebServiceClient#getServiceName()} method in order to find per service
310-
* keys.
308+
* <p>
309+
* The module also install the defaults aws credentials provider.
310+
* Provider precedence is as follows:
311+
* </p>
312+
*
313+
* <ul>
314+
* <li>application *.conf file (application.conf, application.prod.conf, etc...)</li>
315+
* <li>environment (EnvironmentVariableCredentialsProvider)</li>
316+
* <li>jvm system properties (SystemPropertiesCredentialsProvider)</li>
317+
* <li>aws configuration profile (ProfileCredentialsProvider)</li>
318+
* <li>ec2 (EC2ContainerCredentialsProviderWrapper)</li>
319+
* </ul>
311320
*
312321
* @author edgar
313322
* @since 0.7.0
314323
*/
315-
@SuppressWarnings({"rawtypes", "unchecked" })
324+
@SuppressWarnings({"rawtypes", "unchecked"})
316325
public class Aws implements Jooby.Module {
317326

318327
/** The logging system. */
319328
private final Logger log = LoggerFactory.getLogger(getClass());
320329

321-
private Builder<BiFunction<AWSCredentialsProvider, Config, AmazonWebServiceClient>> callbacks =
330+
private Builder<BiFunction<AWSCredentialsProvider, Config, Object>> callbacks =
322331
ImmutableList.builder();
323332

324333
private List<BiFunction> after = new ArrayList<>();
@@ -336,8 +345,7 @@ public class Aws implements Jooby.Module {
336345
* @param callback A creation callback.
337346
* @return This module.
338347
*/
339-
public Aws with(
340-
final BiFunction<AWSCredentialsProvider, Config, AmazonWebServiceClient> callback) {
348+
public Aws with(final BiFunction<AWSCredentialsProvider, Config, Object> callback) {
341349
requireNonNull(callback, "Callback is required.");
342350
callbacks.add(callback);
343351
return this;
@@ -360,7 +368,7 @@ public Aws with(
360368
* @param callback A creation callback.
361369
* @return This module.
362370
*/
363-
public Aws with(final Function<AWSCredentialsProvider, AmazonWebServiceClient> callback) {
371+
public Aws with(final Function<AWSCredentialsProvider, Object> callback) {
364372
return with((creds, conf) -> callback.apply(creds));
365373
}
366374

@@ -416,9 +424,21 @@ public <T extends AmazonWebServiceClient> Aws doWith(final Function<T, Object> c
416424
public void configure(final Env env, final Config config, final Binder binder) {
417425

418426
callbacks.build().forEach(it -> {
419-
ConfigCredentialsProvider creds = new ConfigCredentialsProvider(config);
420-
AmazonWebServiceClient service = it.apply(creds, config);
421-
creds.service(service.getServiceName());
427+
ForwardingCredentialsProvider fcp = new ForwardingCredentialsProvider();
428+
Object service = it.apply(fcp, config);
429+
String serviceName;
430+
if (service instanceof AmazonWebServiceClient) {
431+
serviceName = ((AmazonWebServiceClient) service).getServiceName();
432+
} else {
433+
serviceName = service.getClass().getSimpleName()
434+
.replace("AWS", "")
435+
.replace("Amazon", "")
436+
.replace("JavaClient", "")
437+
.replace("ServiceClient", "")
438+
.replace("Client", "")
439+
.toLowerCase();
440+
}
441+
fcp.setProvider(CredentialsFactory.create(config, serviceName));
422442
Class serviceType = service.getClass();
423443
Class[] interfaces = serviceType.getInterfaces();
424444
if (interfaces.length > 0) {
@@ -432,7 +452,7 @@ public void configure(final Env env, final Config config, final Binder binder) {
432452
}
433453

434454
private void after(final Env env, final Binder binder, final Config config,
435-
final AmazonWebServiceClient service) {
455+
final Object service) {
436456
after.forEach(it -> {
437457
try {
438458
Object dep = it.apply(service, config);

modules/jooby-aws/src/main/java/org/jooby/internal/aws/ConfigCredentialsProvider.java renamed to modules/jooby-aws/src/main/java/org/jooby/internal/aws/CredentialsFactory.java

Lines changed: 41 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -203,78 +203,68 @@
203203
*/
204204
package org.jooby.internal.aws;
205205

206-
import static java.util.Objects.requireNonNull;
207-
208206
import com.amazonaws.auth.AWSCredentials;
209207
import com.amazonaws.auth.AWSCredentialsProvider;
208+
import com.amazonaws.auth.AWSCredentialsProviderChain;
209+
import com.amazonaws.auth.AWSStaticCredentialsProvider;
210210
import com.amazonaws.auth.BasicAWSCredentials;
211211
import com.amazonaws.auth.BasicSessionCredentials;
212-
import com.google.common.base.Joiner;
212+
import com.amazonaws.auth.EC2ContainerCredentialsProviderWrapper;
213+
import com.amazonaws.auth.EnvironmentVariableCredentialsProvider;
214+
import com.amazonaws.auth.SystemPropertiesCredentialsProvider;
215+
import com.amazonaws.auth.profile.ProfileCredentialsProvider;
216+
import com.google.common.base.Strings;
213217
import com.typesafe.config.Config;
214218

215-
public class ConfigCredentialsProvider implements AWSCredentialsProvider {
219+
import java.util.LinkedList;
220+
import java.util.function.Consumer;
221+
import java.util.stream.Stream;
222+
223+
public class CredentialsFactory {
216224

217225
private static final String ACCESS_KEY = "accessKey";
218226

219227
private static final String SECRET_KEY = "secretKey";
220228

221229
private static final String SESSION_TOKEN = "sessionToken";
222230

223-
private Config config;
224-
225-
private String accessKey;
226-
227-
private String secretKey;
228-
229-
private String sessionToken;
230-
231-
public ConfigCredentialsProvider(final Config config) {
232-
this.config = requireNonNull(config, "Config is required.");
233-
}
234-
235-
public ConfigCredentialsProvider service(final String serviceName) {
236-
// global vs service specific key
237-
String accessKey = key("aws", ACCESS_KEY);
238-
String secretKey = key("aws", SECRET_KEY);
239-
String sessionToken = key("aws", SESSION_TOKEN);
231+
public static AWSCredentialsProvider create(final Config conf, final String serviceName) {
232+
LinkedList<AWSCredentialsProvider> chain = chain();
240233

241-
String serviceAccessKey = key("aws", serviceName, ACCESS_KEY);
242-
String serviceSecretKey = key("aws", serviceName, SECRET_KEY);
243-
String serviceSessionToken = key("aws", serviceName, SESSION_TOKEN);
234+
applicationCredentials(conf, serviceName, chain::addFirst);
244235

245-
if (this.config.hasPath(serviceAccessKey)) {
246-
accessKey = serviceAccessKey;
247-
}
248-
if (this.config.hasPath(serviceSecretKey)) {
249-
secretKey = serviceSecretKey;
250-
}
251-
// override
252-
this.accessKey = this.config.getString(accessKey);
253-
this.secretKey = this.config.getString(secretKey);
254-
// session token
255-
if (this.config.hasPath(serviceSessionToken)) {
256-
this.sessionToken = this.config.getString(serviceSessionToken);
257-
} else if (this.config.hasPath(sessionToken)) {
258-
this.sessionToken = this.config.getString(sessionToken);
259-
}
260-
return this;
236+
return new AWSCredentialsProviderChain(chain);
261237
}
262238

263-
@Override
264-
public AWSCredentials getCredentials() {
265-
if (sessionToken != null) {
266-
return new BasicSessionCredentials(accessKey, secretKey, sessionToken);
239+
private static void applicationCredentials(Config conf, String serviceName,
240+
Consumer<AWSCredentialsProvider> consumer) {
241+
String accessKey = find(conf, "aws." + serviceName + "." + ACCESS_KEY, "aws." + ACCESS_KEY);
242+
if (accessKey != null) {
243+
String secretKey = find(conf, "aws." + serviceName + "." + SECRET_KEY, "aws." + SECRET_KEY);
244+
String sessionToken = find(conf, "aws." + serviceName + "." + SESSION_TOKEN,
245+
"aws." + SESSION_TOKEN);
246+
AWSCredentials credentials = sessionToken == null
247+
? new BasicAWSCredentials(accessKey, secretKey)
248+
: new BasicSessionCredentials(accessKey, secretKey, sessionToken);
249+
consumer.accept(new AWSStaticCredentialsProvider(credentials));
267250
}
268-
return new BasicAWSCredentials(accessKey, secretKey);
269251
}
270252

271-
@Override
272-
public void refresh() {
273-
// noop
253+
private static LinkedList<AWSCredentialsProvider> chain() {
254+
LinkedList<AWSCredentialsProvider> chain = new LinkedList<>();
255+
chain.add(new EnvironmentVariableCredentialsProvider());
256+
chain.add(new SystemPropertiesCredentialsProvider());
257+
chain.add(new ProfileCredentialsProvider());
258+
chain.add(new EC2ContainerCredentialsProviderWrapper());
259+
return chain;
274260
}
275261

276-
private String key(final String... parts) {
277-
return Joiner.on(".").join(parts);
262+
private static String find(Config conf, String... keys) {
263+
return Stream.of(keys)
264+
.filter(conf::hasPath)
265+
.findFirst()
266+
.map(conf::getString)
267+
.map(Strings::emptyToNull)
268+
.orElse(null);
278269
}
279-
280270
}

0 commit comments

Comments
 (0)