Архитектурный и дизайнерский вопрос о загрузке фотографий с iPhone-приложения и S3

Я хочу разрешить пользователям приложения iPhone загружать фотографии и использовать Amazon S3. Есть два способа, которые я вижу в этом:

  • Загрузка с iPhone на мой сервер, который затем проксирует его на Amazon S3.
  • Загрузка с iPhone прямо на S3

Для варианта 1 безопасность проста. Я никогда не должен рассказывать iPhone свой секрет S3. Даунсайд - это то, что все проксируется через наш сервер для загрузки, какие поражения идут в S3.

Для варианта 2, теоретически это лучше, но проблема заключается в том, как вы разрешаете iPhone (или любое мобильное приложение на другой платформе) записывать в мое ведро S3, не предоставляя ему мой секрет? Кроме того, я не уверен, что это хороший дизайн или нет, потому что поток будет: iphone загружается на S3, получает URL-адрес, а затем сообщает серверу, что URL-адрес, поэтому он может добавить его в нашу базу данных для ссылки в будущее. Однако, поскольку связь разделена на две части (iphone- > S3 vs iPhone- > My-Server), она оставляет ее хрупкой как неатомную операцию.

Я нашел более старую информацию, ссылаясь на Загрузка с помощью браузера с использованием POST, но не уверен, что это по-прежнему рекомендуемый подход. Я надеюсь на лучшее решение, где мы можем просто использовать API REST, а не полагаться на POST. Я также вижу AWS iOS Beta SDK, но их документы не помогли, и я нашел статья Amazon, которая была одинаково бесполезной, поскольку она предупреждает вас о том, что не делать, но не говорит вам об альтернативном подходе:

Мобильные AWS SDK подписывают API запросы, отправленные в Amazon Web Services (AWS), чтобы подтвердить личность учетной записи AWS, запрос. В противном случае, злонамеренный разработчик может легко делать запросы в другую инфраструктуру разработчика. Запросы подписываются с использованием AWS Идентификатор ключа доступа и секретный ключ доступа что обеспечивает AWS. Секретный доступ Ключ похож на пароль, и он крайне важно хранить секрет.

Совет. Вы можете просмотреть все свои AWS учетные данные безопасности, включая Access Идентификатор ключа и секретный ключ доступа на Веб-сайт AWS по адресу http://aws.amazon.com/security-credentials.

Включение учетных данных в исходный код является проблематичным для программного обеспечения, в том числе мобильных приложений, поскольку вредоносные пользователи могут декомпилировать программное обеспечение или просмотреть исходный код для извлечения Секретный ключ доступа.

Есть ли у кого-нибудь советы по наилучшему архитектурному дизайну и потоку?

Обновление: Чем больше я вникаю в это, кажется, что куча поплов рекомендует использовать метод HTTP POST с файлом политики json, как описано здесь: http://docs.amazonwebservices.com/AmazonS3/2006-03-01/dev/index.html?UsingHTTPPOST.html.

С этим потоком будет что-то вроде (1) iPhone делает запрос на мой сервер, запрашивая файл политики (2), сервер генерирует файл политики json и возвращает клиента (3) iPhone делает HTTP POST из фотографии + json политики для S3. Ненавижу, что я использую HTTP POST по-видимому kludgy, но он кажется лучше, потому что он устраняет необходимость моего сервера для хранения фотографии вообще.

Ответ 1

Я уже обсуждал этот вопрос > AWS Identity and Access Management для создания временных, привилегии для каждого пользователя. Сервис отличный, но он все еще находится в бета-версии, и пока он не является частью мобильного SDK. У меня есть чувство, когда эта штука выпущена навсегда, вы сразу увидите ее на мобильном SDK.

До тех пор генерирует назначенные URL для ваших пользователей или прокси-сервера через ваш собственный сервер, как это предлагали другие. Назначенный URL-адрес позволит вашим пользователям временно GET или PUT на объект S3 в одном из ваших ковшей без фактического наличия ваших учетных данных (они хэшируются в подпись). Подробнее о здесь.

EDIT. Я применил правильное решение этой проблемы, используя бета-версию предварительного просмотра IAM. Это доступно на GitHub, и вы можете прочитать об этом здесь.

Ответ 2

Вы можете загружать прямо с вашего iPhone на S3 с помощью REST API и иметь ответственность за создание части заголовка Authorization, требующего секретного ключа. Таким образом, вы не рискуете подвергать ключ доступа всем, у кого есть взломанный iPhone, в то время как вы не ставите бремя загрузки файла на сервер. Точные данные запроса можно найти в разделе "Пример объекта PUT" в разделе "Подписание и проверка запросов REST" . я настоятельно рекомендую прочитать этот документ, прежде чем продолжить.

Следующий код, написанный на Python, генерирует часть значения заголовка Authorization, полученного из вашего секретного ключа S3. Вы должны заменить свой секретный ключ доступа и имя ведра в форме виртуального хоста для _S3_SECRET и _S3_BUCKET_NAME ниже, соответственно:

import base64
from datetime import datetime
import hmac
import sha

# Replace these values.
_S3_SECRET = "my-s3-secret"
_S3_BUCKET_NAME = "my-bucket-name"

def get_upload_header_values(content_type, filename): 
  now = datetime.utcnow()
  date_string = now.strftime("%a, %d %b %Y %H:%M:%S +0000")
  full_pathname = '/%s/%s' % (_S3_BUCKET_NAME, filename)
  string_to_sign = "PUT\n\n%s\n%s\n%s" % (
      content_type, date_string, full_pathname)
  h = hmac.new(_S3_SECRET, string_to_sign, sha)
  auth_string = base64.encodestring(h.digest()).strip()
  return (date_string, auth_string)

Вызов с именем файла foo.txt и типом контента text/plain дает:

>>> get_upload_header_values('text/plain', 'foo.txt')
('Wed, 06 Feb 2013 00:57:45 +0000', 'EUSj3g70aEsEqSyPT/GojZmY8eI=')

Обратите внимание, что если вы запустите этот код, время, которое будет возвращено, будет другим, и поэтому кодированный HMAC-дайджест будет отличаться.

Теперь клиент iPhone просто должен выдать запрос PUT на S3, используя возвращенную дату и дайджест HMAC. Предполагая, что

  • сервер возвращает date_string и auth_string выше в некотором объекте JSON с именем serverJson
  • Ваш ключ доступа S3 (а не ваш секрет, который находится только на сервере) называется kS3AccessKey
  • Ваше имя байта S3 (установлено в my-bucket-name выше) называется kS3BucketName
  • содержимое файла сортируется в объекте NSData с именем data
  • имя файла, отправленного на сервер, представляет собой строку с именем filename
  • тип контента, который был отправлен на сервер, представляет собой строку с именем contentType

Затем вы можете сделать следующее, чтобы создать NSURLRequest:

NSString *serverDate = [serverJson objectForKey:@"date"]
NSString *serverHmacDigest = [serverJson objectForKey:@"hmacDigest"]

// Create the headers.
NSMutableDictionary *headers = [[NSMutableDictionary alloc] init];
[headers setObject:contentType forKey:@"Content-Type"];
NSString *host = [NSString stringWithFormat:@"%@.s3.amazonaws.com", kS3BucketName]
[headers setObject:host forKey:@"Host"];
[headers setObject:serverDate forKey:@"Date"];
NSString *authorization = [NSString stringWithFormat:@"AWS %@:%@", kS3AccessKey, serverHmacDigest];
[headers setObject:authorization forKey:@"Authorization"];

// Create the request.
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setAllHTTPHeaderFields:headers];
[request setHTTPBody:data];
[request setHTTPMethod:@"PUT"];
NSString *postUrl = [NSString stringWithFormat:@"http://%@.s3.amazonaws.com/%@", kS3BucketName, filename];
[request setURL:[NSURL URLWithString:postUrl]];

Затем вы можете отправить запрос. Если вы используете отличную библиотеку AFNetworking, вы можете обернуть request в объект AFXMLRequestOperation, используя XMLDocumentRequestOperationWithRequest:success:failure:, а затем вызывает его метод start. Не забудьте выпустить headers и request, когда это будет сделано.

Обратите внимание, что клиент получил значение заголовка Date с сервера. Это связано с тем, что, как описывает Amazon в "Требование времени печати" :

"Действительная метка времени (с использованием заголовка HTTP-даты или альтернативы даты x-amz) является обязательной для аутентифицированных запросов. Кроме того, временная метка клиента, включенная в аутентифицированный запрос, должна находиться в пределах 15 минут от Amazon S3, когда запрос получен. Если нет, запрос будет терпеть неудачу с кодом состояния ошибки RequestTimeTooSkewed."

Поэтому вместо того, чтобы полагаться на клиента, имеющего правильное время для успешного выполнения запроса, полагайтесь на сервер, который должен использовать NTP (и демона, например ntpd).

Ответ 3

Загрузите на свой сервер, а затем отправьте сообщение на S3. С точки зрения архитектуры вы захотите сделать это со своего сервера. Есть много вещей, которые могут пойти не так во время передачи данных, с которой вы можете справиться лучше на сервере, и если вы хотите хранить любые данные о изображении, отправляемом на S3, в любом случае, вероятно, у вас будет вызов на стороне сервера.

Кроме того, секретный ключ доступа хранится в более безопасной среде. Ваш собственный.

Если вас беспокоит масштабируемость, и вы собираетесь делать значительное количество передач S3, я бы рассмотрел возможность размещения вашего сервера и экземпляр EC2. Передача данных между ними бесплатна (если вы храните данные в следующих центрах обработки данных).

Плата за передачу данных не передается между Amazon EC2 и Amazon S3 в том же регионе или для данных, передаваемых между регионом Amazon EC2 в Северной Вирджинии и стандартным регионом США Amazon S3. Amazon Simple Storage Service (Amazon S3)

Есть много сообщений здесь о SO Amazon - EC2 стоимость? (пример) о плюсах и минусах использования EC2.

Ответ 4

Я запутался. Почему бы amazon придумать w/ios sdk для загрузки данных на s3, тогда скажите нам не использовать его (вложение учетных данных в исходный код проблематично для программного обеспечения, включая мобильные приложения, поскольку вредоносные пользователи могут декомпилировать программное обеспечение или просмотреть источник код для извлечения секретного ключа доступа)

Ответ 5

они могли бы предоставить sdk для этой цели, чтобы приложение могло разрешить аутентификацию для отдельных учетных записей s3? например, приложение, которое позволяет пользователям хранить файлы в своем собственном (пользовательском) ведро вместо поставщика? я чувствую недостаток безопасности при слиянии ключей с приложением и его распространении. любой может (неправильно) использовать их, как только ключи будут раскрыты в любом случае (его никогда не защищают, когда вы его выдаете). с другой стороны, сохранение функциональности, зарезервированной для сервера, будет держать ваши ключи прозрачными для пользователя, не так ли?