AWS.S3.upload() 403 Fehler bei dem Versuch, Mehrteiliger Hochladen
TL; DR
Wenn Sie eine Datei direkt aus dem Browser zu laden versuchen, dies3.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:
- Der Benutzer initiiert einen Upload durch eine Datei über einen Standard-HTML
<input type="file">
wählen. - 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. - 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 dieAWS.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:
- 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. - 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 einem403 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
- 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? - 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? - Warum würde S3
partNumber=1
eines mehrteiligen Uploads akzeptieren, und dann 403 auf dempartNumber=2
des gleichen Uploads?