私は、クライアントマシンからREST API経由でAmazon S3に直接ファイルをアップロードする機能を、サーバーサイドのコードを一切使用せず、JavaScriptのみで実装しています。すべてうまくいくのですが、1つだけ心配なことがあります。
Amazon S3 REST APIにリクエストを送信する場合、リクエストに署名して Authentication
ヘッダに署名を入れる必要があります。署名を作成するには、自分の秘密鍵を使わなければなりません。しかし、すべてのことはクライアントサイドで行われるため、秘密鍵はページのソースから簡単に明らかになってしまいます(ソースを難読化/暗号化しても)。
どのように対処すればよいのでしょうか?また、それは全く問題ないのでしょうか?特定の秘密鍵の使用を、特定のCORS OriginからのREST API呼び出しに限定し、PUTとPOSTメソッドのみに限定したり、鍵をS3と特定のバケットにのみリンクさせることができるかもしれませんね。他の認証方法があるのでは?
サーバーレスソリューションが理想ですが、サーバーサイドでの処理も考慮します(自分のサーバーにファイルをアップロードしてS3に送信するような処理は除く)。
欲しいのは「Browser-Based Uploads Using POST」だと思うのですが。
基本的に、サーバーサイドのコードは必要ですが、それが行うのは署名付きポリシーの生成だけです。クライアント側のコードに署名付きポリシーがあれば、サーバーを介さずに直接S3にPOSTでアップロードすることができます。
以下、公式ドキュメントリンクです:
図: 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にファイルを送信していることに注意してください。
ユーザーの一人がファイルをアップロードしようとするたびに、あなたはサーバー上にPOLICY
とSIGNATURE
を作成することになります。そして、そのページをユーザーのブラウザに返します。すると、ユーザーはあなたのサーバーを介さずに直接S3にファイルをアップロードすることができます。
ポリシーに署名する際、通常、数分後にポリシーが失効するようにします。これにより、ユーザーはアップロードする前にサーバーと会話するようになります。これにより、アップロードを監視し、制限することができます。
あなたのサーバーを行き来するデータは、署名されたURLだけです。あなたの秘密鍵はサーバー上で秘密にされます。
これはAWS S3 Cognitoで行うことができます。 ここでこのリンクを試してください:
http://docs.aws.amazon.com/AWSJavaScriptSDK/guide/browser-examples.html#Amazon_S3。
このコードも試してください。
Region、IdentityPoolId、およびバケット名を変更するだけです。
<。!-スニペットを開始:js hide:false -->。
<!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ヘッダーを追加してシステムを悪用することは簡単にできる。
大きな問題は、異なるユーザーを区別することができないことです。あるユーザーには自分のファイルのリストアップやアクセスを許可し、他のユーザーにはそれをさせないようにすることはできません。 不正使用を発見した場合、鍵を変更する以外にはどうすることもできません。(攻撃者はおそらく再び鍵を手に入れることができます)。
最善の方法は、JavaScriptクライアント用のキーを持つ"IAMユーザー"を作成することです。このユーザーには、1つのバケットに対する書き込みアクセス権だけを与えてください。(ただし、理想的にはListBucket操作を有効にしないことです。攻撃者にとってより魅力的なものになります。)
サーバーがあれば(月額20ドルのシンプルなマイクロインスタンスでも)、サーバー上で鍵に署名し、リアルタイムで不正使用を監視/防止することができます。 サーバーがない場合、できることは、定期的に不正使用を事後的に監視することです。 私ならこうします:
そのIAMユーザーの鍵を定期的にローテーションする:毎晩、その IAM ユーザーの新しい鍵を生成し、最も古い鍵を交換します。2つの鍵があるため、それぞれの鍵は2日間有効です。
S3ロギングを有効にし、1時間ごとにログをダウンロードする。アップロードが多すぎる」「ダウンロードが多すぎる」というアラートを設定する。総ファイルサイズとアップロードされたファイル数の両方をチェックしたい。また、グローバルな合計と、IPアドレスごとの合計(閾値は低め)の両方を監視する必要があります。
これらのチェックは、デスクトップ上で実行できるため、quot;serverless"で行うことができます。(つまり、S3が全ての作業を行い、これらのプロセスはS3バケットの不正使用を警告するために存在するだけで、月末に巨大なAWS請求書を受け取ることはありません)。
受け入れられた回答に詳細を追加すると、AWS署名バージョン4を使用して、私のブログを参照してコードの実行中のバージョンを確認できます。
ここで要約します:
ユーザーがアップロードするファイルを選択したらすぐに、次の操作を行います。 1。 Webサーバーに電話をかけ、必要なパラメーターを生成するサービスを開始します。
2。 このサービスでは、AWS IAMサービスに電話をかけ、一時的なクレジットを取得します。
3。 クレジットを取得したら、バケットポリシーを作成します(ベース64エンコードされた文字列)。 次に、一時的なシークレットアクセスキーを使用してバケットポリシーに署名し、最終的な署名を生成します。
4。 必要なパラメーターをUIに送り返します。
5。 これを受け取ったら、htmlフォームオブジェクトを作成し、必要なパラメーターを設定して、それをPOSTします。
詳細については、参照してください。 https://wordpress1763.wordpress.com/2016/10/03/browser-based-upload-aws-signature-version-4/。
署名を作成するには、秘密鍵を使用する必要があります。 しかし、すべてのもの。 クライアント側で発生するため、秘密鍵を簡単に公開できます。 ページソースから(ソースを難読化/暗号化しても)。
これはあなたが誤解しているところです。 デジタル署名が使用されるまさにその理由は、秘密鍵を明らかにすることなく、正しいものを検証できるようにするためです。 この場合、デジタル署名は、ユーザーがフォームポストに設定したポリシーを変更できないようにするために使用されます。
ここにあるようなデジタル署名は、Web全体のセキュリティに使用されます。 誰か(NSA?)本当にそれらを壊すことができました、彼らはあなたのS3バケットよりもはるかに大きなターゲットを持っているでしょう:)。
JavascriptブラウザからAWS S3にファイルをアップロードし、すべてのファイルをS3バケットにリストする簡単なコードを提供しました。
ステップ:。
1。 Create IdentityPoolIdを作成する方法を知るにはhttp://docs.aws.amazon.com/cognito/latest/developerguide/identity-pools.html。 2。 Goto S3のコンソールページとバケットプロパティからのオープンコース構成、それに続くXMLコードを書き込みます。
<?xmlバージョン= "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> *< / AllowHeader>
< / CORSRule>。
< / CORSConfiguration>。
3。 次のコードを含むHTMLファイルを作成し、資格情報を変更し、ブラウザでファイルを開いてお楽しみください。
< script type = "text / javascript">。
AWS.config.region = 'ap-north-1'; //地域。
AWS.config.credentials = new AWS.CognitoIdentityCredentials({。
IdentityPoolId: 'ap-north-1:*****-*****'、。
});。
var buett = new AWS.S3({。
パラム:{。
バケット:「MyBucket」。
}。
});。
var fileChooser = document.getElementById( 'file-chooser');。
varボタン= document.getElementById( 'upload-button');。
var results = document.getElementById( 'results');。
関数upload(){。
var file = fileChooser.files [0];。
consolor.log(file.name);。
if(ファイル){。
results.innerHTML = '';。
var params = {。
キー:n + '.pdf'、。
ContentType:file.type、。
本文:ファイル。
};。
buque.upload(params、function(err、data){。
results.innerHTML =エラー。 ? 「エラー。!':' UPLOADED ';。
});。
}その他{。
results.innerHTML = 'アップロードするものは何もありません。';。
}}。
< / script>。
< body>。
< input type = "file" id = "file-chooser" />。
< input type = "button" onclick = "upload()" value = "Upload to S3">。
< div id = "results">< / div>。
< / body>。
サーバサイドのコードがない場合、セキュリティはクライアントサイドのJavaScriptコードへのアクセスのセキュリティに依存します(つまり、コードを持っている人は誰でも何かをアップロードできます)。
そこで、S3バケットに書き込み可能(読み取り不可)な特別なバケットを作成し、クライアント側で署名付きのコンポーネントを必要としないようにすることをお勧めします。
バケット名(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;
使用する構成オブジェクトはSSM パラメーターストアに保存され、次のようになります。
{
"bucket": "my-bucket-name",
"region": "us-west-2",
"bucketAcl": "private",
"accessKey": "MY_ACCESS_KEY",
"secretKey": "MY_SECRET_ACCESS_KEY",
}