<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Yuichi Sato</title>
    <description>The latest articles on DEV Community by Yuichi Sato (@sassssan68).</description>
    <link>https://dev.to/sassssan68</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3682693%2F484b6f46-a771-4425-a5de-ce2c21c25ed2.png</url>
      <title>DEV Community: Yuichi Sato</title>
      <link>https://dev.to/sassssan68</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sassssan68"/>
    <language>en</language>
    <item>
      <title>How to Stop Using Access Keys and Keep Them from Leaking</title>
      <dc:creator>Yuichi Sato</dc:creator>
      <pubDate>Tue, 21 Jul 2026 13:02:07 +0000</pubDate>
      <link>https://dev.to/aws-builders/how-to-stop-using-access-keys-and-keep-them-from-leaking-2k9a</link>
      <guid>https://dev.to/aws-builders/how-to-stop-using-access-keys-and-keep-them-from-leaking-2k9a</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This article was translated with the help of AI.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Are you issuing access keys just to use the AWS CLI?&lt;/p&gt;

&lt;p&gt;I used to do exactly that. Access keys are convenient, aren't they?&lt;br&gt;
Once you set them up with &lt;code&gt;aws configure&lt;/code&gt;, they keep working forever.&lt;br&gt;
But behind that convenience lies a scary story.&lt;br&gt;
A leak can mean a bill of tens of thousands of dollars, leaked data, and lost trust. And once it's out, you can't take it back.&lt;/p&gt;

&lt;p&gt;This article covers how to deal with access keys, how to stop using them, and how to keep them from leaking.&lt;/p&gt;

&lt;h1&gt;
  
  
  What Are Access Keys?
&lt;/h1&gt;

&lt;p&gt;Let's quickly review what access keys are.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An access key is a credential tied to an IAM user, made up of a pair: an access key ID and a secret access key.&lt;br&gt;
You use them to operate AWS from the AWS CLI or SDKs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pros
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Work anywhere, and the mechanism is simple&lt;/li&gt;
&lt;li&gt;Easy to get started&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Cons
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;No expiration&lt;/li&gt;
&lt;li&gt;Rotation is manual&lt;/li&gt;
&lt;li&gt;A leak leads directly to abuse&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And here's the reality waiting for you when one does leak.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A leaked access key gets used to spin up crypto mining or huge amounts of resources, resulting in a bill of hundreds of thousands to millions of dollars&lt;/li&gt;
&lt;li&gt;Depending on the permissions, the contents of S3 or your database get scraped clean, leaking personal data&lt;/li&gt;
&lt;li&gt;The breach gets reported, you're stuck apologizing, and the trust you've built up collapses in an instant&lt;/li&gt;
&lt;li&gt;Once information or a leaked access key is out on the internet, you can never fully take it back&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;"It's just one access key" can turn into something this big.&lt;br&gt;
That's why I say access keys are scary.&lt;/p&gt;

&lt;h1&gt;
  
  
  Scenarios and Alternatives
&lt;/h1&gt;

&lt;p&gt;So you want to stop using access keys. What should you use instead?&lt;br&gt;
There's an alternative prepared for each scenario.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Access key&lt;/th&gt;
&lt;th&gt;Alternative&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Developer&lt;/td&gt;
&lt;td&gt;Set up with &lt;code&gt;aws configure&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;aws login&lt;/code&gt; / &lt;code&gt;aws sso login&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CI/CD (GitHub Actions, etc.)&lt;/td&gt;
&lt;td&gt;Register in Secrets&lt;/td&gt;
&lt;td&gt;OIDC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AWS resources (EC2/Lambda/ECS)&lt;/td&gt;
&lt;td&gt;Embed in the resource&lt;/td&gt;
&lt;td&gt;IAM role&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;On-prem / external servers&lt;/td&gt;
&lt;td&gt;Distribute to servers&lt;/td&gt;
&lt;td&gt;IAM Roles Anywhere&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Let's go through them one by one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Developers Use aws login
&lt;/h2&gt;

&lt;p&gt;This is the case for us developers.&lt;br&gt;
What you use is &lt;code&gt;aws login&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/cli/latest/reference/login/" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/cli/latest/reference/login/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you authenticate in the browser, it automatically sets up temporary credentials for you.&lt;br&gt;
It works with the same feel as &lt;code&gt;aws sso login&lt;/code&gt;, which you use in environments that have IAM Identity Center.&lt;br&gt;
It's a good fit for environments where "setting up IAM Identity Center is overkill."&lt;br&gt;
With this, running your operations without access keys becomes easy to achieve.&lt;/p&gt;

&lt;h2&gt;
  
  
  CI/CD Uses OIDC
&lt;/h2&gt;

&lt;p&gt;This is the case for CI/CD like GitHub Actions.&lt;br&gt;
Instead of registering an access key in Secrets, you use OIDC.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_oidc.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_oidc.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It ties GitHub and an IAM role together and issues access permissions temporarily, so you can access AWS without any access key.&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS Resources Use IAM Roles
&lt;/h2&gt;

&lt;p&gt;This is the case for AWS resources like EC2, Lambda, and ECS.&lt;br&gt;
Instead of embedding an access key, you use an IAM role.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you attach a role, temporary credentials are handed over automatically, so you don't need to hold an access key.&lt;/p&gt;

&lt;h2&gt;
  
  
  On-Prem and External Servers Use IAM Roles Anywhere
&lt;/h2&gt;

&lt;p&gt;This is the case for operating from on-prem or servers outside AWS.&lt;br&gt;
Instead of distributing an access key, you use IAM Roles Anywhere.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/rolesanywhere/latest/userguide/introduction.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/rolesanywhere/latest/userguide/introduction.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It establishes trust with X.509 certificates and lets you obtain temporary credentials, so you can operate servers outside AWS without handing out access keys either.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Common Idea
&lt;/h2&gt;

&lt;p&gt;When you line them up like this, you notice a shared idea.&lt;br&gt;
Unlike long-term credentials with no expiration such as access keys, every one of them uses temporary credentials with an expiration.&lt;br&gt;
Shifting over to temporary credentials like this is what matters.&lt;/p&gt;

&lt;h1&gt;
  
  
  If You Still Have to Use Them
&lt;/h1&gt;

&lt;p&gt;"So is there even any scenario left where you'd use an access key?" you might think.&lt;br&gt;
If anything is left, it's roughly the stuff that can't be migrated.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Old tools that don't support roles or OIDC and only accept access keys&lt;/li&gt;
&lt;li&gt;Environments that can't manage certificates and can't set up IAM Roles Anywhere&lt;/li&gt;
&lt;li&gt;Legacy apps that can't be modified&lt;/li&gt;
&lt;li&gt;One-off, throwaway testing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are the "no way around it" situations. If you can't stop using them, then harden your defenses.&lt;/p&gt;

&lt;h1&gt;
  
  
  Keeping Them from Leaking
&lt;/h1&gt;

&lt;p&gt;This "keeping them from leaking" isn't only for people who keep using access keys.&lt;br&gt;
Secrets aren't just access keys.&lt;br&gt;
Database passwords and API tokens leak the same way.&lt;br&gt;
Even if you stop using access keys, "keeping secrets from leaking" follows you for life.&lt;/p&gt;

&lt;h2&gt;
  
  
  This Is How Secrets Leak
&lt;/h2&gt;

&lt;p&gt;So, how do you think secrets actually leak?&lt;br&gt;
Let me group it into three broad categories.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sharing and Handoff
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Committing to Git and pushing&lt;/li&gt;
&lt;li&gt;Pasting into Slack, chat, or email to share&lt;/li&gt;
&lt;li&gt;Showing up in slide decks, blog posts, or screenshots&lt;/li&gt;
&lt;li&gt;Sharing or uploading &lt;code&gt;.env&lt;/code&gt; or credentials files as-is&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Embedded in Distributables
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Embedded in frontend JS or mobile apps&lt;/li&gt;
&lt;li&gt;Left in a Docker image layer and shipped&lt;/li&gt;
&lt;li&gt;Mixed into build artifacts or installers meant for distribution&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Environment and Operations
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Printed in CI/CD build logs&lt;/li&gt;
&lt;li&gt;Pulled out after a local PC gets infected with malware&lt;/li&gt;
&lt;li&gt;Carried off by someone who leaves the company&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Defense in Three Layers
&lt;/h2&gt;

&lt;p&gt;Leaks happen in all sorts of ways, but what they share is that "careless mistakes" vastly outnumber "malicious attacks."&lt;br&gt;
Let's stop those careless mistakes with mechanisms.&lt;br&gt;
Specifically, defense in three layers: prevention, mitigation, and detection.&lt;/p&gt;

&lt;p&gt;Note that the following uses the most common case, mixing secrets into code (Git), as the example. The idea applies to other leak paths too.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prevention
&lt;/h3&gt;

&lt;p&gt;Stop it before you leak it, right at the entrance. There are two places to stop it: the client and the server.&lt;/p&gt;

&lt;p&gt;On the client, gitleaks.&lt;br&gt;
Set up a pre-commit hook, and it rejects secrets the moment you try to commit.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/gitleaks/gitleaks" rel="noopener noreferrer"&gt;https://github.com/gitleaks/gitleaks&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the server side, GitHub's Push Protection.&lt;br&gt;
It blocks changes that contain secrets at the moment you push.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.github.com/en/code-security/secret-scanning/introduction/about-push-protection" rel="noopener noreferrer"&gt;https://docs.github.com/en/code-security/secret-scanning/introduction/about-push-protection&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Mitigation
&lt;/h3&gt;

&lt;p&gt;Even if something leaks, minimize the damage.&lt;/p&gt;

&lt;p&gt;With a least-privilege policy, narrow down what that access key can do.&lt;br&gt;
With IAM policy Conditions (like &lt;code&gt;aws:SourceIp&lt;/code&gt;), limit where access can come from.&lt;br&gt;
Rotate regularly so old credentials don't linger.&lt;/p&gt;

&lt;h3&gt;
  
  
  Detection
&lt;/h3&gt;

&lt;p&gt;Even if something leaks, notice it quickly. There are two things to watch.&lt;/p&gt;

&lt;p&gt;One is whether a secret has been mixed into your code.&lt;br&gt;
By building gitleaks into CI and running it on every push, you can catch any contamination immediately.&lt;/p&gt;

&lt;p&gt;The other is whether a leaked access key is being used.&lt;br&gt;
GuardDuty analyzes sources like CloudTrail and detects abnormal credential usage, such as use from outside AWS.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/guardduty/" rel="noopener noreferrer"&gt;https://aws.amazon.com/guardduty/&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  If It Does Leak
&lt;/h1&gt;

&lt;p&gt;If it does leak, the first response is what counts.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🚨 If a secret leaks, revoke it immediately, before you even think about deleting the commit.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Git keeps history, so deleting the commit is meaningless.&lt;br&gt;
Once you revoke it, the value left in the history can no longer be used.&lt;/p&gt;

&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;There are alternatives for getting off access keys&lt;/li&gt;
&lt;li&gt;There are also situations where you can't stop using them&lt;/li&gt;
&lt;li&gt;Defend against leaks with three layers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Security is the foundation of business. That said, don't overthink it. Just start with &lt;code&gt;aws login&lt;/code&gt; right at hand.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>iam</category>
      <category>security</category>
      <category>awscli</category>
    </item>
    <item>
      <title>S3 Presigned URLs SSE-KMS: Common Pitfalls and How to Avoid Them</title>
      <dc:creator>Yuichi Sato</dc:creator>
      <pubDate>Tue, 16 Jun 2026 10:44:48 +0000</pubDate>
      <link>https://dev.to/aws-builders/s3-presigned-urls-sse-kms-common-pitfalls-and-how-to-avoid-them-46o7</link>
      <guid>https://dev.to/aws-builders/s3-presigned-urls-sse-kms-common-pitfalls-and-how-to-avoid-them-46o7</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This article was originally written in Japanese and published on Qiita. It has been translated with the help of AI.&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Original article: &lt;a href="https://qiita.com/sassssan68/items/9f37fac0f4e2210deb0c" rel="noopener noreferrer"&gt;https://qiita.com/sassssan68/items/9f37fac0f4e2210deb0c&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You switched S3 encryption from SSE-S3 to SSE-KMS, and suddenly your presigned URLs started returning errors.&lt;br&gt;
Sound familiar?&lt;br&gt;
It happened to me.&lt;/p&gt;

&lt;p&gt;Presigned URLs and SSE-KMS are both common features when used on their own, but &lt;strong&gt;combining them comes with some tricky permission requirements&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This article covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Why presigned URLs fail when you switch to SSE-KMS&lt;/li&gt;
&lt;li&gt;Easy-to-miss permission pitfalls (IAM policies and KMS key policies)&lt;/li&gt;
&lt;li&gt;Different gotchas for downloads (GET) and uploads (PUT)&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
  
  
  Background
&lt;/h1&gt;
&lt;h2&gt;
  
  
  What Is a Presigned URL?
&lt;/h2&gt;

&lt;p&gt;A presigned URL grants temporary access to an S3 object.&lt;br&gt;
Even users without IAM credentials can use the URL to download or upload S3 objects.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/using-presigned-url.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/AmazonS3/latest/userguide/using-presigned-url.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Common use cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Letting a user download a file temporarily&lt;/li&gt;
&lt;li&gt;Letting a user upload a file (e.g., form submission)
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Generate a download presigned URL (valid for 3600 seconds)&lt;/span&gt;
aws s3 presign s3://your-bucket/your-file.pdf &lt;span class="nt"&gt;--expires-in&lt;/span&gt; 3600
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The important thing to keep in mind is that &lt;strong&gt;a presigned URL borrows the permissions of the user who generated it&lt;/strong&gt;.&lt;br&gt;
If the generator doesn't have the required permissions, the URL can still be issued, but actual access will fail.&lt;/p&gt;
&lt;h2&gt;
  
  
  What Is SSE-KMS?
&lt;/h2&gt;

&lt;p&gt;SSE-KMS is one of S3's server-side encryption methods. It encrypts objects using a customer managed key in AWS KMS.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingKMSEncryption.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingKMSEncryption.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Comparing S3 encryption methods:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Key Manager&lt;/th&gt;
&lt;th&gt;Compatibility with Presigned URLs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SSE-S3&lt;/td&gt;
&lt;td&gt;Fully managed by AWS&lt;/td&gt;
&lt;td&gt;Works without issues&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSE-KMS (AWS managed key)&lt;/td&gt;
&lt;td&gt;Managed by AWS (&lt;code&gt;aws/s3&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Has constraints&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSE-KMS (customer managed key)&lt;/td&gt;
&lt;td&gt;Managed by user&lt;/td&gt;
&lt;td&gt;Has constraints&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h1&gt;
  
  
  Why It Works with SSE-S3 but Fails with SSE-KMS
&lt;/h1&gt;

&lt;p&gt;With SSE-S3, S3 manages the keys internally, so no extra permission setup is needed.&lt;br&gt;
With SSE-KMS, on the other hand, &lt;strong&gt;separate permissions to access the KMS key are required&lt;/strong&gt;. This is the part you need to watch out for.&lt;/p&gt;

&lt;p&gt;Worth noting: this applies even when you use the AWS managed key (&lt;code&gt;aws/s3&lt;/code&gt;) for SSE-KMS.&lt;br&gt;
You might think, "AWS manages this key, so it should work like SSE-S3 without extra permissions, right?" But &lt;strong&gt;as long as it's SSE-KMS, KMS permissions are required regardless of who manages the key&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When you access an object via a presigned URL, here's what happens internally:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With SSE-S3&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Request comes in via the presigned URL&lt;/li&gt;
&lt;li&gt;S3 decrypts the data with its internal key&lt;/li&gt;
&lt;li&gt;Data is returned&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;With SSE-KMS&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Request comes in via the presigned URL&lt;/li&gt;
&lt;li&gt;S3 asks KMS to decrypt the data key &lt;strong&gt;← Without KMS key permissions, this fails&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;KMS decrypts and returns the data key&lt;/li&gt;
&lt;li&gt;S3 decrypts the data with the data key&lt;/li&gt;
&lt;li&gt;Data is returned&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With SSE-KMS, &lt;strong&gt;you need permissions for both S3 and the KMS key&lt;/strong&gt;.&lt;br&gt;
If the user generating the presigned URL doesn't have permission to use the KMS key, the URL can still be issued, but the actual request will return an error.&lt;/p&gt;
&lt;h1&gt;
  
  
  Common Pitfalls
&lt;/h1&gt;
&lt;h2&gt;
  
  
  1. IAM Policies Need KMS Permissions
&lt;/h2&gt;

&lt;p&gt;The IAM user or role generating the presigned URL needs to have KMS-related permissions added to its policy.&lt;/p&gt;
&lt;h3&gt;
  
  
  For Downloads (GET)
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AllowS3GetObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::my-bucket/*"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AllowKMSDecrypt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"kms:Decrypt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:kms:ap-northeast-1:123456789012:key/your-key-id"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;s3:GetObject&lt;/code&gt; alone isn't enough — &lt;strong&gt;without &lt;code&gt;kms:Decrypt&lt;/code&gt;, you'll get an error&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  For Uploads (PUT)
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AllowS3PutObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3:PutObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::my-bucket/*"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AllowKMSForUpload"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"kms:GenerateDataKey"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:kms:ap-northeast-1:123456789012:key/your-key-id"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;For uploads, &lt;code&gt;kms:GenerateDataKey&lt;/code&gt; is required.&lt;br&gt;
S3 needs to ask KMS to generate a data key in order to encrypt the object.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⚠️ Note&lt;/strong&gt;&lt;br&gt;
For multipart uploads, &lt;code&gt;kms:Decrypt&lt;/code&gt; is also required.&lt;br&gt;
Each part is encrypted using the same data key, which means the encrypted data key needs to be decrypted on subsequent parts.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  2. Explicitly Specify Signature Version 4 in Boto3
&lt;/h2&gt;

&lt;p&gt;When using SSE-KMS, &lt;strong&gt;Signature Version 4 (SigV4) is mandatory&lt;/strong&gt;.&lt;br&gt;
This is documented in the official AWS docs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingKMSEncryption.html#aws-signature-version-4-sse-kms" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingKMSEncryption.html#aws-signature-version-4-sse-kms&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Recent versions of Boto3 use SigV4 by default, but depending on your environment or configuration, it may fall back to SigV2.&lt;br&gt;
In that case, you'll get an &lt;code&gt;InvalidArgument&lt;/code&gt; error like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Error&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Code&amp;gt;&lt;/span&gt;InvalidArgument&lt;span class="nt"&gt;&amp;lt;/Code&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Message&amp;gt;&lt;/span&gt;
    Requests specifying Server Side Encryption with AWS KMS managed keys
    require AWS Signature Version 4.
  &lt;span class="nt"&gt;&amp;lt;/Message&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ArgumentName&amp;gt;&lt;/span&gt;Authorization&lt;span class="nt"&gt;&amp;lt;/ArgumentName&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Error&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;To make sure it works reliably, explicitly specify SigV4 when creating the client.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;botocore.config&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Config&lt;/span&gt;

&lt;span class="n"&gt;s3_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;s3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signature_version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;s3v4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I actually got tripped up by this myself.&lt;br&gt;
Everything worked fine with SSE-S3, but the moment I switched to SSE-KMS, presigned URLs stopped working — and the cause was that I forgot to explicitly specify SigV4.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⚠️ Note&lt;/strong&gt;&lt;br&gt;
SigV4 may not be the default in environments like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Older versions of Boto3 / Botocore&lt;/li&gt;
&lt;li&gt;When &lt;code&gt;signature_version&lt;/code&gt; is explicitly set in &lt;code&gt;~/.aws/config&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Legacy region settings&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When using SSE-KMS, &lt;strong&gt;always specify &lt;code&gt;Config(signature_version="s3v4")&lt;/code&gt;&lt;/strong&gt; to be safe.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  3. KMS Key Policy Permissions
&lt;/h2&gt;

&lt;p&gt;If you created the KMS key with default settings, the &lt;code&gt;root&lt;/code&gt; delegation policy means IAM policies alone are sufficient.&lt;br&gt;
If you've modified the key policy, however, &lt;strong&gt;the KMS key policy must also grant permission, or you'll get an error&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;KMS keys have a key policy that controls "who can use this key."&lt;br&gt;
Permission must be granted in &lt;strong&gt;both&lt;/strong&gt; the IAM policy and the key policy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example Key Policy
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AllowPresignedUrlRole"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Principal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"AWS"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::123456789012:role/PresignedUrlGeneratorRole"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"kms:Decrypt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"kms:GenerateDataKey"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;br&gt;
Using &lt;code&gt;*&lt;/code&gt; for &lt;code&gt;Resource&lt;/code&gt; in a KMS key policy is fine.&lt;br&gt;
Since the key policy is attached to the key itself, &lt;code&gt;*&lt;/code&gt; refers to that very key.&lt;/p&gt;

&lt;p&gt;KMS keys created with default settings include a policy with &lt;code&gt;root&lt;/code&gt; as the &lt;code&gt;Principal&lt;/code&gt;.&lt;br&gt;
Specifying &lt;code&gt;root&lt;/code&gt; delegates the decision to the account's IAM policies, which means &lt;strong&gt;IAM policy alone is enough to use the KMS key&lt;/strong&gt;.&lt;br&gt;
However, if you've removed or modified this statement for security reasons, you'll need to explicitly grant the presigned URL generator's principal in the key policy.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  4. PUT Presigned URLs Need SSE Parameters in the Signature
&lt;/h2&gt;

&lt;p&gt;This applies when you don't use bucket default encryption and instead specify the KMS key explicitly in the request.&lt;br&gt;
If the bucket's default encryption is set to SSE-KMS, S3 handles encryption automatically server-side, so this step isn't needed.&lt;/p&gt;

&lt;p&gt;When generating a presigned URL for upload, &lt;strong&gt;you need to include the SSE-KMS parameters in the signature&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  With AWS CLI
&lt;/h3&gt;

&lt;p&gt;The AWS CLI's &lt;code&gt;presign&lt;/code&gt; command is designed for GET (download) use cases.&lt;br&gt;
For PUT use cases that need SSE-KMS parameters, it's more reliable to use an SDK.&lt;/p&gt;

&lt;h3&gt;
  
  
  With AWS SDK (Python / Boto3)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;botocore.config&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Config&lt;/span&gt;

&lt;span class="n"&gt;s3_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;s3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signature_version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;s3v4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;# For downloads (GET)
&lt;/span&gt;&lt;span class="n"&gt;download_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s3_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_presigned_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;get_object&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bucket&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-bucket&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-file.pdf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;ExpiresIn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# For uploads (PUT)
&lt;/span&gt;&lt;span class="n"&gt;upload_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s3_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_presigned_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;put_object&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bucket&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-bucket&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;upload/new-file.pdf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ServerSideEncryption&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aws:kms&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SSEKMSKeyId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;arn:aws:kms:ap-northeast-1:123456789012:key/your-key-id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;ExpiresIn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For PUT presigned URLs, include &lt;code&gt;ServerSideEncryption&lt;/code&gt; and &lt;code&gt;SSEKMSKeyId&lt;/code&gt; in &lt;code&gt;Params&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⚠️ Note&lt;/strong&gt;&lt;br&gt;
The headers in the PUT request must match the parameters included in the signature.&lt;br&gt;
When making the request from the client side (curl, fetch, etc.), include the following headers:&lt;/p&gt;


&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; PUT &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"x-amz-server-side-encryption: aws:kms"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"x-amz-server-side-encryption-aws-kms-key-id: arn:aws:kms:ap-northeast-1:123456789012:key/your-key-id"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--upload-file&lt;/span&gt; ./new-file.pdf &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&amp;lt;presigned-url&amp;gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Missing headers will result in a &lt;code&gt;SignatureDoesNotMatch&lt;/code&gt; error.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;br&gt;
For multipart uploads (&lt;code&gt;upload_part&lt;/code&gt;), SSE parameters are specified at the &lt;code&gt;create_multipart_upload&lt;/code&gt; step.&lt;br&gt;
The presigned URLs for individual parts don't need &lt;code&gt;ServerSideEncryption&lt;/code&gt; or &lt;code&gt;SSEKMSKeyId&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Troubleshooting Checklist
&lt;/h1&gt;

&lt;p&gt;Here's a checklist for when you hit errors with presigned URLs and SSE-KMS:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Target&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Does the IAM policy include &lt;code&gt;kms:Decrypt&lt;/code&gt;?&lt;/td&gt;
&lt;td&gt;Presigned URL generator&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Does the IAM policy include &lt;code&gt;kms:GenerateDataKey&lt;/code&gt; (for PUT)?&lt;/td&gt;
&lt;td&gt;Presigned URL generator&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Is &lt;code&gt;Config(signature_version="s3v4")&lt;/code&gt; set in Boto3?&lt;/td&gt;
&lt;td&gt;Application&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Does the KMS key policy permit the generator's principal?&lt;/td&gt;
&lt;td&gt;KMS key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Are SSE parameters included in the signature for PUT (when not using default encryption)?&lt;/td&gt;
&lt;td&gt;Application&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Do the PUT request headers match the signature (when not using default encryption)?&lt;/td&gt;
&lt;td&gt;Client&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Is the presigned URL still within its expiration window?&lt;/td&gt;
&lt;td&gt;URL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Is the KMS key in an enabled state (not disabled or pending deletion)?&lt;/td&gt;
&lt;td&gt;KMS key&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;The biggest reason presigned URLs and SSE-KMS trip people up is that &lt;strong&gt;switching to SSE-KMS introduces a separate requirement: permissions on the KMS key itself&lt;/strong&gt;.&lt;br&gt;
On top of that, you also need to be careful about SigV4 and how SSE parameters are handled.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;SSE Method&lt;/th&gt;
&lt;th&gt;Additional Permission Setup Required&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SSE-S3&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSE-KMS&lt;/td&gt;
&lt;td&gt;IAM policy + KMS key policy&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Key takeaways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;When you switch to SSE-KMS, add KMS permissions&lt;/strong&gt; — &lt;code&gt;kms:Decrypt&lt;/code&gt;, plus &lt;code&gt;kms:GenerateDataKey&lt;/code&gt; for PUT&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In Boto3, explicitly set &lt;code&gt;Config(signature_version="s3v4")&lt;/code&gt;&lt;/strong&gt; — without SigV4, you'll get &lt;code&gt;InvalidArgument&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check the KMS key policy too&lt;/strong&gt; — IAM policies alone may not be enough&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Include SSE parameters in PUT presigned URLs&lt;/strong&gt; — header mismatches lead to &lt;code&gt;SignatureDoesNotMatch&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You're most likely to hit these errors when migrating from SSE-S3 to SSE-KMS, so use this checklist when you do!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>s3</category>
      <category>kms</category>
      <category>security</category>
    </item>
    <item>
      <title>Stop Paying Too Much for CloudWatch Logs — Auto-Archive to S3 via Firehose</title>
      <dc:creator>Yuichi Sato</dc:creator>
      <pubDate>Tue, 21 Apr 2026 11:58:09 +0000</pubDate>
      <link>https://dev.to/aws-builders/stop-paying-too-much-for-cloudwatch-logs-auto-archive-to-s3-via-firehose-3f2f</link>
      <guid>https://dev.to/aws-builders/stop-paying-too-much-for-cloudwatch-logs-auto-archive-to-s3-via-firehose-3f2f</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This article was originally written in Japanese and published on Qiita. It has been translated with the help of AI.&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Original article: &lt;a href="https://qiita.com/sassssan68/items/da2aa98bba12748daca7" rel="noopener noreferrer"&gt;https://qiita.com/sassssan68/items/da2aa98bba12748daca7&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Have you ever calculated how much it actually costs to keep CloudWatch Logs long-term?&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;TL;DR&lt;br&gt;
Keeping logs in CloudWatch Logs long-term is expensive.&lt;br&gt;
Subscription → Firehose → S3 (Deep Archive) is more stable and cost-effective.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I recently had an audit requirement to retain logs for 18 months. When I estimated the CloudWatch Logs cost…&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;18 months = $1,069.20&lt;br&gt;
— That's way too much!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So I followed the AWS-recommended architecture — &lt;strong&gt;Subscription → Firehose → S3&lt;/strong&gt; — and combined it with a lifecycle policy to transition to Deep Archive. The result:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;~85% cost reduction&lt;br&gt;
Plus fully automated, stable operations&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This article covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Why CreateExportTask is not recommended (per AWS)&lt;/li&gt;
&lt;li&gt;How much cost you can actually save (with formulas)&lt;/li&gt;
&lt;li&gt;How to set up Subscription → Firehose → S3 (Deep Archive)&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Why You Shouldn't Keep Logs in CloudWatch Logs Long-Term
&lt;/h1&gt;

&lt;p&gt;Audit and regulatory requirements often mandate log retention for years. However, storing large volumes of logs in CloudWatch Logs gets expensive fast:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;High storage cost&lt;/strong&gt; — CloudWatch Logs storage pricing is heavy&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scales linearly&lt;/strong&gt; — The more data you store, the worse it gets&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not designed for long-term archival&lt;/strong&gt; — It's a monitoring tool, not a storage solution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This raises the question: &lt;strong&gt;What's the right way to handle long-term log retention?&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  AWS Says Export Task Is "Not Recommended" — Here's Why
&lt;/h1&gt;

&lt;p&gt;From the CloudWatch console, you can manually export logs to S3. To automate this, you'd use the &lt;code&gt;CreateExportTask&lt;/code&gt; API:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_CreateExportTask.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_CreateExportTask.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You could call this periodically from Lambda or EventBridge Scheduler. However, the AWS documentation explicitly discourages this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;br&gt;
We recommend that you don't regularly export to Amazon S3 as a way to continuously archive your logs. For that use case, we instead recommend that you use subscriptions. For more information about subscriptions, see Real-time processing of log data with subscriptions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;On top of that, there's a &lt;strong&gt;concurrency limit of 1 export task at a time&lt;/strong&gt;. If you're exporting from multiple log groups or across multiple time ranges, tasks will queue up, causing failures and delays.&lt;/p&gt;

&lt;p&gt;Given these limitations, CreateExportTask is unreliable for audit-grade long-term retention. &lt;strong&gt;As the AWS docs say, subscriptions are the way to go.&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  The AWS-Recommended Architecture: Subscription → Firehose → S3
&lt;/h1&gt;

&lt;p&gt;AWS recommends using CloudWatch Logs subscription filters:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/Subscriptions.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/Subscriptions.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With Firehose, logs are transferred in near real-time — no manual operations required, stable and suitable for long-term archival.&lt;/p&gt;

&lt;p&gt;But when I first saw this architecture, I thought: &lt;em&gt;"This looks expensive. Is it actually cheaper than just leaving logs in CloudWatch?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So I ran the numbers.&lt;/p&gt;

&lt;h1&gt;
  
  
  Cost Comparison: CloudWatch Logs vs. Subscription → Firehose → S3 (Deep Archive)
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Bottom line:&lt;/strong&gt;&lt;br&gt;
Over 18 months, Subscription → Firehose → S3 (Deep Archive) is approximately &lt;strong&gt;85% cheaper&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Note:&lt;/strong&gt;&lt;br&gt;
If your retention period is &lt;strong&gt;2 months or less&lt;/strong&gt;, CloudWatch Logs may actually be cheaper.&lt;br&gt;
The conclusion in this article assumes &lt;strong&gt;long-term retention&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Assumptions
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Retention period: 18 months&lt;/li&gt;
&lt;li&gt;Monthly log volume: 100 GB&lt;/li&gt;
&lt;li&gt;Tokyo region pricing (as of April 2026):

&lt;ul&gt;
&lt;li&gt;CloudWatch Logs storage: $0.033/GB/month&lt;/li&gt;
&lt;li&gt;Firehose delivery: $0.036/GB&lt;/li&gt;
&lt;li&gt;S3 Standard: $0.025/GB/month&lt;/li&gt;
&lt;li&gt;Glacier Deep Archive: $0.002/GB/month&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Case 1:&lt;/strong&gt; Keep all logs in CloudWatch Logs for the full 18 months&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Case 2:&lt;/strong&gt; Keep logs in CloudWatch for 2 weeks (for analysis), simultaneously stream via Firehose → S3 Standard → Glacier Deep Archive after 1 day&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Comparison Table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Case 1&lt;/th&gt;
&lt;th&gt;Formula&lt;/th&gt;
&lt;th&gt;Case 2&lt;/th&gt;
&lt;th&gt;Formula&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CloudWatch Logs storage&lt;/td&gt;
&lt;td&gt;$1,069.20&lt;/td&gt;
&lt;td&gt;0.033 × (100 × 18) × 18&lt;/td&gt;
&lt;td&gt;$27.72&lt;/td&gt;
&lt;td&gt;0.033 × (100 × 14/30) × 18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Firehose delivery&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;$64.80&lt;/td&gt;
&lt;td&gt;0.036 × 100 × 18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S3 Standard (1 day)&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;$1.50&lt;/td&gt;
&lt;td&gt;0.025 × (100 × 1/30) × 18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Glacier Deep Archive&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;$64.80&lt;/td&gt;
&lt;td&gt;0.002 × (100 × 18) × 18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$1,069.20&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$158.82&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Savings&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$910.38 (~85% reduction)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Note:

&lt;ul&gt;
&lt;li&gt;Average stored volume for 2-week retention: monthly log volume × (14/30)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;By keeping logs in CloudWatch for just 2 weeks (for analysis) and archiving older logs in Deep Archive, you can achieve approximately &lt;strong&gt;85% cost savings&lt;/strong&gt; over 18 months.&lt;/p&gt;

&lt;h1&gt;
  
  
  How to Set It Up
&lt;/h1&gt;

&lt;p&gt;The setup involves five steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create an S3 bucket with a lifecycle rule (transition to Glacier Deep Archive after 1 day)&lt;/li&gt;
&lt;li&gt;Create a Firehose stream (source: Direct PUT, destination: S3)&lt;/li&gt;
&lt;li&gt;Create an IAM role for the subscription filter&lt;/li&gt;
&lt;li&gt;Create a CloudWatch Logs subscription filter&lt;/li&gt;
&lt;li&gt;Verify logs are flowing to S3&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For steps 3 and 4, the AWS documentation provides a complete walkthrough including the IAM policy:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/SubscriptionFilters.html#FirehoseExample" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/SubscriptionFilters.html#FirehoseExample&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Export Task is not recommended&lt;/strong&gt; (per AWS documentation)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Firehose is the most operationally practical solution&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;S3 lifecycle rules enable cost-optimized long-term archival&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For short-term log retention, CloudWatch Logs works just fine. But if you need to retain logs for &lt;strong&gt;months to years&lt;/strong&gt;, &lt;strong&gt;Subscription → Firehose → S3 (Deep Archive) is the practical solution&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When long-term retention becomes a requirement, it's worth revisiting your architecture.&lt;/p&gt;

&lt;p&gt;I hope this helps anyone else dealing with the same log retention cost challenges.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloudwatch</category>
      <category>s3</category>
      <category>costoptimization</category>
    </item>
    <item>
      <title>Cryptographic Erasure: A Data Deletion Strategy Using AWS KMS</title>
      <dc:creator>Yuichi Sato</dc:creator>
      <pubDate>Tue, 17 Mar 2026 11:57:10 +0000</pubDate>
      <link>https://dev.to/aws-builders/cryptographic-erasure-a-data-deletion-strategy-using-aws-kms-o7b</link>
      <guid>https://dev.to/aws-builders/cryptographic-erasure-a-data-deletion-strategy-using-aws-kms-o7b</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This article was originally written in Japanese and published on Qiita. It has been translated with the help of AI.&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Original article: &lt;a href="https://qiita.com/sassssan68/items/a406e971217c1523026b" rel="noopener noreferrer"&gt;https://qiita.com/sassssan68/items/a406e971217c1523026b&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Are you really sure your data is deleted?&lt;/p&gt;

&lt;p&gt;With a shift in mindset and AWS KMS, you can effectively achieve data deletion.&lt;/p&gt;

&lt;p&gt;This article covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Challenges of traditional data deletion&lt;/li&gt;
&lt;li&gt;The concept of Cryptographic Erasure&lt;/li&gt;
&lt;li&gt;How to implement Cryptographic Erasure with AWS KMS&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Background and Challenges
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Scenario 1: User Account Deletion
&lt;/h2&gt;

&lt;p&gt;Imagine you're running a SaaS service and a user requests to close their account. You need to delete all of that user's data.&lt;/p&gt;

&lt;p&gt;You might think, "Just run a DELETE statement on the database, right?" But in reality, data exists in many places:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Production database ← You can delete here&lt;/li&gt;
&lt;li&gt;Database backups ← But there are 30 days' worth...&lt;/li&gt;
&lt;li&gt;Log files ← Stored in S3...&lt;/li&gt;
&lt;li&gt;Analytics data warehouse ← Copies in Redshift too...&lt;/li&gt;
&lt;li&gt;Dev/staging environments ← Using masked production data...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Deleting everything without missing anything is practically very difficult.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scenario 2: System Decommissioning / Service Shutdown
&lt;/h2&gt;

&lt;p&gt;When a SaaS service is no longer profitable and you decide to shut it down, you still need to delete all the data you've been handling.&lt;/p&gt;

&lt;p&gt;Just like Scenario 1, data is scattered across databases, backups, logs, and more. On top of that, cloud environments automatically replicate data for availability and fault tolerance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multi-AZ RDS and DynamoDB&lt;/li&gt;
&lt;li&gt;Automated backups and snapshots stored in S3&lt;/li&gt;
&lt;li&gt;Cross-region replicas&lt;/li&gt;
&lt;li&gt;Data forwarded via CloudWatch Logs or Kinesis to downstream systems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On-premises, you could physically destroy the disks as a last resort, but in the cloud, you can't touch physical storage (Shared Responsibility Model). This makes it even harder to confidently say, "Everything has been deleted."&lt;/p&gt;

&lt;h2&gt;
  
  
  The Risks of "Thinking You Deleted It"
&lt;/h2&gt;

&lt;p&gt;Lingering data poses risks such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data breaches&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Data you thought was deleted gets restored from backups and leaked&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Regulatory violations&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Failure to comply with GDPR's "Right to Be Forgotten" can result in fines&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit findings&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Being flagged for "no evidence of data deletion"&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  What Is Cryptographic Erasure?
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Destroy the Key, and the Data Becomes Garbage
&lt;/h2&gt;

&lt;p&gt;Cryptographic Erasure is a sanitization method defined in NIST SP 800-88.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://csrc.nist.gov/pubs/sp/800/88/r2/final" rel="noopener noreferrer"&gt;https://csrc.nist.gov/pubs/sp/800/88/r2/final&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's essentially a shift in mindset:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Traditional approach&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Deleting data = erasing the data itself&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cryptographic Erasure approach&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Deleting data = making the data impossible to decrypt (by destroying the key)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Encrypted data without its key is nothing more than meaningless bytes.&lt;/p&gt;

&lt;p&gt;In other words, &lt;strong&gt;if you delete the key, the data is effectively deleted — even if it physically remains&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Traditional Deletion vs. Cryptographic Erasure
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Traditional Deletion&lt;/th&gt;
&lt;th&gt;Cryptographic Erasure&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;↓ (Cannot be decrypted)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backups&lt;/td&gt;
&lt;td&gt;❌ (Cannot delete during retention period)&lt;/td&gt;
&lt;td&gt;↓ (Cannot be decrypted)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Logs&lt;/td&gt;
&lt;td&gt;❌ (Mixed with other logs)&lt;/td&gt;
&lt;td&gt;↓ (Cannot be decrypted)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Analytics&lt;/td&gt;
&lt;td&gt;△ (Requires manual work)&lt;/td&gt;
&lt;td&gt;↓ (Cannot be decrypted)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Encryption key&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Traditional Deletion&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Complete deletion is difficult&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cryptographic Erasure&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Deleting the key renders all data unrecoverable&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Benefits of Cryptographic Erasure
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Invalidate all distributed data at once&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Wherever the data exists, destroying the key invalidates it all&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Safe even if backups are restored&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Restored backups are unreadable without the key&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auditable evidence&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;A record of "when the key was deleted" is preserved&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  What Is AWS KMS?
&lt;/h1&gt;

&lt;p&gt;AWS KMS (Key Management Service) is a key management service provided by AWS. It allows you to securely create, store, and delete encryption keys.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/kms/latest/developerguide/overview.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/kms/latest/developerguide/overview.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Key features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fully managed&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;AWS handles key storage and protection&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit-ready&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Key usage history is recorded in CloudTrail&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integrated with other AWS services&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Can be used to encrypt S3, RDS, EBS, and more&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Types of KMS Keys
&lt;/h2&gt;

&lt;p&gt;AWS KMS offers three types of keys:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Can Be Deleted?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AWS owned keys&lt;/td&gt;
&lt;td&gt;Used internally by AWS; invisible to users&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AWS managed keys&lt;/td&gt;
&lt;td&gt;Automatically created, e.g., &lt;code&gt;aws/s3&lt;/code&gt;, &lt;code&gt;aws/rds&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Customer managed keys&lt;/td&gt;
&lt;td&gt;Created and managed by the user&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Two Types of Customer Managed Keys
&lt;/h3&gt;

&lt;p&gt;Customer managed keys are further divided into two types based on the origin of the key material (the actual cryptographic key data):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Key Material&lt;/th&gt;
&lt;th&gt;Immediate Deletion&lt;/th&gt;
&lt;th&gt;Characteristics&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;KMS-generated key&lt;/td&gt;
&lt;td&gt;Generated and managed by AWS&lt;/td&gt;
&lt;td&gt;❌ (7–30 day waiting period)&lt;/td&gt;
&lt;td&gt;Easy to operate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Imported key&lt;/td&gt;
&lt;td&gt;Brought in from outside&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;Useful when immediate deletion is required&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Features Required for Cryptographic Erasure
&lt;/h2&gt;

&lt;p&gt;Customer managed keys in AWS KMS have all the features needed for Cryptographic Erasure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Key creation&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Keys can be created per user or per system&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Key management&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Keys can be given aliases for easier identification&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Key deletion&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Keys that are no longer needed can be deleted&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Envelope Encryption
&lt;/h2&gt;

&lt;p&gt;AWS KMS uses a method called "Envelope Encryption." This is the technical mechanism that makes Cryptographic Erasure work.&lt;/p&gt;

&lt;p&gt;Here's how it works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A KMS key is used to generate a "data key"&lt;/li&gt;
&lt;li&gt;The data key is used to encrypt the actual data&lt;/li&gt;
&lt;li&gt;The data key itself is encrypted with the KMS key and stored&lt;/li&gt;
&lt;li&gt;The plaintext data key is immediately discarded&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So only two things are stored: the "encrypted data key" and the "encrypted data." Neither can be decrypted without the original KMS key.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is the key point:&lt;/strong&gt;&lt;br&gt;
When a KMS key is deleted, the data key can no longer be decrypted, and consequently, the data itself becomes unrecoverable. This is the mechanism behind "destroy the key, invalidate all the data."&lt;/p&gt;
&lt;h1&gt;
  
  
  Practical Design
&lt;/h1&gt;
&lt;h2&gt;
  
  
  Scenario 1: User Account Deletion
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Design Approach: Issue a KMS Key per User
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;At user registration:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;User A registers → KMS Key A is created&lt;/li&gt;
&lt;li&gt;User B registers → KMS Key B is created&lt;/li&gt;
&lt;li&gt;User C registers → KMS Key C is created&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When User A closes their account:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Schedule deletion of KMS Key A&lt;/li&gt;
&lt;li&gt;After the waiting period (7–30 days), the key is deleted&lt;/li&gt;
&lt;li&gt;User A's data becomes unrecoverable&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;User B and User C's data remain unaffected.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Benefits of This Design
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Only the departing user's data is invalidated&lt;/li&gt;
&lt;li&gt;No impact on other users&lt;/li&gt;
&lt;li&gt;Easier to comply with GDPR's "Right to Be Forgotten"&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Scenario 2: System Decommissioning / Service Shutdown
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Design Approach: Issue a KMS Key per System (or Tenant)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;At system build:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;System X → KMS Key X is created&lt;/li&gt;
&lt;li&gt;All data for System X is encrypted with KMS Key X&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;At system decommissioning:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Schedule deletion of KMS Key X&lt;/li&gt;
&lt;li&gt;After the waiting period (7–30 days), the key is deleted&lt;/li&gt;
&lt;li&gt;All data for System X becomes unrecoverable&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Even if backups remain, the data is unreadable after restoration.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Benefits of This Design
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;All system data can be invalidated at once&lt;/li&gt;
&lt;li&gt;Covers tape backups and DR site data as well&lt;/li&gt;
&lt;li&gt;Auditable evidence of "complete deletion" is preserved&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Comparing the Two Scenarios
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;User Account Deletion&lt;/th&gt;
&lt;th&gt;System Decommissioning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Key granularity&lt;/td&gt;
&lt;td&gt;Per user&lt;/td&gt;
&lt;td&gt;Per system&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Number of keys&lt;/td&gt;
&lt;td&gt;Many (one per user)&lt;/td&gt;
&lt;td&gt;Few (one per system)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scope of deletion impact&lt;/td&gt;
&lt;td&gt;Single user&lt;/td&gt;
&lt;td&gt;Entire system&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h1&gt;
  
  
  Implementation Considerations
&lt;/h1&gt;
&lt;h2&gt;
  
  
  Cost
&lt;/h2&gt;

&lt;p&gt;AWS KMS incurs the following charges (as of February 2026):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;KMS key storage&lt;/td&gt;
&lt;td&gt;~$1/month/key/region&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API requests&lt;/td&gt;
&lt;td&gt;~$0.03/10,000 requests&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you create a key per user, costs grow with the number of users.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example: 100,000 users&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Key storage alone: ~$100,000/month&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Deletion Is Not Immediate (With Exceptions)
&lt;/h2&gt;

&lt;p&gt;KMS key deletion has a mandatory waiting period:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Minimum: 7 days&lt;/li&gt;
&lt;li&gt;Maximum: 30 days&lt;/li&gt;
&lt;li&gt;Default: 30 days&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;During this period, deletion can be cancelled. This is a safety feature to prevent accidental deletion, but it cannot meet requirements for "delete everything right now."&lt;/p&gt;
&lt;h3&gt;
  
  
  Exception: Use Imported Keys When Immediate Deletion Is Required
&lt;/h3&gt;

&lt;p&gt;KMS keys with externally imported key material are the exception.&lt;/p&gt;

&lt;p&gt;Running the following command immediately deletes the key material. Unless it's re-imported, the data is permanently unrecoverable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Immediately delete the key material&lt;/span&gt;
aws kms delete-imported-key-material &lt;span class="nt"&gt;--key-id&lt;/span&gt; &amp;lt;your-key-id&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⚠️ Warning&lt;/strong&gt;&lt;br&gt;
Imported keys require generating, managing, and importing key material externally, which adds operational complexity. Carefully evaluate whether immediate deletion is truly necessary.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Design Early
&lt;/h2&gt;

&lt;p&gt;Retrofitting Cryptographic Erasure into an existing system is difficult. Applying it to existing systems may require re-encrypting all data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Customer Managed Keys
&lt;/h2&gt;

&lt;p&gt;When you enable encryption for S3 or RDS, AWS managed keys may be used by default. AWS managed keys (&lt;code&gt;aws/s3&lt;/code&gt;, &lt;code&gt;aws/rds&lt;/code&gt;, etc.) &lt;strong&gt;cannot be deleted&lt;/strong&gt;. Since you can't delete them, you can't perform Cryptographic Erasure — so &lt;strong&gt;always specify a customer managed key&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here are the default encryption settings for major services and what's needed for Cryptographic Erasure:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;Default Encryption&lt;/th&gt;
&lt;th&gt;For Cryptographic Erasure&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;S3&lt;/td&gt;
&lt;td&gt;SSE-S3 or aws/s3&lt;/td&gt;
&lt;td&gt;Configure SSE-KMS with a customer managed key (also enable &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucket-key.html" rel="noopener noreferrer"&gt;S3 Bucket Key&lt;/a&gt; to reduce KMS API costs)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RDS&lt;/td&gt;
&lt;td&gt;aws/rds&lt;/td&gt;
&lt;td&gt;Create with a customer managed key specified&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EBS&lt;/td&gt;
&lt;td&gt;aws/ebs&lt;/td&gt;
&lt;td&gt;Create with a customer managed key specified&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;Here are the key takeaways about Cryptographic Erasure:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Shift your mindset&lt;/strong&gt; from "delete the data" to "delete the key"&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Invalidate distributed data all at once&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;AWS KMS makes it achievable in a &lt;strong&gt;fully managed&lt;/strong&gt; way&lt;/li&gt;
&lt;li&gt;Applicable to both &lt;strong&gt;user account deletions&lt;/strong&gt; and &lt;strong&gt;system decommissioning&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost&lt;/strong&gt; and &lt;strong&gt;design&lt;/strong&gt; require upfront planning&lt;/li&gt;
&lt;li&gt;Cryptographic Erasure &lt;strong&gt;requires customer managed keys&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Lingering data poses risks such as data breaches and regulatory violations. Let's address them properly with Cryptographic Erasure!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>kms</category>
      <category>security</category>
      <category>encryption</category>
    </item>
  </channel>
</rss>
