サーバーサイドコードなしで、JavaScriptのみを使用して、REST APIを介してクライアントマシンからAmazon S3への直接ファイルアップロードを実装しています。すべてうまくいきますが、一つのことは私を心配しています...
Amazon S3 REST APIにリクエストを送信する場合、リクエストに署名し、Authentication
ヘッダーに署名を追加する必要があります。署名を作成するには、秘密鍵を使用する必要があります。しかし、すべてはクライアント側で行われるため、秘密キーはページソースから簡単に明らかにできます(ソースを難読化/暗号化しても)。
どうすればこれを処理できますか?そしてそれはまったく問題ですか?特定の秘密キーの使用を、特定のCORS OriginからのREST API呼び出しと、PUTおよびPOSTメソッドのみに制限したり、キーをS3と特定のバケットのみにリンクしたりできますか?別の認証方法がありますか?
「サーバーレス」ソリューションは理想的ですが、サーバーにファイルをアップロードしてからS3に送信することを除いて、サーバーサイドの処理を含めることを検討できます。
あなたが望むのは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に直接送信していることに注意してください。
ユーザーの1人がファイルをアップロードするたびに、サーバー上でPOLICY
およびSIGNATURE
を作成します。ユーザーのブラウザにページを返します。ユーザーは、サーバーを経由せずにファイルをS3に直接アップロードできます。
ポリシーに署名すると、通常、数分後にポリシーの有効期限が切れます。これにより、ユーザーはアップロードする前にサーバーと通信する必要があります。これにより、必要に応じてアップロードを監視および制限できます。
サーバーとの間で送受信されるデータは、署名付きURLのみです。秘密鍵はサーバー上で秘密のままです。
「サーバーレス」ソリューションが必要だと言っています。しかし、それは、「自分の」コードをループに入れることができないことを意味します。 (注:コードをクライアントに渡すと、そのコードは「自分の」コードになります。)CORSのロックは役に立たなくなります。システムを悪用する正しいCORSヘッダー。
大きな問題は、異なるユーザーを区別できないことです。 1人のユーザーに自分のファイルのリスト/アクセスを許可することはできませんが、他のユーザーがそれを行うことを禁止することはできません。不正行為を検出した場合は、キーを変更する以外に何もできません。 (攻撃者はおそらく再び攻撃を仕掛けることができるでしょう。)
あなたの最善の策は、JavaScriptクライアントのキーを持つ「IAMユーザー」を作成することです。 1つのバケットへの書き込みアクセスのみを許可します。 (ただし、理想的には、ListBucket操作を有効にしないでください。これにより、攻撃者にとってより魅力的になります。)
サーバーがあれば(月額20ドルの単純なマイクロインスタンスでも)、リアルタイムで不正行為を監視/防止しながら、サーバー上のキーに署名できます。サーバーがない場合、最善の方法は、事後の悪用を定期的に監視することです。ここに私がやることがあります:
1)そのIAMユーザーのキーを定期的にローテーションする:毎晩、そのIAMユーザーの新しいキーを生成し、最も古いキーを置き換えます。キーが2つあるため、各キーは2日間有効です。
2)S3ロギングを有効にして、1時間ごとにログをダウンロードします。 「アップロードが多すぎる」および「ダウンロードが多すぎる」アラートを設定します。合計ファイルサイズとアップロードされたファイルの数の両方を確認する必要があります。また、グローバルな合計とIPアドレスごとの合計(より低いしきい値)の両方を監視する必要があります。
これらのチェックは、デスクトップで実行できるため、「サーバーレス」で実行できます。 (つまり、S3はすべての作業を行います。これらのプロセスは、S3バケットの悪用を警告するためにあり、月末にgiant AWSの請求書を受け取りません。)
受け入れられた回答にさらに情報を追加すると、AWS署名バージョン4を使用して、実行中のバージョンのコードを確認するために私のブログを参照できます。
ここに要約します:
ユーザーがアップロードするファイルを選択したらすぐに、次の手順を実行します。1. Webサーバーを呼び出して、必要なパラメーターを生成するサービスを開始します。
このサービスで、AWS IAMサービスを呼び出して一時的な認証を取得します
資格を取得したら、バケットポリシー(base 64エンコード文字列)を作成します。次に、一時的なシークレットアクセスキーでバケットポリシーに署名して、最終的な署名を生成します
必要なパラメーターをUIに送り返す
これを受け取ったら、htmlフォームオブジェクトを作成し、必要なパラメータを設定してPOST it_を設定します。
詳細については、 https://wordpress1763.wordpress.com/2016/10/03/browser-based-upload-aws-signature-version-4/ を参照してください。
署名を作成するには、秘密鍵を使用する必要があります。しかし、すべてはクライアント側で行われるため、秘密キーはページソースから簡単に明らかにできます(ソースを難読化/暗号化しても)。
これはあなたが誤解しているところです。デジタル署名が使用されるまさにその理由は、秘密鍵を明かすことなく、何かが正しいと検証できるようにするためです。この場合、デジタル投稿は、ユーザーがフォーム投稿に設定したポリシーを変更できないようにするために使用されます。
ここにあるようなデジタル署名は、ウェブ全体のセキュリティに使用されます。誰か(NSA?)が本当にそれらを破ることができた場合、彼らはあなたのS3バケットよりもはるかに大きなターゲットを持っているでしょう:)
サーバー側のコードがない場合、セキュリティはクライアント側のJavaScriptコードへのアクセスのセキュリティに依存します(つまり、コードを持っているすべての人が何かをアップロードできます)。
そのため、クライアント側で署名済みコンポーネントを必要としないように、単純にパブリック書き込み可能(読み取り不可)の特別なS3バケットを作成することをお勧めします。
バケット名(GUID eg)は、悪意のあるアップロードに対する唯一の防御になります(ただし、潜在的な攻撃者は自分への書き込みのみであるため、バケットを使用してデータを転送することはできません)
JavascriptブラウザーからAWS S3にファイルをアップロードし、S3バケット内のすべてのファイルをリストする簡単なコードを指定しました。
手順:
Create IdentityPoolIdの作成方法を知るには http://docs.aws.Amazon.com/cognito/latest/developerguide/identity-pools.html
S3のコンソールページに移動し、バケットプロパティからcors設定を開き、次のXMLコードをそこに書き込みます。
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
<AllowedMethod>GET</AllowedMethod>
<AllowedMethod>PUT</AllowedMethod>
<AllowedMethod>DELETE</AllowedMethod>
<AllowedMethod>HEAD</AllowedMethod>
<AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>
次のコードを含むHTMLファイルを作成し、資格情報を変更し、ブラウザーでファイルを開いて楽しんでください。
<script type="text/javascript">
AWS.config.region = 'ap-north-1'; // Region
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'ap-north-1:*****-*****',
});
var bucket = new AWS.S3({
params: {
Bucket: 'MyBucket'
}
});
var fileChooser = document.getElementById('file-chooser');
var button = document.getElementById('upload-button');
var results = document.getElementById('results');
function upload() {
var file = fileChooser.files[0];
console.log(file.name);
if (file) {
results.innerHTML = '';
var params = {
Key: n + '.pdf',
ContentType: file.type,
Body: file
};
bucket.upload(params, function(err, data) {
results.innerHTML = err ? 'ERROR!' : 'UPLOADED.';
});
} else {
results.innerHTML = 'Nothing to upload.';
} }
</script>
<body>
<input type="file" id="file-chooser" />
<input type="button" onclick="upload()" value="Upload to S3">
<div id="results"></div>
</body>
Nodeと serverless を使用してポリシードキュメントを生成する方法は次のとおりです。
"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;
使用される構成オブジェクトはSSM Parameter Store に保存され、次のようになります
{
"bucket": "my-bucket-name",
"region": "us-west-2",
"bucketAcl": "private",
"accessKey": "MY_ACCESS_KEY",
"secretKey": "MY_SECRET_ACCESS_KEY",
}
サードパーティのサービスを使用する場合、auth0.comはこの統合をサポートします。 auth0サービスは、AWS一時セッショントークンのサードパーティSSOサービス認証を交換し、アクセス許可を制限します。
参照: https://github.com/auth0-samples/auth0-s3-sample/
およびauth0ドキュメント。