I use the following Python/Boto code to generate one-time file-upload URLs to an Amazon S3 bucket:
from boto.s3.connection import S3Connection
def get_signed_upload_url():
s3 = S3Connection(ACCESS_KEY_ID, SECRET_ACCESS_KEY, is_secure=True)
return s3.generate_url(300, 'PUT', bucket=BUCKET, key=KEY,
headers={'Content-Type': 'text/plain'})
It has worked fine for several years, and it continues to work today.
But now, I'm in the process of converting to IAM roles — which will save me from hard-coding the ACCESS_KEY_ID and SECRET_ACCESS_KEY. Hence, I have removed the hard-coded keys, resulting in this code:
from boto.s3.connection import S3Connection
def get_signed_upload_url():
s3 = S3Connection(is_secure=True)
return s3.generate_url(300, 'PUT', bucket=BUCKET, key=KEY,
headers={'Content-Type': 'text/plain'})
With this code, whenever I generate a URL and do a PUT request to it from my client-side web app (via Ajax), I get an HTTP 400 Bad Request error from S3:
<Error>
<Code>InvalidToken</Code>
<Message>The provided token is malformed or otherwise invalid.</Message>
<!-- account-specific stuff removed -->
</Error>
Why is this happening?
Some extra details:
- This is deployed to an EC2 server that gets its IAM role assigned automatically. I've confirmed the authorization from the role is correctly made available to my Python code. All other parts of my code — including
generate_url()calls that generateGETURLs — work fine, using theACCESS_KEY_IDpassed automatically to the EC2 instance's environment. - Python 2.7.9 and Boto 2.38.0.
- The IAM role appears to have all appropriate permissions, as evidenced by the fact that other parts of my code are successfully interacting with S3 (getting items and creating items) using the role. It's only this particular
PUTthat's not working. - The front-end logic sends a
Content-Typeheader, which is why that header is in there. I tried removing theContent-Typeheader from this Boto code and from the front-end logic, but the problem still happens.