it-swarm.dev

Unggah file langsung Amazon S3 dari browser klien - pengungkapan kunci pribadi

Saya menerapkan unggahan file langsung dari mesin klien ke Amazon S3 melalui API REST hanya menggunakan JavaScript, tanpa kode sisi server. Semua berfungsi dengan baik tetapi satu hal yang mengkhawatirkan saya ...

Ketika saya mengirim permintaan ke Amazon S3 REST API, saya harus menandatangani permintaan dan memasukkan tanda tangan ke header Authentication. Untuk membuat tanda tangan, saya harus menggunakan kunci rahasia saya. Tetapi semua hal terjadi di sisi klien, jadi, kunci rahasia dapat dengan mudah diungkapkan dari sumber halaman (bahkan jika saya mengaburkan/mengenkripsi sumber saya).

Bagaimana saya bisa menangani ini? Dan apakah itu masalah? Mungkin saya dapat membatasi penggunaan kunci pribadi khusus hanya untuk REST panggilan API dari Asal CORS tertentu dan hanya metode PUT dan POST atau mungkin tautan kunci ke hanya S3 dan bucket tertentu? Mungkin ada metode otentikasi lain?

Solusi "Serverless" sangat ideal, tetapi saya dapat mempertimbangkan untuk melibatkan beberapa pemrosesan di sisi server, tidak termasuk mengunggah file ke server saya dan kemudian mengirim ke S3.

132
Olegas

Saya pikir yang Anda inginkan adalah Upload Berbasis Browser Menggunakan POST.

Pada dasarnya, Anda memang membutuhkan kode sisi server, tetapi yang dilakukannya hanyalah menghasilkan kebijakan yang ditandatangani. Setelah kode sisi klien memiliki kebijakan yang ditandatangani, ia dapat mengunggah menggunakan POST langsung ke S3 tanpa data melalui server Anda.

Berikut tautan resmi dokumen: 

Diagram: http://docs.aws.Amazon.com/AmazonS3/latest/dev/UsingHTTPPOST.html

Kode contoh: http://docs.aws.Amazon.com/AmazonS3/latest/dev/HTTPPOSTExamples.html

Kebijakan yang ditandatangani akan masuk dalam html Anda dalam bentuk seperti ini:

<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>

Perhatikan tindakan FORMULIR mengirimkan file langsung ke S3 - bukan melalui server Anda.

Setiap kali salah satu pengguna Anda ingin mengunggah file, Anda akan membuat POLICY dan SIGNATURE di server Anda. Anda mengembalikan halaman ke browser pengguna. Pengguna kemudian dapat mengunggah file langsung ke S3 tanpa melalui server Anda.

Saat Anda menandatangani polis, biasanya polis Anda akan kedaluwarsa setelah beberapa menit. Ini memaksa pengguna Anda untuk berbicara dengan server Anda sebelum mengunggah. Ini memungkinkan Anda memantau dan membatasi unggahan jika diinginkan.

Satu-satunya data yang masuk atau dari server Anda adalah URL yang ditandatangani. Kunci rahasia Anda tetap dirahasiakan di server.

195
secretmike

Anda mengatakan Anda menginginkan solusi "tanpa server". Tetapi itu berarti Anda tidak memiliki kemampuan untuk memasukkan kode "Anda" apa pun di loop. (CATATAN: Setelah Anda memberikan kode kepada klien, itu adalah kode "mereka" sekarang.) Mengunci CORS tidak akan membantu: Orang dapat dengan mudah menulis alat berbasis web (atau proxy berbasis web) yang menambahkan header CORS yang benar untuk menyalahgunakan sistem Anda.

Masalah besar adalah bahwa Anda tidak dapat membedakan antara pengguna yang berbeda. Anda tidak dapat mengizinkan satu pengguna untuk membuat daftar/mengakses file-nya, tetapi mencegah orang lain melakukannya. Jika Anda mendeteksi penyalahgunaan, tidak ada yang dapat Anda lakukan selain mengubah kunci. (Yang penyerang mungkin bisa dapatkan lagi.)

Taruhan terbaik Anda adalah membuat "pengguna IAM" dengan kunci untuk klien javascript Anda. Hanya berikan akses tulis ke satu ember saja. (tetapi idealnya, jangan aktifkan operasi ListBucket, yang akan membuatnya lebih menarik bagi penyerang.)

Jika Anda memiliki server (bahkan mikro instan sederhana dengan harga $ 20/bulan), Anda bisa menandatangani kunci di server Anda sambil memantau/mencegah penyalahgunaan secara realtime. Tanpa server, yang terbaik yang dapat Anda lakukan adalah memantau penyalahgunaan secara berkala. Inilah yang akan saya lakukan:

1) secara berkala putar kunci untuk pengguna IAM itu: Setiap malam, buat kunci baru untuk pengguna IAM itu, dan ganti kunci yang tertua. Karena ada 2 kunci, setiap kunci akan berlaku selama 2 hari.

2) aktifkan S3 logging, dan unduh log setiap jam. Tetapkan lansiran pada "terlalu banyak unggahan" dan "terlalu banyak unduhan". Anda akan ingin memeriksa ukuran total file dan jumlah file yang diunggah. Dan Anda akan ingin memantau total global, dan juga total alamat per-IP (dengan ambang batas yang lebih rendah).

Pemeriksaan ini dapat dilakukan "tanpa server" karena Anda dapat menjalankannya di desktop. (mis. S3 melakukan semua pekerjaan, proses-proses ini hanya ada di sana untuk mengingatkan Anda akan penyalahgunaan ember S3 Anda sehingga Anda tidak mendapatkan tagihan raksasa AWS pada akhir bulan.)

15
BraveNewCurrency

Menambahkan lebih banyak info ke jawaban yang diterima, Anda dapat merujuk ke blog saya untuk melihat versi kode yang sedang berjalan, menggunakan AWS Signature versi 4.

Akan diringkas di sini:

Segera setelah pengguna memilih file yang akan diunggah, lakukan hal berikut: 1. Lakukan panggilan ke server web untuk memulai layanan untuk menghasilkan params yang diperlukan

  1. Dalam layanan ini, lakukan panggilan ke layanan AWS IAM untuk mendapatkan kredit sementara

  2. Setelah Anda memiliki kredibilitas, buat kebijakan bucket (string 64 dikodekan basis). Kemudian tanda tangani kebijakan ember dengan kunci akses rahasia sementara untuk menghasilkan tanda tangan akhir

  3. kirim parameter yang diperlukan kembali ke UI

  4. Setelah ini diterima, buat objek bentuk html, atur params yang diperlukan dan POST.

Untuk info rinci, silakan merujuk https://wordpress1763.wordpress.com/2016/10/03/browser-based-upload-aws-signature-version-4/

8
RajeevJ

Untuk membuat tanda tangan, saya harus menggunakan kunci rahasia saya. Tapi semuanya terjadi di sisi klien, jadi, kunci rahasia dapat dengan mudah diungkapkan dari sumber halaman (bahkan jika saya mengaburkan/mengenkripsi sumber saya).

Di sinilah Anda salah paham. Alasan utama tanda tangan digital digunakan adalah agar Anda dapat memverifikasi sesuatu sebagai benar tanpa mengungkapkan kunci rahasia Anda. Dalam hal ini tanda tangan digital digunakan untuk mencegah pengguna memodifikasi kebijakan yang Anda tetapkan untuk posting bentuk.

Tanda tangan digital seperti yang ada di sini digunakan untuk keamanan di seluruh web. Jika seseorang (NSA?) Benar-benar dapat menghancurkan mereka, mereka akan memiliki target yang jauh lebih besar daripada ember S3 Anda :)

4
OlliM

Jika Anda tidak memiliki kode sisi server, keamanan Anda bergantung pada keamanan akses ke kode JavaScript Anda di sisi klien (yaitu setiap orang yang memiliki kode dapat mengunggah sesuatu).

Jadi saya akan merekomendasikan, untuk hanya membuat bucket S3 khusus yang dapat ditulis untuk umum (tetapi tidak dapat dibaca), sehingga Anda tidak memerlukan komponen yang ditandatangani di sisi klien.

Nama bucket (mis. GUID) akan menjadi satu-satunya pertahanan Anda terhadap unggahan berbahaya (tetapi penyerang potensial tidak dapat menggunakan ember Anda untuk mentransfer data, karena hanya menulis kepadanya)

2

Saya telah memberikan kode sederhana untuk mengunggah file dari browser Javascript ke AWS S3 dan mencantumkan semua file dalam bucket S3.

Langkah-langkah:

  1. Untuk mengetahui cara membuat Buat IdentityPoolId http://docs.aws.Amazon.com/cognito/latest/developerguide/identity-pools.html

    1. Goto S3's console page dan buka konfigurasi cors dari bucket properties dan tulis kode XML berikut ini.

      <?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. Buat file HTML yang berisi kode berikut, ubah kredensial, buka file di browser, dan nikmati.

      <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

Inilah cara Anda membuat dokumen kebijakan menggunakan node dan 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;

Objek konfigurasi yang digunakan disimpan di SSM Parameter Store dan terlihat seperti ini

{
    "bucket": "my-bucket-name",
    "region": "us-west-2",
    "bucketAcl": "private",
    "accessKey": "MY_ACCESS_KEY",
    "secretKey": "MY_SECRET_ACCESS_KEY",
}
0
Samir Patel

Jika Anda bersedia menggunakan layanan pihak ke-3, auth0.com mendukung integrasi ini. Layanan auth0 menukar otentikasi layanan SSO pihak ke-3 untuk token sesi sementara AWS akan membatasi izin.

Lihat: https://github.com/auth0-samples/auth0-s3-sample/
dan dokumentasi auth0.

0
Jason