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.

4voto

Nilesh Pawar Points 567

J'ai donné un code simple pour télécharger des fichiers depuis un navigateur Javascript vers AWS S3 et lister tous les fichiers dans le seau S3.

Des pas :

  1. Pour savoir comment créer Créer IdentityPoolId http://docs.aws.amazon.com/cognito/latest/developerguide/identity-pools.html

    1. Allez sur la page de la console de S3 et ouvrez la configuration des cors à partir des propriétés du seau et écrivez le code XML suivant dans cette configuration.

      <?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. Créez un fichier HTML contenant le code suivant : modifiez les informations d'identification, ouvrez le fichier dans le navigateur et profitez-en.

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

2voto

Ruediger Jungbeck Points 694

Si vous n'avez pas de code côté serveur, votre sécurité dépend de la sécurité de l'accès à votre code JavaScript côté client (c'est-à-dire que tous ceux qui ont le code peuvent télécharger quelque chose).

Je vous recommande donc de créer simplement un seau S3 spécial qui est accessible en écriture publique (mais pas en lecture), de sorte que vous n'ayez pas besoin de composants signés du côté client.

Le nom du seau (un GUID par exemple) sera votre seule défense contre les téléchargements malveillants (mais un attaquant potentiel ne pourrait pas utiliser votre seau pour transférer des données, car il n'est accessible qu'en écriture).

2voto

Samir Patel Points 352

Voici comment générer un document de politique à l'aide d'un nœud et de sans serveur

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

L'objet de configuration utilisé est stocké dans le SSM Magasin de paramètres et ressemble à ceci

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

0voto

Jason Points 661

Si vous souhaitez utiliser un service tiers, auth0.com prend en charge cette intégration. Le service auth0 échange l'authentification du service SSO d'une tierce partie contre un jeton de session temporaire AWS avec des autorisations limitées.

Voir : https://github.com/auth0-samples/auth0-s3-sample/
et la documentation de auth0.

-1voto

Le Dong Thuc Points 76

J'ai créé une interface utilisateur basée sur VueJS et Go pour télécharger le binaire dans AWS Secrets Manager. https://github.com/ledongthuc/awssecretsmanagerui

Il est utile de télécharger un fichier sécurisé et de mettre à jour les données textuelles plus facilement. Vous pouvez y faire référence si vous le souhaitez.

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