Skip to content

Commit

Permalink
Adds support for IAM task role credentials, and AWS session credentials.
Browse files Browse the repository at this point in the history
  • Loading branch information
Martin Charlesworth committed Aug 29, 2021
1 parent e52f6f0 commit 9450a83
Show file tree
Hide file tree
Showing 8 changed files with 145 additions and 64 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,13 @@
import com.amazonaws.services.sqs.model.SendMessageRequest;
import fish.payara.cloud.connectors.amazonsqs.api.AmazonSQSConnection;
import fish.payara.cloud.connectors.amazonsqs.api.AmazonSQSConnectionFactory;
import java.util.Date;

import javax.annotation.Resource;
import javax.ejb.Schedule;
import javax.ejb.Stateless;
import javax.resource.ConnectionFactoryDefinition;
import javax.resource.spi.TransactionSupport.TransactionSupportLevel;
import java.time.LocalTime;

/**
*
Expand All @@ -63,17 +64,23 @@
transactionSupport = TransactionSupportLevel.NoTransaction,
properties = {"awsAccessKeyId=${ENV=accessKey}",
"awsSecretKey=${ENV=secretKey}",
"region=eu-west-2"})
"awsSessionToken=${ENV=sessionToken}",
"profileName=${ENV=profileName}",
"region=${ENV=region}"})
@Stateless
public class NewTimerSessionBean {

@Resource(lookup="java:comp/env/SQSConnectionFactory")
AmazonSQSConnectionFactory factory;

@Schedule(second = "*/1", hour="*", minute="*")
@Schedule(second = "*/3", hour="*", minute="*")
public void myTimer() {
try (AmazonSQSConnection connection = factory.getConnection()) {
connection.sendMessage(new SendMessageRequest(System.getenv("queueURL"), "Hello World"));
} catch (Exception e) {}
String msg = "Hello World @ " + LocalTime.now();
System.out.println("<< Sending message: " + msg);
connection.sendMessage(new SendMessageRequest(System.getenv("queueURL"), msg));
} catch (Exception e) {
e.printStackTrace();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,23 +54,25 @@
@MessageDriven(activationConfig = {
@ActivationConfigProperty(propertyName = "awsAccessKeyId", propertyValue = "${ENV=accessKey}"),
@ActivationConfigProperty(propertyName = "awsSecretKey", propertyValue = "${ENV=secretKey}"),
@ActivationConfigProperty(propertyName = "queueURL", propertyValue = "${ENV=queueURL}"),
@ActivationConfigProperty(propertyName = "awsSessionToken", propertyValue = "${ENV=sessionToken}"),
@ActivationConfigProperty(propertyName = "profileName", propertyValue = "${ENV=profileName}"),
@ActivationConfigProperty(propertyName = "queueURL", propertyValue = "${ENV=queueURL}"),
@ActivationConfigProperty(propertyName = "pollInterval", propertyValue = "1000"),
@ActivationConfigProperty(propertyName = "region", propertyValue = "eu-west-2")
@ActivationConfigProperty(propertyName = "region", propertyValue = "${ENV=region}")
})
public class ReceiveSQSMessage implements AmazonSQSListener {

@OnSQSMessage
public void receiveMessage(Message message) {
System.out.println("Got message " + message.getBody());
System.out.println(">> Got message " + message.getBody());
Map<String,MessageAttributeValue> mattrs = message.getMessageAttributes();
for (String key : mattrs.keySet()) {
System.out.println("Got Message attribute " + key + "," + mattrs.get(key).getStringValue());
System.out.println(">> Got Message attribute " + key + "," + mattrs.get(key).getStringValue());
}

Map<String,String> attrs = message.getAttributes();
for (String key : attrs.keySet()) {
System.out.println("Got attribute " + key + "," + attrs.get(key));
System.out.println(">> Got attribute " + key + "," + attrs.get(key));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
awsSecretKey=
awsAccessKeyId=
awsSessionToken=
region=us-east-1
profileName=
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package fish.payara.cloud.connectors.amazonsqs.api;

import com.amazonaws.auth.*;
import com.amazonaws.auth.profile.ProfileCredentialsProvider;
import com.amazonaws.util.StringUtils;

/**
* @author Martin Charlesworth
*/
public class AwsCredentialsProviderUtils {

/**
* Creates the appropriate type of {@link AWSCredentialsProvider} depending on the configuration provided.
*/
public static AWSCredentialsProvider getProvider(String accessKey,
String secretKey,
String sessionToken,
String profileName) {
AWSCredentialsProvider awsCredentialsProvider = null;
if (isValidParam(profileName)) {
// uses specified credentials profile
awsCredentialsProvider = new ProfileCredentialsProvider(profileName);

} else {
if (isValidParam(accessKey) && isValidParam(secretKey)) {
if (isValidParam(sessionToken)) {
// uses temporary session based credentials
awsCredentialsProvider = new AWSStaticCredentialsProvider(new BasicSessionCredentials(accessKey, secretKey, sessionToken));
} else {
// uses basic access key + secret key credentials
awsCredentialsProvider = new AWSStaticCredentialsProvider(new BasicAWSCredentials(accessKey, secretKey));
}
}
}
if (awsCredentialsProvider == null) {
// if neither credentials nor profile are provided, fall back on the default provider chain, which will
// eventually give us a EC2ContainerCredentialsProviderWrapper, if running in ECS.
awsCredentialsProvider = DefaultAWSCredentialsProviderChain.getInstance();
}

return awsCredentialsProvider;
}

private static boolean isValidParam(String value) {
return !StringUtils.isNullOrEmpty(value) && !value.startsWith("${ENV");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,17 @@
*/
package fish.payara.cloud.connectors.amazonsqs.api.inbound;

import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.profile.ProfileCredentialsProvider;
import com.amazonaws.auth.*;
import com.amazonaws.util.StringUtils;
import fish.payara.cloud.connectors.amazonsqs.api.AmazonSQSListener;
import fish.payara.cloud.connectors.amazonsqs.api.AwsCredentialsProviderUtils;

import javax.resource.ResourceException;
import javax.resource.spi.Activation;
import javax.resource.spi.ActivationSpec;
import javax.resource.spi.InvalidPropertyException;
import javax.resource.spi.ResourceAdapter;
import java.util.logging.Logger;

/**
* Activation Specification for Amazon SQS
Expand All @@ -63,6 +63,7 @@ public class AmazonSQSActivationSpec implements ActivationSpec, AWSCredentialsPr

private String awsAccessKeyId;
private String awsSecretKey;
private String awsSessionToken;
private String queueURL;
private String region;
private Integer maxMessages = 10;
Expand All @@ -79,17 +80,8 @@ public void validate() throws InvalidPropertyException {
throw new InvalidPropertyException("region must be specified");
}

// Validate profileName if present, skip validation off other keys
if (StringUtils.isNullOrEmpty(profileName)) {

// validate keys if profileName isn't available
if (StringUtils.isNullOrEmpty(awsAccessKeyId)) {
throw new InvalidPropertyException("awsAccessKeyId must be specified");
}

if (StringUtils.isNullOrEmpty(awsSecretKey)) {
throw new InvalidPropertyException("awsSecretKey must be specified");
}
if (profileName != null && profileName.startsWith("${ENV")) {
throw new InvalidPropertyException("invalid profileName");
}

if (StringUtils.isNullOrEmpty(queueURL)) {
Expand Down Expand Up @@ -123,6 +115,14 @@ public void setAwsSecretKey(String awsSecretKey) {
this.awsSecretKey = awsSecretKey;
}

public String getAwsSessionToken() {
return awsSessionToken;
}

public void setAwsSessionToken(String awsSessionToken) {
this.awsSessionToken = awsSessionToken;
}

public String getQueueURL() {
return queueURL;
}
Expand Down Expand Up @@ -195,30 +195,19 @@ public void setProfileName(String profileName) {
this.profileName = profileName;
}

private boolean isValidProfile() {
return !StringUtils.isNullOrEmpty(profileName) && !profileName.startsWith("${ENV");
}

@Override
public AWSCredentials getCredentials() {
// Return Credentials based on what is present, profileName taking priority.
if (StringUtils.isNullOrEmpty(getProfileName())) {
return new AWSCredentials() {
@Override
public String getAWSAccessKeyId() {
return awsAccessKeyId;
}

@Override
public String getAWSSecretKey() {
return awsSecretKey;
}
};
} else {
return new ProfileCredentialsProvider(getProfileName()).getCredentials();
}
return AwsCredentialsProviderUtils.getProvider(awsAccessKeyId, awsSecretKey, awsSessionToken, profileName)
.getCredentials();
}

@Override
public void refresh() {

}


}
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,16 @@
*/
package fish.payara.cloud.connectors.amazonsqs.api.outbound;

import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.profile.ProfileCredentialsProvider;
import com.amazonaws.services.sqs.AmazonSQS;
import com.amazonaws.services.sqs.AmazonSQSClientBuilder;
import com.amazonaws.services.sqs.model.SendMessageBatchRequest;
import com.amazonaws.services.sqs.model.SendMessageBatchRequestEntry;
import com.amazonaws.services.sqs.model.SendMessageBatchResult;
import com.amazonaws.services.sqs.model.SendMessageRequest;
import com.amazonaws.services.sqs.model.SendMessageResult;
import com.amazonaws.util.StringUtils;
import fish.payara.cloud.connectors.amazonsqs.api.AmazonSQSConnection;
import fish.payara.cloud.connectors.amazonsqs.api.AwsCredentialsProviderUtils;

import javax.resource.NotSupportedException;
import javax.resource.ResourceException;
Expand Down Expand Up @@ -80,22 +77,12 @@ public class AmazonSQSManagedConnection implements ManagedConnection, AmazonSQSC
private final AmazonSQS sqsClient;

AmazonSQSManagedConnection(Subject subject, ConnectionRequestInfo cxRequestInfo, AmazonSQSManagedConnectionFactory aThis) {
AWSCredentialsProvider credentialsProvider;
if (StringUtils.isNullOrEmpty(aThis.getProfileName())) {
credentialsProvider = new AWSStaticCredentialsProvider(new AWSCredentials() {
@Override
public String getAWSAccessKeyId() {
return aThis.getAwsAccessKeyId();
}

@Override
public String getAWSSecretKey() {
return aThis.getAwsSecretKey();
}
});
} else {
credentialsProvider = new ProfileCredentialsProvider(aThis.getProfileName());
}

AWSCredentialsProvider credentialsProvider = AwsCredentialsProviderUtils.getProvider(
aThis.getAwsAccessKeyId(),
aThis.getAwsSecretKey(),
aThis.getAwsSessionToken(),
aThis.getProfileName());

sqsClient = AmazonSQSClientBuilder.standard().withRegion(aThis.getRegion()).withCredentials(credentialsProvider).build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ public class AmazonSQSManagedConnectionFactory implements ManagedConnectionFacto
@ConfigProperty(description = "AWS Access Key", type = String.class)
private String awsAccessKeyId;

@ConfigProperty(description = "AWS Session Token", type = String.class)
private String awsSessionToken;

@ConfigProperty(description = "Region hosting the queue", type = String.class)
private String region;

Expand All @@ -94,6 +97,14 @@ public void setAwsAccessKeyId(String awsAccessKey) {
this.awsAccessKeyId = awsAccessKey;
}

public String getAwsSessionToken() {
return awsSessionToken;
}

public void setAwsSessionToken(String awsSessionToken) {
this.awsSessionToken = awsSessionToken;
}

public String getRegion() {
return region;
}
Expand Down
38 changes: 35 additions & 3 deletions AmazonSQS/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,46 @@ To use the JCA adapter the AmazonSQSRAR-<version>.rar should be deployed to your

To deploy the JCA adapter on Payara Micro use the following commands.

The example uses a number of environment variables for connecting to your Amazon account.
To run the example, first set your queue ARN and AWS region:

```shell
export queueURL=<your SQS queue ARN>
export region=us-east-1
```

A number of different ways are supported to connect to your AWS account.

## AWS Profile

A profile from your ~/.aws/credentials file can be used:

```shell
export profileName="<your profile>"
```

## Session based credentials

Alternatively, temporary session keys can be used, if your AWS account uses federated login:

```shell
export accessKey="<your amazon access key>"
export secretKey="<your amazon secret key>"
export sessionToken="<your session token>"
```

## Basic Credentials

Standard access key ID and secret key credentials can be provided:

```shell
export accessKey="<your amazon access key>"
export queueURL="<URL of the SQS queue in your amazon account>"
export secretKey="<your amazon secret key>"
```

## DefaultAWSCredentialsProviderChain

If no credentials or profile are provided as environment variables, the [DefaultAWSCredentialsProviderChain](https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/auth/DefaultAWSCredentialsProviderChain.html) will be used.

These environment variables must be set before running the example

```shell
Expand Down Expand Up @@ -87,7 +119,7 @@ It is also possible to send messages to the queue using a defined connection fac
A full example of this is shown below;
```java
try (AmazonSQSConnection connection = factory.getConnection()) {
connection.sendMessage(new SendMessageRequest("queueURL", "Hello World"));
connection.sendMessage(new SendMessageRequest("queueURL", "Hello World"));
} catch (Exception e) {}

```
Expand Down

0 comments on commit 9450a83

Please sign in to comment.