<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[From S3 AccessDenied to DevSecOps: Building the CloudMart Secure Web Assets Pipeline]]></title><description><![CDATA[From S3 AccessDenied to DevSecOps: Building the CloudMart Secure Web Assets Pipeline]]></description><link>https://s3accessdeniedtodevsecops.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/567830c04d5f969862dc485f/71c5c66a-0ea0-44b2-acdd-8f0afd81aca6.png</url><title>From S3 AccessDenied to DevSecOps: Building the CloudMart Secure Web Assets Pipeline</title><link>https://s3accessdeniedtodevsecops.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Sun, 06 Sep 2026 11:17:31 GMT</lastBuildDate><atom:link href="https://s3accessdeniedtodevsecops.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[From S3 AccessDenied to DevSecOps: Building the CloudMart Secure Web Assets Pipeline]]></title><description><![CDATA[Introduction
Cloud storage problems often look simple from the outside. A customer clicks a page, an image does not load, and the browser returns an AccessDenied error. But behind that small failure i]]></description><link>https://s3accessdeniedtodevsecops.hashnode.dev/from-s3-accessdenied-to-devsecops-building-the-cloudmart-secure-web-assets-pipeline</link><guid isPermaLink="true">https://s3accessdeniedtodevsecops.hashnode.dev/from-s3-accessdenied-to-devsecops-building-the-cloudmart-secure-web-assets-pipeline</guid><dc:creator><![CDATA[Malik Dixon]]></dc:creator><pubDate>Fri, 08 May 2026 18:27:46 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/567830c04d5f969862dc485f/bb5f0fb5-2cc0-47e2-8d9a-0f10ab04487f.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Introduction</h2>
<p>Cloud storage problems often look simple from the outside. A customer clicks a page, an image does not load, and the browser returns an <code>AccessDenied</code> error. But behind that small failure is a bigger engineering lesson: public access, identity permissions, deployment automation, security scanning, and observability all have to work together.</p>
<p>That is the core idea behind the <strong>CloudMart Secure Web Assets Pipeline</strong> project.</p>
<p>This project started from a beginner cloud troubleshooting scenario: CloudMart, a fast-growing e-commerce startup, hosts website files and product images in Amazon S3. Customers are beginning to report that product images are broken. The immediate issue is S3 public access configuration, but the deeper issue is operational maturity. If the fix depends on manually navigating through the AWS Console, the same problem may recur later.</p>
<p>So instead of treating the lab as a one-time console fix, I rebuilt it as a DevSecOps project using <strong>AWS CloudFormation</strong>, <strong>GitHub Actions</strong>, <strong>Snyk</strong>, <strong>Checkov</strong>, <strong>cfn-lint</strong>, endpoint validation, and observability planning.</p>
<p>The goal was not just to make the website load. The goal was to build a repeatable, secure, auditable deployment pipeline.</p>
<hr />
<h2>The Business Problem</h2>
<p>CloudMart uses Amazon S3 to host static website files and product images because S3 is durable, low-cost, and easy to scale. That is a reasonable architecture for public website assets.</p>
<p>The problem appears when customers cannot load product images. For an e-commerce company, broken images are not just a technical inconvenience. They affect customer confidence, conversion rates, and revenue.</p>
<p>The original symptom is simple:</p>
<pre><code class="language-text">403 Forbidden
AccessDenied
</code></pre>
<p>But the likely causes include:</p>
<ul>
<li><p>S3 Block Public Access is still enabled.</p>
</li>
<li><p>The bucket policy does not allow the public <code>s3:GetObject</code> access.</p>
</li>
<li><p>Website assets were uploaded with the wrong permissions.</p>
</li>
<li><p>A developer changed the settings manually.</p>
</li>
<li><p>There is no automated test confirming that assets load after deployment.</p>
</li>
<li><p>There is no security gate confirming that public access is limited to read-only website content.</p>
</li>
</ul>
<p>The project, therefore, reframes the issue from <strong>“fix this bucket”</strong> to <strong>“build a secure deployment process that prevents the issue from coming back.”</strong></p>
<hr />
<h2>Project Objective</h2>
<p>The objective of the CloudMart Secure Web Assets Pipeline is to build an automated AWS deployment system that:</p>
<ol>
<li><p>Provisions an S3 static website environment using CloudFormation.</p>
</li>
<li><p>Deploys website assets through GitHub Actions.</p>
</li>
<li><p>Validates CloudFormation before deployment.</p>
</li>
<li><p>Scans infrastructure code for security issues.</p>
</li>
<li><p>Documents intentional security exceptions.</p>
</li>
<li><p>Confirms the website and product image return HTTP 200.</p>
</li>
<li><p>Produces evidence that can be used in a portfolio, audit review, or interview.</p>
</li>
</ol>
<p>This turns the original S3 permissions lab into a stronger cloud engineering project by demonstrating not only AWS fundamentals but also infrastructure-as-code, CI/CD, DevSecOps controls, and operational validation.</p>
<hr />
<h2>Architecture Overview</h2>
<p>The architecture is intentionally simple, but professional.</p>
<pre><code class="language-text">Developer
   |
   | git push
   v
GitHub Repository
   |
   v
GitHub Actions Pipeline
   |
   |-- YAML validation
   |-- CloudFormation validation
   |-- cfn-lint
   |-- Checkov IaC scan
   |-- Snyk IaC scan
   |-- CloudFormation deployment
   |-- S3 asset sync
   |-- Endpoint validation
   v
AWS CloudFormation
   |
   v
Amazon S3 Static Website Bucket
   |
   v
CloudMart Website Endpoint
   |
   v
Customers load website files and product images
</code></pre>
<p>The deployment pipeline becomes the control plane. Engineers no longer need to adjust the bucket in the AWS Console manually. Instead, the desired configuration lives in source control.</p>
<hr />
<h2>Source Code Structure</h2>
<p>The project repository is organized like this:</p>
<pre><code class="language-text">cloudmart-secure-web-assets/
├── .github/workflows/deploy.yml
├── infra/cloudmart-static-site.yml
├── website/index.html
├── website/error.html
├── website/images/sample-product.svg
├── scripts/validate-site.sh
├── docs/project-brief.md
├── docs/security-exceptions.md
├── docs/validation-report-template.md
├── docs/metrics-plan.md
├── .gitignore
├── .yamllint.yml
└── README.md
</code></pre>
<p>Each folder has a clear purpose.</p>
<p>The <code>infra/</code> folder contains the CloudFormation template. The <code>website/</code> folder contains the deployable static website files. The <code>.github/workflows/</code> folder contains the CI/CD workflow. The <code>scripts/</code> folder contains post-deployment validation logic. The <code>docs/</code> folder contains project evidence, security exceptions, metrics planning, and reporting templates.</p>
<p>That structure matters because a clean repository makes the project easier to review, maintain, and explain in an interview.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/567830c04d5f969862dc485f/330f5d5a-dbe5-45ad-a291-021bfb543957.png" alt="" style="display:block;margin:0 auto" />

<p><em>CloudFormation infrastructure verification from the AWS Console.</em></p>
<h2>CloudFormation Infrastructure</h2>
<p>The CloudFormation template provisions the core AWS resources:</p>
<ul>
<li><p>S3 bucket for static website hosting</p>
</li>
<li><p>Static website configuration with <code>index.html</code> and <code>error.html</code></p>
</li>
<li><p>S3 bucket policy for public read access</p>
</li>
<li><p>Server-side encryption</p>
</li>
<li><p>Versioning</p>
</li>
<li><p>Public access block configuration</p>
</li>
<li><p>CloudWatch log group for deployment audit evidence</p>
</li>
</ul>
<p>The most important design choice is the bucket policy.</p>
<p>Because this is an S3 static website endpoint, public users need read access to website files. However, that does not mean the bucket should be fully public. The public permission is scoped to only one action:</p>
<pre><code class="language-json">{
  "Action": "s3:GetObject",
  "Resource": "arn:aws:s3:::bucket-name/*"
}
</code></pre>
<p>The project does <strong>not</strong> allow public uploads, deletions, bucket listings, permission changes, or ACL changes.</p>
<p>This distinction is important. A public static website bucket is not automatically a bad design. The question is whether public access is intentional, limited, documented, and validated.</p>
<img src="https://cdn.hashnode.com/uploads/covers/567830c04d5f969862dc485f/b828948c-905e-4b95-9984-9dceaa52195f.png" alt="" style="display:block;margin:0 auto" />

<p><em>Website information from the S3 bucket</em></p>
<hr />
<h2>GitHub Actions CI/CD Pipeline</h2>
<p>The GitHub Actions workflow automates the deployment lifecycle.</p>
<p>On push to <code>main</code>the pipeline:</p>
<ol>
<li><p>Checks out the repository.</p>
</li>
<li><p>Installs validation tools.</p>
</li>
<li><p>Runs YAML validation.</p>
</li>
<li><p>Validates the CloudFormation template.</p>
</li>
<li><p>Runs <code>cfn-lint</code>.</p>
</li>
<li><p>Runs Checkov.</p>
</li>
<li><p>Runs Snyk IaC scanning.</p>
</li>
<li><p>Deploys the CloudFormation stack.</p>
</li>
<li><p>Reads the stack outputs.</p>
</li>
<li><p>Syncs website files to S3.</p>
</li>
<li><p>Runs endpoint validation.</p>
</li>
<li><p>Uploads a validation report artifact.</p>
</li>
</ol>
<p>This pipeline replaces manual deployment with a repeatable process.</p>
<p>A strong cloud project should not end at “I created a bucket.” It should show how infrastructure changes are reviewed, scanned, deployed, and verified.</p>
<img src="https://cdn.hashnode.com/uploads/covers/567830c04d5f969862dc485f/1b8b1353-59b0-4a15-adea-8ada0fbfc288.png" alt="" style="display:block;margin:0 auto" />

<p><em>GitHub Actions CI/CD Pipeline verification</em></p>
<hr />
<h2>DevSecOps Security Gates</h2>
<p>Security was added directly into the CI/CD process. The pipeline includes several gates.</p>
<h3>Gate 1: YAML Validation</h3>
<p>YAML validation catches formatting mistakes before the workflow or infrastructure deployment fails unexpectedly.</p>
<h3>Gate 2: CloudFormation Validation</h3>
<p>AWS CloudFormation validation checks whether the template is structurally valid before deployment.</p>
<h3>Gate 3: cfn-lint</h3>
<p><code>cfn-lint</code> checks resource properties, references, and template correctness. It helps catch AWS-specific mistakes before deployment.</p>
<h3>Gate 4: Checkov IaC Scan</h3>
<p>Checkov scans the CloudFormation template for security misconfigurations. It helps identify risky patterns in infrastructure as code.</p>
<h3>Gate 5: Snyk IaC Scan</h3>
<p>Snyk adds another DevSecOps layer by scanning infrastructure code for security risks. In this project, Snyk detects that the S3 bucket has public-policy controls disabled. That finding is expected for this specific use case because S3 static website hosting requires public read access.</p>
<p>The important part is not pretending the finding does not exist. The important part is documenting the exception and controlling it.</p>
<h3>Gate 6: Post-Deployment Endpoint Validation</h3>
<p>The pipeline runs a shell script that checks:</p>
<ul>
<li><p>Website root URL</p>
</li>
<li><p><code>index.html</code></p>
</li>
<li><p>Sample product image</p>
</li>
</ul>
<p>Each must return HTTP 200.</p>
<p>This validates the business outcome: customers can load the website and product images.</p>
<hr />
<h2>Security Exception: Public S3 Website Access</h2>
<p>The project includes a documented security exception for public read access.</p>
<p>The exception states that public <code>s3:GetObject</code> access is allowed only because the bucket hosts public static website assets. The controls are intentionally narrow:</p>
<ul>
<li><p>Public reading is allowed.</p>
</li>
<li><p>Public upload is denied.</p>
</li>
<li><p>Public delete is denied.</p>
</li>
<li><p>Public listing is not granted.</p>
</li>
<li><p>Public ACLs are blocked.</p>
</li>
<li><p>Insecure transport is denied.</p>
</li>
<li><p>Infrastructure changes are managed through CloudFormation.</p>
</li>
<li><p>CI/CD security scans run before deployment.</p>
</li>
</ul>
<p>This is exactly the kind of documentation that separates a basic lab from a consultant-style project.</p>
<p>Security work is not always about eliminating every warning. Sometimes it is about understanding risk, documenting exceptions, and proving that compensating controls are in place.</p>
<hr />
<h2>Observability and Metrics</h2>
<p>The project also includes an observability and metrics plan.</p>
<p>The two main observability sources are:</p>
<ul>
<li><p>GitHub Actions for CI/CD execution evidence</p>
</li>
<li><p>Datadog for synthetic uptime monitoring and dashboards</p>
</li>
</ul>
<p>Recommended Datadog checks include:</p>
<ul>
<li><p>Homepage uptime</p>
</li>
<li><p>Product image uptime</p>
</li>
<li><p>HTTP response status</p>
</li>
<li><p>Response time</p>
</li>
<li><p>Deployment success/failure trends</p>
</li>
<li><p>Security scan trends</p>
</li>
</ul>
<p>The metrics plan uses DevOps and DevSecOps measurements, such as:</p>
<table>
<thead>
<tr>
<th>Metric</th>
<th>Meaning</th>
</tr>
</thead>
<tbody><tr>
<td>Deployment Frequency</td>
<td>How often successful deployments happen</td>
</tr>
<tr>
<td>Lead Time for Changes</td>
<td>Time from commit to deployment</td>
</tr>
<tr>
<td>Change Failure Rate</td>
<td>Percentage of deployments that fail validation</td>
</tr>
<tr>
<td>Mean Time to Detect</td>
<td>How quickly the team detects an outage</td>
</tr>
<tr>
<td>Mean Time to Restore</td>
<td>How quickly the team restores service</td>
</tr>
<tr>
<td>IaC Findings by Severity</td>
<td>Number of infrastructure security issues</td>
</tr>
</tbody></table>
<p>These metrics strengthen the project by connecting engineering work to operational performance.</p>
<hr />
<h2>Pipeline Issues and Resolutions</h2>
<p>During maintenance, several real CI/CD issues were identified and resolved. These are valuable because they show practical troubleshooting, not just a clean final-state demo.</p>
<h3>Issue 1: <a href="https://github.com/mdixon47/aws-cloudmart-secure-web-assets/blob/main/docs/issues-and-resolutions.md#1-high-snyk-cc-tf-96-98-reported-as-failures-they-arent">Snyk High Findings Reported as Failures</a></h3>
<p>The pipeline reported Snyk findings for:</p>
<pre><code class="language-text">SNYK-CC-TF-96
SNYK-CC-TF-98
</code></pre>
<p>These findings relate to S3 public policy controls being disabled. At first glance, they look like high-severity failures. However, in this project, they are documented exceptions because the S3 bucket intentionally hosts a public static website.</p>
<p>The confusion stemmed from differences among Snyk's display surfaces. The Snyk dashboard and bare CLI output show human-readable high findings, but the CI gate uses JSON output and post-processing logic to compare findings against the repository allowlist.</p>
<p>The resolution was to document how to read Snyk output and update the local test script to run the same Snyk gate logic as CI. The authoritative CI result showed two high findings, both matched to documented exceptions, and the security scan passed.</p>
<img src="https://cdn.hashnode.com/uploads/covers/567830c04d5f969862dc485f/e14e3071-f110-4a7b-92a6-5c256bf13ca1.png" alt="" style="display:block;margin:0 auto" />

<p><em>Failures noted in Synk before corrections are reported as failures</em></p>
<img src="https://cdn.hashnode.com/uploads/covers/567830c04d5f969862dc485f/3a70a14d-7f84-4b92-823d-019361994eb3.png" alt="" style="display:block;margin:0 auto" />

<p><em>After updating Sync Output</em></p>
<h3>Issue 2: <a href="https://github.com/mdixon47/aws-cloudmart-secure-web-assets/blob/main/docs/issues-and-resolutions.md">Empty S3 Bucket Output Caused</a> <code>aws s3 sync s3:///</code></h3>
<p>Another failure occurred when the pipeline attempted to run:</p>
<pre><code class="language-bash">aws s3 sync website/ s3:///
</code></pre>
<p>The bucket name was empty because the CloudFormation stack output could not be read. The underlying stack was stuck <code>ROLLBACK_COMPLETE</code> from a failed first-time create.</p>
<p>The resolution was to strengthen the <code>Read stack outputs</code> step with <code>set -euo pipefail</code> and explicit error handling. Now the pipeline fails early with a useful error message if required outputs are missing.</p>
<p>This is an important improvement because the original failure appeared during <code>aws s3 sync</code>, but the real problem was the CloudFormation stack state.</p>
<h3>Issue 3: <a href="https://github.com/mdixon47/aws-cloudmart-secure-web-assets/blob/main/docs/issues-and-resolutions.md#3-configure-aws-credentialsv5-nodejs-20-deprecation">GitHub Actions Node.js 20 Deprecation Warning</a></h3>
<p>The pipeline was green but showed a runner warning that <code>aws-actions/configure-aws-credentials@v5</code> was still running on Node.js 20.</p>
<p>The fix was to upgrade the workflow to <code>aws-actions/configure-aws-credentials@v6</code>use Node 24. After the update, the deployment completed without any Node.js 20 deprecation annotations.</p>
<p>The lesson is simple: do not assume every latest major version of a GitHub Action has upgraded its runtime. Each action maintainer ships runtime upgrades on a different schedule.</p>
<hr />
<h2>What This Project Proves</h2>
<p>This project proves several practical cloud engineering skills:</p>
<ul>
<li><p>S3 static website hosting</p>
</li>
<li><p>S3 bucket policy design</p>
</li>
<li><p>Public access troubleshooting</p>
</li>
<li><p>CloudFormation infrastructure as code</p>
</li>
<li><p>GitHub Actions CI/CD</p>
</li>
<li><p>DevSecOps security scanning</p>
</li>
<li><p>Security exception documentation</p>
</li>
<li><p>Endpoint validation</p>
</li>
<li><p>Observability planning</p>
</li>
<li><p>CI/CD troubleshooting</p>
</li>
<li><p>Audit-ready documentation</p>
</li>
</ul>
<p>The strongest part of the project is that it does not stop at deployment. It includes validation, security review, metrics, and maintenance lessons.</p>
<p>That is what real engineering work looks like.</p>
<img src="https://cdn.hashnode.com/uploads/covers/567830c04d5f969862dc485f/b59dc63b-f737-4a0a-b399-8246e726e1d8.png" alt="" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/uploads/covers/567830c04d5f969862dc485f/16a11f71-5e59-49be-9302-57654d075ad3.png" alt="" style="display:block;margin:0 auto" />

<h2>Conclusion</h2>
<p>The CloudMart Secure Web Assets Pipeline is more than an S3 lab. It is a complete cloud engineering project that connects business impact, infrastructure automation, security controls, observability, and troubleshooting.</p>
<p>The original issue was broken product images. The final result is a repeatable DevSecOps deployment system that can detect errors, document risks, validate outcomes, and support future production upgrades, such as CloudFront, Origin Access Control, AWS WAF, and GitHub OIDC.</p>
<p>That is the difference between fixing a cloud problem once and designing a system that prevents it from happening again. Please take the time to review the GitHub repository: <a href="https://github.com/mdixon47/aws-cloudmart-secure-web-assets">https://github.com/mdixon47/aws-cloudmart-secure-web-assets</a></p>
]]></content:encoded></item></channel></rss>