it-swarm.com.de

Direkter Datei-Upload von Amazon S3 vom Client-Browser - Offenlegung privater Schlüssel

Ich implementiere einen direkten Dateiupload vom Client-Computer zu Amazon S3 über die API REST, die nur JavaScript ohne serverseitigen Code verwendet. Alles funktioniert gut, aber eine Sache beunruhigt mich ...

Wenn ich eine Anfrage an die Amazon S3 REST API sende, muss ich die Anfrage signieren und eine Signatur in den Header Authentication einfügen. Um eine Signatur zu erstellen, muss ich meinen geheimen Schlüssel verwenden. Da jedoch alles auf einer Client-Seite geschieht, kann der geheime Schlüssel leicht aus der Seitenquelle herausgefunden werden (auch wenn ich meine Quellen verschleiere/verschlüssele).

Wie kann ich damit umgehen? Und ist das überhaupt ein Problem? Vielleicht kann ich die Verwendung bestimmter privater Schlüssel nur auf REST API-Aufrufe von einem bestimmten CORS Origin und auf nur PUT- und POST Methoden beschränken oder den Schlüssel nur mit S3 und einem bestimmten Bucket verknüpfen? Möglicherweise gibt es andere Authentifizierungsmethoden?

Die "Serverless" -Lösung ist ideal, aber ich kann in Betracht ziehen, einige serverseitige Prozesse einzubeziehen, das Hochladen einer Datei auf meinen Server auszuschließen und dann an S3 zu senden.

132
Olegas

Ich denke, Sie möchten browserbasierte Uploads mit POST.

Grundsätzlich benötigen Sie serverseitigen Code, erstellen jedoch nur signierte Richtlinien. Sobald der clientseitige Code über die signierte Richtlinie verfügt, kann er mit POST direkt in S3 hochgeladen werden, ohne dass die Daten Ihren Server passieren.

Hier sind die offiziellen Doc-Links: 

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

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

Die signierte Richtlinie würde in Ihrem HTML-Format in einer Form wie folgt aussehen:

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

Beachten Sie, dass die FORM-Aktion die Datei direkt an S3 sendet - nicht über Ihren Server.

Jedes Mal, wenn einer Ihrer Benutzer eine Datei hochladen möchte, würden Sie die POLICY und SIGNATURE auf Ihrem Server erstellen. Sie kehren die Seite zum Browser des Benutzers zurück. Der Benutzer kann dann eine Datei direkt in S3 hochladen, ohne Ihren Server zu durchlaufen.

Wenn Sie die Richtlinie signieren, wird die Richtlinie normalerweise nach einigen Minuten abgelaufen. Dadurch müssen Ihre Benutzer vor dem Hochladen mit Ihrem Server sprechen. Auf diese Weise können Sie Uploads überwachen und begrenzen, wenn Sie dies wünschen.

Die einzigen Daten, die zu oder von Ihrem Server gehen, sind die signierten URLs. Ihre geheimen Schlüssel bleiben auf dem Server geheim.

195
secretmike

Sie sagen, Sie wollen eine "Serverless" -Lösung. Das bedeutet jedoch, dass Sie keine Möglichkeit haben, "Ihren" Code in die Schleife zu schreiben. (HINWEIS: Sobald Sie Ihren Code einem Client übergeben haben, ist dies jetzt "sein" Code.) Das Sperren von CORS wird nicht helfen: Die Benutzer können problemlos ein nicht webbasiertes Tool (oder einen webbasierten Proxy) schreiben, das hinzugefügt wird den richtigen CORS-Header, um Ihr System zu missbrauchen.

Das große Problem ist, dass Sie nicht zwischen den verschiedenen Benutzern unterscheiden können. Sie können nicht zulassen, dass ein Benutzer seine Dateien auflistet/darauf zugreift, andere jedoch daran hindern. Wenn Sie Missbrauch feststellen, können Sie nichts anderes tun, als den Schlüssel zu ändern. (Was der Angreifer vermutlich gerade wieder bekommen kann.)

Am besten erstellen Sie einen "IAM-Benutzer" mit einem Schlüssel für Ihren Javascript-Client. Geben Sie ihm nur Schreibzugriff auf nur einen Bucket. (Aktivieren Sie die ListBucket-Operation jedoch im Idealfall nicht, um sie für Angreifer attraktiver zu machen.)

Wenn Sie einen Server hätten (sogar eine einfache Mikroinstanz für 20 US-Dollar/Monat), könnten Sie die Schlüssel auf Ihrem Server signieren, während Sie den Missbrauch in Echtzeit überwachen/verhindern. Ohne einen Server können Sie am besten regelmäßig auf Missbrauch überprüfen. Folgendes würde ich tun:

1) Drehen Sie die Tasten für diesen IAM-Benutzer in regelmäßigen Abständen: Generieren Sie jeden Abend einen neuen Schlüssel für diesen IAM-Benutzer und ersetzen Sie den ältesten Schlüssel. Da es 2 Schlüssel gibt, ist jeder Schlüssel 2 Tage gültig.

2) Aktivieren Sie die S3-Protokollierung und laden Sie die Protokolle stündlich herunter. Setzen Sie Warnungen auf "zu viele Uploads" und "zu viele Downloads". Sie sollten sowohl die Gesamtdateigröße als auch die Anzahl der hochgeladenen Dateien überprüfen. Außerdem sollten Sie sowohl die Gesamtsummen als auch die IP-Adressen (mit einem niedrigeren Schwellenwert) überwachen.

Diese Prüfungen können "Serverless" durchgeführt werden, da Sie sie auf Ihrem Desktop ausführen können. (Das heißt, S3 erledigt die ganze Arbeit, diese Prozesse dienen lediglich dazu, Sie auf Missbrauch Ihres S3-Bucket hinzuweisen, sodass Sie am Monatsende keine giant AWS-Rechnung erhalten.)

15

Wenn Sie der akzeptierten Antwort weitere Informationen hinzufügen, können Sie in meinem Blog eine aktuelle Version des Codes mit AWS Signature Version 4 anzeigen.

Werde hier zusammenfassen:

Sobald der Benutzer eine hochzuladende Datei auswählt, gehen Sie wie folgt vor: 1. Rufen Sie den Webserver an, um einen Dienst zur Erzeugung der erforderlichen Parameter zu initiieren

  1. Rufen Sie in diesem Dienst den AWS IAM-Dienst an, um eine temporäre Gutschrift zu erhalten

  2. Wenn Sie die Berechtigung haben, erstellen Sie eine Bucket-Richtlinie (Base 64-kodierte Zeichenfolge). Signieren Sie dann die Bucket-Richtlinie mit dem temporären geheimen Zugriffsschlüssel, um die endgültige Signatur zu erstellen

  3. senden Sie die erforderlichen Parameter an die Benutzeroberfläche zurück

  4. Erstellen Sie anschließend ein HTML-Formularobjekt, legen Sie die erforderlichen Parameter und POST fest.

Ausführliche Informationen finden Sie unter https://wordpress1763.wordpress.com/2016/10/03/browser-based-upload-aws-signature-version-4/

8
RajeevJ

Um eine Signatur zu erstellen, muss ich meinen geheimen Schlüssel verwenden. Aber alle Dinge. geschieht auf einer Client-Seite, sodass der geheime Schlüssel leicht aufgedeckt werden kann von der Seitenquelle (auch wenn ich meine Quellen verschleiere/verschlüssele).

Hier haben Sie missverstanden. Der Grund, warum digitale Signaturen verwendet werden, ist, dass Sie etwas als richtig prüfen können, ohne Ihren geheimen Schlüssel preiszugeben. In diesem Fall wird die digitale Signatur verwendet, um zu verhindern, dass der Benutzer die Richtlinie ändert, die Sie für den Formularbeitrag festgelegt haben.

Digitale Signaturen wie hier werden für die Sicherheit rund um das Web verwendet. Wenn jemand (NSA?) Wirklich in der Lage wäre, sie zu brechen, hätten sie viel größere Ziele als Ihr S3-Bucket :)

4
OlliM

Wenn Sie keinen serverseitigen Code haben, hängt Ihre Sicherheit von der Sicherheit des Zugriffs auf Ihren JavaScript-Code auf der Clientseite ab (dh jeder, der über den Code verfügt, kann etwas hochladen).

Ich würde also empfehlen, einfach einen speziellen S3-Bucket zu erstellen, der öffentlich beschreibbar ist (aber nicht lesbar ist), so dass Sie keine signierten Komponenten auf der Clientseite benötigen.

Der Bucket-Name (z. B. GUID) ist Ihre einzige Verteidigung gegen böswillige Uploads (aber ein potenzieller Angreifer könnte Ihren Bucket nicht zum Übertragen von Daten verwenden, da er nur an ihn schreibt)

2

Ich habe einen einfachen Code angegeben, um Dateien aus dem Javascript-Browser in AWS S3 hochzuladen und alle Dateien im S3-Bucket aufzulisten.

Schritte:

  1. So erstellen Sie Create IdentityPoolId http://docs.aws.Amazon.com/cognito/latest/developerguide/identity-pools.html

    1. Gehe zur S3-Konsolenseite und öffne die cors-Konfiguration aus den Bucket-Eigenschaften und schreibe den folgenden XML-Code in diese.

      <?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. Erstellen Sie eine HTML-Datei mit folgendem Code, ändern Sie die Anmeldeinformationen, öffnen Sie die Datei im Browser und genießen Sie es.

      <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

So erstellen Sie ein Richtliniendokument mit Knoten und 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;

Das verwendete Konfigurationsobjekt wird in SSM Parameter Store gespeichert und sieht so aus

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

Wenn Sie einen Drittanbieter-Service nutzen möchten, unterstützt auth0.com diese Integration. Der Auth0-Dienst tauscht eine SSO-Dienstauthentifizierung eines Drittanbieters gegen ein temporäres AWS-Sitzungstoken aus, dessen Berechtigungen eingeschränkt sind.

Siehe: https://github.com/auth0-samples/auth0-s3-sample/
und die auth0-Dokumentation.

0
Jason