Error in Service Callout AWS KMS API

We are doing a service callout to AWS KMS and below is our SC code

 

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ServiceCallout continueOnError="false" enabled="true" name="SC-AWSSign">
    <DisplayName>SC-AWSSign</DisplayName>
    <Properties/>
    <Request>
        <Set>
            <Headers>
                <Header name="X-Amz-Target">TrentService.Sign</Header>
                <Header name="Content-Type">application/x-amz-json-1.1</Header>
                <Header name="X-Amz-Content-Sha256">beaead3198f7da13ab969765e08b24fc913697e929e726aeaebf0eba3</Header>
                <Header name="X-Amz-Date">20220727T113413Z</Header>
                <Header name="Authorization">AWS4-HMAC-SHA256 Credential=AKIST2KDSDA6T5BN67L/20220727/us-east-1/kms/aws4_request, SignedHeaders=content-type;host;x-amz-content-sha256;x-amz-date;x-amz-target, Signature=3de8ab17d924b424222e502e51f474596eb8eb37a832b2bc7178f48d7b3271</Header>
            </Headers>
            <Verb>POST</Verb>
            <Payload contentType="application/json">
                {"KeyId":"23c29362-cd00-4c9c-803f-c1ee961be6c3"
                 "Message": "ZXlKaGJHY2lPaUFpVWxNeU5UWWlMQ0FpZEhsd0lqb2dJa3BYVkNKOS5leUpwWVhRaU9pQXhOalU0T0RJNE9EZ3dMQ0FpWlhod0lqb2dNVFkxT0Rnek1qUTRNQ3dnSW5OMVlpSTZJQ0l3YjJFeE9UbGxhR3Q0YzJRd1MxcEVhVEJvT0NJc0lDSnBjM01pT2lBaU1HOWhNVGs1WldocmVITmtNRFEZ2lMQ0FpWVhWa0lqb2dJbWgwZEhCek9pOHZiVzFqTG05cmRHRndjbVYyYVdWM0xtTnZiUzl2WVhWMGFESXZkakV2ZEc5clpXNGlMQ0FpYW5ScElqb2dJakJtWVdaaVltUTRMV05qTW1FdE5EbGlaaTFpT1ROakxURXdNMkZqT0dJM1pHTTROeUo5",
                "SigningAlgorithm": "RSASSA_PKCS1_V1_5_SHA_256"      
                }
            </Payload>
        </Set>
    </Request>
    <Response>awsresponse</Response>
    <HTTPTargetConnection>
        <Properties/>
        <URL>https://kms.us-east-1.amazonaws.com?Action=Sign</URL>
    </HTTPTargetConnection>
</ServiceCallout>

 

This code is giving below error.

{
"fault": {
"faultstring": "Execution of ServiceCallout SC-AWSSign failed. Reason: ResponseCode 403 is treated as error",
"detail": {
"errorcode": "steps.servicecallout.ExecutionFailed"
}
}
}

Is this because we are using https and AWS is rejecting the request or is there a firewall issue which is causing this problem ?

Just to add we are using apigee hybrid

 

 

 

Solved Solved
0 7 421
2 ACCEPTED SOLUTIONS

With your modifications, it looks right to me. If I were diagnosing this I would want to print out awsresponse.content . You said it is not visible in trace by default, and I understand what you mean. you could add a policy to explicitly show it in trace, like this: 

<AssignMessage name='AM-Diagnostics'>
  <AssignVariable>
    <Name>diagnostics</Name>
    <Ref>awsresponse.content</Ref>
  </AssignVariable> 
</AssignMessage> 

View solution in original post

You are able to see the signature, and it is correct?

View solution in original post

7 REPLIES 7

I am not an AWS expert but...

I think the 403 is from Amazon, telling you "that call is not authorized."  In my experience, that can be returned for multiple reasons, and you need to look in the payload of the response from the AWS endpoint to figure out the specific reason.  The payload you showed is from Apigee.  You need to look into the payload from AWS for the explanation.  A typical reason is that the signature does not validate. When the signature you provide in the request does not match the request you are making, the AWS endpoint will return 403 and an appropriate message in the payload.

AWS v4 Signatures can be tricky to get right.  I notice that you showed a hard-coded ServiceCallout policy, which specific values in the configuration for the Payload, the Content-Sha256, and the Authorization header.  how did you compute these values? Where did you get them?

As one consideration, the Content-Sha256 is a digest on the Payload.  The payload you showed as hard-coded in this policy configuration has indentation and spacing.  When I compute the SHA256 of the payload as you have pasted it in above, I get 

54ce14b18187a00265ef75a5ee8fd69d53a5c3c97a0ed2b447377a2cb2f36941

...which differs from the content-sha256 value shown in your policy configuration. Why? 

Depending on how you format the payload, how much indentation, whether there are spaces between the colons and the values, and so on... the SHA256 will be different. Even if the JSON is semantically the same, with all those appearance-only differences, the SHA256 will be different. And the AWS endpoint will check the digest, and if it finds that the SHA256 claimed in the header does not match the SHA256 it computes on the actual content you pass, then the signature validation will fail. You will see a 403. 

It's tricky to get it just right.  Hard-coding the sha256... is fragile and not likely to not result in success. 

As another example, the x-amz-date header needs to be "now", at the time you send the request. You cannot copy a AWS v4 signature that worked 20 minutes ago and expect it to work if you send the request "now."  AWS will reject it as stale.  (I believe the maximum allowed timespan between x-amz-date and "now" is 15 minutes, not sure if that is bi-directional)

So hard-coding things is troublesome and hard to get right.  There is a Java callout that helps compute the right signature and headers for AWS v4 signing.  It's available here. Given a Message object containing a payload like the one shown in your ServiceCallout policy, this callout will 

  • compute the content-sha256 correctly and insert that header into the message
  • format and insert the correct x-amz-date header into the message
  • set the host header
  • compute the signature and set the correctly formatted Authorization header using AWS4-HMAC-SHA256

After that, you can use a ServiceCallout policy that references that message, to connect to an endpoint like the AWS KMS endpoint.  

Check the readme for examples. 

If you choose to use this callout, your flow in Apigee will be something like this: 

 

 

 

        <Step>
          <!-- create the message and set the payload + any static headers you need -->
          <Name>AM-Construct-Outgoing-KMS-Message</Name>
        </Step>
        <Step>
          <!-- insert the headers required for AWS v4 signature -->
          <Name>JC-AWSSignV4-Outgoing-KMS-Message</Name>
        </Step>
        <Step>
          <!-- send the message with the computed headers -->
          <Name>SC-Send-KMS-Message</Name>
        </Step>

 

 

 

 

Thanks for the detailed explanation. The request is working via POSTMAN but fails when I tried in SC. I am not sure if I have to add autogenerated header from POSTMAN.

The values in the SC code which I have pasted in this thread are bit modified for security reasons and hence when you compute you might not getting correct values.

Authorization header is auto derived from the access and secret key which I have entered in authorization tab in POSTMAN where type of authorization is AWS Signature.

The only thing which we have to figure out is why it works via POSTMAN and not via SC

 

Hi

I tried using the steps you have mentioned in the thread.
Created AM, JC and then SC
Now I am getting ResponseCode 400 error

The AWS API document which you have shared has explanation for this error as either of these
InvalidParameterValue
InvalidQueryParameter
MissingAction
MissingParameter

I have checked these and added all required parameter. The document also states that only 'Version' parameter is mandatory which I have already added.

Below are my policies. Do you think if something is wrong in AM , JC or SC

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<AssignMessage name="AM-Construct-Outgoing-AWS-Message">
    <AssignTo createNew="true" type="request">outgoingAwsMessage</AssignTo>
    <Set>
        <Headers>
            <!-- per https://docs.aws.amazon.com/kms/latest/APIReference/kms-api-reference.pdf -->
            <Header name="X-Amz-Target">TrentService.Sign</Header>
        </Headers>
        <QueryParams>
            <QueryParam name="Action">Sign</QueryParam>
            <QueryParam name="Version">2014-11-01</QueryParam>
        </QueryParams>
        <Verb>POST</Verb>
        <Payload contentType="application/x-amz-json-1.1">{
      "KeyId": "23c29362-cd00-4c9c-803f-c1ee961be6c3"
      "Message": "ZXlKaGJHY2lPaUpTVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0o5LmV5SnBZWFFpT2pFMk5UZzVNRFV5TURBc0ltVjRjQ0k2TVRZMU9Ea3dPRGMwTUN3aWMzVmlJam9pTUc5aE1UazVaV2hyZUhOa01FdGFSR2t3YURnaUxDSnBjM01pT2lJd2IyRXhPVGxsYUd0NGMyUXdTMXBFYVRCb09DSXNJbUYxWkNJNkltaDBkSEJ6T2k4dmJXMWpMbTlyZEdGd2NtVjJhV1YzTG1OdmJTOXZZWFYwYURJdmRqRXZkRzlyWlc0aUxDSnFkR2tpT2lJd1ptRm1ZbUprT0Mxall6SmhMVFE1WW1ZdFlqa3pZeTB4TUROaFl6aGlOMlJqT0RjaWZR",
      "SigningAlgorithm": "RSASSA_PKCS1_V1_5_SHA_256"
  }</Payload>
    </Set>
</AssignMessage>




<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<JavaCallout name="JC-AWSSignV4">
    <Properties>
        <Property name="service">kms</Property>
        <Property name="endpoint">https://kms.us-east-1.amazonaws.com</Property>
        <Property name="region">us-east-1</Property>
        <Property name="key">AKIAYT2KDLPA6T5BN67L</Property>
        <Property name="secret">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</Property>
        <Property name="source">outgoingAwsMessage</Property>
        <Property name="sign-content-sha256">true</Property>
    </Properties>
    <ClassName>com.google.apigee.callouts.AWSV4Signature</ClassName>
    <ResourceURL>java://apigee-callout-awsv4sig-20210609.jar</ResourceURL>
</JavaCallout>

----------

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ServiceCallout name="SC-Send-AWS-Message">
    <Request variable="outgoingAwsMessage"/>
    <Response>awsresponse</Response>
    <HTTPTargetConnection>
        <URL>https://kms.us-east-1.amazonaws.com?Action=Sign</URL>
    </HTTPTargetConnection>
</ServiceCallout>

 

I think after making minor changes it worked. Following changes I made in AM and SC

Added QueryParam in AM

<QueryParams>
 <QueryParam name="Version">2014-11-01</QueryParam>
 <QueryParam name="Action">Sign</QueryParam>
</QueryParams>

And then removed queryparam from HTTPTargetConnection URL

<HTTPTargetConnection>
  <URL>https://kms.us-east-1.amazonaws.com</URL>
</HTTPTargetConnection>

Now I get successful 200 response but the signature is not getting generated as I get in Postman.  The SC response is not visible in trace. Also created below EV to get signature value but that variable does not get printed in trace.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ExtractVariables continueOnError="false" enabled="true" name="EV-Sign">
 <DisplayName>EV-Sign</DisplayName>
 <Properties/>
 <JSONPayload>
 <Variable name="aws_signature">
 <JSONPath>$.Signature</JSONPath>
 </Variable>
 </JSONPayload>
 <Source clearPayload="false">awsresponse</Source>
</ExtractVariables>

The response which I get in POSTMAN is

{
"KeyId": "arn:aws:kms:us-east-1:592324025281:key/23c29362-cd00-4c9c-803f-c1ee961be6c3",
"KeyOrigin": "AWS_KMS",
"Signature": "R8wdye5KKPSO2zMBl/Ybw1FsC/YryVPBbMTkrK0bLt+2Q4a9VbCNb+yeDZPyawhgH8+sWw8o8bkvKM4LSNeTIR22h4HqzMJ5w2oAvcRuaJp2X2pKFSESar21A1ULdbC2IRV7CzLqS19ERl3ksyVlYwQiJ3paDCnk5i2wWP5u/og7pqf3ExpZJPLBIBG13mtWDqMRkbsTWAsZQDmuOsGrXgKmAgnEn3WCx9TnBObFkX3W4oFX/9/j/OEexoWMUsGDQ4ReFKaEL7qM1GQpFP/saxf8W7e/1/J4HyZwhT0oivG2210+3hAjgnM9rNHrejqo4vaMg5rwCeMhLbpCHgOxAA==",
"SigningAlgorithm": "RSASSA_PKCS1_V1_5_SHA_256"
}

 

 

With your modifications, it looks right to me. If I were diagnosing this I would want to print out awsresponse.content . You said it is not visible in trace by default, and I understand what you mean. you could add a policy to explicitly show it in trace, like this: 

<AssignMessage name='AM-Diagnostics'>
  <AssignVariable>
    <Name>diagnostics</Name>
    <Ref>awsresponse.content</Ref>
  </AssignVariable> 
</AssignMessage> 

You are able to see the signature, and it is correct?

yes I am getting the signature which is corrcet.