<?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: Jay French</title>
    <description>The latest articles on DEV Community by Jay French (@jayfrenchcloud).</description>
    <link>https://dev.to/jayfrenchcloud</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3828152%2F569540ce-74c0-4a49-9ada-4bba09fe7e68.png</url>
      <title>DEV Community: Jay French</title>
      <link>https://dev.to/jayfrenchcloud</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jayfrenchcloud"/>
    <language>en</language>
    <item>
      <title>How Kubernetes Drift Detection Saved Us From Infrastructure Chaos</title>
      <dc:creator>Jay French</dc:creator>
      <pubDate>Mon, 23 Mar 2026 13:29:11 +0000</pubDate>
      <link>https://dev.to/jayfrenchcloud/how-kubernetes-drift-detection-saved-us-from-infrastructure-chaos-14b</link>
      <guid>https://dev.to/jayfrenchcloud/how-kubernetes-drift-detection-saved-us-from-infrastructure-chaos-14b</guid>
      <description>&lt;p&gt;Three months into a production migration, we discovered that 14 of our 47 deployments had quietly drifted from their declared state. Not in a dramatic, pager-firing way. In the slow, invisible way that turns a Tuesday afternoon into a Friday incident.&lt;/p&gt;

&lt;p&gt;That's the thing about configuration drift. It doesn't announce itself. It accumulates.&lt;/p&gt;

&lt;p&gt;Here's what happened, what we built to fix it, and why I think most teams are one bad deploy away from the same problem.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;p&gt;We were running a mid-sized Kubernetes cluster across three environments: dev, staging, and production. Standard GitOps workflow. ArgoCD handling deployments. Helm charts checked into Git. Everything was "declarative." Everything was "source-of-truth."&lt;/p&gt;

&lt;p&gt;Except it wasn't.&lt;/p&gt;

&lt;p&gt;Engineers were patching things manually under pressure. &lt;code&gt;kubectl edit&lt;/code&gt; became a habit. Resource limits got tweaked directly on pods. ConfigMaps were updated in-cluster without touching the repo. Nobody flagged it because nothing broke. The cluster kept humming. The dashboards stayed green.&lt;/p&gt;

&lt;p&gt;Then we started seeing weird behavior. A service that should have been running with a 512Mi memory limit was sitting at 2Gi. Another deployment had two replicas when the Helm chart clearly declared three. A sidecar container version was six weeks behind what we'd intended to ship.&lt;/p&gt;

&lt;p&gt;None of it was catastrophic. All of it was real. And we had no idea how long it had been that way.&lt;/p&gt;




&lt;h2&gt;
  
  
  Point 1: GitOps Sync Status Isn't the Same as Drift Detection
&lt;/h2&gt;

&lt;p&gt;This is the part that trips people up. ArgoCD told us our apps were "Synced." And technically, they were, at the moment of last sync. But sync status is a snapshot, not a continuous assertion. If someone runs &lt;code&gt;kubectl edit&lt;/code&gt; after a sync, ArgoCD doesn't know. It's not watching for that.&lt;/p&gt;

&lt;p&gt;Drift detection means continuously comparing what's running in the cluster against what's declared in Git, and alerting when they diverge. That's a different problem than deployment sync. Most teams conflate them and pay for it later.&lt;/p&gt;

&lt;p&gt;We built a reconciliation loop using a combination of ArgoCD's resource tracking and a custom controller that scraped live cluster state on a 5-minute interval, diffed it against our Helm-rendered manifests, and pushed the deltas into a monitoring pipeline. Nothing fancy. About 400 lines of Go and a Prometheus exporter.&lt;/p&gt;

&lt;p&gt;The first run returned 14 drifted resources. Four of them in production.&lt;/p&gt;




&lt;h2&gt;
  
  
  Point 2: The Real Problem Is Toil and Pressure, Not Malicious Intent
&lt;/h2&gt;

&lt;p&gt;Every one of those manual edits had a story.&lt;/p&gt;

&lt;p&gt;A memory limit bumped because an OOMKill was happening at 2 AM and someone needed to stop the bleeding. A replica count changed because load spiked and autoscaling hadn't kicked in fast enough. A ConfigMap updated because a third-party API changed its endpoint and we needed 30 seconds to fix it, not 30 minutes to run a pipeline.&lt;/p&gt;

&lt;p&gt;These aren't reckless engineers. These are engineers solving real problems with the tools in front of them.&lt;/p&gt;

&lt;p&gt;The issue is the feedback loop, or the lack of one. Without drift detection, that 2 AM fix becomes permanent. Nobody goes back. The PR never gets opened. The Helm chart never gets updated. And six weeks later, someone deploys from Git and rolls back the fix that's been holding production together.&lt;/p&gt;

&lt;p&gt;Sound familiar?&lt;/p&gt;

&lt;p&gt;The fix isn't telling people to stop using &lt;code&gt;kubectl edit&lt;/code&gt;. It's making the correct path faster than the escape hatch, and making drift visible so it can't quietly accumulate.&lt;/p&gt;




&lt;h2&gt;
  
  
  Point 3: Alerting on Drift Changes the Culture
&lt;/h2&gt;

&lt;p&gt;Once engineers could see a drift dashboard, broken down by namespace, by team, by resource type, behavior shifted. Not because we mandated it. Because visibility creates accountability in a way that process documents never do.&lt;/p&gt;

&lt;p&gt;We tagged each drift event with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The last-known modifier (pulled from audit logs via Kubernetes API)&lt;/li&gt;
&lt;li&gt;Time since divergence&lt;/li&gt;
&lt;li&gt;Severity of the delta&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A replica count change is low severity. A security context change is high. A resource limit change that's 4x the declared value gets a PagerDuty alert.&lt;/p&gt;

&lt;p&gt;Within three weeks of launching the dashboard, the team had self-corrected 11 of the 14 original drifted resources without us asking. They just didn't want to see red in their namespace.&lt;/p&gt;

&lt;p&gt;We also made it a blocker on our weekly architecture review. Any service with unresolved drift older than 72 hours got a 5-minute explanation from the owning team. Not punitive, just a forcing function for documentation and communication.&lt;/p&gt;




&lt;h2&gt;
  
  
  Point 4: Drift Detection Has to Be Cheap to Maintain
&lt;/h2&gt;

&lt;p&gt;Here's where most homegrown solutions fall apart. You build the thing, it works, then it becomes another system someone has to babysit.&lt;/p&gt;

&lt;p&gt;We kept ours deliberately simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No custom UI. A Grafana dashboard pulling from Prometheus.&lt;/li&gt;
&lt;li&gt;The controller runs as a standard Kubernetes deployment with a ServiceAccount scoped to read-only cluster access.&lt;/li&gt;
&lt;li&gt;The diff logic uses server-side apply dry-runs, which Kubernetes gives you for free.&lt;/li&gt;
&lt;li&gt;Total compute overhead is negligible.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We've been running it for eight months. It's needed exactly two bug fixes and one config update when we migrated Helm chart versions. That's it.&lt;/p&gt;

&lt;p&gt;Complexity is debt. Every additional feature you bolt on is another thing that can fail or get abandoned.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Takeaway
&lt;/h2&gt;

&lt;p&gt;Drift detection isn't a Kubernetes problem. It's a systems problem. The cluster just happens to be where the drift lives.&lt;/p&gt;

&lt;p&gt;If you're running GitOps and you've never run a diff between your declared manifests and your live cluster state, you probably have drift. You just don't know what it looks like yet.&lt;/p&gt;

&lt;p&gt;The question worth sitting with: what decisions are you currently making based on cluster state that you think matches Git, but doesn't?&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>platformengineering</category>
      <category>gitop</category>
    </item>
    <item>
      <title>Building a Secure GCP Foundation From an AWS Engineer's Perspective</title>
      <dc:creator>Jay French</dc:creator>
      <pubDate>Fri, 20 Mar 2026 03:00:57 +0000</pubDate>
      <link>https://dev.to/jayfrenchcloud/building-a-secure-gcp-foundationfrom-an-aws-engineers-perspective-mmb</link>
      <guid>https://dev.to/jayfrenchcloud/building-a-secure-gcp-foundationfrom-an-aws-engineers-perspective-mmb</guid>
      <description>&lt;h2&gt;
  
  
  Building a Secure GCP Foundation: An AWS Engineer's First Lab
&lt;/h2&gt;

&lt;p&gt;I have two AWS certifications and essentially zero GCP experience. So I set a constraint for myself: build a security-first GCP environment from scratch, using only the console (ClickOps), in a single sitting. No tutorials. Just apply what I know about cloud security principles and see how GCP implements them.&lt;/p&gt;

&lt;p&gt;Here's exactly what I built, how it maps to AWS, and the security decisions I made at every step.&lt;/p&gt;




&lt;h2&gt;
  
  
  Starting Point: Project Isolation
&lt;/h2&gt;

&lt;p&gt;In AWS, the highest-level security boundary is the &lt;strong&gt;AWS Account&lt;/strong&gt;. In GCP, that equivalent is a &lt;strong&gt;Project&lt;/strong&gt;. Before touching anything else, I created a new project called &lt;code&gt;secure-app-foundation&lt;/code&gt;.&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2F9ava77g59mqj04ammfty.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.amazonaws.com%2Fuploads%2Farticles%2F9ava77g59mqj04ammfty.png" alt=" " width="800" height="539"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I like starting with project isolation because the project boundary is a fundamental security and billing boundary in GCP. Every resource lives inside a project, and IAM policies, API enablement, and billing are all scoped to it."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is the same reason you wouldn't deploy a production workload into your personal AWS account — isolation is the first control, not an afterthought.&lt;/p&gt;

&lt;p&gt;From there, I enabled only the APIs I needed. GCP requires you to explicitly enable services (Compute, Storage, etc.) — a nice security-by-default posture that AWS doesn't enforce at the same level.&lt;/p&gt;




&lt;h2&gt;
  
  
  Network: Custom VPC, Not the Default
&lt;/h2&gt;

&lt;p&gt;GCP creates a "default" VPC in every new project — just like AWS creates a default VPC in every new region. And just like in AWS, you should never use it for anything real.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I used a custom VPC instead of the default to avoid inherited permissive behavior and to make network intent explicit."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I created:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A VPC named &lt;code&gt;secure-vpc&lt;/code&gt; with &lt;strong&gt;custom mode&lt;/strong&gt; (manual subnet creation)&lt;/li&gt;
&lt;li&gt;A subnet &lt;code&gt;app-subnet&lt;/code&gt; in &lt;code&gt;us-central1&lt;/code&gt; with CIDR &lt;code&gt;10.10.1.0/24&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Faqj4j2rpcg2rr6b1x91t.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.amazonaws.com%2Fuploads%2Farticles%2Faqj4j2rpcg2rr6b1x91t.png" alt=" " width="800" height="118"&gt;&lt;/a&gt;&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fnu2kfqdob1gcp3w9nrcu.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.amazonaws.com%2Fuploads%2Farticles%2Fnu2kfqdob1gcp3w9nrcu.png" alt=" " width="800" height="113"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The AWS equivalent: creating a custom VPC with a private subnet instead of using the default. In both clouds, the default network has overly permissive firewall/security-group defaults that you'd spend time undoing anyway.&lt;/p&gt;




&lt;h2&gt;
  
  
  Identity: Workload Service Account
&lt;/h2&gt;

&lt;p&gt;In AWS, you attach an IAM Role to an EC2 instance to give it permissions without embedding credentials. In GCP, the equivalent is a &lt;strong&gt;Service Account&lt;/strong&gt; attached to a VM.&lt;/p&gt;

&lt;p&gt;I created a service account named &lt;code&gt;app-runtime-sa&lt;/code&gt; — and critically, &lt;strong&gt;I did not grant it any permissions at creation time.&lt;/strong&gt;&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fny52ew08fiklhl1f0hoi.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.amazonaws.com%2Fuploads%2Farticles%2Fny52ew08fiklhl1f0hoi.png" alt=" " width="800" height="764"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I separated workload identity from human access and attached a service account to the VM rather than relying on user credentials. Permissions get added only when the workload has a specific, documented need."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is least privilege in practice. It's easy to add permissions later. It's very hard to audit and remove over-provisioned permissions after the fact.&lt;/p&gt;

&lt;h3&gt;
  
  
  AWS ↔ GCP Concept Mapping
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;AWS&lt;/th&gt;
&lt;th&gt;GCP&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Workload Identity&lt;/td&gt;
&lt;td&gt;IAM Role → EC2 Instance Profile&lt;/td&gt;
&lt;td&gt;Service Account → VM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Human Access&lt;/td&gt;
&lt;td&gt;IAM User / SSO Role&lt;/td&gt;
&lt;td&gt;Google Account / Workforce Identity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Network Boundary&lt;/td&gt;
&lt;td&gt;VPC / Security Groups&lt;/td&gt;
&lt;td&gt;VPC / Firewall Rules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Isolated Environment&lt;/td&gt;
&lt;td&gt;AWS Account&lt;/td&gt;
&lt;td&gt;GCP Project&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Posture Monitoring&lt;/td&gt;
&lt;td&gt;AWS Security Hub&lt;/td&gt;
&lt;td&gt;Security Command Center&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Object Storage&lt;/td&gt;
&lt;td&gt;S3 + Bucket Policy&lt;/td&gt;
&lt;td&gt;GCS + Uniform Access&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Compute: Hardened VM with No Public IP
&lt;/h2&gt;

&lt;p&gt;I deployed a VM (&lt;code&gt;app-vm-01&lt;/code&gt;) into &lt;code&gt;app-subnet&lt;/code&gt; and applied several hardening decisions immediately.&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Frjgzx4qza9j5qzush5ps.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.amazonaws.com%2Fuploads%2Farticles%2Frjgzx4qza9j5qzush5ps.png" alt=" " width="800" height="502"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. No External IP Address&lt;/strong&gt;&lt;br&gt;
Removed the default public IP entirely. The VM has no direct internet-facing interface. Connectivity is only possible through explicitly defined firewall rules and internal network paths.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Disabled Project-Wide SSH Keys&lt;/strong&gt;&lt;br&gt;
GCP has a feature that propagates SSH keys across all VMs in a project. Convenient — and terrible for security. Disabled it so this VM only accepts keys explicitly assigned to it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Attached the &lt;code&gt;app-runtime-sa&lt;/code&gt; Service Account&lt;/strong&gt;&lt;br&gt;
The VM runs as a dedicated service account with no permissions, rather than the default compute service account (which often has broader access than needed).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I intentionally removed the public IP to reduce the attack surface and treated connectivity as an explicitly controlled path rather than the default."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In AWS terms: this is like launching an EC2 instance in a private subnet with no Elastic IP, assigned to a minimal IAM role. You only reach it through a bastion, SSM, or VPN — never directly from the internet.&lt;/p&gt;




&lt;h2&gt;
  
  
  Firewall Rules: Explicit Allowlist, Nothing Else
&lt;/h2&gt;

&lt;p&gt;GCP firewall rules work differently from AWS Security Groups in one important way: they're defined at the &lt;strong&gt;VPC level&lt;/strong&gt; and applied via &lt;strong&gt;target tags or service accounts&lt;/strong&gt;, not attached directly to instances. This is more flexible, but requires more intentional design.&lt;/p&gt;

&lt;p&gt;I created two ingress rules:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;allow-ssh-admin&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
Allows TCP:22 from a specific home IP address only. Priority 1000. No broad &lt;code&gt;/0&lt;/code&gt; ranges.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;allow-internal&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
Allows SSH from within the &lt;code&gt;10.10.1.0/24&lt;/code&gt; subnet range for internal workload communication.&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fmw53tbzs6467rpnrbiid.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.amazonaws.com%2Fuploads%2Farticles%2Fmw53tbzs6467rpnrbiid.png" alt=" " width="800" height="92"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I constrained ingress to only what was required and avoided the common mistake of using overly broad allow rules like &lt;code&gt;0.0.0.0/0&lt;/code&gt; on sensitive ports."&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Storage: Private Bucket with Uniform Access
&lt;/h2&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2F1to9jubafh20fi0qrkwa.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.amazonaws.com%2Fuploads%2Farticles%2F1to9jubafh20fi0qrkwa.png" alt=" " width="800" height="95"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I created a Cloud Storage bucket (&lt;code&gt;secure-app-logs-657483678438&lt;/code&gt;) as the log sink for this environment. Two non-negotiable settings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Uniform bucket-level access&lt;/strong&gt; — disables per-object ACLs so all access is controlled through IAM only. No accidental public objects through legacy ACLs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Public access prevention: Enforced&lt;/strong&gt; — prevents any configuration from making this bucket publicly accessible, even if a future IAM binding would otherwise allow it.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;"For storage, I enforced uniform bucket-level access and public access prevention to reduce accidental exposure through object ACLs."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The S3 equivalent: enabling "Block all public access" at the bucket level and using bucket policies instead of ACLs. The principle is identical.&lt;/p&gt;




&lt;h2&gt;
  
  
  Role Design: Operator, Auditor, Workload
&lt;/h2&gt;

&lt;p&gt;Even in a small lab environment, I structured IAM roles as if this were a real production system:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I would normally split roles by operator, auditor, and workload identity. For the lab, I modeled that pattern even if the environment was small — because the habit matters more than the scale."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Operator&lt;/strong&gt; — Humans who manage infrastructure. Scoped, not project-owner.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auditor&lt;/strong&gt; — Read-only access to logs, IAM, and resource configs. Never write access.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Workload Identity&lt;/strong&gt; — The &lt;code&gt;app-runtime-sa&lt;/code&gt;. Only gets permissions the application actually needs, added on demand.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What I Didn't Get To: Security Command Center
&lt;/h2&gt;

&lt;p&gt;I wanted to explore Security Command Center (GCP's equivalent to AWS Security Hub) for centralized findings and misconfiguration detection — but SCC requires an &lt;strong&gt;organizational account&lt;/strong&gt;, not just a standalone project.&lt;/p&gt;

&lt;p&gt;That's actually an important lesson: at enterprise scale, you're always working within an organization hierarchy, and tools like SCC, org policies, and VPC Service Controls only make sense in that context. In a real role, this is where most of the interesting work lives — building guardrails at the org level that automatically apply to every project, rather than configuring each one manually.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Cloud security principles transfer.&lt;/strong&gt; Least privilege, explicit allowlists, no public exposure by default — these aren't AWS concepts or GCP concepts. They're cloud security fundamentals.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The mental model maps cleanly.&lt;/strong&gt; Once you understand AWS deeply, GCP is largely a translation exercise. Projects ↔ Accounts, Service Accounts ↔ IAM Roles, Firewall Rules ↔ Security Groups.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ClickOps first, IaC second.&lt;/strong&gt; Doing this manually first forces you to understand every decision. Converting it to Terraform afterward makes you a better IaC author because you know what each resource actually does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Design for org scale from day one.&lt;/strong&gt; The patterns you use in a 1-project lab — role separation, custom VPCs, no default credentials — are exactly the patterns you need when managing 200 projects.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Convert the entire setup to &lt;strong&gt;Terraform&lt;/strong&gt; — codify every decision as Infrastructure as Code&lt;/li&gt;
&lt;li&gt;Set up a &lt;strong&gt;CI/CD pipeline&lt;/strong&gt; (GitHub Actions or Cloud Build) with security checks before any infra change is applied&lt;/li&gt;
&lt;li&gt;Explore &lt;strong&gt;VPC Service Controls&lt;/strong&gt; to build a data perimeter around sensitive resources&lt;/li&gt;
&lt;li&gt;Get access to an org account and dig into &lt;strong&gt;Security Command Center&lt;/strong&gt; and org-level policies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fundamentals are the same across clouds. The vocabulary is different. If you understand &lt;em&gt;why&lt;/em&gt; security controls exist, picking up a new platform is mostly learning new names for familiar ideas.&lt;/p&gt;

</description>
      <category>gcp</category>
      <category>aws</category>
      <category>cloudsecurity</category>
      <category>devsecops</category>
    </item>
  </channel>
</rss>
