S3 Preshared URL - generated from EC2 - role credentials expiration

0

Hi, Apologies for the long question but I want to be quite specific. This topic is also covered in this re:Post answer but I could not find an answer to the specific case below.

I am writing a Python application that needs to upload objects to S3 and generate S3 Preshared URLs for those objects. The application runs on an EC2 instance with an Instance Profile (for obvious reasons I do not want to use IAM User credentials inside of the EC2 instance). The role associated with the instance profile has a maximum of 12 hours. I want the Presigned URLs to have a 1 hour validity.

The main problem with this setup is that according to the presigned URL documentation (quote) :* If you created a presigned URL by using a temporary token, then the URL expires when the token expires, even if you created the URL with a later expiration time.*

The corner case I want to avoid is creating a pre-shared URL (I want validity of one hour) when the credentials associated with the Instance role will expire in, say, 30 minutes. I understand that the presigned URL will be valid only for 30 minutes. This is unacceptable for the application customers.

The solution I am looking at is: every time my application needs to create a presigned URL it would check first the current expiry time of the credentials of the EC2 instance it runs on. If less than an hour it would renew the credentials (for, say, another 12 hours). Then create the preshared URL with 1 hour validity. Looks simple enough.

I asked Q for a solution and it provided me a python function I quote below. Unfortunately it does not work. When trying to access credentials.expiration it fails with error: AttributeError: 'DeferredRefreshableCredentials' object has no attribute 'expiration'.

After a (long) conversation with Q, I learned that apparently the class DeferredRefreshableCredentials is an undocumented internal class (visible in the boto3 source code).

At last, my questions:

  1. Have I understood the issue correctly? Am I missing something?
  2. is there a way to implement my desired solution above (check credentials, renew if needed, then generate preshared URL) without having to get into the guts of the boto3 implementation? I actually need a solution in Python and Java so if possible I would like a solution with standard SDK calls.

Thank you

Non working python function provided by Q:

def renew_credentials_if_needed(credentials):
  expiration = credentials.expiration
  if expiration < datetime.datetime.now() + datetime.timedelta(minutes=30):
    # Credentials expire in less than 30 minutes
    sts_client = boto3.client('sts')
    response = sts_client.assume_role(
      RoleArn='my-role-arn',
      RoleSessionName="Session1",
      DurationSeconds=3600
    )
    return response['Credentials']
  else:
    # Credentials are valid, no need to renew
    return credentials
2 Answers
2
Accepted Answer

Hi Rafa,

The simplest way to achieve your desired solution would be to obtain new credentials by assuming a role using STS for every pre-signed URL you want to create, and with those assumed credentials create the pre-signed URL. That way you have total control over the session expiry time. It's pretty much the same code you already have, but instead of checking the expiry time, just create temporary credentials for every pre-signed URL. For reference the assume_role doc is here https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/sts/client/assume_role.html#

AWS
answered 5 months ago
profile picture
EXPERT
reviewed 5 months ago
0

Thank you Pablo for your reply. I have verified your solution works. For the temporary credentials I created a new IAM role different from the one assigned to the instance profile. Permissions for GetObject and PutObject granted through bucket policy to the new role as principal.

The following "toy" script (no error checking, hardcoded values) illustrates the sequence of calls.

import boto3

# role info
role_to_assume = 'MyRole' 
account_id = "123456789012"
role_duration = 7200

# file and preshared URL info
bucket = "foo-1234"
path = "bar"
file_name = "file6.txt"
object_key = path + "/" + file_name
region = 'eu-west-1'
url_expire = 3600

# Create an STS client
sts_client = boto3.client('sts')

# Assume role
assumed_role_object=sts_client.assume_role(
    RoleArn="arn:aws:iam::" + account_id + ":role/"+role_to_assume,
    RoleSessionName="RoleForPresharedURL",
    DurationSeconds = role_duration    
)
temp_credentials = assumed_role_object['Credentials']

# S3 client with temp credentials from role
s3_client = boto3.client(
    's3',
    aws_access_key_id=temp_credentials['AccessKeyId'],
    aws_secret_access_key=temp_credentials['SecretAccessKey'],
    aws_session_token=temp_credentials['SessionToken'],
    region_name=region
)

# upload file to bucket
s3_client.upload_file(file_name, bucket, object_key)

# presigned URL 
presigned_url = s3_client.generate_presigned_url(
    'get_object',
    Params = {'Bucket': bucket, 'Key': object_key},
    ExpiresIn = url_expire
)
print(presigned_url)
answered 5 months ago

You are not logged in. Log in to post an answer.

A good answer clearly answers the question and provides constructive feedback and encourages professional growth in the question asker.

Guidelines for Answering Questions