1
votes

I have serverless aws lambda that will get/put object onto encrypted S3 buckets via presigned urls. getObject works perfectly. putObject generates SignatureDoesNotMatch error once I encrypt the bucket and I cannot understand why. I have played with headers and such, but still cannot get it to work. Code / policies below:

lambda

    const generatepresignedurl = (req, res, callback) => {

    var fileurls = [];
    const body = JSON.parse(req.body);
    const theBucket = body['theBucket'];
    const theKey = body['theKey'];
    const theContentType = body['theContentType'];
    const theOperation = body['theOperation'];

    /*setting the presigned url expiry time in seconds, also check if user making the request is an authorized user
     for your app (this will be specific to your app’s auth mechanism so i am skipping it)*/
    const signedUrlExpireSeconds = 60 * 60;

    if (theOperation == 'getObject') {
        var params = {
            Bucket: theBucket,
            Key: theKey,
            Expires: signedUrlExpireSeconds
        };
    } else {
        var params = {
            Bucket: theBucket,
            Key: theKey,
            Expires: signedUrlExpireSeconds,
            ACL: 'bucket-owner-full-control',
            ContentType: theContentType,
            ServerSideEncryption: 'AES256'
        };
    }

    s3.getSignedUrl(theOperation, params, function (err, url) {
        if (err) {
            console.log('Error Getting Presigned URL from AWS S3');
            // callback(null, ({ success: false, message: 'Pre-Signed URL error', urls: fileurls }));
            callback(null, {error: err});
        }
        else {
            fileurls[0] = url;
            console.log('Presigned URL: ', fileurls[0]);
            callback(null, { success: true, message: 'AWS SDK S3 Pre-signed urls generated successfully.', urls: fileurls });
        }
    });

}

Calling code is here:

Generate PreSigned Url

Function callStandAloneAWSService(lambda As String, request As String, contentType As String) As String

    Dim httpserver As New MSXML2.XMLHTTP

    With httpserver

        Dim theURL As String
        theURL = AWS_WEBSERVICE_URL_DEV

        .Open "POST", theURL & lambda 'variable that contains generatepresignedurl

        .setRequestHeader "Content-type", contentType

        .send request

        Do: DoEvents: Loop Until .readyState = 4 'make sure we are ready to recieve response

        callStandAloneAWSService = .responseText

    End With

End Function

Upload to PreSigned URL (original question did not have serversidencryption header)

Function uploadToPreSignedURL(url As String, whichFile As String, contentType) As Boolean

    'code to create binaryFile

    Dim httpserver As New MSXML2.XMLHTTP

    With httpserver

        .Open "POST", url
        .setRequestHeader "Content-type", "text/plain" 'contentType
        .send binaryFile
        Do: DoEvents: Loop Until .readyState = 4 'make sure we are ready to recieve response

        If Len(.responseText) = 0 Then

            uploadToPreSignedURL = True

        Else

            'extract error message from xml and write to report mail

        End If

    End With

End Function

bucket policy

{
    "Version": "2012-10-17",
    "Id": "S3PolicyId1",
    "Statement": [
        {
            "Sid": "DenyIncorrectEncryptionHeader",
            "Effect": "Deny",
            "Principal": "*",
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::mybiggetybucket/*",
            "Condition": {
                "StringNotEquals": {
                    "s3:x-amz-server-side-encryption": "AES256"
                }
            }
        },
        {
            "Sid": "DenyUnEncryptedObjectUploads",
            "Effect": "Deny",
            "Principal": "*",
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::mybiggetybucket/*",
            "Condition": {
                "Null": {
                    "s3:x-amz-server-side-encryption": "true"
                }
            }
        }
    ]
}

FWIW, I can run this via aws cli and get it to work:

aws s3api put-object --bucket mybiggetybucket --key test.json --body package-lock.json --server-side-encryption "AES256"

1
Does your upload work if you temporarily suppress the S3 bucket policy?jarmod
@jarmod yes, it does.Scott Holtzman

1 Answers

2
votes

To quote the Server Side Encryption docs:

You can't enforce SSE-S3 encryption on objects that are uploaded using presigned URLs. You can specify server-side encryption only with the AWS Management Console or an HTTP request header. For more information, see Specifying Conditions in a Policy.

I was able to get something similar working. I needed to use PUT rather than POST, and I needed to supply x-amz-server-side-encryption:AES256 as a header, as follows:

const axios = require('axios');
const AWS = require('aws-sdk');

const s3 = new AWS.S3();

const params = {
  Bucket: 'mybucket',
  Key: 'myfolder/myfile.txt',
  Expires: 60 * 60,
  ACL: 'bucket-owner-full-control',
  ContentType: 'text/plain',
  ServerSideEncryption: 'AES256',
};

const axiosConfig = {
  headers: {
    'Content-Type': 'text/plain',
    'x-amz-server-side-encryption': 'AES256',
  },
};

const uploadTextFile = (presignedurl) => {
  axios.put(presignedurl, 'some text here', axiosConfig).then((res) => {
    console.log('File uploaded');
  }).catch((error) => {
    console.error(error);
  });
};

s3.getSignedUrl('putObject', params, (err, url) => {
  if (err) {
    console.error(err);
  } else {
    console.log('Pre-signed URL:', url);
    uploadTextFile(url);
  }
});