AWS Assume Role Instance Profile Implementation within Boomi
AWS Assume Role Instance Profile allows a resource with an assigned AWS role to create a temporary set of credentials to be used to perform specific tasks that the assumed role has the privilege to execute. The following article outlines how to implement AWS Assume Roles with S3 within Boomi. The implementation will be for an AWS role assigned to an EC2 instance that is running a Boomi Integration Runtime (i.e. Atom). The example integration will execute the S3 Put Object (Upsert). Additional S3 Actions can be executed by configuring the stringToSign variable within the Groovy script. Each AWS operation does have multiple URI request parameters that can be set. Please review AWS’s documentation for additional configuration options.
Another article on using AWS Assume Role with User Credentials can be found here
Overview
The reference implementation starts by using a Role that is applied to an EC2 instance. An HTTP API call is made to http://169.254.169.254/latest/api/token to get a token. A second call is made to get session credentials. The second call is to http://169.254.169.254/latest/meta-data/iam/security-credentials/
The next step will set a few Dynamic Document Properties that will be used within the AWS Signing Key script. The AWS Signing Key script created a string to sign, which is made of up the HTTP Method, Canonical Uri, Canonical Query String, Canonical Headers, Signed Headers, and Hashed Payload. The output of the script is the Authorization header to be used for the HTTP calls that execute the Put Object S3 Action.
Boomi Configuration
Process Overview
Figure 1. Process Overview.
There are two steps within the integration: Get Instance Profile Session Token and Send Data to S3 with Session Token.
AWS IAM Role for EC2 Instances
An IAM Role must be applied to the EC2 instance that defines the permissions for the S3 bucket. There are two tabs within the role that we will focus on, which are Permissions and Trust relationships. The Role can be created by creating a new AWS Role and selecting the EC2 use case.
Figure 2. AWS Role.
Below is the AWS Policy applied to the AWS Role. The name of the AWS S3 bucket being used is ab-ec2-s3-bucket.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::ab-ec2-s3-bucket"
]
},
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject",
"s3:PutObjectAcl"
],
"Resource": [
"arn:aws:s3:::ab-ec2-s3-bucket/*"
]
}
]
}
The trust relationships are defined below for the AWS Role.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
Next, the role needs to be applied to the EC2 instance that is running the Boomi Integration Runtime.
Figure 3. EC2 IAM Role within Security Settings.
Boomi Integration Configuration
After the EC2 instance has been set up, we will start to configure the Boomi integration.
To obtain the credentials, two HTTP calls must be executed. The first call sends a REST API call to http://169.254.169.254/latest/api/token
.
Figure 4. HTTP Connection for AWS Instance Profile Token.
Figure 5. HTTP HTTP Operation for AWS Instance Profile Token.
*Table 1. HTTP Connection Header from AWS Instance Profile Token.”
Name | Value |
---|---|
Header: X-aws-ec2-metadata-token-ttl-seconds | 21600 |
Resource Path: latest/api/token | latest/api/token |
The first request will return a plain text token that will be used in a subsequent X-aws-ec2-metadata-token header.
The next REST API call will be to http://169.254.169.254/latest/meta-data/iam/security-credentials/\<role-applied-to-EC2>
. It will use the same HTTP connection as the first API call and will only need a different HTTP Operation. http://169.254.169.254/latest/meta-data/iam/security-credentials/ab-EC2-S3-Permissions
will be used based on the role being used in Figure 2.
Figure 6. HTTP Operation for AWS Session Token.
Table 2. HTTP Operation headers for AWS Session Token.
Name | Value |
---|---|
Header: X-aws-ec2-metadata-token | <token-from-initial-rest-call> |
Resource Path: latest/meta-data/iam/security-credentials/ab-EC2-S3-Permissions | latest/meta-data/iam/security-credentials/ |
Below is a sample JSON response.
{
"Code" : "Success",
"LastUpdated" : "2012-04-26T16:39:16Z",
"Type" : "AWS-HMAC",
"AccessKeyId" : "ASIAIOSFODNN7EXAMPLE",
"SecretAccessKey" : "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
"Token" : "token",
"Expiration" : "2017-05-17T15:09:54Z"
}
Set Property Shape
Finally, a Set Property shape will set three Dynamic Process Properties: DPP_AWS_ACCESS_KEY, DPP_AWS_SECRET_KEY, and DPP_AWS_SESSION_TOKEN from the previous response.
Dynamic Process Property | JSON Element |
---|---|
DPP_AWS_ACCESS_KEY | AccessKeyId |
DPP_AWS_SECRET_KEY | SecretAccessKey |
DPP_AWS_SESSION_TOKEN | SessionToken |
Figure 7. Set Property Shape for Session Credentials.
S3 Put Object
Once the temporary credentials have been obtained, an Authorization header can be created to Put an object in S3. A few Dynamic Document Properties are set and then a Groovy script is executed to create the S3 Authorization header.
Figure 8. Put Object: Set Properties Shape Configuration.
Table 3. Put Object: Set Properties Configuration.
Dynamic Document Property Name | Value |
---|---|
DDP_AWS_BUCKET | <s3-bucket-name> |
DDP_FILE_NAME | <file-name> |
DDP_HTTP_METHOD | PUT |
DDP_CONTENT_TYPE | i.e. text/csv |
x-amz-security-token | DPP_AWS_SESSION_TOKEN (from AWS STS request) |
S3 Authorization Header: Groovy Script
A Groovy script will create the Authorization header for sending data to S3. The values from the previous step will be used to build AWS’s string to sign and will be encrypted with the temporary AWS Secret Key. The following script does not require any additional custom libraries.
The stringToSign variable will be slightly different for each S3 Action. Refer to AWS’s documentation for the exact format of each stringToSign. Links to AWS’s documentation are within the Reference section. This Groovy script is designed only for S3 PUT.
Additional, S3 Actions can be found here:
// Groovy 2.4
// https://docs.aws.amazon.com/AmazonS3/latest/userguide/RESTAuthentication.html
/*-------------------------------------------------------
Required inputs:
DPP: DPP_AWS_ACCESS_KEY
DPP: DPP_AWS_SECRET_KEY
DPP: DPP_AWS_SESSION_TOKEN // Only required if assuming role
DDP: DDP_AWS_BUCKET
DDP: DDP_FILE_NAME
DDP: DDP_HTTP_METHOD
DDP: DDP_CONTENT_TYPE //Only required for PUT requests
outputs:
DDP: Authorization
DDP: Date
------------------------------------------------------*/
import com.boomi.execution.ExecutionUtil;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
// Set global variables
String accessKey = ExecutionUtil.getDynamicProcessProperty("DPP_AWS_ACCESS_KEY");
String secretKey = ExecutionUtil.getDynamicProcessProperty("DPP_AWS_SECRET_KEY");
String securityToken = ExecutionUtil.getDynamicProcessProperty("DPP_AWS_SESSION_TOKEN");
for (int i = 0; i < dataContext.getDataCount(); i++) {
InputStream is = dataContext.getStream(i);
Properties props = dataContext.getProperties(i);
String bucketName = props.getProperty("document.dynamic.userdefined.DDP_AWS_BUCKET");
String keyName = props.getProperty("document.dynamic.userdefined.DDP_FILE_NAME");
String method = props.getProperty("document.dynamic.userdefined.DDP_HTTP_METHOD");
String contentType = props.getProperty("document.dynamic.userdefined.DDP_CONTENT_TYPE");
contentType = contentType ?: "";
// Get Current Date
DateFormat longDateFormat = new SimpleDateFormat("E, dd MMM yyyy HH:mm:ss Z");
Date date = new Date();
String longDate = longDateFormat.format(date);
// Set String to sign
String stringToSign = method + "\n" +
"\n" +
contentType + "\n" + // contentType will be blank unless it's a PUT request
longDate + "\n" +
"x-amz-security-token:" + securityToken + "\n" +
"/" + bucketName + "/" + keyName;
String signature = Base64.getEncoder().encodeToString(getHMAC1(secretKey.getBytes(), stringToSign));
String authorization = "AWS " + accessKey + ":" + signature;
// Create Authorized URL
props.setProperty("document.dynamic.userdefined.Authorization", authorization);
props.setProperty("document.dynamic.userdefined.Date", longDate);
dataContext.storeStream(is, props);
}
static byte[] getHMAC1(byte[] key, String input) {
byte[] hash;
Mac sha1HMAC = Mac.getInstance("HmacSHA1");
SecretKeySpec secretKey = new SecretKeySpec(key, "HmacSHA1");
sha1HMAC.init(secretKey);
hash = sha1HMAC.doFinal(input.getBytes(StandardCharsets.UTF_8));
return hash;
}
Put Object HTTP Configuration
The HTTP connection will be configured to send data to S3. The HTTP connection will be different than the one used to Assume Roles.
Figure 9. S3 HTTP Connection Configuration.
Table 4. S3 HTTP Connection Configuration.
Name | Value |
---|---|
URL | https://s3.s3-region.amazonaws.com |
Authentication Type | None (Being set using the Groovy Authentication header script) |
Figure 10. S3 HTTP Operation Configuration.
Table 5. S3 HTTP Operation Configuration.
Name | Value |
---|---|
Content Type | <content-type> (can be set dynamically) |
HTTP Method | PUT |
Header: x-amz-security-token | Check Is replacement variable? |
Header: Authorization | Check Is replacement variable? |
Header: Date | Check Is replacement variable? |
Resource Path: DDP_AWS_BUCKET | Check Is replacement variable? |
Resource Path: / | / |
Resource Path: DDP_FILE_NAME | Check Is replacement variable? |
References
- AWS Retrieve security credentials from instance metadata
- AWS S3 AssumeRoles Parameters and Examples
- AWS S3 Authenticating Request AWS Signature Version 4
- AWS S3 REST API Documentation
- AWS S3 REST API DeleteObject
- AWS S3 REST API GetObject
- AWS S3 REST API PutObject
The article was originally posted at Boomi Community.