Currently, I have an issue by creating a valid signature v4 presigned url for a PUT request.
The urls are generated on the server side and are then provided to clients.
The clients should use the urls to upload a file over an API Gateway into an Amazon S3 bucket.
To authenticate the request API Gateway IAM authentication is used.
For my use case, a direct upload into an S3 bucket via "s3-presigned-url" is not possible.
The following code describes the generation of the presigned url and is written in Typescript. The generation of the signature v4 url is based on the AWS provided package #aws-sdk/signature-v4.
import { SignatureV4 } from "#aws-sdk/signature-v4";
import { Sha256 } from "#aws-crypto/sha256-js";
import { formatUrl } from "#aws-sdk/util-format-url";
const createSignedUrl = async (credentials: {
accessKeyId: string,
secretAccessKey: string,
sessionToken: string,
}, requestParams: {
method: "GET" | "PUT",
host: string,
protocol: string,
path: string,
}) => {
const sigv4 = new SignatureV4({
service: "execute-api",
region: process.env.AWS_REGION!,
credentials: {
accessKeyId: credentials.accessKeyId,
secretAccessKey: credentials.secretAccessKey,
sessionToken: credentials.sessionToken,
},
sha256: Sha256,
applyChecksum: false
});
const signedUrlRequest = await sigv4.presign({
method: requestParams.method,
hostname: requestParams.host,
path: requestParams.path,
protocol: requestParams.protocol,
headers: {
host: requestParams.host,
},
}, {
expiresIn: EXPIRES_IN,
});
const signedUrl = formatUrl(signedUrlRequest);
return signedUrl
};
I use Postman to test the presinged urls.
If I generate a presigned url for an GET request, everything works fine.
If I generate a presigned url for an PUT request and don't set a body in Postman for the PUT request, everything works fine. But I have an empty file in my bucket ;-(.
If I generate a presigned url for an PUT request and set a body in Postman (via Body -> binary -> [select file]), it fails!
Error message:
The request signature we calculated does not match the signature you provided. ...
The AWS documentation https://docs.aws.amazon.com/general/latest/gr/create-signed-request.html describes that the payload has to be hashed within the canonical request. But I don't have the payload at that time.
Is there also an UNSIGNED-PAYLOAD option if I want to generate an presigned url for a PUT request that is sent to an API Gateway, like described in the documentation for the AWS S3 service?
How do I configure the SignatureV4 object or the presign(...) method call to generate a valid PUT request url with UNSIGNED-PAYLOAD?
I was able to compare my generated canonical requests with the canonical request that is expected by the Amazon API Gateway.
The Amazon API Gateway always expects a hash of the payload no matter if I add the query param X-Amz-Content-Sha256=UNSIGNED-PAYLOAD to the url or not.
Thus the option "UNSIGNED-PAYLOAD" as canonical request hash value for API Gateway IAM Authentication is not possible, as would be possible with Amazon S3 service.
Related
I am trying to achieve redirection from cloudfront to API gateway based on the path.
I have my UI with a cloudfront distribution with source being S3 bucket hosted on for eg.
www.example.com
and it serves the requests.
I want the URLs with the pattern
www.example.com/share/*
to be redirected to API Gateway. The API Gateway has redirection internally where it points to other URLs.
If I use the API gateway endpoint directly in the browser, it fetches the expected result.
I am unsure of how to redirect from cloudfront to API Gateway. I have put cloudwatch logs on API Gateway and can see that cloudfront to API Gateway Redirection isn't working.
I have tried adding API Gateway as origin and add the same as a behaviour in cloudfront, but no success.
You can do redirect by using Lambda#Edge in CloudFront. AWS provides an example redirect function here.
Example below
'use strict';
exports.handler = (event, context, callback) => {
/*
* Generate HTTP redirect response with 302 status code and Location header.
*/
const response = {
status: '302',
statusDescription: 'Found',
headers: {
location: [{
key: 'Location',
value: 'http://URL/PATH',
}],
},
};
callback(null, response);
};
However it seems like perhaps rather than doing a redirect you should consider using a different a secondary origin in your CloudFront distribution to serve the API Gateway path if it is part of your application.
To do this you would need to add a custom domain to your API Gateway with the domain name of your site and then within CloudFront add an origin with a matching pattern of /share to forward to your API Gateway.
Take a read of this page for more information.
One way would be to use lambda#edge for a viewer request to return 302 HTTP code to the client with redirection url.
Generating an HTTP Redirect (Generated Response)
In python, such a lambda could look like (from docs):
def lambda_handler(event, context):
# logic to check the url and generate new url.
response = {
'status': '302',
'statusDescription': 'Found',
'headers': {
'location': [{
'key': 'Location',
'value': '<your-api-gateway-url>'
}]
}
}
return response
I'm trying to register a temporary quicksight user and generate an embed url to put in my React App. However, when calling the register user api I get a 403 error for the CORS preflight OPTIONS request:
Access to XMLHttpRequest at 'https://quicksight.ap-southeast-2.amazonaws.com/accounts//namespaces/default/users' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource."
I've also tried using us-east-1 as my region, but that also fails.
Users sign into my webapp with Cognito credentials. The identity pool has an associated IAM role, and I've attached a policy to that role giving access to register a new quicksight user and get the embed url. My webapp currently uses the aws-sdk library to assume the role through sts, and then make the subsequent quicksight calls.
The React app is hosted on Amplify
quicksightRegisterUser(data) {
var params = {
AwsAccountId: 'QQQ',
Email: 'XXX',
IdentityType: 'IAM' ,
Namespace: 'default',
UserRole: "READER",
IamArn: 'arn:aws:iam::YYY:role/ZZZ',
SessionName: 'XXX',
UserName:'XXX'
};
var quicksight = new QuickSight();
quicksight.registerUser(params, function (err, data1) {
if (err) {
console.log("err register user");
console.log(err);
} // an error occurred
else {
console.log("Register User1");
console.log(data1)
}
})
}
As #sideshowbarker mentioned, you can't call the Quicksight API from your webapp.
The solution I found was to set-up a Lambda to generate the Embedding URL, given the user's Cognito Username and password.
Full details of the solution, and a step-by-step tutorial, can be found here:
https://github.com/aws-samples/amazon-quicksight-embedding-sample
How do I upload a file to S3 with a signed URL?
I tried the following:
const AWS = require('aws-sdk');
const s3 = new AWS.S3({ accessKeyId: "", secretAccessKey: "" });
const url = s3.getSignedUrl("putObject", {
Bucket: "SomeBucketHere",
Key: "SomeNameHere",
ContentType: "binary/octet-stream",
Expires: 600
});
But when I try uploading with Postman using the following steps, I get the SignatureDoesNotMatch error.
PUT method with URL from the above code
Body: binary (radio button), choose file, select a file to upload
Hit Send
I can confirm that the IAM permissions are not the problem here. I have complete access to the Bucket.
What's wrong and how do I test my signed URL?
This issue once caused me a lot of pain to get around.
All I had to do was add a header to the Postman request.
Header: Content-Type = binary/octet-stream
Once I changed this, the file uploads successfully.
Hope this saves someone a lot of trouble down the road.
Not sure if it's of any help anymore, but I believe you need to pass in the signature version.
In python you have something like:
import boto3 as boto3
from botocore.client import Config
...
s3_client = boto3.client('s3', config=Config(signature_version='s3v4'))
url = s3_client.generate_presigned_url(
ClientMethod='put_object',
Params={
'Bucket': bucket,
'Key': key,
},
ExpiresIn=60
)
print url
So the equivalent in Javascript would be:
var AWS = require('aws-sdk');
var s3 = new AWS.S3({
signatureVersion: 'v4',
});
exports.handler = (event, context, callback) => {
const url = s3.getSignedUrl('putObject', {
Bucket: '**\[YOUR-S3-BUCKET\]**',
Key: 'mykey',
Expires: 10,
});
callback(null, url);
};
Sources:
Presigned URL for S3 Bucket Expires Before Specified Expiration Time
Using pre-signed URLs to upload a file to a private S3 bucket
I have setup a s3 bucket to host my website and setup cloudfront to point to my s3 bucket. All requests are sent to cloudfront will go to my s3 bucket. What I want to do is to setup a redirect rule only apply to one of the file in my s3 bucket. There is a javascript file /folderA/index.js in the s3. And I want to apply a rule to redirect the request on this file to another file which is /folderB/index.js. How can I set it up? Whether I need to set up on s3 bucket or cloudfront?
I don't think you can define a S3 redirect rule for a file. I believe it's at a bucket level.
You can copy /folderB/index.js content into /folderA/index.js so no redirect is needed.
Alternatively, you can setup a viewer request lambda#edge function that will redirect in case path equals /folderA/index.js
something like:
'use strict';
exports.handler = (event, context, callback) => {
/*
* Generate HTTP redirect response with 301 status code and Location header.
*/
const request = event.Records[0].cf.request;
const uri = request.uri;
if (ur.endsWith('/folderA/index.js`) {
const redirectTo = //where you want to redirect
console.log(`Redirecting ${uri} with to: ${redirectTo}`);
const response = {
status: '301',
statusDescription: 'Found',
headers: {
location: [{
key: 'Location',
value: redirectTo,
}],
},
};
callback(null, response);
} else {
callback(null, request);
}
};
I enabled access to unauthenticated identities to do some quick testing before integrating authentication. My configuration code is the following,
Amplify.configure({
Auth: {
identityPoolId: 'us-east-1:example',
region: 'us-east-1',
userPoolId: 'us-east-1_example',
userPoolWebClientId: 'us-east-1_example'
},
API: {
endpoints: [
{
name: "example-name",
endpoint: "https://example.execute-api.us-east-1.amazonaws.com/prod/example-path"
},
]
}
});
and my GET request code is the following,
example() {
const apiName = 'example-name';
const path = '/example-path';
API.get(apiName, path).then(response => {
console.log(response)
}).catch(error => {
console.log(error)
})
}
I followed everything on GitHub and my API gateway and Lambda functions are working correctly when I run a "test" and through postman. But on react-native it's giving me a 403 status code without any detailed explanation. Does this have to do with accessing using unauthenticated identity? Also, I used "example" in my code to hide my personal information, I typed in everything correctly since I'm not getting any syntax error (identity pool recognizes access every time I run it, but cloudWatch doesn't show any log of gateway access)
The Endpoint in Amplify.configure is the InvokeURL from API Gateway, you just need to include the stage (/prod in this case) and not the other routes. The other routes are just the path parameters for API.() calls.