Connect to AWS IAM API from MuleSoft.
We will learn using this blog how to use AWS IAM API using AWS Signature Version 4 and then access those API from MuleSoft Dataweave only.
Mulesoft, AWS IAM, DataWeave, CryptoLibrary, Mulesoft Crypto, Amazon Web Service, Identity and Access Management
Amazon Web Service | Anupam Chakraborty | Jul 19 2020 07:12 PM
Introduction to AWS IAM
In this blog, we will see, how to access AWS Identity and Access Management API from an external system using the AWS Signature Version 4 signing and then we will access these API from MuleSoft. For this blog, I will only use Create User and Delete User API, however, the process to access any other API should be the same.
AWS Identity and Access Management (IAM) is a web service for securely controlling access to AWS services. IAM consists of Users, Groups (A way to group users and apply policy), Roles, Policy Documents (Saved as JSON). IAM is universal, does not apply to regions, the root account has complete admin access by default. It is important to note that new users have no permission when created, New User has an access key, secret key, and also a password.
Prerequisite
- MuleSoft Anypoint Studio 7.1 With Mule Runtime 4.2
- Amazon Web Service Account with Access to IAM
AWS Signature Version 4
AWS Suggest that to make any AWS API Call, Requests must be signed using an access key ID and a secret access key. To sign requests, AWS now recommends that we use Signature Version 4.
Signature Version 4 is the process to add authentication information to AWS requests sent by HTTP. For security, most requests to AWS must be signed with an access key, which consists of an access key ID and secret access key.
Creating a Signature with AWS Signature Version 4 comprises of 4 steps. Let us try to go through these steps here.
Task 1: Create a canonical request for Signature Version 4
Canonical Request is a standardized string format with the following details. AWS suggests that we should use these exact details otherwise, the request will be declined.
CanonicalRequest =
HTTPRequestMethod + '\n' +
CanonicalURI + '\n' +
CanonicalQueryString + '\n' +
CanonicalHeaders + '\n' +
SignedHeaders + '\n' +
HexEncode(Hash(RequestPayload))
Now in our case, we need to make an API call to the AWS IAM API as follows:
GET https://iam.amazonaws.com/?Action=CreateUser&UserName=<UserName>&Version=2010-05-08
Hence as per the above details, our request method is GET
HTTPRequestMethod = GET
We have to create canonical URI--the part of the URI from domain to query string (we will use / if no path)
CanonicalURI = /
We have to create a canonical query string. In our API call, request parameters are in the query string. Query string values must be URL-encoded (space=%20). The parameters must be sorted by name.
canonical_querystring = 'Action=CreateUser&UserName=<UserName>&Version=2010-05-08&X-Amz-Algorithm=AWS4-HMAC-&X-Amz-Credential=<URLEncode(access_key + '/' + credential_scope)>&X-Amz-Date=<DateTimeStampInUTC>&X-Amz-Expires=30&X-Amz-SignedHeaders=host
Now there is one unknown variable that is Credential_Scope. This should be created as follows:
credential_scope = <DateStampInUTC>/<region>/iam/aws4_request
We have to create the canonical headers and signed headers. Header names must be trimmed and lowercase and sorted in code point order from low to high. Note trailing New Line in canonical_headers. signed_headers is the list of headers that are being included as part of the signing process. For requests that use query strings, only "host" is included in the signed headers.
canonical_headers = 'host:iam.amazonaws.com<newline>'
signed_headers = 'host'
We have to create a payload hash. For GET requests, the payload is an empty string (""). So, we need to hash this with SHA-256. So in my case, the canonical request is created as follows:
GET
/
Action=CreateUser&UserName=TestAPCUser&Version=2010-05-08&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=*****************%2F20200718%2Fus-east-1%2Fiam%2Faws4_request&X-Amz-Date=20200718T211159Z&X-Amz-Expires=30&X-Amz-SignedHeaders=host
host:iam.amazonaws.com
host
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
Task 2: Create a string to sign for Signature Version 4
The string to sign includes meta-information about our request and about the canonical request that you created in the earlier steps. To create the string to sign, we will concatenate the algorithm, date and time, credential scope, and digest of the canonical request, as shown in the following pseudocode:
StringToSign =
Algorithm + \n +
RequestDateTime + \n +
CredentialScope + \n +
HashedCanonicalRequest
Here let us again derive each variable.
- As we discussed earlier, we use SHA-256 for our Hash, the Algorithm is defined as algorithm = 'AWS4-HMAC-SHA256'
- RequestDateTime = <DateTimeStampInUTC>
- We have already defined the Credential Scope in our earlier section.
- The final part is again an SHA-256 hashed Canonical Request that we have retrieved earlier.
In my case, this turned out something like this.
AWS4-HMAC-SHA256
20200718T211159Z
20200718/us-east-1/iam/aws4_request
a402b5461e3e5893bece467a33f434bf674a20c35f434d9059bd8c97d6cddd44
Task 3: Calculate the signature for AWS Signature Version 4
This is again a 4 Step process of recursive Hashing with Key. Following is the step that will define this
kSecret = <our AWS secret access key>
kDate = Sign DateStampInUTC with the key "AWS4" + kSecret
kRegion = Sign Region with the key kDate
kService = Sign Service with the key kRegion
kSigning = Sign "aws4_request" with the key kService
This is an example python function to do this signature
def sign(key, msg):
return hmac.new(key, msg.encode('utf-8'), hashlib.sha256).digest()
def getSignatureKey(key, dateStamp, regionName, serviceName):
kDate = sign(('AWS4' + key).encode('utf-8'), dateStamp)
kRegion = sign(kDate, regionName)
kService = sign(kRegion, serviceName)
kSigning = sign(kService, 'aws4_request')
return kSigning
Finally, we will generate a signature by signing the String to Sign generated in Step 2 with the signatureKey generated in the above step.
For me, the signature got created something like this:
5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400e06b5924a6f2b5d7
Task 4: Add the signature to the HTTP request
Now consider the canonical query string that we created in our step 1. For my case it was like this:
canonical_querystring = 'Action=CreateUser&UserName=<UserName>&Version=2010-05-08&X-Amz-Algorithm=AWS4-HMAC-&X-Amz-Credential=<URLEncode(access_key + '/' + credential_scope)>&X-Amz-Date=<DateTimeStampInUTC>&X-Amz-Expires=30&X-Amz-SignedHeaders=host'
I will add the final Signature to this query string with the key X-Amz-Signature and then add the Host details at the beginning. For me, it turned out something like this:
https://iam.amazonaws.com?Action=CreateUser&UserName=<UserName>&Version=2010-05-08&X-Amz-Algorithm=AWS4-HMAC-&X-Amz-Credential=<URLEncode(access_key + '/' + credential_scope)>&X-Amz-Date=<DateTimeStampInUTC>&X-Amz-Expires=30&X-Amz-SignedHeaders=host&X-Amz-Signature=5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400e06b5924a6f2b5d7
Now, all we need to do is make an API GET call with the above URL and our requested user will be create in the AWS IAM System.
Call AWS IAM API using MuleSoft.
So far, we saw how to use AWS Signature Version 4 in order to execute AWS API, now let us execute the same from MuleSoft. The following DataWeave will exactly do create the URL we need for our purpose.
%dw 2.0
output application/java
import * from dw::core::URL
import java!java::lang::System
import * from dw::core::Binaries
import dw::Crypto
//Create Some AWS Constants that we will need
var AwsIAMConstants = {
awsDateTimeFormat : "yyyyMMdd'T'HHmmss",
awsDateFormat : "yyyyMMdd",
iamHost : p("aws.host"),
iamService : p("aws.service"),
iamRegion : p("aws.region"),
iamEndPoint : p("aws.endPoint"),
iamAlgo : "AWS4-HMAC-SHA256",
iamReqId : "aws4_request",
iamExpiry : "30"
}
// Generate the line Separator. We should be able to hardcode "\n" as well
var ln = System::lineSeparator()
var method = "GET"
var uri = "/"
var qParam = attributes.queryString ++ "&Version=2010-05-08"
var accessKey = p("aws.accessKey")
var secretKey = p("aws.secretKey")
var amzDate = ((now() >> "UTC") as String {format: AwsIAMConstants.awsDateTimeFormat}) ++ "Z"
var dateStamp = ((now() >> "UTC") as String {format: AwsIAMConstants.awsDateFormat})
// Generate the canonicalHeader, credentialScope and the strSignedHdr.
var strCanonicalHeader = "host:" ++ AwsIAMConstants.iamHost ++ ln
var strSignedHdr = "host"
var credentialScope = dateStamp ++ "/" ++ AwsIAMConstants.iamRegion ++ "/" ++ AwsIAMConstants.iamService ++ "/" ++ AwsIAMConstants.iamReqId
// Generate the CanonicalQuery
var strCanonicalQry = qParam ++ "&X-Amz-Algorithm=" ++ AwsIAMConstants.iamAlgo ++
"&X-Amz-Credential=" ++ encodeURIComponent(accessKey ++ '/' ++ credentialScope) ++
"&X-Amz-Date=" ++ amzDate ++
"&X-Amz-Expires=" ++ AwsIAMConstants.iamExpiry ++
"&X-Amz-SignedHeaders=" ++ strSignedHdr
// Derive the Payload Hash. In our case payload is Empty
var payloadHash = lower(toHex(Crypto::hashWith((payload default "") as Binary, "SHA-256")) as String)
// Derive the Canonical Request and Hash the same
var strCanonicalReq = method ++ ln ++ uri ++ ln ++ strCanonicalQry ++ ln ++ strCanonicalHeader ++ ln ++ strSignedHdr ++ ln ++ payloadHash
var hashCanonicalReq = lower(toHex(Crypto::hashWith(strCanonicalReqas Binary, "SHA-256")) as String)
// Derive the StringToSign
var strToSign = AwsIAMConstants.iamAlgo ++ ln ++ amzDate ++ ln ++ credentialScope ++ ln ++ hashCanonicalReq
// Derive the Signature using 4 step
var kDate = Crypto::HMACBinary ("AWS4" ++ secretKey as Binary, dateStamp as Binary, "HmacSHA256")
var kRegion = Crypto::HMACBinary (kDate as Binary, AwsIAMConstants.iamRegion as Binary, "HmacSHA256")
var kService = Crypto::HMACBinary (kRegion as Binary, AwsIAMConstants.iamService as Binary, "HmacSHA256")
var kSigning = Crypto::HMACBinary (kService as Binary, AwsIAMConstants.iamReqId as Binary, "HmacSHA256")
var signature = Crypto::HMACWith (kSigning as Binary, strToSign as Binary, "HmacSHA256")
---
// Add the final Signature to this query string with the key X-Amz-Signature and then add the Host details at the beginning
AwsIAMConstants.iamEndPoint ++ "?" ++ strCanonicalQry ++ "&X-Amz-Signature=" ++ signature
We can use the DataWeave Script to define a variable like vars.url and push that into an HTTP Request.
This concludes this blog where we learned how to use AWS Signature Version 4 and also how to access them in MuleSoft using DataWeave. Thank you for reading my blog and do let me know if you were able to get this going. Let me know if you have any other questions as well in the comment section.
Comments:
Leave a Comment:
Please login to post comment into this page.