AWS S3 Upload Access Denied through Presigned post generated with AWS-SDK PHP
Asked Answered
S

1

5

i'm trying to upload a file (an image for my tests) to my s3 bucket with a pre-signed post generated with AWS SDK PHP. Firstable i generate the pre-signed post, then i manually create the request with given PostObjectV4 datas with Postman or via a simple html form... After filling everything, the request result in Access Denied :-(. The user associated with the client to generate the PostObjectV4 has Allowed s3:PutObject policy on the corresponding bucket.

I've already tried to :

  • Set my bucket as public write, and it works ! This indicates me a problem of permission / policy... Unfortunately my bucket don't have to be public...
  • Upload a file via aws command line, it works too

PHP code of pre-signed post generation (datas are in $postObject) :

$assetAwsS3Key = $this->getAssetAwsS3Key($asset);

$options = [
    ['starts-with', '$key', 'myDir/'],
];

// Optional: configure expiration time string
$expires = '+24 hours';

// Set some defaults for form input fields
$formInputs = ['acl' => 'private'];

$postObject = new PostObjectV4(
    $this->buildAwsS3UserClient(),
    $this->awsBucketName,
    $formInputs,
    $options,
    $expires
);

// Get attributes to set on an HTML form, e.g., action, method, enctype
$formAttributes = $postObject->getFormAttributes();

// Get form input fields. This will include anything set as a form input in
// the constructor, the provided JSON policy, your AWS access key ID, and an
// auth signature.
$formInputs = $postObject->getFormInputs();

return ['formAttributes' => $formAttributes, 'formInputs' => $formInputs];

My user (used for client generation) policy :

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:GetObject",
                "s3:DeleteObject"
            ],
            "Resource": [
                "arn:aws:s3:::awsBucketName/*"
            ]
        }
    ]
}

My simple html form for test upload:

<form method="post" action="https://my-bucket.s3.eu-west-3.amazonaws.com" enctype="multipart/form-data">
        <input type="hidden" name="key" value="myKey/sources/myImg.jpg" /><br />
        <input type="file"   name="file" /> <br />
        <input type="hidden" name="X-Amz-Credential" value="MYUSERACCESSKEY/20190510/eu-west-3/s3/aws4_request" />
        <input type="hidden" name="X-Amz-Algorithm" value="AWS4-HMAC-SHA256" /> 
        <input type="hidden" name="X-Amz-Date" value="20190510T132109Z" />
        <input type="hidden" name="Policy" value='MYBASE64ENCODEDPOLICY' />
        <input type="hidden" name="X-Amz-Signature" value="MYSIGNATURE" />
        <input type="submit" name="submit"/>
    </form>

Do you have an idea/clue why this upload fail while the bucket isn't public write-access allowed ?

Many thanks in advance !

Stamper answered 10/5, 2019 at 15:17 Comment(2)
Did you ever figure out the solution for this? I have the same question and would love to know the answer.Dissepiment
Hi @JeremyGlover please check my new answer to the post, this was too long for a comment, hope this we'll help or you got rid of the problem already ;)Stamper
S
3

I got rid of the problem, i'll share the changes i made here since my first post in order to get the upload to work. Please adapt it to your needs if necessary.

The AWS configuration used: - Same in-line policy attached to my user as in the first post(please be sure of the bucket name you set in the resource key) - S3 Bucket with no policy attached to it - S3 Bucket All Public Access Disabled

Here is my up to date php code for creating the postObject :

    $objectKey = $this->objectKeyGenerator->getObjectKey($object);

    $options = [
        ['bucket' => $this->getBucketName()],
        ['eq', '$key', $objectKey],
        ['acl' => 'private']
    ];

    // Optional: configure expiration time string
    $expires = '+2 hours';

    // Set some defaults for form input fields
    $formInputs = [
        'acl' => $acl,
        'key' => $objectKey
    ];

    $postObject = new PostObjectV4(
        $this->getS3Client(),
        $this->getBucketName(),
        $formInputs,
        $options,
        $expires
    );

    // Get attributes to set on an HTML form, e.g., action, method, enctype
    $formAttributes = $postObject->getFormAttributes();

    // Get form input fields. This will include anything set as a form input in
    // the constructor, the provided JSON policy, your AWS access key ID, and an
    // auth signature.
    $formInputs = $postObject->getFormInputs();

    return ['formAttributes' => $formAttributes, 'formInputs' => $formInputs];

What changes in the postObject generation compared to my first post :

  • I did not use 'starts-with' option anymore and generate the object key manually so that the uploaded file will have to be uploaded only at the generated key (if another key is given it will fail because of the key non-equality), but i think this is was not what was leading to the error and i'm pretty sure it will always work with 'starts-with' option.
  • bucket and acl attributes are now in the $options array as the Create PostObjectV4 sample code given in the AWS SDK for PHP Developer Guide (see [https://docs.aws.amazon.com/sdk-for-php/v3/developer-guide/s3-presigned-post.html]).
  • In the $formInputs array, key is present, so that i can get it back in the postObject (because the key submitted must be the one calculated). I think this is not mandatory.

I tried to reproduce the error by removing the bucket and acl from the $option array and this led to an '403 Forbidden error' but with a message stating 'Invalid according to Policy: Extra input fields: bucket' which i find not very obvious... I haven't investigate more on this error.

I'm also sharing my html form that changed a bit since first post, input fields order changed and acl field was added because required by the policy.

    <form method="post" action="https://my-bucket.s3.eu-west-3.amazonaws.com/" enctype="multipart/form-data">
    <input type="hidden" name="key" value="object/key.txt" /><br />
    <input type="hidden" name="acl" value="private"/>
    <input type="hidden" name="X-Amz-Credential" value="MYUSERACCESSKEY/20190510/eu-west-3/s3/aws4_request" />      
    <input type="hidden" name="X-Amz-Algorithm" value="AWS4-HMAC-SHA256" /> 
    <input type="hidden" name="X-Amz-Date" value="20200108T093921Z" />
    <input type="hidden" name="Policy" value='MYBASE64ENCODEDPOLICY' />
    <input type="hidden" name="X-Amz-Signature" value="MYSIGNATURE" />
    File: 
    <input type="file"   name="file" /> <br />
    <!-- The elements after this will be ignored -->
    <input type="submit" name="submit"/>

To conclude, in my original post i don't talk about my s3 bucket public-access and policy configuration and i think that the problem could have been here.

Hope this helps, feel free to ask for details if necessary.

Stamper answered 8/1, 2020 at 10:43 Comment(1)
Great work. I ended up getting mine working as well and used the key array key vs. the starts-with comparison because I wanted to ensure that the file got uploaded to the exact spot I was expecting it. For my form inputs, I'm setting the acl and content-type keys. For the form options, I'm setting the acl, bucket, key, and content-type keys. Thanks for following up and I'm glad to see we're both able to move forward.Dissepiment

© 2022 - 2024 — McMap. All rights reserved.