it-swarm.dev

تحميل ملف Amazon S3 المباشر من متصفح العميل - الكشف عن المفتاح الخاص

أقوم بتنفيذ تحميل مباشر للملفات من جهاز العميل إلى Amazon S3 عبر REST API باستخدام جافا سكريبت فقط ، دون أي كود من جانب الخادم. كل شيء يعمل بشكل جيد ولكن هناك شيء واحد يقلقني ...

عندما أرسل طلبًا إلى Amazon S3 REST API ، أحتاج إلى توقيع الطلب ووضع توقيع في رأس Authentication. لإنشاء توقيع ، يجب أن استخدم المفتاح السري الخاص بي. ولكن كل الأشياء تحدث من جانب العميل ، لذلك ، يمكن بسهولة الكشف عن المفتاح السري من مصدر الصفحة (حتى إذا كنت أعتمي/أفرز مصادري).

كيف يمكنني التعامل مع هذا؟ وهل هي مشكلة على الإطلاق؟ ربما يمكنني قصر استخدام معين للمفتاح الخاص فقط على REST مكالمات API من أصل CORS محدد و PUT و POST فقط الطرق أو ربما ربط المفتاح بـ S3 ومجموعة محددة فقط؟ قد يكون هناك طرق مصادقة أخرى؟

يعد الحل "Serverless" مثاليًا ، لكن يمكنني التفكير في إشراك بعض معالجة serveride ، باستثناء تحميل ملف إلى الخادم الخاص بي ثم إرساله إلى S3.

138
Olegas

أعتقد أن ما تريده هو عمليات التحميل المستندة إلى المستعرض باستخدام 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 - وليس عبر الخادم الخاص بك.

في كل مرة يريد فيها أحد المستخدمين تحميل ملف ، يمكنك إنشاء POLICY و SIGNATURE على الخادم الخاص بك. يمكنك إعادة الصفحة إلى متصفح المستخدم. يمكن للمستخدم بعد ذلك تحميل ملف مباشرة إلى S3 دون المرور بخادمك.

عندما تقوم بتوقيع السياسة ، عادة ما تنتهي صلاحية السياسة بعد بضع دقائق. هذا يفرض على المستخدمين التحدث إلى خادمك قبل التحميل. يتيح لك ذلك مراقبة عمليات التحميل والحد منها إذا كنت ترغب في ذلك.

البيانات الوحيدة التي تذهب إلى أو من الخادم الخاص بك هي عناوين URL الموقعة. مفاتيحك السرية تبقى سرية على الخادم.

198
secretmike

أنت تقول أنك تريد حلاً "بدون خادم". لكن هذا يعني أنك لا تملك القدرة على وضع أي كود "خاص بك" في الحلقة. (ملاحظة: بمجرد إعطاء الرمز الخاص بك إلى عميل ، أصبح رمزه "الآن".) لن يساعد تأمين CORS: يمكن للأشخاص بسهولة كتابة أداة غير قائمة على الويب (أو وكيل يستند إلى الويب) يضيف رأس CORS الصحيح لإساءة استخدام نظامك.

المشكلة الكبيرة هي أنه لا يمكنك التمييز بين المستخدمين المختلفين. لا يمكنك السماح لمستخدم واحد بسرد/الوصول إلى ملفاته ، ولكن منع الآخرين من القيام بذلك. إذا اكتشفت سوء المعاملة ، فلا يوجد شيء يمكنك القيام به حيال ذلك باستثناء تغيير المفتاح. (والتي يمكن للمهاجم أن يحصل عليها مرة أخرى.)

أفضل رهان هو إنشاء "مستخدم IAM" باستخدام مفتاح لعميل جافا سكريبت الخاص بك. فقط أعطه حق الوصول للكتابة إلى دلو واحد فقط. (ولكن من الناحية المثالية ، لا تقم بتمكين عملية ListBucket ، مما يجعلها أكثر جاذبية للمهاجمين.)

إذا كان لديك خادم (حتى نسخة مصغرة بسيطة بمبلغ 20 دولارًا شهريًا) ، فيمكنك توقيع المفاتيح على الخادم أثناء مراقبة/منع إساءة الاستخدام في الوقت الفعلي. بدون خادم ، فإن أفضل ما يمكنك فعله هو مراقبة دوريا بحثًا عن إساءة بعد وقوعها. إليك ما سأفعله:

1) تدوير المفاتيح لهذا المستخدم IAM بشكل دوري: كل ليلة ، إنشاء مفتاح جديد لهذا المستخدم IAM ، واستبدال أقدم مفتاح. نظرًا لوجود مفتاحين ، فسيظل كل مفتاح صالحًا لمدة يومين.

2) تمكين تسجيل S3 ، وتنزيل السجلات كل ساعة. تعيين التنبيهات على "الكثير من التحميلات" و "الكثير من التنزيلات". سوف تحتاج إلى التحقق من كلاً من إجمالي حجم الملف وعدد الملفات التي تم تحميلها. وسوف ترغب في مراقبة كل المجاميع العالمية ، وكذلك مجاميع عنوان بروتوكول الإنترنت (بحد أدنى).

يمكن إجراء هذه الاختبارات "بدون خادم" لأنه يمكنك تشغيلها على سطح المكتب. (على سبيل المثال ، S3 تقوم بكامل العمل ، فهذه العمليات موجودة هناك لتنبيهك بإساءة استخدام دلو S3 الخاص بك حتى لا تحصل على عملاق AWS فاتورة في نهاية الشهر.)

16
BraveNewCurrency

إضافة المزيد من المعلومات إلى الإجابة المقبولة ، يمكنك الرجوع إلى مدونتي لرؤية نسخة قيد التشغيل من الشفرة ، باستخدام AWS Signature الإصدار 4.

سوف ألخص هنا:

بمجرد أن يحدد المستخدم ملفًا ليتم تحميله ، قم بما يلي: 1. قم بإجراء مكالمة إلى خادم الويب لبدء خدمة لإنشاء المعاملات المطلوبة

  1. في هذه الخدمة ، قم بإجراء مكالمة إلى خدمة AWS IAM للحصول على رصيد مؤقت

  2. بمجرد حصولك على الرصيد ، قم بإنشاء سياسة مجموعة (مجموعة 64 سلسلة مشفرة). ثم قم بتوقيع سياسة المجموعة باستخدام مفتاح الوصول السري المؤقت لإنشاء التوقيع النهائي

  3. إرسال المعلمات الضرورية مرة أخرى إلى واجهة المستخدم

  4. بمجرد استلام هذا ، قم بإنشاء كائن نموذج html ، واضبط المعلمات المطلوبة و POST عليه.

للحصول على معلومات مفصلة ، يرجى الرجوع https://wordpress1763.wordpress.com/2016/10/03/browser-based-upload-aws-signature-version-4/

8
RajeevJ

لإنشاء توقيع ، يجب أن استخدم المفتاح السري الخاص بي. ولكن كل الأشياء تحدث من جانب العميل ، لذلك ، يمكن بسهولة الكشف عن المفتاح السري من مصدر الصفحة (حتى إذا كنت أعتمي/أفرز مصادري).

هذا هو المكان الذي أسيء فهمه. السبب وراء استخدام التوقيعات الرقمية هو أنه يمكنك التحقق من صحة شيء ما دون الكشف عن المفتاح السري. في هذه الحالة ، يتم استخدام التوقيع الرقمي لمنع المستخدم من تعديل السياسة التي قمت بتعيينها لنشر النموذج.

يتم استخدام التوقيعات الرقمية مثل التوقيع هنا للأمان في جميع أنحاء الويب. إذا كان شخص ما (NSA؟) قادرًا حقًا على كسرها ، فسيكون له أهداف أكبر بكثير من دلو S3 :)

4
OlliM

إذا لم يكن لديك أي رمز جانب الخادم ، فإن الأمان يعتمد على أمان الوصول إلى شفرة JavaScript الخاصة بك على جانب العميل (أي يمكن لكل شخص لديه الكود تحميل شيء ما).

لذلك أوصي ، ببساطة بإنشاء دلو S3 خاص يمكن كتابته (لكن ليس قابلاً للقراءة) ، لذلك لا تحتاج إلى أي مكونات موقعة من جانب العميل.

سيكون اسم الجرافة (a GUID على سبيل المثال) هو دفاعك الوحيد ضد عمليات التحميل الخبيثة (لكن المهاجم المحتمل لم يتمكن من استخدام الجرافة الخاصة بك لنقل البيانات ، لأنه يكتب له فقط)

2
Ruediger Jungbeck

لقد قدمت رمزًا بسيطًا لتحميل الملفات من مستعرض Javascript إلى AWS S3 وسرد جميع الملفات في دلو S3.

الخطوات:

  1. لمعرفة كيفية إنشاء Create IdentityPoolId http://docs.aws.Amazon.com/cognito/latest/developerguide/identity-pools.html

    1. صفحة وحدة التحكم في Goto S3 وتهيئة التكوين مفتوحة من خصائص المجموعة واكتب كود 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>
      
    2. إنشاء ملف 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>
      
2
Nilesh Pawar

إليك كيفية إنشاء مستند سياسة باستخدام العقدة و 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",
}
0
Samir Patel

إذا كنت على استعداد لاستخدام خدمة طرف ثالث ، فإن auth0.com يدعم هذا التكامل. تقوم خدمة auth0 بتبادل مصادقة خدمة SSO لجهة خارجية لرمز جلسة عمل مؤقت لـ AWS ستحد من الأذونات.

راجع: https://github.com/auth0-samples/auth0-s3-sample/
ووثائق auth0.

0
Jason