178 votes

Téléchargement direct de fichiers Amazon S3 depuis le navigateur du client - divulgation de la clé privée

J'implémente un téléchargement direct de fichiers de la machine cliente vers Amazon S3 via l'API REST en utilisant uniquement JavaScript, sans aucun code côté serveur. Tout fonctionne bien mais une chose me préoccupe...

Lorsque j'envoie une demande à l'API REST d'Amazon S3, je dois signer la demande et mettre une signature dans le dossier de l'API. Authentication en-tête. Pour créer une signature, je dois utiliser ma clé secrète. Mais tout se passe du côté client, donc la clé secrète peut être facilement révélée à partir de la source de la page (même si j'obfusque/chiffre mes sources).

Comment puis-je gérer cela ? Et est-ce un problème ? Peut-être puis-je limiter l'utilisation d'une clé privée spécifique aux seuls appels d'API REST à partir d'une origine CORS spécifique et aux seules méthodes PUT et POST ou peut-être lier la clé au seul S3 et à un seau spécifique ? Peut-être existe-t-il d'autres méthodes d'authentification ?

La solution "sans serveur" est idéale, mais je peux envisager d'impliquer un certain traitement côté serveur, à l'exception du téléchargement d'un fichier sur mon serveur et de son envoi à S3.

232voto

secretmike Points 4517

Je pense que ce que vous voulez, c'est des téléchargements par navigateur en utilisant POST.

En fait, vous avez besoin d'un code côté serveur, mais il ne fait que générer des politiques signées. Une fois que le code côté client a la politique signée, il peut télécharger en utilisant POST directement vers S3 sans que les données passent par votre serveur.

Voici les liens vers les documents officiels :

Diagramme : http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingHTTPPOST.html

Exemple de code : http://docs.aws.amazon.com/AmazonS3/latest/dev/HTTPPOSTExamples.html

La police signée sera placée dans votre code html sous la forme suivante :

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

Remarquez que l'action FORM envoie le fichier directement à S3 - pas via votre serveur.

Chaque fois que l'un de vos utilisateurs souhaite télécharger un fichier, vous devez créer le fichier POLICY et SIGNATURE sur votre serveur. Vous renvoyez la page au navigateur de l'utilisateur. L'utilisateur peut alors télécharger un fichier directement sur S3 sans passer par votre serveur.

Lorsque vous signez la politique, vous la faites généralement expirer après quelques minutes. Cela oblige vos utilisateurs à communiquer avec votre serveur avant de télécharger. Vous pouvez ainsi surveiller et limiter les téléchargements si vous le souhaitez.

Les seules données qui entrent ou sortent de votre serveur sont les URL signées. Vos clés secrètes restent secrètes sur le serveur.

45voto

Joomler Points 1447

Vous pouvez le faire par AWS S3 Cognito essayez ce lien ici :

http://docs.aws.amazon.com/AWSJavaScriptSDK/guide/browser-examples.html#Amazon_S3

Essayez aussi ce code

Changez simplement la région, l'IdentityPoolId et le nom de votre seau.

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

Pour plus de détails, veuillez consulter - Github

16voto

BraveNewCurrency Points 4096

Vous dites que vous voulez une solution "sans serveur". Mais cela signifie que vous n'avez pas la possibilité de mettre un seul de "votre" code dans la boucle. (REMARQUE : dès que vous donnez votre code à un client, c'est "son" code maintenant.) Verrouiller CORS ne va pas aider : Les gens peuvent facilement écrire un outil non basé sur le web (ou un proxy basé sur le web) qui ajoute le bon en-tête CORS pour abuser de votre système.

Le gros problème est que vous ne pouvez pas faire la différence entre les différents utilisateurs. Vous ne pouvez pas permettre à un utilisateur de lister/accéder à ses fichiers, mais empêcher les autres de le faire. Si vous détectez un abus, il n'y a rien que vous puissiez faire à part changer la clé. (Que l'attaquant peut vraisemblablement obtenir à nouveau).

Votre meilleure chance est de créer un "utilisateur IAM" avec une clé pour votre client javascript. Ne lui donnez qu'un accès en écriture à un seul seau. (mais idéalement, n'activez pas l'opération ListBucket, cela la rendra plus attractive pour les attaquants).

Si vous aviez un serveur (même une simple micro instance à 20 $/mois), vous pourriez signer les clés sur votre serveur tout en surveillant/prévenant les abus en temps réel. Sans serveur, le mieux que vous puissiez faire est de surveiller périodiquement les abus après coup. Voici ce que je ferais :

1) faire tourner périodiquement les clés pour cet utilisateur IAM : Chaque nuit, générez une nouvelle clé pour cet utilisateur IAM, et remplacez la clé la plus ancienne. Comme il y a 2 clés, chaque clé sera valide pendant 2 jours.

2) activez la journalisation S3, et téléchargez les journaux toutes les heures. Définissez des alertes sur "trop de téléchargements" et "trop de téléchargements". Vous voudrez vérifier à la fois la taille totale des fichiers et le nombre de fichiers téléchargés. Et vous voudrez surveiller à la fois les totaux globaux et les totaux par adresse IP (avec un seuil inférieur).

Ces contrôles peuvent être effectués "sans serveur" car vous pouvez les exécuter sur votre bureau. (c.-à-d. que S3 fait tout le travail, ces processus sont juste là pour vous alerter en cas d'abus de votre seau S3 afin que vous ne receviez pas d'avertissement. géant Facture AWS à la fin du mois).

14voto

RajeevJ Points 141

Pour ajouter plus d'informations à la réponse acceptée, vous pouvez vous référer à mon blog pour voir une version du code en cours d'exécution, utilisant AWS Signature version 4.

Je vais résumer ici :

Dès que l'utilisateur sélectionne un fichier à télécharger, procédez comme suit : 1. Faites un appel au serveur web pour initier un service afin de générer les paramètres requis.

  1. Dans ce service, faites un appel au service AWS IAM pour obtenir une accréditation temporaire.

  2. Une fois que vous avez l'accréditation, créez une politique de seau (chaîne codée en base 64). Ensuite, signez la politique du seau avec la clé d'accès secrète temporaire pour générer la signature finale.

  3. renvoyer les paramètres nécessaires à l'interface utilisateur

  4. Une fois le message reçu, créez un objet de formulaire html, définissez les paramètres requis et postez-le.

Pour des informations détaillées, veuillez vous référer à https://wordpress1763.wordpress.com/2016/10/03/browser-based-upload-aws-signature-version-4/

4voto

OlliM Points 1450

Pour créer une signature, je dois utiliser ma clé secrète. Mais tout se passe du côté client, donc, la clé secrète peut être facilement révélée à partir de la source de la page (même si j'obfuscate/encrypte mes sources).

C'est là que vous avez mal compris. La raison pour laquelle les signatures numériques sont utilisées est que vous pouvez vérifier que quelque chose est correct sans révéler votre clé secrète. Dans ce cas, la signature numérique est utilisée pour empêcher l'utilisateur de modifier la politique que vous avez définie pour le message du formulaire.

Les signatures numériques telles que celle-ci sont utilisées pour la sécurité sur tout le web. Si quelqu'un (la NSA ?) était vraiment capable de les casser, ils auraient des cibles bien plus importantes que votre seau S3 :)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X