How to securely transfer files with presigned URLs
Securely sharing large files and providing controlled access to private data are strategic imperatives for modern organizations. In an era of distributed workforces and expanding digital landscapes, enabling efficient collaboration and information exchange is crucial for driving innovation, accelerating decision-making, and delivering exceptional customer experiences. At the same time, the protection of sensitive data remains a top priority since unauthorized exposure can have severe consequences for an organization.
<p><a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/using-presigned-url.html" target="_blank" rel="noopener">Presigned URLs</a> address this challenge while maintaining governance of internal resources. They provide time-limited access to objects in <a href="https://aws.amazon.com/pm/serv-s3/?gclid=Cj0KCQjwlN6wBhCcARIsAKZvD5hsq5VMgLNMhc552uF3GRbeQUqMGg-QcSxW-kmBZBRJubpqRK-AyQ8aAgFREALw_wcB&trk=fecf68c9-3874-4ae2-a7ed-72b6d19c8034&sc_channel=ps&ef_id=Cj0KCQjwlN6wBhCcARIsAKZvD5hsq5VMgLNMhc552uF3GRbeQUqMGg-QcSxW-kmBZBRJubpqRK-AyQ8aAgFREALw_wcB:G:s&s_kwcid=AL!4422!3!536452728638!e!!g!!amazon%20s3!11204620052!112938567994" target="_blank" rel="noopener">Amazon Simple Storage Service (Amazon S3)</a> buckets, which can be configured with granular permissions and expiration rules. Presigned URLs provide secure, temporary access to private Amazon S3 objects without exposing long-term credentials or requiring public access. This enables convenient collaboration and file transfers. </p>
<p>Presigned URLs are also used to exchange data among trusted business applications. This architectural pattern significantly reduces the payload size over the network by not using file transfers. However, it’s critical that you implement safeguards to help prevent inadvertent data exposure when using presigned URLs.</p>
<p>In this blog post, we provide prescriptive guidance for using presigned URLs in AWS securely. We show you best practices for generating and distributing presigned URLs, security considerations, and recommendations for monitoring usage and access patterns.</p>
<h2>Best practices for presigned URL generation</h2>
<p>Ensuring secure <a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/ShareObjectPreSignedURL.html" target="_blank" rel="noopener">presigned URL generation</a> is paramount. Aligning layered protections with business objectives facilitates secure, temporary data access. Strengthening generation policies is crucial for responsible use of presigned URLs. The following are some key technical considerations to securely generate presigned URLs:</p>
<ol>
<li>Tightly scope <a href="https://aws.amazon.com/iam" target="_blank" rel="noopener">AWS Identity and Access Management (IAM)</a> <a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html" target="_blank" rel="noopener">permissions</a> to the minimum required Amazon S3 actions and resources reduces unintended exposure.</li>
<li>Use temporary credentials such as roles instead of access keys whenever possible. If you’re using access keys, regularly rotating them as a safeguard against prolonged unauthorized access if credentials are compromised.</li>
<li>Use <a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/privatelink-interface-endpoints.html" target="_blank" rel="noopener">VPC endpoints for S3</a>, which allow your <a href="https://docs.aws.amazon.com/vpc/latest/userguide/what-is-amazon-vpc.html" target="_blank" rel="noopener">Amazon Virtual Private Cloud (Amazon VPC)</a> to connect directly to S3 buckets without going over internet address space. This improves isolation and security.</li>
<li>Require <a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_mfa.html" target="_blank" rel="noopener">multi-factor authentication (MFA)</a> for generation actions to add identity assurance.</li>
<li><a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/using-presigned-url.html#PresignedUrl-Expiration" target="_blank" rel="noopener">Just-in-time</a> creation improves security by making sure that presigned URLs have minimal lifetimes.</li>
<li>Adhere to <a href="https://docs.aws.amazon.com/wellarchitected/latest/framework/sec_permissions_least_privileges.html" target="_blank" rel="noopener">least privilege access</a> and <a href="https://docs.aws.amazon.com/whitepapers/latest/logical-separation/encrypting-data-at-rest-and--in-transit.html" target="_blank" rel="noopener">encrypting in transit</a> to mitigate downstream risks of unintended data access and exposure when using presigned URLs.</li>
<li>Use unique nonces (a random or sequential value) in URLs to help prevent unauthorized access. Verify nonces to prevent replay attacks. This makes guessing URLs difficult when combined with time-limited access.</li>
</ol>
<h2>Solution overview</h2>
<p>Presigned URLs simplify data exchange among trusted business applications, reducing the need for individual access management. Using unique, one-time nonces enhances security by minimizing unauthorized use and replay attacks. Access restrictions can further improve security by limiting presigned URL usage from a single application and revoking access after the limit is reached.</p>
<p>This solution implements two APIs:</p>
<ul>
<li>Presigned URL generation API</li>
<li>Access object API</li>
</ul>
<h3>Presigned URL generation API</h3>
<p>This API generates a presigned URL and a corresponding nonce, which are stored in <a href="https://docs.aws.amazon.com/dynamodb/" target="_blank" rel="noopener">Amazon DynamoDB</a>. It returns the URL for accessing the object API to customers.</p>
<p>The following architecture illustrates a serverless AWS solution that generates presigned URLs with a unique nonce for secure, controlled, one-time access to Amazon S3 objects. The <a href="https://aws.amazon.com/api-gateway/" target="_blank" rel="noopener">Amazon API Gateway</a> receives user requests, an <a href="https://aws.amazon.com/pm/lambda/?gclid=CjwKCAjwt-OwBhBnEiwAgwzrUujvEw_Yc240UGBb50fRdjG-9QyGFUKYY6rdYbHgXRJKPhF2hZjUtxoCUH0QAvD_BwE&trk=73f686c8-9606-40ad-852f-7b2bcafa68fe&sc_channel=ps&ef_id=CjwKCAjwt-OwBhBnEiwAgwzrUujvEw_Yc240UGBb50fRdjG-9QyGFUKYY6rdYbHgXRJKPhF2hZjUtxoCUH0QAvD_BwE:G:s&s_kwcid=AL!4422!3!651212652666!e!!g!!amazon%20lambda!909122559!45462427876" target="_blank" rel="noopener">AWS Lambda function</a> generates the nonce and a presigned URL, which is stored in DynamoDB for validation, and returns the presigned URL to the user.</p>
<div id="attachment_34448" class="wp-caption aligncenter">
<img aria-describedby="caption-attachment-34448" src="https://infracom.com.sg/wp-content/uploads/2024/06/img1.png" alt="Figure 1: Generating a presigned URL with a unique nonce" width="716" height="401" class="size-full wp-image-34448">
<p id="caption-attachment-34448" class="wp-caption-text">Figure 1: Generating a presigned URL with a unique nonce</p>
</div>
<p>The workflow includes the following steps:</p>
<ol>
<li>The producer application sends a request to generate a presigned URL for accessing an Amazon S3 object.</li>
<li>The request is received by Amazon API Gateway, which acts as the entry point for the API and routes the request to the appropriate Lambda function.</li>
<li>The Lambda function is invoked and performs the following tasks:
<ol>
<li>Generates a unique nonce for the presigned URL.</li>
<li>Creates a presigned URL for the requested S3 object, with a specific expiration time and other access conditions.</li>
<li>Stores the nonce and presigned URL in a DynamoDB table for future validation.</li>
</ol> </li>
<li>The producer application shares the nonce with other trusted applications it shares data with.</li>
</ol>
<h3>Access object API</h3>
<p>The consumer applications receive the nonce in the payload from the producer application. The consumer application uses the Access object API to access the Amazon S3 object. Upon the first access, the nonce is validated and then removed from DynamoDB. Thus, limiting use of the presigned URL to one. Subsequent requests to the URL are prohibited by the Lambda authorizer for added security.</p>
<p>The following architecture illustrates a serverless AWS solution that facilitates a secure one-time access to Amazon S3 objects through presigned URLs. It uses API Gateway as the entry point, Lambda authorizer for nonce validation, another Lambda function for access redirection by interacting with DynamoDB, and subsequent nonce removal to help prevent further access through the same URL.</p>
<div id="attachment_34449" class="wp-caption aligncenter">
<img aria-describedby="caption-attachment-34449" loading="lazy" src="https://infracom.com.sg/wp-content/uploads/2024/06/img2.png" alt="Figure 2: Solution for secure one-time Amazon S3 access through a presigned URL" width="721" height="501" class="size-full wp-image-34449">
<p id="caption-attachment-34449" class="wp-caption-text">Figure 2: Solution for secure one-time Amazon S3 access through a presigned URL</p>
</div>
<p>The workflow consists of the following steps:</p>
<ol>
<li>The consumer application requests the Amazon S3 object using the nonce it receives from the producer application.</li>
<li>API Gateway receives the request and validates it using the Lambda authorizer to determine whether the request is for a valid nonce or not.</li>
<li>The Lambda authorizer <span>ValidateNonce</span> function validates that the nonce exists in the DynamoDB table and sends the allow policy to API Gateway.</li>
<li>If Lambda authorizer finds the nonce doesn’t exist, it means the nonce has already been used and it sends a <span>deny</span> policy to API Gateway, thus not allowing the request to continue.</li>
<li>When API Gateway receives the allow policy, it routes the request to <span>the AccessObject</span> Lambda function.</li>
<li>The <span>AccessObject</span> Lambda function:
<ol>
<li>Retrieves the presigned URL (unique value) associated with the nonce from the DynamoDB table.</li>
<li>Deletes the nonce from the Dynamo DB table, thus invalidating the presigned URL for future use.</li>
<li>Redirects the request to the S3 object.</li>
</ol> </li>
<li>Subsequent attempts to access the S3 object with the same presigned URL will be denied by the Lambda authorizer function, because the nonce has been removed from the DynamoDB table.</li>
</ol>
<p>To help you understand the solution, we have developed the code in Python and AWS CDK, which can be downloaded from <a href="https://awsiammedia.s3.amazonaws.com/public/sample/2384-securely-enable-large-file-transfers-presigned-urls/lambda-s3presigned-nonce-api-main.zip" target="_blank" rel="noopener">Presigned URL Nonce Code</a>. This code illustrates how to generate and use presigned URLs between two business applications.</p>
<h3>Prerequisites</h3>
<p>To follow along with the post, you must have the following items:</p>
<h3>To implement the solution</h3>
<ol>
<li>Generate and save a strong random nonce string using the <span>GenerateURL</span> <a href="https://docs.aws.amazon.com/lambda/" target="_blank" rel="noopener">Lambda</a> function whenever you create a presigned URL programmatically:
<div class="hide-language">
<pre class="unlimited-height-code"><code class="lang-text">def create_nonce():
# Generate a nonce with 16 bytes (128 bits)
nonce = secrets.token_bytes(16)
return nonce
def store_nonce(nonce, url):
res = ddb_client.put_item(TableName=ddb_table, Item={‘nonce_id’: {‘S’: nonce.hex()}, ‘url’: {‘S’: url}})
return res
<li>Include the nonce as a URL parameter for easier extraction during validation. For example: The consumer application can request the Amazon S3 object using the following URL: <code>https:///stage/access-object?nonce=</code></li>
<li>When the object is accessed, use Lambda to extract the nonce in Lambda authorizer and validate if the nonce exists.
<ol>
<li>Look up the extracted nonce in the DynamoDB table to validate it matches a generated value:
<div class="hide-language">
<pre class="unlimited-height-code"><code class="lang-text">def validate_nonce(nonce):
try:
response = nonce_table.get_item(Key={'nonce_id': nonce})
print('The ddb key response is {}'.format(response))
except ClientError as e:
logger.error(e)
return False
if 'Item' in response:
# Nonce found
return True
else:
# Nonce not found
return False</code></pre>
</div> </li>
<li>If the nonce is valid and found in DynamoDB, allow access to the S3 object. The nonce is deleted from DynamoDB after one use to help prevent replay.
<div class="hide-language">
<pre class="unlimited-height-code"><code class="lang-text"> if validate_nonce(nonce):
logger.info('Valid nonce:'+ nonce)
return generate_policy('*', 'Allow', event['methodArn'])
else:
logger.info('Invalid nonce: '+ nonce)
return generate_policy('*', 'Deny', event['methodArn'])</code></pre>
</div> </li>
</ol> </li>
</ol>
<blockquote>
<p><strong>Note</strong>: You can improve nonce security by using Python’s secrets modules. <a href="https://docs.python.org/3/library/secrets.html#secrets.token_bytes" target="_blank" rel="noopener">Secrets.token_bytes(16)</a> generates a binary token and <a href="https://docs.python.org/3/library/secrets.html#secrets.token_hex" target="_blank" rel="noopener">secrets.token_hex(16)</a> produces a hexadecimal string. You can further improve your defense against <a href="https://en.wikipedia.org/wiki/Brute-force_attack" target="_blank" rel="noopener">brute-force attacks</a> by opting for a 32-byte nonce.</p>
</blockquote>
<h2>Clean up</h2>
<p>To avoid incurring future charges, use the following command from the AWS CDK toolkit to clean up all the resources you created for this solution:</p>
<ul>
<li><code>cdk destroy –force</code></li>
</ul>
<p>For more information about the AWS CDK toolkit, see <a href="https://docs.aws.amazon.com/cdk/latest/guide/cli.html#cli-ref" target="_blank" rel="noopener">Toolkit reference</a>.</p>
<h2>Best practices for presigned URL sharing and monitoring</h2>
<p>Ensuring proper governance when sharing presigned URLs broadly is crucial. These measures not only unlock the benefits of URL sharing safely, but also limit vulnerabilities. Continuously monitoring usage patterns and implementing automated revocation procedures further enhances protection. Balancing business needs with layered security is essential for fostering collaboration effectively.</p>
<ol>
<li>Use HTTPS <a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingEncryption.html" target="_blank" rel="noopener">encryption</a> enforced by TLS certificates to protect URLs in transit using and <a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/example-bucket-policies.html#example-bucket-policies-HTTP-HTTPS" target="_blank" rel="noopener">S3 policy</a>.</li>
<li>Define granular <a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/cors.html" target="_blank" rel="noopener">CORS permissions</a> on S3 buckets to restrict which sites can request presigned URL access.</li>
<li>Configure <a href="https://aws.amazon.com/waf/" target="_blank" rel="noopener">AWS WAF</a> rules to check that a nonce exists in headers, rate limit requests, and if origins are known, allow only approved IPs. Use AWS WAF to monitor and filter suspicious access patterns, while also sending API Gateway and <a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/ServerLogs.html" target="_blank" rel="noopener">S3 access logs</a> to <a href="https://aws.amazon.com/pm/cloudwatch/?gclid=CjwKCAjwt-OwBhBnEiwAgwzrUjo-zP77JGwpiORXoFBGl_MooynAINpy-IANsJNv9ehvuyikNP628xoCCU4QAvD_BwE&trk=f6e79447-9b4c-4310-8415-1a76de2de47f&sc_channel=ps&ef_id=CjwKCAjwt-OwBhBnEiwAgwzrUjo-zP77JGwpiORXoFBGl_MooynAINpy-IANsJNv9ehvuyikNP628xoCCU4QAvD_BwE:G:s&s_kwcid=AL!4422!3!629393325805!!!g!!!16080176300!133788122638" target="_blank" rel="noopener">Amazon CloudWatch</a> for monitoring:
<ol>
<li><strong>Enable WAF Logging</strong>: Use WAF logging to send the AWS WAF web ACL logs to <a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/WhatIsCloudWatchLogs.html" target="_blank" rel="noopener">Amazon CloudWatch Logs</a>, providing detailed access data to analyze usage patterns and detect suspicious activities.</li>
<li><strong>Validate nonces</strong>: Create a Lambda authorizer to require a properly formatted nonce in the headers or URL parameters. Block requests without an expected nonce. This prevents replay attacks that use invalid URLs.</li>
<li><strong>Implement rate limiting</strong>: If a nonce isn’t used, implement rate limiting by configuring AWS WAF <a href="https://docs.aws.amazon.com/waf/latest/developerguide/waf-rule-statement-type-rate-based.html" target="_blank" rel="noopener">rate-based rules</a> to allow normal usage levels and set thresholds to throttle excessive requests originating from a single IP address. The WAF will automatically start blocking requests from an IP when the defined rate limit is exceeded, which helps mitigate denial-of-service attempts that try to overwhelm the system with high request volumes.</li>
<li><strong>Configure IP allow lists</strong>: Configure IP allow lists by defining AWS WAF rules to only allow requests from pre-approved IP address ranges, such as your organization IPs or other trusted sources. The WAF will only permit access from the specified, trusted IP addresses, which helps block untrusted IPs from being able to abuse the shared presigned URLs.</li>
</ol> </li>
<li>Analyze logs and metrics by reviewing the access logs in CloudWatch Logs. This allows you to detect anomalies in request volumes, patterns, and originating IP addresses. Additionally, graph the relevant metrics over time and set CloudWatch alarms to be notified of any spikes in activity. Closely monitoring the log data and metrics helps identify potential issues or suspicious behavior that might require further investigation or action.</li>
</ol>
<p>By establishing consistent visibility into presigned URL usage, implementing swift revocation capabilities, and adopting adaptive security policies, your organization can maintain effective oversight and control at scale despite the inherent risks of sharing these temporary access mechanisms. Analytics around presigned URL usage, such as access logs, denied requests, and integrity checks, should also be closely monitored.</p>
<h2>Conclusion</h2>
<p>In this blog post, we showed you the potential of presigned URLs as a mechanism for secure data sharing. By rigorously adhering to best practices, implementing stringent security controls, and maintaining vigilant monitoring, you can strike a balance between collaborative efficiency and robust data protection. This proactive approach not only bolsters defenses against potential threats, but also establishes presigned URLs as a reliable and practical solution for fostering sustainable, secure data collaboration.</p>
<h2>Next steps</h2>
<ul>
<li>Conduct a comprehensive review of your current data sharing practices to identify areas where presigned URLs could enhance security and efficiency.</li>
<li>Implement the recommended best practices outlined in this blog post to securely generate, share, and monitor presigned URLs within your AWS environment.</li>
<li>Continuously monitor usage patterns and access logs to detect anomalies and potential security breaches and implement automated responses to mitigate risks swiftly.</li>
<li>Follow the <a href="https://aws.amazon.com/blogs/security/" target="_blank" rel="noopener">AWS Security Blog</a> to read more posts like this and stay informed about advancements in AWS security.</li>
</ul>
<p> <br>If you have feedback about this post, submit comments in the<strong> Comments</strong> section below. If you have questions about this post, <a href="https://console.aws.amazon.com/support/home" target="_blank" rel="noopener noreferrer">contact AWS Support</a>.</p>
<p><strong>Want more AWS Security news? Follow us on <a title="Twitter" href="https://twitter.com/AWSsecurityinfo" target="_blank" rel="noopener noreferrer">Twitter</a>.</strong></p>
<!-- '"` -->