<?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: Priya Ranjan Sahu</title>
    <description>The latest articles on DEV Community by Priya Ranjan Sahu (@priyaranjansahu).</description>
    <link>https://dev.to/priyaranjansahu</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%2F4084807%2F9b8bc662-32b2-42a7-8027-86c7a465f8d7.png</url>
      <title>DEV Community: Priya Ranjan Sahu</title>
      <link>https://dev.to/priyaranjansahu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/priyaranjansahu"/>
    <language>en</language>
    <item>
      <title>One storage security audit suite for Azure, AWS, and GCP</title>
      <dc:creator>Priya Ranjan Sahu</dc:creator>
      <pubDate>Wed, 19 Aug 2026 10:38:54 +0000</pubDate>
      <link>https://dev.to/priyaranjansahu/one-storage-security-audit-suite-for-azure-aws-and-gcp-cml</link>
      <guid>https://dev.to/priyaranjansahu/one-storage-security-audit-suite-for-azure-aws-and-gcp-cml</guid>
      <description>&lt;p&gt;Every cloud environment I've touched has the same two storage problems: a bucket someone made public and forgot, and a storage account that should be behind a private endpoint but isn't. The checks differ slightly per provider, but the question is always the same — "is this storage reachable by someone who shouldn't see it?"&lt;/p&gt;

&lt;p&gt;I got tired of answering that question with ad-hoc scripts that died in one environment and were rewritten in the next. So I built one suite that covers all three clouds and writes every result to the same CSV/JSON shape.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it checks
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;AWS (S3)&lt;/strong&gt; — the three ways a bucket becomes public:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Public Access Block fully enabled&lt;/li&gt;
&lt;li&gt;Bucket policy reported as &lt;code&gt;IsPublic&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;ACL grants to &lt;code&gt;AllUsers&lt;/code&gt; / &lt;code&gt;AuthenticatedUsers&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Plus a security-config audit for default encryption (SSE-S3/SSE-KMS) and versioning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GCP (Cloud Storage)&lt;/strong&gt; — IAM bindings to &lt;code&gt;allUsers&lt;/code&gt; / &lt;code&gt;allAuthenticatedUsers&lt;/code&gt;, uniform bucket-level access, and public access prevention.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Azure (Storage Accounts)&lt;/strong&gt; — accounts with and without Private Endpoints, plus TLS version, secure transfer, and public network access posture.&lt;/p&gt;

&lt;p&gt;Everything is read-only. No module creates, modifies, or deletes anything — you run it, get a report, fix what it flags.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# AWS: only the public buckets, as JSON&lt;/span&gt;
python aws/s3/audit-public-buckets/audit_public_buckets.py &lt;span class="nt"&gt;--only-public&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt; json &lt;span class="nt"&gt;--output-file&lt;/span&gt; public.json

&lt;span class="c"&gt;# GCP: security configuration matrix&lt;/span&gt;
python gcp/storage/audit-security-config/audit_bucket_security.py &lt;span class="nt"&gt;--project&lt;/span&gt; my-project &lt;span class="nt"&gt;--output-file&lt;/span&gt; compliance.csv

&lt;span class="c"&gt;# Azure: storage accounts with no private endpoint&lt;/span&gt;
./azure/storage/audit-missing-private-endpoints/audit-storage-without-pe.ps1 &lt;span class="nt"&gt;-OutputPath&lt;/span&gt; exposed.csv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Azure side also ships Bash and KQL variants so you can run the same check from Resource Graph in the portal.&lt;/p&gt;

&lt;h2&gt;
  
  
  The report contract
&lt;/h2&gt;

&lt;p&gt;Every module writes the same shape — CSV or JSON — with a boolean "finding" column you actually act on:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;Finding column&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AWS public buckets&lt;/td&gt;
&lt;td&gt;&lt;code&gt;is_public&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AWS / GCP security config&lt;/td&gt;
&lt;td&gt;&lt;code&gt;hardened&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GCP public IAM&lt;/td&gt;
&lt;td&gt;&lt;code&gt;public&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Azure (no PE)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PrivateEndpointCount == 0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Summaries go to stderr, so stdout stays clean for automation. Redacted example outputs for every module are in &lt;a href="https://github.com/priyaranjan-sahu/multi-cloud-automation-scripts/tree/main/docs/samples" rel="noopener noreferrer"&gt;&lt;code&gt;docs/samples/&lt;/code&gt;&lt;/a&gt;, and the full field contract is in &lt;a href="https://github.com/priyaranjan-sahu/multi-cloud-automation-scripts/blob/main/docs/report-format.md" rel="noopener noreferrer"&gt;&lt;code&gt;docs/report-format.md&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug that cost me half a day
&lt;/h2&gt;

&lt;p&gt;The AWS and GCP scripts in each provider are named the same — &lt;code&gt;audit_public_buckets.py&lt;/code&gt;, &lt;code&gt;audit_bucket_security.py&lt;/code&gt;. That's fine until your test files import both. Python imports by module name, so once pytest cached the AWS version, the GCP tests silently imported the wrong code and failed with the most confusing TypeError.&lt;/p&gt;

&lt;p&gt;The fix was loading the modules by explicit file path instead of leaning on &lt;code&gt;sys.path&lt;/code&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="n"&gt;spec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;importlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;util&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spec_from_file_location&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;module&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;importlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;util&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;module_from_spec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec_module&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Half a day, gone, over a naming collision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping it honest
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pester&lt;/strong&gt; validates the Azure modules (parse, parameters, read-only guarantees)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;pytest&lt;/strong&gt; (mocked cloud clients) covers the AWS/GCP modules&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI&lt;/strong&gt; runs PSScriptAnalyzer, ShellCheck, ruff, markdownlint, and yamllint on every push&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test coverage sits at 85%&lt;/strong&gt; and is published via Codecov&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first CI run failed in four jobs at once — the lesson being that lint rules move under you (a newer PSScriptAnalyzer started flagging &lt;code&gt;Write-Host&lt;/code&gt; as a warning I'd never seen before).&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it lives
&lt;/h2&gt;

&lt;p&gt;The whole thing is free and open source:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/priyaranjan-sahu/multi-cloud-automation-scripts" rel="noopener noreferrer"&gt;https://github.com/priyaranjan-sahu/multi-cloud-automation-scripts&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you manage storage across more than one cloud, it's probably a better starting point than whatever script you were about to rewrite for the fifth time. PRs and issues welcome.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>security</category>
      <category>cloud</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
