1

AWS.S3.upload() 403 Fehler bei dem Versuch, Mehrteiliger Hochladen

TL; DR

Wenn Sie eine Datei direkt aus dem Browser zu laden versuchen, die s3.upload() Methode durch das AWS SDK für Sie Javascript in Ihrem Browser zur Verfügung gestellt mit kombiniert mit temporäre IAM-Credentials generiert durch einen Anruf an AWS.STS.getFederationToken() alles funktioniert gut für nicht-multipart Uploads und für die ersten Teil eines mehrteiligen Uploads.

Aber wenn s3.upload() Versuche, den zweiten Teil eines mehrteiligen Upload S3 mit einem 403 Access Denied Fehler reagiert zu senden.

Warum?



Der Kontext

ich einen Uploader in meiner app bin Umsetzung, die mehrteiliger ermöglichen (chunked) uploads direkt aus dem Browser auf meinem S3 Eimer.

Um dies zu erreichen, verwende ich die s3.upload() Methode der AWS SDK for Javascript in the Browser, die ich verstehe, um nicht mehr als Zucker für seine zugrunde liegende Verwendung von new AWS.S3.ManagedUpload() zu sein.

Eine einfache Illustration dessen, was ich bin versucht, finden Sie hier: https://aws.amazon.com/blogs/developer/announcing-the-amazon-s3-managed-uploader-in-the-aws-sdk-for-javascript/

Außerdem bin ich von meiner API-Schicht temporäre IAM Benutzer Anmeldeinformationen vend auch AWS.STS.getFederationToken() als Mittel über die Börse zu ermächtigen, .

Die 1,2,3:

  1. Der Benutzer initiiert einen Upload durch eine Datei über einen Standard-HTML <input type="file"> wählen.
  2. Dies löst eine erste Anforderung an meine API-Ebene aus, um sicherzustellen, dass der Benutzer die erforderlichen Berechtigungen auf meinem eigenen System hat, um diese Aktion auszuführen. Wenn dies der Fall ist, ruft mein Server Policy param auf, die ihre Berechtigungen auf nichts anderes als das Hochladen beschränken die Datei zum angegebenen Schlüssel. Und dann die resultierenden temporären Guthaben an den Browser zurückgegeben.
  3. Nun, da der Browser die temporäre Kreditwürdigkeit hat, die er benötigt, kann er sie verwenden, um einen neuen AWS.S3 Client zu erstellen und dann die AWS.S3.upload() Methode auszuführen, um einen (vermeintlich) automatischen mehrteiligen Upload der Datei durchzuführen.



The Code

api.myapp.com/vendUploadCreds.js

Dies ist die Methode, API-Schicht genannt, die den temporären Hochladen creds erzeugt und vends. An diesem Punkt wurde der Account bereits authentifiziert und autorisiert, die Credits zu erhalten und die Datei hochzuladen.

module.exports = function vendUploadCreds(request, response) { 

    var account = request.params.account; 
    var file = request.params.file; 
    var bucket = 'cdn.myapp.com'; 

    var sts = new AWS.STS({ 
     AccessKeyId : process.env.MY_AWS_ACCESS_KEY_ID, 
     SecretAccessKey : process.env.MY_AWS_SECRET_ACCESS_KEY 
    }); 

    /// The following policy is *exactly* the same as the S3 policy 
    /// attached to the IAM user that executes this STS request. 

    var policy = { 
     Version : '2012-10-17', 
     Statement : [ 
      { 
       Effect : 'Allow', 
       Action : [ 
        's3:ListBucket', 
        's3:ListBucketMultipartUploads', 
        's3:ListBucketVersions', 
        's3:ListMultipartUploadParts', 
        's3:AbortMultipartUpload', 
        's3:GetObject', 
        's3:GetObjectVersion', 
        's3:PutObject', 
        's3:PutObjectAcl', 
        's3:PutObjectVersionAcl', 
        's3:DeleteObject', 
        's3:DeleteObjectVersion' 
       ], 
       Resource : [ 
        'arn:aws:s3:::' + bucket + '/' + account._id + '/files/' + file.name 
       ], 
       Condition : { 
        StringEquals : { 
         's3:x-amz-acl' : ['private'] 
        } 
       } 
      } 
     ] 
    }; 

    sts.getFederationToken({ 
     DurationSeconds : 129600, /// 36 hours 
     Name : account._id + '-uptoken', 
     Policy : JSON.stringify(policy) 
    }, function(err, data) { 

     if (err) console.log(err, err.stack); // an error occurred 

     response.send(data); 

    }); 

} 


console.myapp.com/uploader.js

Dies ist eine abgeschnittene Darstellung des uploader auf dem Browser-Seite, die die API vendUploadCreds Methode ruft zuerst und verwendet dann die resultierende temporäre Creds, um den mehrteiligen Upload auszuführen.

uploader.getUploadCreds(account, file) { 

    /// A request is sent to api.myapp.com/vendUploadCreds 
    /// Upon successful response, the creds are returned. 

    request('https://api.myapp.com/vendUploadCreds', { 
     params : { 
      account : account, 
      file : file 
     } 
    }, function(error, data) { 
     upload.credentials = data.credentials; 
     this.uploadFile(upload); 
    }); 

} 

uploader.uploadFile : function(upload) { 

    var uploadID = upload.id; 

    /// The `upload` object coming through via the args has 
    /// a `credentials` property containing the creds obtained 
    /// via the `vendUploadCreds` method above. 

    var credentials = new AWS.Credentials({ 
     accessKeyId : upload.credentials.AccessKeyId, 
     secretAccessKey : upload.credentials.SecretAccessKey, 
     sessionToken : upload.credentials.SessionToken 
    }); 

    AWS.config.region = 'us-east-1'; 

    var s3 = new AWS.S3({ 
     credentials, 
     signatureVersion : 'v2', /// 'v4' also attempted 
     params : { 
      Bucket : 'cdn.myapp.com' 
     } 
    }); 

    var uploader = s3.upload({ 
     Key : upload.key, 
     ACL : 'private', 
     ContentType : upload.file.type, 
     Body : upload.file 
    },{ 
     queueSize : 3, 
     partSize : 1024 * 1024 * 5 
    }); 

    uploader.on('httpUploadProgress', function(event) { 
     var total = event.total; 
     var loaded = event.loaded; 
     var percent = loaded/total; 
     percent = Math.ceil(percent * 100); 
     console.log('Uploaded ' + percent + '% of ' + upload.key); 
    }); 

    uploader.send(function(error, result) { 
     console.log(error, result); 
    }); 

} 


cdn.myapp.com S3 Bucket CORS Konfiguration

So weit ich das beurteilen kann, ist dies weit offen, so CORS nicht das Problem sein sollte?

<?xml version="1.0" encoding="UTF-8"?> 
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> 
<CORSRule> 
    <AllowedOrigin>*</AllowedOrigin> 
    <AllowedMethod>GET</AllowedMethod> 
    <AllowedMethod>PUT</AllowedMethod> 
    <AllowedMethod>POST</AllowedMethod> 
    <AllowedMethod>DELETE</AllowedMethod> 
    <MaxAgeSeconds>3000</MaxAgeSeconds> 
    <ExposeHeader>ETag</ExposeHeader> 
    <AllowedHeader>*</AllowedHeader> 
</CORSRule> 
</CORSConfiguration> 


Der Fehler

Okay, wenn ich eine Datei laden versuchen, wird es wirklich verwirrend:

  1. Jede Datei unter 5Mb Uploads just fine. Dateien unter 5 MB (die minimale Teilegröße für einen S3-Multipart-Upload) erfordern keinen mehrteiligen Upload, daher sendet s3.upload() sie als Standard-PUT-Anforderung. Macht Sinn, und es gelingt ihnen gut.
  2. Jede Datei über 5Mb scheint zu laden, aber nur für den ersten Teil. Dann, wenn s3.upload() versucht, den zweiten Teil zu senden, antwortet S3 mit einem 403 Access Denied Fehler.

Ich hoffe, dass Sie einen Fan von Informationen sind, weil hier ein Abbild der Fehler, die ich von Chrome, wenn ich versuche, Astrud Gilberto Melancholie klassischen "So Nice (Summer Samba)" (MP3, 6 zu.6Mb):

Allgemeine

Request URL:https://s3.amazonaws.com/cdn.myapp.com/5a2cbda70b9b741661ad98df/files/Astrud-Gilberto-So-Nice-1512903188573.mp3?partNumber=2&uploadId=ljaviv9n25aRKwc4HKGhBbbXTWI3wSGZwRRi39fPSEvU2dcM9G7gO6iu5w7va._dMTZil4e_b53Iy5ngojJqRr5F6Uo_ZXuF27yaqizeARmUVf5ZVeah8ZjYwkZV8C0i3rhluYoxFHUPxlLMjaKLww-- 
Request Method:PUT 
Status Code:403 Forbidden 
Remote Address:52.216.165.77:443 
Referrer Policy:no-referrer-when-downgrade 

Antwortheader

Access-Control-Allow-Methods:GET, PUT, POST, DELETE 
Access-Control-Allow-Origin:* 
Access-Control-Expose-Headers:ETag 
Access-Control-Max-Age:3000 
Connection:close 
Content-Type:application/xml 
Date:Sun, 10 Dec 2017 10:53:12 GMT 
Server:AmazonS3 
Transfer-Encoding:chunked 
Vary:Origin, Access-Control-Request-Headers, Access-Control-Request-Method 
x-amz-id-2:0Mzo7b/qj0r5Is7aJIIJ/U2VxTTulWsjl5kJpTnEhy/B0fQDlRuANcursnxI71LA16AdePVSc/s= 
x-amz-request-id:DA008A5116E0058F 

Anfrageheaders

Accept:*/* 
Accept-Encoding:gzip, deflate, br 
Accept-Language:en-US,en;q=0.9 
Authorization:AWS ASIAJAR5KXKAOPTC64PQ:Wo9lbflZuVVS9+UTTDSjU0iPUbI= 
Cache-Control:no-cache 
Connection:keep-alive 
Content-Length:1314943 
Content-Type:application/octet-stream 
DNT:1 
Host:s3.amazonaws.com 
Origin:http://132.12.23.145:8080 
Pragma:no-cache 
Referer:http://132.12.23.145:8080/ 
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36 
X-Amz-Date:Sun, 10 Dec 2017 10:53:09 GMT 
x-amz-security-token:FQoDYXdzENT//////////wEaDK9srK2+5FN91W+T+SLSA/LdEwpOiY7wDkgggOMhuGEiqIXAQrFMk/EqvZFl8Npqx414WsL9E310rj5mU1RGXsxuN+ers1r6NVPpJIlXSDG7bnwlGabejNvDL9vMX5HJHGbZOEVUoaL60/T5NM+0TZtH61vHAEVmRVFKOB0tSez8TEU1jQ2cJME0THn5RuV/6CuIpA9dlEYO7/ajB5UKT3F1rBkt12b0DeWmKG2pvTJRwa8nrsF6Hk6dk1B1Hl1fUwAh9rD17O9Roi7MFLKisPH+96WX08liC8k+n+kPPOox6ZZM/lOMwlNinDjLc2iC+JD/6uxyAGpNbQ7OHAUsF7DOiMvw6Nv6PrImrBvnK439BhLOk1VXCfxxmtTWGim8TD1w1EciZcJhsuCMpDF8fMnhF/JFw3KNOJXHUtpTGRjNbOPcPojVs3FgIt+9MllIA0pGMr2bYmA3HvKewnhD2qeKkG3DPDIbpwuRoY4wIXCP5OclmoHp5nE5O94aRIvkBvS1YmqDQO+jTiI7/O7vlX63q9sGqdIA4nwzh5ASTRJhC2rKgxepFirEB53dCev8i9f1pwXG3/4H3TvPCLVpK94S7/csNJexJP75bPBpo4nDeIbOBKKIMuUDK1pQsyuGwuUolKS00QU= 
X-Amz-User-Agent:aws-sdk-js/2.164.0 callback 

Abfrage-Zeichenfolge Params

partNumber:2 
uploadId:ljaviv9n25aRKwc4HKGhBbbXTWI3wSGZwRRi39fPSEvU2dcM9G7gO6iu5w7va._dMTZil4e_b53Iy5ngojJqRr5F6Uo_ZXuF27yaqizeARmUVf5ZVeah8ZjYwkZV8C0i3rhluYoxFHUPxlLMjaKLww-- 

tatsächliche Antwort Körper

Und hier ist der Körper der Antwort von S3:

<?xml version="1.0" encoding="UTF-8"?> 
<Error><Code>AccessDenied</Code><Message>Access Denied</Message><RequestId>8277A4969E955274</RequestId><HostId>XtQ2Ezv0Wa81Rm2jymB5ZwTe+OHfwTcnNapYMgceqZCJeb75YwOa1AZZ5/10CAeVgmfeP0BFXnM=</HostId></Error> 


Die Fragen

  1. Es ist offensichtlich kein Problem mit den Krediten, die durch die sts.generateFederationToken() Anfrage erstellt wurden, denn wenn es dann wäre, würden die kleineren (nicht mehrteiligen) Uploads ebenfalls fehlschlagen, oder?
  2. Es ist offensichtlich kein Problem mit der CORS-Konfiguration auf dem cdn.myapp.com-Bucket, denn wenn es dann wäre, würden die kleineren (nicht mehrteiligen) Uploads ebenfalls fehlschlagen, oder?
  3. Warum würde S3 partNumber=1 eines mehrteiligen Uploads akzeptieren, und dann 403 auf dem partNumber=2 des gleichen Uploads?

Antwort

0

Eine Lösung

Nach vielen Stunden Ringen mit diesem Ich fand heraus, dass das Problem mit dem Condition Block der IAM-Politik, die ich sendete durch als Policy param meiner AWS.STS.getFederationToken() war anfordern. Insbesondere sendet AWS.S3.upload() sendet nur eine x-amz-acl Header für die erstePUT Anfrage, die der Anruf an S3.initiateMultipartUpoad ist.

Der x-amz-acl Header ist nicht für die eigentlichen Teile des Upload in den nachfolgenden PUT Anfragen enthalten.

Ich hatte die folgende Bedingung auf meine IAM-Politik, die ich, dass alle Uploads zu gewährleisten, wurde verwendet, muss eine ACL von ‚privaten‘ haben:

Condition : { 
    StringEquals : { 
     's3:x-amz-acl' : ['private'] 
    } 
} 

So die anfängliche PUT Anfrage an S3.initiateMultipartUpload war in Ordnung , aber die nachfolgenden PUT s sind fehlgeschlagen, weil sie nicht den Header x-amz-acl hatten.

Die Lösung bestand darin, die Richtlinie, die ich an den temporären Benutzer anfügte, zu bearbeiten und die s3:PutObject-Berechtigung in eine eigene Anweisung zu verschieben und dann die Bedingung so anzupassen, dass sie nur dann gilt, wenn der Zielwert existiert. Die endgültige Richtlinie sieht so aus:

var policy = { 
    Version : '2012-10-17', 
    Statement : [ 
     { 
      Effect : 'Allow', 
      Action : [ 
       's3:PutObject' 
      ], 
      Resource : [ 
       'arn:aws:s3:::' + bucket + '/' + account._id + '/files/' + file.name 
      ], 
      Condition : { 
       StringEqualsIfExists : { 
        's3:x-amz-acl' : ['private'] 
       } 
      } 
     }, 
     { 
      Effect : 'Allow', 
      Action : [ 
       's3:AbortMultipartUpload' 
      ], 
      Resource : [ 
       'arn:aws:s3:::' + bucket + '/' + account._id + '/files/' + file.name 
      ] 
     } 
    ] 
}; 

Hoffentlich hilft das jemand anderem davon, drei Tage darauf zu verschwenden.

Verwandte Themen