<?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: Shane</title>
    <description>The latest articles on DEV Community by Shane (@shane_law).</description>
    <link>https://dev.to/shane_law</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%2F4023699%2F68c93a6c-b82a-4bc8-af1d-23fd59d782ac.png</url>
      <title>DEV Community: Shane</title>
      <link>https://dev.to/shane_law</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shane_law"/>
    <language>en</language>
    <item>
      <title>The cloud comparison chart lied to you (a little)</title>
      <dc:creator>Shane</dc:creator>
      <pubDate>Fri, 17 Jul 2026 08:43:10 +0000</pubDate>
      <link>https://dev.to/shane_law/the-cloud-comparison-chart-lied-to-you-a-little-39o9</link>
      <guid>https://dev.to/shane_law/the-cloud-comparison-chart-lied-to-you-a-little-39o9</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F06rrh7id8cv89tkmxs82.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F06rrh7id8cv89tkmxs82.png" alt=" " width="800" height="182"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every "AWS to Azure to GCP" cheat sheet does the same thing: it gives you a table. S3 equals Blob Storage. EC2 equals Compute Engine. Done, ship it.&lt;/p&gt;

&lt;p&gt;Except it's not done. The table tells you where to start, not where you'll get burned.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it actually breaks
&lt;/h2&gt;

&lt;p&gt;Take storage. S3 is flat: bucket, then objects. Azure inserts a Storage Account above the container, and that account is where networking rules, access keys, and replication settings actually live. Port an S3-shaped Terraform module over without accounting for that layer, and you'll spend an afternoon figuring out why your access policies don't apply where you expect.&lt;/p&gt;

&lt;p&gt;Or take networking. An AWS VPC is regional. A GCP VPC is global by default. "Just create a VPC" means two completely different blast radii depending on which cloud you're standing in.&lt;/p&gt;

&lt;p&gt;The one that actually matters for security people: &lt;code&gt;aws_iam_role&lt;/code&gt; doesn't map to an Azure resource at all. Azure's permission model is structurally different, role assignments work differently, service principals aren't IAM roles wearing a different name. Treating them as equivalent is where real misconfigurations come from, not typos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built Cloud Rosetta
&lt;/h2&gt;

&lt;p&gt;I work in cloud infrastructure day to day, and I kept hitting these gaps mid-project, usually the expensive way. So I built a dictionary that does what the comparison charts don't: for each concept, it names the equivalent across AWS, Azure, and GCP, explains specifically where the analogy breaks, and links the actual Terraform resource for each provider so you can go verify instead of trusting me.&lt;/p&gt;

&lt;p&gt;It's a single self-contained HTML file, no tracker, works offline, and the dictionary itself is open to PRs, especially from people with production scars on whichever cloud is the "minority" one in a given entry.&lt;/p&gt;

&lt;p&gt;Live site: &lt;a href="https://edeak54.github.io/cloud-rosetta/" rel="noopener noreferrer"&gt;https://edeak54.github.io/cloud-rosetta/&lt;/a&gt;&lt;br&gt;
Source: &lt;a href="https://github.com/edeak54/cloud-rosetta" rel="noopener noreferrer"&gt;https://github.com/edeak54/cloud-rosetta&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you've been burned by an analogy that didn't hold, open an issue, that's exactly the kind of entry this needs more of.&lt;/p&gt;

</description>
      <category>cloud</category>
      <category>aws</category>
      <category>azure</category>
      <category>gcp</category>
    </item>
    <item>
      <title>Decoupling &amp; Securing Web App Storage: Building a Zero-Trust S3 Perimeter with Terraform</title>
      <dc:creator>Shane</dc:creator>
      <pubDate>Fri, 10 Jul 2026 08:31:54 +0000</pubDate>
      <link>https://dev.to/shane_law/decoupling-securing-web-app-storage-building-a-zero-trust-s3-perimeter-with-terraform-3m4g</link>
      <guid>https://dev.to/shane_law/decoupling-securing-web-app-storage-building-a-zero-trust-s3-perimeter-with-terraform-3m4g</guid>
      <description>&lt;p&gt;When hosting a scalable, multi-AZ 3-tier web application (like WordPress), storing media files locally on the EC2 instances is an architectural dead end. The moment your auto-scaler destroys an instance, your user uploads vanish.&lt;/p&gt;

&lt;p&gt;The standard fix is offloading media to Amazon S3. But this introduces a massive security risk: if your instance profile or an IAM key leaks, your entire storage layer is exposed to the public internet.&lt;/p&gt;


&lt;div class="crayons-card c-embed"&gt;

  &lt;br&gt;
The Security Risk: If your instance profile or an IAM key leaks, your entire storage layer is exposed to the public internet. IAM permissions alone are often not enough for high-stakes production data.&lt;br&gt;

&lt;/div&gt;


&lt;p&gt;In this post, we’ll build a production-ready, highly available media storage layer that implements a &lt;strong&gt;Zero-Trust Network Perimeter&lt;/strong&gt; around S3 using Terraform. Even if someone steals your IAM credentials, they cannot access the data unless they are sitting inside your private VPC. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Strategy: Network-Level Isolation
&lt;/h2&gt;

&lt;p&gt;Instead of relying solely on identity (who is asking?), we enforce network-level isolation (where are they asking from?). Even if someone steals your IAM credentials, they cannot access the data unless they are sitting inside your specific VPC.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The Infrastructure
&lt;/h3&gt;

&lt;p&gt;A highly available network across multiple Availability Zones (AZs) with isolated private subnets where our application servers live.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The Gateway
&lt;/h3&gt;

&lt;p&gt;An AWS VPC Endpoint (S3 Gateway) configured inside our route tables so traffic to S3 never traverses the public internet.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The Lock
&lt;/h3&gt;

&lt;p&gt;A restrictive S3 Bucket Policy that explicitly denies any API calls (s3:GetObject, s3:PutObject) unless the request originates from our specific VPC Endpoint ID.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;## Create the VPC Endpoint for S3 ##&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_vpc_endpoint"&lt;/span&gt; &lt;span class="s2"&gt;"s3"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_id&lt;/span&gt;
  &lt;span class="nx"&gt;service_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"com.amazonaws.&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_region&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.s3"&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_endpoint_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Gateway"&lt;/span&gt;

  &lt;span class="nx"&gt;route_table_ids&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private_route_table_ids&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"s3-gateway-endpoint"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;## Enforce the Zero-Trust Bucket Policy ##&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket_policy"&lt;/span&gt; &lt;span class="s2"&gt;"secure_perimeter"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_s3_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;media_assets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

  &lt;span class="nx"&gt;policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;Version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;
    &lt;span class="nx"&gt;Statement&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;Sid&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AccessFromVpceOnly"&lt;/span&gt;
        &lt;span class="nx"&gt;Effect&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Deny"&lt;/span&gt;
        &lt;span class="nx"&gt;Principal&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"*"&lt;/span&gt;
        &lt;span class="nx"&gt;Action&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"s3:*"&lt;/span&gt;
        &lt;span class="nx"&gt;Resource&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="nx"&gt;aws_s3_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;media_assets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;aws_s3_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;media_assets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/*"&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="nx"&gt;Condition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;StringNotEquals&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"aws:sourceVpce"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc_endpoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&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;div class="crayons-card c-embed"&gt;

  

&lt;p&gt;When implementing this pattern, there is a common sequencing trap that trips up engineering teams.&lt;/p&gt;

&lt;p&gt;If you apply this strict bucket policy before your application servers are fully configured or before your CI/CD runner is granted a specific exception, you will instantly lock yourself out of your own bucket. The Deny block in AWS is absolute.&lt;/p&gt;

&lt;p&gt;Always verify your VPC Endpoint route tables are fully propagated before attaching the explicit network deny statement to the bucket policy.&lt;/p&gt;


&lt;/div&gt;



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

&lt;p&gt;By shifting our security model from identity-only to identity + network perimeter, we eliminate entire classes of credential-theft attacks. It doesn't matter if an attacker finds an exposed AWS key on GitHub—if they aren't executing code from within your specific VPC subnets, the data remains dark.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>terraform</category>
      <category>security</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
