EDUCAÇÃO E TECNOLOGIA

How to Pre-signed an Amazon AWS S3 Bucket URL

Introduction

I recently needed to write a file to the amazon bucket and share the url to the business team.

Here I found several posts explaining how to write and read files in the amazon bucket, so in this post I will explain just how to pre-sign a file after it is written to the amazon server.

Security Material

To pre-sign a url you will use an Amazon generated key pair (Secret Key and Access Key). This key pair that will be used by iFlow can be stored as an externalized parameter or hard code, but for security we will store it as security material.

For this case, we will create a security material object called “AWS_S3_AccessKey” to store the access key, and another object called “AWS_S3_SecretKey” to store the secret key.

Iflow

This IFlow is very simple since all the logic of the service is in the groovy script, and we use just one Content Modifier to load some parameters before the program, and another content modifier (if necessary for you) to assemble a body with the response .

  1. Parameters

    The first step of the flow is where some parameters are loaded, before pre-signing the URL.With the exception of url and filename (green), all parameters (red) can be configured through external parameters.

    The url and filename will usually be informed at runtime of the service.



    Below is description of each parameter:

    p_url -> URL for pre-signed.
    p_fileName -> FIlename.

    p_secretKeyAlias -> Name of the Security Material where the Secret Key is stored.
    p_acessKeyAlias -> Name of the Security Material where the Access Key is stored.
    p_region -> Bucket region
    p_bucket -> Bucket name
    p_expires -> Time to expiry url (in seconds)

  1. Groovy

    Below the groovy that will finally pre-sign the url, the return will be done through the p_urlFile parameter,

import com.sap.gateway.ip.core.customdev.util.Message;
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat
import java.lang.Object;
import java.util.List;
import java.util.TimeZone;
import org.apache.commons.codec.digest.DigestUtils;
import com.sap.it.api.ITApiFactory;
import com.sap.it.api.mapping.ValueMappingApi
import com.sap.it.api.securestore.SecureStoreService
import com.sap.it.api.securestore.UserCredential
import com.sap.it.api.securestore.exception.SecureStoreException def Message processData(Message message) { def body = message.getBody(java.lang.String) as String //************* Hash do Body using apache commons DigestUtils sha256Hex ************* def hashBody = DigestUtils.sha256Hex(body) //************* Mapping the properties - The filename was set in previous groovyScript ************* def map = message.getProperties(); //************* Iniciating variables ************* String signurl = map.get("p_signUrl"); String method = "GET"; String region = map.get("p_region"); String bucket = map.get("p_bucket"); String service = "s3"; String host = bucket + ".s3." + region + ".amazonaws.com"; String endpoint = "s3." + region + ".amazonaws.com"; String filename = map.get("p_fileName"); String expires = map.get("p_expires"); String urlfile = "https://" + host + "/" + filename; //************* Acess Secure Parameters ************* def apiacesskey_alias = map.get("p_acessKeyAlias"); def apisecretkey_alias = map.get("p_secretKeyAlias"); def secureStorageService = ITApiFactory.getService(SecureStoreService.class, null); def secureParameterAcessKey = secureStorageService.getUserCredential(apiacesskey_alias); def secureParameterSecretKey = secureStorageService.getUserCredential(apisecretkey_alias); def access_key = secureParameterAcessKey.getPassword().toString(); def secret_key = secureParameterSecretKey.getPassword().toString(); // ************ Create a timestamp for headers and the credential string ************ def now = new Date() def amzFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'") def formattedDate = new SimpleDateFormat("EEEE, MMMM dd, yyyy, hh:mm a '('zzz')'") def stampFormat = new SimpleDateFormat("yyyyMMdd") def amzDate = amzFormat.format(now) def date_stamp = stampFormat.format(now) //************* Canonical Request variables ************* String canonical_uri = "/" + filename; String canonical_querystring = ""; String canonical_headers = "X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=" + access_key + "%2F" + date_stamp + "%2F" + region + "%2Fs3%2Faws4_request&X-Amz-Date=" + amzDate + "&X-Amz-Expires=" + expires + "&X-Amz-SignedHeaders=host"; String signed_headers = "host:" + host; String canonical_request = method + "\n" + canonical_uri + "\n" + canonical_headers + "\n" + signed_headers + "\n\nhost\nUNSIGNED-PAYLOAD"; //************* Sing to Sing variables ************* String algorithm = "AWS4-HMAC-SHA256"; String credential_scope = date_stamp + "/" + region + "/" + service + "/" + "aws4_request"; String string_to_sign = algorithm + "\n" + amzDate + "\n" + credential_scope + "\n" + DigestUtils.sha256Hex(canonical_request); //************* Generating the Singning Key ************* byte[] signing_key = getSignatureKey(secret_key, date_stamp, region, service); //************* Generating the HmacSHA256 - Amazon ************* byte[] signature = HmacSHA256(string_to_sign, signing_key); //************* Generating the Hex of the Signature ************* String strHexSignature = bytesToHex(signature); //************* Generating the authorization header signed - Amazon V4 S3 Bucket ************* String authorization_header = algorithm + " " + "Credential=" + access_key + "/" + credential_scope + ", " + "SignedHeaders=" + signed_headers + ", " + "Signature=" + strHexSignature; //************* Seting the headers of HTTP call ************* String urlfileSigned = urlfile + "?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=" + access_key + "%2F" + date_stamp + "%2F" + region + "%2Fs3%2Faws4_request&X-Amz-Date=" + amzDate + "&X-Amz-Expires=" + expires + "&X-Amz-SignedHeaders=host&X-Amz-Signature=" + strHexSignature; String urlReturn; urlReturn = urlfileSigned; message.setProperty("p_urlFile", urlReturn); //************* Setting the body to be store in Amazon ************* message.setBody(body) return message;
} //************* Function bytes to Hex ************* String bytesToHex(byte[] bytes) { char[] hexArray = "0123456789ABCDEF".toCharArray(); char[] hexChars = new char[bytes.length * 2]; for (int j = 0; j < bytes.length; j++) { int v = bytes[j] & 0xFF; hexChars[j * 2] = hexArray[v >>> 4]; hexChars[j * 2 + 1] = hexArray[v & 0x0F]; } return new String(hexChars).toLowerCase();
}
//************* Function HmacSHA256 ************* byte[] HmacSHA256(String data, byte[] key) throws Exception { String algorithm = "HmacSHA256"; Mac mac = Mac.getInstance(algorithm); mac.init(new SecretKeySpec(key, algorithm)); return mac.doFinal(data.getBytes("UTF8"));
}
//************* Function getSignature ************* byte[] getSignatureKey(String key, String dateStamp, String regionName, String serviceName) throws Exception { byte[] kSecret = ("AWS4" + key).getBytes("UTF8"); byte[] kDate = HmacSHA256(dateStamp, kSecret); byte[] kRegion = HmacSHA256(regionName, kDate); byte[] kService = HmacSHA256(serviceName, kRegion); byte[] kSigning = HmacSHA256("aws4_request", kService); return kSigning;
}

Test

To test just run iFlow with the parameters loaded, below a test showing the parameters before and after the execution of the groovy script.

Input:

Output:

I hope I have contributed to the community, and look forward to receiving your feedback in the comments!