Apigee Edge | Issue with generating Azure SasToken using Java callout.

I have created an executable jar and trying to call using Javacallout policy. This jar will read all the properties (as constructor parameter in the Execute implemented class) passed from policy and call the azure blob service classes from Azure SDK. Due to some Apigee bundle size limit, I am using following maven dependency(not updated version) of Azure. I am getting following error as response in callout policy, no exception variables (set in messageContext in exception block) available in Apigee trace as well. #Excpetation : SIGNED_TOKEN_KEY token in the callout response.
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-storage-file-datalake</artifactId>
<version>12.3.0</version>
</dependency>

This is working fine as a standalone jar as well as calling the execute(...) method from Junit test class. I am sharing the error details as well as code snippet. Appreciate for your help in advance. 

Javacallout response (2 types error) :

1) 

<Property name="error.cause.cause">access denied ("java.lang.RuntimePermission" "getenv.HTTP_PROXY")</Property>
<Property name="error">Failed to execute JavaCallout. null</Property>
<Property name="type">ErrorPoint</Property>
<Property name="state">PROXY_REQ_FLOW</Property>
<Property name="error.class">com.apigee.kernel.exceptions.spi.UncheckedException</Property>
2)
<Property name="error">Failed to execute JavaCallout. Could not initialize class com.azure.core.util.Configuration</Property>
<Property name="type">ErrorPoint</Property>
<Property name="state">PROXY_REQ_FLOW</Property>
<Property name="error.class">com.apigee.kernel.exceptions.spi.UncheckedException</Property>
<Property name="error.cause">Could not initialize class com.azure.core.util.Configuration</Property>
 Java code :
public AzureStorageCallout(Map <String,String> properties) {
super(properties); //#1 - called the parent class constructor and set the properties declared 
}

public ExecutionResult execute(MessageContext messageContext, ExecutionContext executionContext) {

try {
String connString = getSimpleRequiredProperty(AZURE_CONNECTION_STRING_KEY, messageContext); // method to read the properties values (assigned in #1) from parent class 
String containerName = getSimpleRequiredProperty(CONTAINER_NAME_KEY, messageContext);
String blobName = getSimpleRequiredProperty(BLOB_NAME_KEY, messageContext);
String directoryName = getSimpleOptionalProperty(DIRECTORY_KEY , messageContext);
String verb = getSimpleRequiredProperty(VERB_NAME_KEY, messageContext);
String expiry = getSimpleRequiredProperty(EXPIRY_TIME_KEY, messageContext);
String ctype = getSimpleOptionalProperty(C_TYPE_KEY, messageContext);

BlobServiceClient client = new BlobServiceClientBuilder().connectionString(connString).buildClient();
BlobClient blobClient = client.getBlobContainerClient(containerName)
.getBlobClient(getBlobNameWithDirectory(directoryName,blobName));
BlobSasPermission blobSasPermission = new BlobSasPermission();
if(verb.equals("GET")) {
blobSasPermission.setReadPermission(true);
}
if(verb.equals("PUT")) {
blobSasPermission.setReadPermission(true).setWritePermission(true);
}
OffsetDateTime currentTime = OffsetDateTime.now();
OffsetDateTime expiryTime = getExpiry(messageContext, currentTime);
BlobServiceSasSignatureValues values = new BlobServiceSasSignatureValues(expiryTime, blobSasPermission)
.setStartTime(OffsetDateTime.now()).setContentDisposition(blobName);
String sasToken = blobClient.generateSas(values);
messageContext.setVariable(SIGNED_TOKEN_KEY, sasToken);
return ExecutionResult.SUCCESS;
} catch (Exception ex) {
ExecutionResult executionResult = new ExecutionResult(false, Action.ABORT);
//--Returns custom error message and header
executionResult.setErrorResponse(ex.getMessage());
executionResult.addErrorResponseHeader("ExceptionClass", ex.getClass().getName());

//setExceptionVariables(ex, messageContext);
//--Set flow variables -- may be useful for debugging.
messageContext.setVariable("JAVA_ERROR", ex.getMessage());
messageContext.setVariable("JAVA_STACKTRACE", exceptionStackTrace(ex));
return executionResult;//return ExecutionResult.ABORT;
}
}
}

Solved Solved
0 6 295
1 ACCEPTED SOLUTION

I agree with you that the callout I referenced above is not going to work for your purposes. 

The Blob SAS is structured a little differently. It still uses the same HMAC approach, but the structure of the signature is different. Starting from that callout, I built a different one that I hope will work. 

Assuming you have an access key, You can try this callout to generate a SAS URI with a signature:

https://github.com/DinoChiesa/Apigee-Java-Azure-Storage-Sas

You configure it like this: 

<JavaCallout name='Java-GenerateSas-1'>
  <Properties>
    <Property name="version">2015-04-05</Property>
    <Property name="key">{private.shared_access_key}</Property>
    <Property name="key-encoding">base64</Property>
    <Property name="permissions">r</Property>
    <Property name="resource-uri">https://myaccount.blob.core.windows.net/container/subdir/blob.jpg</Property>
    <Property name="expiry">1h</Property>
    <!-- optional IP address restriction -->
    <Property name="ip">172.134.12.11</Property>
  </Properties>
  <ClassName>com.google.apigee.callouts.azurestorage.SasCallout</ClassName>
  <ResourceURL>java://apigee-azure-storage-sas-callout-20220930.jar</ResourceURL>
</JavaCallout>

Check the README for more options. I am unable to test this directly because I do not have an Azure account. Good luck! 

View solution in original post

6 REPLIES 6

Hi

These error messages

"java.lang.RuntimePermission" "getenv.HTTP_PROXY"
Failed to execute JavaCallout. Could not initialize class com.azure.core.util.Configuration

...Indicate that the permissions for the Java callout within Apigee do not permit instantiation of the Configuration class, or one of its dependencies. One of them is trying to read an environment variable (getenv), specifically the HTTP_PROXY variable, and that is not permitted. Apigee restricts the permissions of what Java callouts can do. In particular, they cannot read system properties, they cannot read environment variables, and they cannot read filesystem files. I'm sure there are other restrictions - check the documentation.

Anyway the reason you are seeing the "could not initialize" is due to a permissions error.

If you are using a Google-hosted Apigee Edge, you cannot modify the permissions on Java callouts. 

In short: you cannot do that. You cannot run that code within a Java callout. Unless there is a way to tell the Azure classes to NOT look at environment variables, you probably will not be able to use the Azure SDK directly within a Java callout class.

There are some potential workarounds:

  • host your Java class in some external container, like Cloud Run or similar
  • build your own self-written Java code that does not try to read environment variables, and continue to use the Java callout.
  • Use Apigee hybrid, in which you have the ability to modify permissions for Java callouts.  **But getting this capability is not a good justification for using hybrid! Really you should have other good reasons for using hybrid, and the permissions flexibility is just a "side effect". 

If your goal is simply to generate a SAS Token.... there is an existing Java callout that does this without relying on the Azure SDK. https://github.com/DinoChiesa/Apigee-Java-AzureEventHubs-SasToken

It says "EventHubs" in the repo name, but the SAS token should work for other Azure services, too.  If it is somehow not adequate, if you need that callout to support additional features, let me know and I'll see if I can get it updated / enhanced. 

Thanks a lot Dino for quickly looking into this and sharing your valuable comments.

I have gone through the Apigee-Java-AzureEventHubs-SasToken reference, even this is similar to my use case, but I couldn't relate with my use case i.e. accessing the Azure container/blob using Sas Token with required sas permission(read/write/list/update/add). It will be great if you could share some code snippet to generate the Sas token using inputs like blob resource, Access key(Key1/key2), sas permission type , expiry time or container connection string using java code without relying on the Azure SDK.

Or If I can use the same generated sas token(geenrated in Apigee-Java-AzureEventHubs-SasToken ) to access my blob, then how can I use/call the endpoint using the token. 
 My blob resource : https://<account_name>.blob.core.windows.net/<container_name>/<Directory_name>/test.jpg

Thanks 
Pinaki

I agree with you that the callout I referenced above is not going to work for your purposes. 

The Blob SAS is structured a little differently. It still uses the same HMAC approach, but the structure of the signature is different. Starting from that callout, I built a different one that I hope will work. 

Assuming you have an access key, You can try this callout to generate a SAS URI with a signature:

https://github.com/DinoChiesa/Apigee-Java-Azure-Storage-Sas

You configure it like this: 

<JavaCallout name='Java-GenerateSas-1'>
  <Properties>
    <Property name="version">2015-04-05</Property>
    <Property name="key">{private.shared_access_key}</Property>
    <Property name="key-encoding">base64</Property>
    <Property name="permissions">r</Property>
    <Property name="resource-uri">https://myaccount.blob.core.windows.net/container/subdir/blob.jpg</Property>
    <Property name="expiry">1h</Property>
    <!-- optional IP address restriction -->
    <Property name="ip">172.134.12.11</Property>
  </Properties>
  <ClassName>com.google.apigee.callouts.azurestorage.SasCallout</ClassName>
  <ResourceURL>java://apigee-azure-storage-sas-callout-20220930.jar</ResourceURL>
</JavaCallout>

Check the README for more options. I am unable to test this directly because I do not have an Azure account. Good luck! 

WDYT, Pinaki?

Thanks a lot, I have verified with this and the solution worked for me with version : 2020-12-06 (test method : knownGood1()).

Again appreciate for the prompt reply!!!

I'm glad to be able to help you. Thanks for letting me know.