0

I want to upload image files to AWS s3 bucket by using pre-signed URLs, But I'm getting an error shown in the screen shot, I've followed the post from this page s3 direct file upload, I would like to know what mistake I'm making and also I want to know whether this is server side issue or there should I use some different approach for making put request to 'pre-signed' URL, thanks ahead.

Error I'm Getting on Postman

My serverless.yml

service: my-service-api

provider:
  name: aws
  runtime: nodejs4.3
  stage:  dev
  region: us-east-1
  iamRoleStatements:
    - Effect: "Allow"
      Action:
        - "dynamodb:*"        
      Resource: "*"
    - Effect: "Allow"
      Action:
        - "s3:*"        
      Resource: "arn:aws:s3:::profile-images/*"   

custom:
  globalResultTtlInSeconds: 1

package:
  individually: true
  include:
    - node_modules/mysql/**
    - node_modules/bluebird/**
    - node_modules/joi/**
  exclude:
    - .git/**
    - .bin/**
    - tmp/**
    - api/**
    - node_modules/**
    - utils/**
    - package.json
    - npm_link.sh
    - templates.yml

functions:
  profiles:
    handler: api/profiles/handler.profiles
    events:
      - http:
          method: POST
          path: api/profiles/uploadURL
          cors: true
          integration: lambda
          request: ${file(./templates.yml):request}  
          authorizer: 
            arn: arn:aws:lambda:us-east-1:000000000000:function:customAuthorizer
            resultTtlInSeconds: ${self:custom.globalResultTtlInSeconds}
            identitySource: method.request.header.Authorization
    package:
      include:
        - api/profiles/**
        - node_modules/node-uuid/**
        - node_modules/jsonwebtoken/**
        - node_modules/rand-token/**          

resources:
  Resources:
    UploadBucket:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: profile-images
        AccessControl: PublicRead
        CorsConfiguration:
          CorsRules:
          - AllowedMethods:
            - GET
            - PUT
            - POST
            - HEAD
            AllowedOrigins:
            - "*"
            AllowedHeaders:
            - "*"   
    IamPolicyInvokeLambdaFunction:
      Type: AWS::IAM::Policy     
      Properties: 
        PolicyName: "lambda-invoke-function"
        Roles:
          - {"Ref" : "IamRoleLambdaExecution"}
        PolicyDocument:
          Version: '2012-10-17'
          Statement:            
              - Effect: Allow
                Action: 
                  - "lambda:InvokeFunction"
                Resource: "*"

My handler file

var s3Params = {
                Bucket: 'profile-images',
                Key: image.name,
                ACL: 'public-read'
            };

 s3.getSignedUrl('putObject', s3Params, function (err, url){
       if(err){
          console.log('presignedURL err:',err);
          context.succeed({error: err});
       }
       else{
          console.log('presignedURL: ',url);
          context.succeed({uploadURL: url});                                           
       }                    
  });
Mark B
  • 183,023
  • 24
  • 297
  • 295
Balkrishna
  • 137
  • 1
  • 8
  • 1
    Why do you need a signed url for uploading the image in lambda function. Alternatively you can have the role under which your Lambda function runs to have access to the S3 bucket and that should be enough – Rajesh Dec 30 '16 at 09:40
  • Thanks @Rajesh, Can you provide me with some example for doing that ? that will be helpful for me to understand – Balkrishna Dec 30 '16 at 10:24
  • @Rajesh check the linked blog post. The objective here appears to be a drag/drop/upload to S3 from a web page, using a pre-signed URL generated by the "server" which is actually a synchronous Lambda function. Uploading the file from Lambda isn't applicable. – Michael - sqlbot Dec 30 '16 at 12:20
  • 1
    @Balkrishna selecting `form-data` in the test tool and using `multupart/form-data` for the Content-Type doesn't make sense for a `PUT`. You want a raw upload and you need the `Content-Type` set to the real MIME type of the object being uploaded. I'm not sure if if this is the only problem, but is definitely not going to work. – Michael - sqlbot Dec 30 '16 at 12:23
  • @Balkrishna A signed URL also has a time validity. Can you make sure that your function uploads the image to S3 within the timeframe. Ex: If the presigned URL is valid for 30 seconds and your image upload starts after 30 seconds you might get an invalid signature exception. – Rajesh Dec 30 '16 at 15:38
  • Thanks Rajesh and Michael for suggestions, After more investigation about this issue I came to know that there is need of setting headers like 'Content-Type': 'image/whatever' and 'x-amz-acl': 'public-read' before making PUT request. Also @Michael-sqlbot your trick of sending raw file worked! – Balkrishna Dec 31 '16 at 07:57

1 Answers1

1

After spending more time on this issue, I realized that this was not problem on server side but the problem was in making request. I needed to set headers for my PUT request because when AWS s3 receives any request it checks the signature of that request versus the headers so if you are setting 'ContentType' or 'ACL' while creating preSignedURL then you have to provide the 'Content-Type' and 'x-amz-acl' in your request.

This is my updated 's3Params'

var s3Params = {
                Bucket: 'profile-images',
                Key: image.name,
                ACL: 'public-read',
                ContentType: image.type
            };

And this is my request Updated request headers screenshot

Lastly I got some help from this post set headers for presigned PUT s3 requests

Community
  • 1
  • 1
Balkrishna
  • 137
  • 1
  • 8