6

Our mobile app will be uploading images to AWS S3. The question is whether to do one of the following options:

  1. Upload the image to our APIs server, then our APIs server uploads the image to S3
    Pros: More secure, as the S3 credentials is only stored at cloud.
    Cons: More pressure on APIs server, as thousands of users will be uploading images, with sizes varying from 2 MB to 10 MB

  2. Let the mobile app upload the image directly to S3, by getting temp S3 credentials from APIs server for each S3 access.
    Pros: Less pressure on API's server, no files will be uploaded to the server.
    Cons: Less secure, as S3 credentials will be exposed to the mobile, regardless the fact, that the mobile app will ask for temp credentials each time to access S3, and that it gets the credential through SSL connection with the APIs server.

So, is option two above still better? As we are already granting temp credentials that is valid for only 15 minutes each time to access S3 through SSL connection.

What is the recommended way to do this?

Anders
  • 64,406
  • 24
  • 178
  • 215
Samir Sabri
  • 163
  • 5
  • What is your API authentication protocol in place? If you have an existing API authentication mechanism in place then extending API server capability to proxy incoming calls to s3 API calls (without storing it locally) may be possible unless you have very low response time requirement. – jhash Mar 08 '17 at 23:48
  • Yes, I gave it another thought – Samir Sabri Jan 24 '20 at 14:21

3 Answers3

5

The preferred way to go is to generate a presigned POST request (your backend server asks for it with your own admin credentials).

Then from client-side you upload using this pre-signed POST.

It's effectively a way to have temporary credentials, but much more easy & secure to deploy as it can be restricted to the exact file you need to have uploaded.

Here is the link if you use the Ruby SDK (https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/PresignedPost.html) and you will easily find that for the SDK you use.

Edit: As per comment, link to official AWS doc: https://docs.aws.amazon.com/AmazonS3/latest/dev/PresignedUrlUploadObject.html

Qortex
  • 321
  • 2
  • 9
  • 1
    This is really the only correct answer. You don't want to give away credentials, and with pre-signed upload URLs you don't have to. Here is the documentation for this feature, which might be helpful: https://docs.aws.amazon.com/AmazonS3/latest/dev/PresignedUrlUploadObject.html – Conor Mancone Jan 13 '20 at 15:56
  • Thanks, added the AWS doc link – Qortex Jan 13 '20 at 15:59
  • 2
    Note that the code that generates pre-signed URLs just performs HMAC using the credentials stored on the server, it does not need to perform requests to AWS servers, it just needs the access key id, secret access key and some code (there are implementations in bash, perl, php, python, java, C#, etc). It's very quick, can't fail, secure, etc. – Z.T. Jan 13 '20 at 17:50
-1

I'm not sure that there's a recommended practice, but I think it instead has to do with the requirements of the application and balancing the pros and cons.

Would it be a low server load? Ok, we can use the server to work with S3, it's obviously the preferred method (and it also keeps the interaction on the same API as the rest of your app service).

Would it be too heavy a load for the server? Ok, alternatives are to scale the server resources, or to see if the risk of having the app work directly with S3 is acceptable.

Is the information sensitive? Can we reasonably secure it if the app were to communicated directly with S3? If it's not highly sensitive and it can be reasonably secured and the risk is less than the cost of scaling the server to handle the loads, then this may be your choice.

The information is highly sensitive and terrible things would happen if the S3 key were sniffed out of SSL or from the device? In that case, it might be worth spending the money on scaling the server architecture.

I don't think there's any one single recommended way... naturally it's more secure to handle S3 from your server only (and you ask this in a security forum, so I assume that's your main focus), but all in all it becomes a balance driven by the requirements of the application.

jleach
  • 106
  • 4
  • In this case there really is a single recommended way: signed upload URLs. You really shouldn't ever send credentials to a client, and all the major cloud providers have options in place so you never have to. – Conor Mancone Jan 13 '20 at 15:57
-1

A good way to implement it is generate temporary credentials that can only upload to a specific bucket portion (the one that the user has access).

Even in the event those credentials get stolen from memory (I suppose you use https for every comunication, so they cannot be stolen during transmission), a final user can only use them for what they were intended: uploading files.

An example of this is presented here

{
   "Version":"2012-10-17",
   "Statement":[
      {
         "Effect":"Allow",
         "Action":"s3:PutObject",
         "Resource":"arn:aws:s3:::my_corporate_bucket/uploads/widgetco/*"
      },
      {
         "Effect":"Deny",
         "NotAction":"s3:PutObject",
         "Resource":"arn:aws:s3:::my_corporate_bucket/uploads/widgetco/*"
      },
      {
         "Effect":"Deny",
         "Action":"s3:*",
         "NotResource":"arn:aws:s3:::my_corporate_bucket/uploads/widgetco/*"
      }
   ]
}

I'd suggest you also periodically check all files in your bucket by verifying allowed extensions/file signatures (be careful, this may become really time consuming). You could also rescrict your policy more, based on file extensions (source here)

{
  "Id": "Policy1464968545158",
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "Stmt1464968483619",
      "Action": [
        "s3:PutObject"
      ],
      "Effect": "Allow",
      "Resource": "arn:aws:s3:::<yourbucket>/*.jpg",
      "Principal": "*"
    },
    {
      "Sid": "Stmt1464968543787",
      "Action": [
        "s3:PutObject"
      ],
      "Effect": "Allow",
      "Resource": "arn:aws:s3:::<yourbucket>/*.png",
      "Principal": "*"
    }
  ]
}
BgrWorker
  • 1,941
  • 1
  • 10
  • 17