Я реализую прямую загрузку файлов с клиентской машины на Amazon S3 через REST API, используя только JavaScript, без какого-либо кода на стороне сервера. Все работает хорошо, но одна вещь меня беспокоит...
Когда я отправляю запрос к Amazon S3 REST API, мне нужно подписать запрос и поместить подпись в заголовок Authentication
. Чтобы создать подпись, я должен использовать свой секретный ключ. Но все происходит на стороне клиента, поэтому секретный ключ может быть легко раскрыт из источника страницы (даже если я обфусцирую/шифрую свои источники).
Как я могу с этим справиться? И является ли это проблемой вообще? Может быть, я могу ограничить использование определенного секретного ключа только вызовами REST API из определенного CORS Origin и только методами PUT и POST или, может быть, привязать ключ только к S3 и определенному bucket? Может быть, есть другие методы аутентификации?
"Бессерверное" решение идеально, но я могу рассмотреть возможность вовлечения некоторой обработки на стороне сервера, исключая загрузку файла на мой сервер и последующую отправку в S3.
Я думаю, то, что вам нужно, - это Browser-Based Uploads Using POST.
По сути, вам нужен код на стороне сервера, но все, что он делает, это генерирует подписанные политики. Как только код на стороне клиента получает подписанную политику, он может загружать данные с помощью POST непосредственно на S3 без прохождения данных через ваш сервер.
Вот официальные ссылки на документацию:
Диаграмма: http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingHTTPPOST.html
Пример кода: http://docs.aws.amazon.com/AmazonS3/latest/dev/HTTPPOSTExamples.html
Подписанная политика будет размещена в html в таком виде:
<html>
<head>
...
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
...
</head>
<body>
...
<form action="http://johnsmith.s3.amazonaws.com/" method="post" enctype="multipart/form-data">
Key to upload: <input type="input" name="key" value="user/eric/" /><br />
<input type="hidden" name="acl" value="public-read" />
<input type="hidden" name="success_action_redirect" value="http://johnsmith.s3.amazonaws.com/successful_upload.html" />
Content-Type: <input type="input" name="Content-Type" value="image/jpeg" /><br />
<input type="hidden" name="x-amz-meta-uuid" value="14365123651274" />
Tags for File: <input type="input" name="x-amz-meta-tag" value="" /><br />
<input type="hidden" name="AWSAccessKeyId" value="AKIAIOSFODNN7EXAMPLE" />
<input type="hidden" name="Policy" value="POLICY" />
<input type="hidden" name="Signature" value="SIGNATURE" />
File: <input type="file" name="file" /> <br />
<!-- The elements after this will be ignored -->
<input type="submit" name="submit" value="Upload to Amazon S3" />
</form>
...
</html>
Обратите внимание, что действие FORM отправляет файл прямо в S3 - не через ваш сервер.
Каждый раз, когда один из ваших пользователей хочет загрузить файл, вы создаете ПОЛИТИКУ
и ЗНАЧЕНИЕ
на своем сервере. Вы возвращаете страницу в браузер пользователя. После этого пользователь может загрузить файл непосредственно на S3, не проходя через ваш сервер.
Когда вы подписываете политику, вы обычно делаете так, чтобы срок действия политики истекал через несколько минут. Это заставляет пользователей обращаться к вашему серверу перед загрузкой. Это позволит вам контролировать и ограничивать загрузку, если вы того пожелаете.
Единственные данные, идущие на ваш сервер или с него, - это подписанные URL-адреса. Ваши секретные ключи остаются на сервере.
Вы можете сделать это в AWS S3 в Cognito в попробуйте вот эту ссылку :
http://docs.aws.amazon.com/AWSJavaScriptSDK/guide/browser-examples.html#Amazon_S3
Также попробуйте этот код
Просто смените регион, IdentityPoolId и имя твое ведро
в
<!DOCTYPE html>
<html>
<head>
<title>AWS S3 File Upload</title>
<script src="https://sdk.amazonaws.com/js/aws-sdk-2.1.12.min.js"></script>
</head>
<body>
<input type="file" id="file-chooser" />
<button id="upload-button">Upload to S3</button>
<div id="results"></div>
<script type="text/javascript">
AWS.config.region = 'your-region'; // 1. Enter your region
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'your-IdentityPoolId' // 2. Enter your identity pool
});
AWS.config.credentials.get(function(err) {
if (err) alert(err);
console.log(AWS.config.credentials);
});
var bucketName = 'your-bucket'; // Enter your bucket name
var bucket = new AWS.S3({
params: {
Bucket: bucketName
}
});
var fileChooser = document.getElementById('file-chooser');
var button = document.getElementById('upload-button');
var results = document.getElementById('results');
button.addEventListener('click', function() {
var file = fileChooser.files[0];
if (file) {
results.innerHTML = '';
var objKey = 'testing/' + file.name;
var params = {
Key: objKey,
ContentType: file.type,
Body: file,
ACL: 'public-read'
};
bucket.putObject(params, function(err, data) {
if (err) {
results.innerHTML = 'ERROR: ' + err;
} else {
listObjs();
}
});
} else {
results.innerHTML = 'Nothing to upload.';
}
}, false);
function listObjs() {
var prefix = 'testing';
bucket.listObjects({
Prefix: prefix
}, function(err, data) {
if (err) {
results.innerHTML = 'ERROR: ' + err;
} else {
var objKeys = "";
data.Contents.forEach(function(obj) {
objKeys += obj.Key + "<br>";
});
results.innerHTML = objKeys;
}
});
}
</script>
</body>
</html>
в
Вы говорите, что вам нужно "бессерверное" решение. Но это означает, что у вас нет возможности поместить любой "свой" код в цикл. (ПРИМЕЧАНИЕ: Как только вы отдаете свой код клиенту, он становится "его"- кодом). Блокировка CORS не поможет: Люди могут легко написать не-веб-инструмент (или веб-прокси), который добавит правильный CORS-заголовок, чтобы злоупотребить вашей системой.
Большая проблема заключается в том, что вы не можете провести различие между различными пользователями. Вы не можете разрешить одному пользователю перечислять/доступать к своим файлам, но запретить это другим. Если вы обнаружите злоупотребление, вы ничего не сможете с этим поделать, кроме как сменить ключ. (Который злоумышленник, предположительно, может просто получить снова).
Лучше всего создать "IAM-пользователя" с ключом для вашего javascript-клиента. Дайте ему доступ на запись только к одному ведру. (но в идеале не включайте операцию ListBucket, это сделает ее более привлекательной для злоумышленников).
Если бы у вас был сервер (даже простой микро экземпляр за $20/месяц), вы могли бы подписывать ключи на своем сервере, отслеживая/предотвращая злоупотребления в реальном времени. Без сервера лучшее, что вы можете сделать, это периодически отслеживать злоупотребления постфактум. Вот что я бы сделал:
периодически чередовать ключи для этого пользователя IAM: Каждую ночь генерируйте новый ключ для этого пользователя IAM и заменяйте самый старый. Поскольку есть 2 ключа, каждый ключ будет действителен в течение 2 дней.
Включите ведение журнала S3 и загружайте журналы каждый час. Установите предупреждения о "слишком большом количестве загрузок" и "слишком большом количестве скачиваний". Вы захотите проверить как общий размер файла, так и количество загруженных файлов. Вы захотите отслеживать как глобальные общие показатели, так и общие показатели по каждому IP-адресу (с более низким порогом).
Эти проверки можно выполнить "бессерверно", поскольку вы можете запустить их на своем рабочем столе. (Т.е. S3 делает всю работу, эти процессы просто предупреждают вас о злоупотреблении вашим ведром S3, чтобы вы не получили огромный счет от AWS в конце месяца).
Добавить подробнее с принятыми ответа, вы можете обратиться в мой блог, чтобы увидеть работающую версию код, используя AWS подпись версии 4.
Суммирую здесь:
Как только пользователь выбирает файл для загрузки, сделайте следующее:
Сделать запрос к веб-серверу запускать сервис для создания требуемых параметров
В этой службе, позвонить в сервис AWS IAM, чтобы получить временный кред
Если у вас есть кред, создать ведро политики (в кодировке base-64 строки). Затем войдите в ведро политику с временным секретный ключ доступа для создания окончательного подписания
отправить необходимые параметры вернуться в ИП
Как только это будет получено, создать объект HTML-форму, задать необходимые параметры и запостить.
Для получения подробной информации, пожалуйста, обратитесь https://wordpress1763.wordpress.com/2016/10/03/browser-based-upload-aws-signature-version-4/
, Чтобы создать подпись, Я должен использовать свой секретный ключ. Но все происходит на стороне клиента, таким образом, секретный ключ может быть легко выявлены из исходного кода страницы (даже если я скрыть/зашифровать свои источники).
Это где вы не поняли. Причина того, используются цифровые подписи, так что вы можете что-то проверить, как правильно, не раскрывая своего секретного ключа. В этом случае цифровая подпись используется для предупреждения пользователя изменять правила, установленные для формы.
Цифровой подписи, такие как здесь используются для безопасности во всем интернете. Если кто-то (АНБ?) действительно удалось сломать их, они будут иметь гораздо больше целей, чем ваш S3 ведро :)
Я привел простой код для загрузки файлов из браузера на JavaScript в AWS S3 и список всех файлов в S3 ведро.
Действия:
<?XML-версия="и" и что quot кодировка="в кодировке UTF-8&;?&1.0 ГТ; <префиксом xmlns CORSConfiguration="и http://s3.amazonaws.com/doc/2006-03-01/">
<тип сценария="и текст/javascript" и> АРМ.конфиг.регион = 'АП-Север-1'; // области АРМ.конфиг.учетные данные = новый АРМ.CognitoIdentityCredentials({ IdentityPoolId: 'АП-Север-1:-', }); ВАР ведро = новый АРМ.С3({ параметры: { Ведро: 'MyBucket' } });
ВАР fileChooser = документ.метода getElementById('файл селектора'); кнопка дисп = документ.метода getElementById('загрузить-кнопка'); результаты дисп = документ.метода getElementById('результаты');
загрузки функция() { файл var = fileChooser.файлы[0]; консоль.журнал(файл.имя);
если (файла) { результаты.innerHTML будет = ''; ВАР параметр params = { Ключ: Н + '.в формате PDF', Значение contentType: файл.типа, Тело: файл }; ведро.загрузить(параметры, функция(ошибаться, данные) { результаты.innerHTML будет = подстраховались ? 'ошибка!' : 'загружено.'; }); } еще { результаты.innerHTML будет = 'ничего не загружать.'; } } </скрипт> <тело> в <тип входного=на"файл" ИД="и файл селектора на" /> в <тип входного="и кнопка" и функция onclick=на"загрузить () на" значение"и загрузить в S3" и> <див ИД="по результатам" и></дел> </тело>
Если у вас нет кода на стороне сервера, ваша безопасность зависит от безопасности доступа к вашему JavaScript-коду на стороне клиента (т.е. каждый, у кого есть код, может что-то загрузить).
Поэтому я бы рекомендовал просто создать специальный S3 bucket, который доступен для публичной записи (но не для чтения), так что вам не нужны никакие подписанные компоненты на стороне клиента.
Имя ведра (например, GUID) будет вашей единственной защитой от вредоносных загрузок (но потенциальный злоумышленник не сможет использовать ваше ведро для передачи данных, потому что оно доступно только для записи).
Вот как можно создать документ, используя узел и независимая
"use strict";
const uniqid = require('uniqid');
const crypto = require('crypto');
class Token {
/**
* @param {Object} config SSM Parameter store JSON config
*/
constructor(config) {
// Ensure some required properties are set in the SSM configuration object
this.constructor._validateConfig(config);
this.region = config.region; // AWS region e.g. us-west-2
this.bucket = config.bucket; // Bucket name only
this.bucketAcl = config.bucketAcl; // Bucket access policy [private, public-read]
this.accessKey = config.accessKey; // Access key
this.secretKey = config.secretKey; // Access key secret
// Create a really unique videoKey, with folder prefix
this.key = uniqid() + uniqid.process();
// The policy requires the date to be this format e.g. 20181109
const date = new Date().toISOString();
this.dateString = date.substr(0, 4) + date.substr(5, 2) + date.substr(8, 2);
// The number of minutes the policy will need to be used by before it expires
this.policyExpireMinutes = 15;
// HMAC encryption algorithm used to encrypt everything in the request
this.encryptionAlgorithm = 'sha256';
// Client uses encryption algorithm key while making request to S3
this.clientEncryptionAlgorithm = 'AWS4-HMAC-SHA256';
}
/**
* Returns the parameters that FE will use to directly upload to s3
*
* @returns {Object}
*/
getS3FormParameters() {
const credentialPath = this._amazonCredentialPath();
const policy = this._s3UploadPolicy(credentialPath);
const policyBase64 = new Buffer(JSON.stringify(policy)).toString('base64');
const signature = this._s3UploadSignature(policyBase64);
return {
'key': this.key,
'acl': this.bucketAcl,
'success_action_status': '201',
'policy': policyBase64,
'endpoint': "https://" + this.bucket + ".s3-accelerate.amazonaws.com",
'x-amz-algorithm': this.clientEncryptionAlgorithm,
'x-amz-credential': credentialPath,
'x-amz-date': this.dateString + 'T000000Z',
'x-amz-signature': signature
}
}
/**
* Ensure all required properties are set in SSM Parameter Store Config
*
* @param {Object} config
* @private
*/
static _validateConfig(config) {
if (!config.hasOwnProperty('bucket')) {
throw "'bucket' is required in SSM Parameter Store Config";
}
if (!config.hasOwnProperty('region')) {
throw "'region' is required in SSM Parameter Store Config";
}
if (!config.hasOwnProperty('accessKey')) {
throw "'accessKey' is required in SSM Parameter Store Config";
}
if (!config.hasOwnProperty('secretKey')) {
throw "'secretKey' is required in SSM Parameter Store Config";
}
}
/**
* Create a special string called a credentials path used in constructing an upload policy
*
* @returns {String}
* @private
*/
_amazonCredentialPath() {
return this.accessKey + '/' + this.dateString + '/' + this.region + '/s3/aws4_request';
}
/**
* Create an upload policy
*
* @param {String} credentialPath
*
* @returns {{expiration: string, conditions: *[]}}
* @private
*/
_s3UploadPolicy(credentialPath) {
return {
expiration: this._getPolicyExpirationISODate(),
conditions: [
{bucket: this.bucket},
{key: this.key},
{acl: this.bucketAcl},
{success_action_status: "201"},
{'x-amz-algorithm': 'AWS4-HMAC-SHA256'},
{'x-amz-credential': credentialPath},
{'x-amz-date': this.dateString + 'T000000Z'}
],
}
}
/**
* ISO formatted date string of when the policy will expire
*
* @returns {String}
* @private
*/
_getPolicyExpirationISODate() {
return new Date((new Date).getTime() + (this.policyExpireMinutes * 60 * 1000)).toISOString();
}
/**
* HMAC encode a string by a given key
*
* @param {String} key
* @param {String} string
*
* @returns {String}
* @private
*/
_encryptHmac(key, string) {
const hmac = crypto.createHmac(
this.encryptionAlgorithm, key
);
hmac.end(string);
return hmac.read();
}
/**
* Create an upload signature from provided params
* https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html#signing-request-intro
*
* @param policyBase64
*
* @returns {String}
* @private
*/
_s3UploadSignature(policyBase64) {
const dateKey = this._encryptHmac('AWS4' + this.secretKey, this.dateString);
const dateRegionKey = this._encryptHmac(dateKey, this.region);
const dateRegionServiceKey = this._encryptHmac(dateRegionKey, 's3');
const signingKey = this._encryptHmac(dateRegionServiceKey, 'aws4_request');
return this._encryptHmac(signingKey, policyBase64).toString('hex');
}
}
module.exports = Token;
Используемые объекты конфигурации хранится в ССМ запись параметра и выглядит следующим образом
{
"bucket": "my-bucket-name",
"region": "us-west-2",
"bucketAcl": "private",
"accessKey": "MY_ACCESS_KEY",
"secretKey": "MY_SECRET_ACCESS_KEY",
}
Если вы готовы использовать 3-й партии обслуживание, auth0.com поддерживает эту интеграцию. В auth0 бирж услуги 3 участника единого входа службы аутентификации для AWS для временной маркер сеанса будут ограниченные разрешения.
См.: https://github.com/auth0-samples/auth0-s3-sample/ и документации auth0.