Secure a Static Website on AWS

Deliver a private S3-hosted page globally over HTTPS with CloudFront.

Introduction

30 Second Summary

A page can be easy for visitors to open while its original files remain exposed in ways they never need to be. That gap can turn a simple online portfolio into a security risk.

In this project, you will build a cloud security portfolio page on AWS and serve it over HTTPS through Amazon CloudFront while the original file stays private in Amazon S3. Your security proof will compare a working CloudFront request with a denied direct S3 request.

What You'll Build

You will load your portfolio from a public CloudFront HTTPS link while the matching S3 object rejects direct anonymous access.

By the end of this project, you'll have:

  • A local portfolio page that presents your first secure cloud architecture in Microsoft Edge.
  • A live HTTPS website that visitors can open through CloudFront while the encrypted S3 origin remains private.
  • A repeatable security proof that records the successful CloudFront path. It also records the denied direct S3 path in an interview-ready architecture brief.
  • Secret Mission: Attach CloudFront's managed security headers policy and verify the returned headers in Microsoft Edge.

Are there any prerequisites?

You need an AWS account with permission to manage AWS Budgets, Amazon S3, and Amazon CloudFront. A Windows computer is the only local prerequisite because the project requires no command-line tools or prior cloud experience.

Before We Start

Your security goal gives every later AWS choice a clear purpose. Visitors use a public CloudFront HTTPS URL while the private Amazon S3 origin blocks direct access to prevent origin bypass.

Set Up Safe Access and a Cost Guardrail

Your public website depends on an account that is protected before any cloud resources exist. Spending visibility belongs at the same starting line.

You will verify administrative access to the AWS Management Console with multi-factor authentication (MFA).

You will also create an AWS Budgets guardrail that warns you when this lab approaches your chosen limit.

Windows Notepad will provide the local editor for your page. Microsoft Edge will provide the browser for previewing your work.

In this step, get ready to:
  • Verify protected administrative access to the AWS Management Console with MFA.
  • Create a recurring Monthly Cost budget with Actual and Forecasted email alerts.
  • Prepare the local Windows workspace for your portfolio files.
Protect your AWS sign-in

An administrative identity can create the services used in this project. MFA adds a second verification step before that identity reaches your account.

Credential work can feel high stakes, so this lab leaves root access keys uncreated.

  • Navigate to the AWS Management Console sign-in page in your browser.
  • Choose your existing non-root administrative identity when one is available.
  • Use the root user only when it is your sole current administrative credential.
  • Complete the MFA challenge for your identity.
  • Confirm the console home loads under the identity you intended to use.
  • Record non-root administrative access as a follow-on security task if you used the root bootstrap path.
  • Skip every option to create root access keys.

Your protected console session is ready. Every AWS resource in this project now begins behind an MFA check.

Why Leave Root Access Keys Uncreated?

AWS strongly recommends that you do not create access keys for the root user. Browser-based console access does not require them.

This lab keeps credentials inside the sign-in flow. That reduces the number of powerful credentials you need to protect.

Cannot Complete Protected Sign-In?

  • Check that you selected the administrative identity intended for this account.
  • Register MFA through the identity's security settings if no MFA challenge appears.
  • Return to the AWS Management Console sign-in page after MFA registration.

Ask for help with the identity type shown on your sign-in screen: help me secure my AWS administrative sign-in with MFA

Create the monthly budget

A cost budget gives you an early warning as AWS usage grows. This project uses a recurring limit of 20 USD so the guardrail exists before storage or delivery resources appear.

The budget form has several similar settings, so take this one screen at a time.

  • Return to the AWS Management Console from earlier.
  • Search for Billing and Cost Management from the console search bar.
  • Select Billing and Cost Management from the results.
  • Select Budgets.
  • Select Create budget.
  • Select Customize (advanced).
  • Select Cost budget.
  • Continue to the budget details.

The cost budget type is selected. The next settings define its monthly scope.

  • Enter cloud-security-lab-budget as the budget name.
  • Select Monthly as the period.
  • Select Recurring budget as the budget type.
  • Select Fixed as the budgeting method.
  • Enter 20 USD as the budget amount.

What Does This Budget Control?

This configuration creates a recurring monthly alerting guardrail. It does not create a hard spending cap.

The two alerts cover money already spent plus spending AWS forecasts for the month. Together they give you an earlier signal than either alert alone.

  • Add an Actual alert threshold at 80%.
  • Enter your email address as the recipient for the Actual alert.
  • Add a Forecasted alert threshold at 80%.
  • Enter your email address as the recipient for the Forecasted alert.
  • Review the budget settings for accuracy.
  • Select Create budget.
  • Complete the email verification request if AWS sends one to your inbox.
  • Open the budget details by selecting cloud-security-lab-budget from the Budgets list.

You should see the budget named cloud-security-lab-budget.

The period should show Monthly. The amount should show 20 USD.

The alerts should show Actual at 80% plus Forecasted at 80%.

Good. Your lab now has spending visibility before its first S3 or CloudFront resource exists.

Allow Time for Cost Data

AWS Budgets cost information can take 8 to 12 hours to refresh. A quiet chart immediately after creation does not mean the budget failed.

Budget Settings Missing?

  • Confirm you chose Customize (advanced) with Cost budget.
  • Check that the amount is 20 USD.
  • Open the budget details to confirm both 80% alerts were saved.

If the form or saved values look different, help me troubleshoot my AWS cost budget settings.

Prepare your Windows workspace

File Explorer keeps the project files together inside your Documents folder. The dedicated folder prevents the portfolio files from getting mixed into unrelated work.

  • Open File Explorer by pressing Windows logo key + E.
  • Select Documents from the navigation pane.
  • Select New.
  • Select Folder.
  • Type cloud-security-portfolio as the folder name.
  • Press Enter to create the folder.

File Explorer should now show Documents\cloud-security-portfolio ready for the project files.

  • Select Start.
  • Type Notepad.
  • Select Notepad from the search results.

Windows Notepad should open to a blank document. Your local editor is ready for the portfolio page.

  • Select Start.
  • Type Microsoft Edge.
  • Select Microsoft Edge from the search results.

Microsoft Edge should open successfully. You now have the browser needed for local previews plus later security checks.

Before the final check, do you expect the protected sign-in, cost guardrail, desktop tools, and project folder to be ready at the same time?

  • Return to AWS Budgets in the browser from earlier.
  • Select cloud-security-lab-budget.
  • Review the budget summary.
  • Switch back to Windows Notepad from the taskbar.
  • Switch back to Microsoft Edge from the taskbar.
  • Return to File Explorer.
  • Select Documents.

You should see a recurring Monthly Cost budget with a Fixed amount of 20 USD plus both 80% email alerts.

Windows Notepad plus Microsoft Edge should both open successfully. File Explorer should show the cloud-security-portfolio folder inside Documents.

Missing a Local Setup Signal?

  • Search for Windows Notepad again if its blank document is not open.
  • Search for Microsoft Edge again if its browser window is not open.
  • Check the Documents location if cloud-security-portfolio is missing from File Explorer.

If a Windows tool or folder still does not appear, help me finish my local Windows setup.

Your access and cost guardrails are ready. Next, you will build the portfolio page locally before placing a private copy in Amazon S3.

Create a Private S3 Portfolio Page

Your protected access and cost guardrail are ready. Now you can build the portfolio page that visitors eventually reach over HTTPS.

A local preview proves the page works. A private Amazon S3 upload lets you test the cloud access boundary directly.

In this step, get ready to:
  • Build the portfolio page in Windows Notepad.
  • Store the page in a private Amazon S3 bucket.
  • Test the direct object URL from an anonymous browser session.
Build and preview the portfolio page

The page makes your architecture visible before any cloud delivery service exists. You can confirm the file works locally before uploading the same file to AWS.

  • Switch back to Windows Notepad from earlier.
  • Create the page shell by pasting this code into the empty document:
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>My Secure AWS Architecture</title>
  <style>
  </style>
</head>
<body>
  <main>
    <p class="eyebrow">Cloud Security Portfolio</p>
    <h1>Public delivery. Private origin.</h1>
    <p class="intro">
      My first AWS architecture serves a static page through Amazon CloudFront
      while the original object remains private in Amazon S3.
    </p>
  </main>
</body>
</html>

What Does This Code Do?

  • The document metadata helps browsers display the page correctly across different screen sizes.
  • The empty style section gives you a clear place for the visual design.
  • The main content states the architecture's public delivery and private origin goal.
  • Save the document as `index.html` inside `Documents\cloud-security-portfolio`.
  • Switch back to Microsoft Edge from earlier.
  • Press **Ctrl+O** to open the local file picker.
  • Select `Documents\cloud-security-portfolio\index.html`.

You should see the portfolio heading with a short architecture introduction. That first local result confirms Edge is reading your HTML file.

Seeing Plain Text or a Blank Page?

Confirm the filename ends with `index.html`. Remove any hidden `.txt` extension that Notepad added.

Check that the opening and closing `html`, `head`, and `body` tags match the code above.

Help me fix the local HTML preview.

  • Return to `index.html` in Notepad.
  • Place the cursor on the blank line between `<style>` and `</style>`.
  • Add the page foundation by pasting this code on that line:
    :root {
      color-scheme: dark;
      font-family: "Segoe UI", Arial, sans-serif;
      --page: #08111f;
      --panel: #111f33;
      --accent: #55d6be;
      --text: #eef6ff;
      --muted: #a9bad0;
      --line: #29415f;
    }

    * {
      box-sizing: border-box;
    }

    body {
      margin: 0;
      min-height: 100vh;
      background: radial-gradient(circle at top, #17365c 0, var(--page) 52%);
      color: var(--text);
    }

    main {
      width: min(900px, 92vw);
      margin: 0 auto;
      padding: 72px 0;
    }

How Does the Foundation Work?

  • The custom properties hold the page colours in one place.
  • The `body` selector creates the dark background and sets the default text colour.
  • The `main` selector centres the content within a readable width.
  • Save `index.html`.
  • Return to the local page in Edge.
  • Refresh the page.

You should see a dark blue background with light text centred inside a wide content area.

Still Seeing a White Background?

Check that the new CSS sits between the opening `<style>` tag and the closing `</style>` tag.

Confirm each custom property begins with two hyphens.

Help me find the CSS placement problem.

  • Return to the closing brace of the `main` selector in Notepad.
  • Add the heading styles directly below it by pasting this code:
    .eyebrow {
      color: var(--accent);
      font-size: 0.85rem;
      font-weight: 700;
      letter-spacing: 0.16em;
      text-transform: uppercase;
    }

    h1 {
      max-width: 720px;
      margin: 12px 0 16px;
      font-size: clamp(2.5rem, 7vw, 5rem);
      line-height: 1;
    }

    .intro {
      max-width: 680px;
      color: var(--muted);
      font-size: 1.15rem;
      line-height: 1.7;
    }

What Changes Here?

  • The `.eyebrow` selector turns the portfolio label into a compact accent line.
  • The `h1` selector scales the main message across different screen widths.
  • The `.intro` selector softens the supporting copy and increases its line spacing.
  • Save `index.html`.
  • Return to the local page in Edge.
  • Refresh the page.

You should see a large `Public delivery. Private origin.` heading beneath a small green portfolio label.

Heading Styles Missing?

Confirm `.eyebrow` matches `class="eyebrow"` in the HTML. Check `.intro` against `class="intro"` as well.

Help me match the HTML classes to the CSS selectors.

  • Return to the introductory paragraph inside `<main>` in Notepad.
  • Place the cursor after the `</p>` that closes that introduction.
  • Add the request flow and security proof by pasting this code:

    <section class="flow" aria-label="Request flow">
      <article class="card">
        <span class="number">1</span>
        <h2>Viewer</h2>
        <p>A browser requests the page through a public HTTPS endpoint.</p>
      </article>

      <article class="card">
        <span class="number">2</span>
        <h2>Amazon CloudFront</h2>
        <p>The distribution delivers cached content and authenticates to the origin.</p>
      </article>

      <article class="card">
        <span class="number">3</span>
        <h2>Amazon S3</h2>
        <p>The encrypted bucket stays private and permits the intended delivery path.</p>
      </article>
    </section>

    <section class="proof">
      <strong>Security proof:</strong>
      the CloudFront URL succeeds, while direct anonymous access to the S3 object is denied.
    </section>

How Does the Request Flow Read?

  • The first card represents the viewer requesting your public HTTPS page.
  • The second card places Amazon CloudFront between the viewer and storage.
  • The third card identifies Amazon S3 as the private origin.
  • The proof section states the pass and deny results that you validate across the project.
  • Save `index.html`.
  • Return to the local page in Edge.
  • Refresh the page.

You should see the Viewer, Amazon CloudFront, and Amazon S3 stages beneath the introduction. You should also see the security proof statement below them.

Request Flow Missing?

Confirm the new sections sit inside `<main>`. They must appear before the closing `</main>` tag.

Help me place the request flow inside the page.

  • Return to the closing brace of the `.intro` selector in Notepad.
  • Style the request-flow layout by pasting this code directly below it:

    .flow {
      display: grid;
      grid-template-columns: repeat(3, 1fr);
      gap: 16px;
      margin: 42px 0;
    }

    .card {
      min-height: 170px;
      padding: 24px;
      border: 1px solid var(--line);
      border-radius: 18px;
      background: rgba(17, 31, 51, 0.88);
      box-shadow: 0 18px 40px rgba(0, 0, 0, 0.25);
    }

How Do the Cards Take Shape?

  • The `.flow` grid places the three architecture stages in equal columns.
  • The `.card` selector gives every stage the same spacing and panel treatment.
  • The border and shadow separate each stage from the dark page background.
  • Save `index.html`.
  • Return to the local page in Edge.
  • Refresh the page.

You should see three dark cards in a single row. Each card represents one stage of the request flow.

Cards Still Stacked Vertically?

Check that the container uses `class="flow"`. Confirm the CSS selector starts with `.flow`.

Help me debug the request-flow grid.

  • Return to the closing brace of the `.card` selector in Notepad.
  • Style the number badges and card text by pasting this code directly below it:

    .number {
      display: inline-grid;
      width: 34px;
      height: 34px;
      place-items: center;
      border-radius: 50%;
      background: var(--accent);
      color: #062018;
      font-weight: 800;
    }

    h2 {
      margin: 18px 0 8px;
      font-size: 1.2rem;
    }

    .card p {
      margin: 0;
      color: var(--muted);
      line-height: 1.55;
    }

What Do These Details Add?

  • The `.number` selector turns each stage number into a circular accent badge.
  • The `h2` spacing separates each badge from its stage title.
  • The `.card p` selector keeps the explanatory text readable without competing with the titles.
  • Save `index.html`.
  • Return to the local page in Edge.
  • Refresh the page.

You should see a green numbered badge inside each card. The supporting text should appear in a softer blue-grey colour.

Number Badges Not Circular?

Confirm each number uses `class="number"`. Check that both `width` and `height` are set to `34px`.

Help me repair the numbered card badges.

  • Return to the closing brace of the `.card p` selector in Notepad.
  • Finish the proof panel and mobile layout by pasting this code directly below it:

    .proof {
      padding: 24px;
      border-left: 4px solid var(--accent);
      border-radius: 0 14px 14px 0;
      background: rgba(85, 214, 190, 0.09);
    }

    .proof strong {
      color: var(--accent);
    }

    @media (max-width: 700px) {
      main {
        padding: 44px 0;
      }

      .flow {
        grid-template-columns: 1fr;
      }
    }

How Does the Final Layout Adapt?

  • The `.proof` selector gives the final security claim a coloured border and tinted background.
  • The media query stacks the cards into one column on screens narrower than `700px`.
  • The smaller page padding gives mobile screens more room for content.
  • Save `index.html`.
  • Return to the local page in Edge.
  • Refresh the page.

You should see a green-accented security proof panel beneath the three cards. The complete page now presents the architecture as a visual story.

Proof Panel Missing Its Accent?

Confirm the proof section uses `class="proof"`. Check that this CSS appears before the closing `</style>` tag.

Help me fix the security proof panel.

✔️ Awesome, I've got everything!

Great. Save `index.html` once more before uploading the exact page you just previewed.

ⓧ I'd like to double check the full code

Compare your complete `index.html` file with this reference.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>My Secure AWS Architecture</title>
  <style>
    :root {
      color-scheme: dark;
      font-family: "Segoe UI", Arial, sans-serif;
      --page: #08111f;
      --panel: #111f33;
      --accent: #55d6be;
      --text: #eef6ff;
      --muted: #a9bad0;
      --line: #29415f;
    }

    * {
      box-sizing: border-box;
    }

    body {
      margin: 0;
      min-height: 100vh;
      background: radial-gradient(circle at top, #17365c 0, var(--page) 52%);
      color: var(--text);
    }

    main {
      width: min(900px, 92vw);
      margin: 0 auto;
      padding: 72px 0;
    }

    .eyebrow {
      color: var(--accent);
      font-size: 0.85rem;
      font-weight: 700;
      letter-spacing: 0.16em;
      text-transform: uppercase;
    }

    h1 {
      max-width: 720px;
      margin: 12px 0 16px;
      font-size: clamp(2.5rem, 7vw, 5rem);
      line-height: 1;
    }

    .intro {
      max-width: 680px;
      color: var(--muted);
      font-size: 1.15rem;
      line-height: 1.7;
    }

    .flow {
      display: grid;
      grid-template-columns: repeat(3, 1fr);
      gap: 16px;
      margin: 42px 0;
    }

    .card {
      min-height: 170px;
      padding: 24px;
      border: 1px solid var(--line);
      border-radius: 18px;
      background: rgba(17, 31, 51, 0.88);
      box-shadow: 0 18px 40px rgba(0, 0, 0, 0.25);
    }

    .number {
      display: inline-grid;
      width: 34px;
      height: 34px;
      place-items: center;
      border-radius: 50%;
      background: var(--accent);
      color: #062018;
      font-weight: 800;
    }

    h2 {
      margin: 18px 0 8px;
      font-size: 1.2rem;
    }

    .card p {
      margin: 0;
      color: var(--muted);
      line-height: 1.55;
    }

    .proof {
      padding: 24px;
      border-left: 4px solid var(--accent);
      border-radius: 0 14px 14px 0;
      background: rgba(85, 214, 190, 0.09);
    }

    .proof strong {
      color: var(--accent);
    }

    @media (max-width: 700px) {
      main {
        padding: 44px 0;
      }

      .flow {
        grid-template-columns: 1fr;
      }
    }
  </style>
</head>
<body>
  <main>
    <p class="eyebrow">Cloud Security Portfolio</p>
    <h1>Public delivery. Private origin.</h1>
    <p class="intro">
      My first AWS architecture serves a static page through Amazon CloudFront
      while the original object remains private in Amazon S3.
    </p>

    <section class="flow" aria-label="Request flow">
      <article class="card">
        <span class="number">1</span>
        <h2>Viewer</h2>
        <p>A browser requests the page through a public HTTPS endpoint.</p>
      </article>

      <article class="card">
        <span class="number">2</span>
        <h2>Amazon CloudFront</h2>
        <p>The distribution delivers cached content and authenticates to the origin.</p>
      </article>

      <article class="card">
        <span class="number">3</span>
        <h2>Amazon S3</h2>
        <p>The encrypted bucket stays private and permits the intended delivery path.</p>
      </article>
    </section>

    <section class="proof">
      <strong>Security proof:</strong>
      the CloudFront URL succeeds, while direct anonymous access to the S3 object is denied.
    </section>
  </main>
</body>
</html>
Create the private bucket and upload the page

An object storage bucket holds files as objects. S3 applies private access and encryption defaults before you add any delivery path.

Keep the Cost Boundary Visible

Creating this bucket starts a usage-based AWS resource. The tiny file keeps this tutorial's expected cost under $1.

Your budget provides spending alerts instead of a hard cap. Keep monitoring your account while the resource exists.

  • Switch back to the AWS Management Console from earlier.
  • Use the console search to open Amazon S3.
  • Choose **General purpose buckets** in the left navigation pane.
  • Choose **Create bucket**.

Choose a Safe Bucket Name

A general purpose bucket name must be globally unique. It must contain 3 to 63 characters.

Use lowercase letters or numbers. Hyphens and periods are also allowed.

  • Create a globally unique lowercase name and record it here: your globally unique lowercase bucket name.
  • Enter your globally unique lowercase bucket name in the **Bucket name** field.
  • Keep **Bucket owner enforced** selected under **Object Ownership**.
  • Keep all four **Block Public Access settings for this bucket** enabled.
  • Keep **Server-side encryption with Amazon S3 managed keys (SSE-S3)** selected under **Default encryption**.
  • Choose **Create bucket**.

You should see your globally unique lowercase bucket name in the general purpose buckets list.

What Protects This Bucket?

  • Bucket owner enforced disables ACLs and keeps access control in policies.
  • Block Public Access prevents public permissions from exposing the bucket.
  • SSE-S3 encrypts new objects at rest with Amazon S3 managed keys.
  • Select your globally unique lowercase bucket name from the buckets list.
  • Choose **Upload**.
  • Choose **Add files**.
  • Select `Documents\cloud-security-portfolio\index.html`.
  • Choose **Upload**.

You should see a successful upload result. Return to the bucket to see `index.html` in the object list.

That is the storage boundary in place. Your page is encrypted in S3 without public-read permission.

Upload Did Not Complete?

Confirm `index.html` still exists inside `Documents\cloud-security-portfolio`. Retry the upload from the bucket's **Upload** page.

Help me diagnose the S3 upload problem.

  • Select `index.html` in the object list.
  • Choose the **Properties** tab.
  • Copy the value from **Object URL** and record it here: your S3 object URL.
Test the anonymous object URL

An InPrivate window removes your normal browser session from this test. The object request must succeed or fail using the bucket's anonymous access rules.

Before you open the URL, do you think an anonymous visitor can retrieve the object from this bucket?

  • Press **Ctrl+Shift+N** in Microsoft Edge to open a new InPrivate window.
  • Paste your S3 object URL into the address bar.
  • Press **Enter** to request the object anonymously.

You should see `AccessDenied` or HTTP `403`. This intended failure proves the direct S3 path is private.

Why Is This Failure Useful?

The uploaded file exists and works locally. S3 still refuses an unsigned request because the bucket has no public-read path.

That denial gives you evidence that visitors cannot bypass the delivery layer and fetch the origin directly.

Does the Object Load Publicly?

Return to the bucket's **Permissions** tab. Confirm every Block Public Access setting remains enabled.

Do not add a public bucket policy or public-read permission. The next step creates the intended delivery path.

Help me find why my S3 object is publicly accessible.

Your page now works locally while its cloud copy rejects direct anonymous access. Next, Amazon CloudFront creates the public HTTPS delivery path without making the S3 origin public.

Deliver the Page Through CloudFront

Your private Amazon S3 origin now rejects anonymous requests. That failure proved the storage boundary works.

Amazon CloudFront provides the public HTTPS endpoint your visitors need. Origin Access Control gives CloudFront authenticated read access without making the bucket public.

In this step, get ready to:
  • Create a standard CloudFront distribution for your private S3 bucket.
  • Apply recommended origin settings so CloudFront configures Origin Access Control.
  • Record the assigned domain name and load the portfolio page over HTTPS.
Create the standard distribution

Origin Access Control works with a regular S3 bucket origin. Your existing private bucket already matches that requirement.

Pay-as-you-go can create usage charges beyond free allowances. This tutorial is expected to stay under $1.

  • Return to the AWS Management Console session from earlier.
  • Use the console search bar to find CloudFront.
  • Select CloudFront from the search results.

You'll reach the CloudFront distributions page. This is where you create the public delivery layer for your private object.

  • Choose Create distribution.
  • Choose Single website or app.
  • Set the distribution name to secure-portfolio-cdn.
  • Choose Amazon S3 as the origin type.
  • Choose Browse S3.
  • Select your private bucket from earlier.
  • Leave the custom domain option unconfigured.
  • Select the pay-as-you-go pricing option.

Why use the manual CloudFront path?

AWS Amplify Hosting offers an easier static hosting workflow. This manual path exposes the private-origin boundary that you are learning to secure.

You control the S3 origin permission through Origin Access Control. That makes the service-to-service access path visible.

Protect the S3 origin with OAC

Recommended origin settings configure OAC for the selected bucket. CloudFront also updates the bucket policy so the distribution receives scoped read access.

  • Choose Use recommended origin settings.
  • Confirm the origin summary names your private S3 bucket.
  • Leave optional AWS WAF protections unselected.
  • Choose Create distribution.

You'll see secure-portfolio-cdn in the distributions list. Its Last modified field initially shows Deploying.

What did the recommended settings change?

  • Origin Access Control signs CloudFront's requests to the private S3 origin.
  • The bucket policy grants s3:GetObject access to the cloudfront.amazonaws.com service principal.
  • The AWS:SourceArn condition limits that permission to this distribution.

Distribution missing from the list?

  • Confirm you chose Create distribution after selecting the recommended origin settings.
  • Confirm the origin type is Amazon S3.
  • Confirm the origin summary names your private bucket.

Ask for help with the wizard settings:

Wait for deployment and test HTTPS

CloudFront applies the distribution configuration across its delivery network. This update can take several minutes, so Deploying is expected during the wait.

  • Wait in the distribution details until Last modified changes from Deploying to a date and time.
  • Record the assigned domain name here: your CloudFront domain name.

The distribution configuration is now active. That completes the delivery setup CloudFront needs to reach your private origin.

Before you test, ask yourself whether the private page will now load through CloudFront.

  • Switch back to Microsoft Edge from earlier.
  • Enter https:// followed by your CloudFront domain name plus /index.html in the address bar.
  • Press Enter.

You'll see your cloud security portfolio page at the CloudFront HTTPS address.

You have the secure delivery path working. Visitors reach the page through CloudFront while the S3 origin keeps rejecting anonymous requests.

CloudFront page not loading?

  • Confirm Last modified no longer shows Deploying.
  • Check that the address ends with /index.html.
  • Verify the distribution origin names your private S3 bucket.

Ask for help tracing the delivery path:

Your portfolio now has a public HTTPS delivery path backed by a private origin. Next, you'll turn this result into repeatable security evidence.

Prove the Security Design

Your Amazon CloudFront URL already serves the portfolio over HTTPS. This successful request proves the intended delivery path works.

A security portfolio also needs observable evidence that the private Amazon S3 origin cannot be bypassed. This step turns your settings into repeatable proof.

In this step, get ready to:
  • Verify the private-origin controls in Amazon S3.
  • Inspect the read-only permission for Origin Access Control.
  • Record the browser results in an architecture brief.
Verify the private origin controls

Three storage controls keep the origin private. The bucket's resource-based policy creates a narrow read path for CloudFront.

  • Switch back to the Amazon S3 console from earlier.
  • Select the private bucket that stores index.html.
  • Check whether all four Block Public Access settings remain enabled.
  • Check whether Object Ownership shows Bucket owner enforced.
  • Select index.html from the object list.
  • Check whether its server-side encryption uses SSE-S3.

You should see every Block Public Access setting enabled. Object Ownership should show Bucket owner enforced.

The object should use SSE-S3 for encryption at rest.

What do these controls prove?

  • Block Public Access protects the bucket from public exposure.
  • Bucket owner enforced disables access control lists.
  • SSE-S3 encrypts the uploaded object at rest.

Bucket policies can feel dense on first read. Three values reveal who can read the object.

  • View the selected bucket's policy.
  • Locate the read-only action named s3:GetObject.
  • Locate the service principal named cloudfront.amazonaws.com.
  • Locate the condition named AWS:SourceArn.
  • Confirm AWS:SourceArn matches the distribution ARN for secure-portfolio-cdn.

The policy grants CloudFront read-only access through s3:GetObject. The service principal identifies CloudFront as the permitted caller.

The AWS:SourceArn condition limits that permission to your intended distribution.

Create the architecture brief

An architecture brief connects each console setting to its security purpose. You will create this Markdown record in Windows Notepad.

  • Switch back to Windows Notepad from earlier.
  • Create a blank document in the existing Notepad window.
  • Save the document as Documents\cloud-security-portfolio\architecture-brief.md.
  • Add the architecture overview by pasting this content:
# Secure AWS Static Site Architecture

## Goal

Deliver a public static page over HTTPS without making its Amazon S3 origin public.

## Request flow

Viewer -> Amazon CloudFront -> Origin Access Control signed request -> private Amazon S3 object

## Security controls

- Amazon S3 Block Public Access remains enabled.
- S3 Object Ownership uses Bucket owner enforced, so ACLs are disabled.
- The object is encrypted at rest with SSE-S3.
- The bucket policy grants read access to the CloudFront service principal for the intended distribution.
- Viewers use the CloudFront HTTPS endpoint instead of the S3 object URL.

What does this brief explain?

  • The goal defines the public delivery path.
  • The request flow traces a viewer request through CloudFront.
  • The security controls explain how the S3 origin stays private.
  • Save architecture-brief.md.
  • Confirm the file contains the Goal heading.
  • Confirm the file contains the Request flow heading.
  • Confirm the file contains the Security controls heading.

Good progress. Your architecture now has a written goal plus a traceable request path.

Did Notepad change the extension?

Check the filename if Notepad appended a text-file extension. Rename the file so its final extension is .md.

Help me preserve the Markdown filename in Windows Notepad.

The remaining sections preserve your validation results. They also explain why a regular private bucket supports this design.

  • Place the cursor after the final Security controls bullet.
  • Keep one blank line before the next heading.
  • Add the remaining sections by pasting this content:
## Validation evidence

- Local page preview: PASS
- CloudFront HTTPS request: PASS
- Direct anonymous S3 request: EXPECTED DENY
- S3 Block Public Access: PASS
- S3 encryption at rest: PASS
- CloudFront-scoped bucket policy: PASS

## Architecture decision

I used a regular private S3 bucket as the origin instead of an S3 website endpoint because Origin Access Control requires a regular S3 bucket origin. This preserves a public delivery path while preventing direct origin access.

## Production follow-ups

- Replace broad administrative access with a least-privilege role.
- Add a custom domain and managed TLS certificate when a domain is available.
- Add monitoring, access logging, and AWS WAF only when requirements justify their cost and operational scope.

What does this evidence record?

  • The validation section records each successful control check.
  • The expected denial records the blocked anonymous request.
  • The architecture decision explains the regular S3 origin choice.
  • The production follow-ups separate this lab from future hardening work.
  • Save architecture-brief.md.
  • Confirm the file contains the Validation evidence heading.
  • Confirm the file contains the Architecture decision heading.
  • Confirm the file contains the Production follow-ups heading.

The finished brief now connects the design to its evidence. It is ready for the final browser comparison.

Brief missing a section?

Check that every section heading begins with ##. Check that Validation evidence follows Security controls.

Help me compare my architecture brief with the required structure.

✔️ Awesome, I've got everything!

Your files now match the finished project record. Make sure both files remain saved.

ⓧ I'd like to double check the full code

Compare architecture-brief.md with this complete reference:

# Secure AWS Static Site Architecture

## Goal

Deliver a public static page over HTTPS without making its Amazon S3 origin public.

## Request flow

Viewer -> Amazon CloudFront -> Origin Access Control signed request -> private Amazon S3 object

## Security controls

- Amazon S3 Block Public Access remains enabled.
- S3 Object Ownership uses Bucket owner enforced, so ACLs are disabled.
- The object is encrypted at rest with SSE-S3.
- The bucket policy grants read access to the CloudFront service principal for the intended distribution.
- Viewers use the CloudFront HTTPS endpoint instead of the S3 object URL.

## Validation evidence

- Local page preview: PASS
- CloudFront HTTPS request: PASS
- Direct anonymous S3 request: EXPECTED DENY
- S3 Block Public Access: PASS
- S3 encryption at rest: PASS
- CloudFront-scoped bucket policy: PASS

## Architecture decision

I used a regular private S3 bucket as the origin instead of an S3 website endpoint because Origin Access Control requires a regular S3 bucket origin. This preserves a public delivery path while preventing direct origin access.

## Production follow-ups

- Replace broad administrative access with a least-privilege role.
- Add a custom domain and managed TLS certificate when a domain is available.
- Add monitoring, access logging, and AWS WAF only when requirements justify their cost and operational scope.

Confirm the unchanged index.html file against this complete reference:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>My Secure AWS Architecture</title>
  <style>
    :root {
      color-scheme: dark;
      font-family: "Segoe UI", Arial, sans-serif;
      --page: #08111f;
      --panel: #111f33;
      --accent: #55d6be;
      --text: #eef6ff;
      --muted: #a9bad0;
      --line: #29415f;
    }

    * {
      box-sizing: border-box;
    }

    body {
      margin: 0;
      min-height: 100vh;
      background: radial-gradient(circle at top, #17365c 0, var(--page) 52%);
      color: var(--text);
    }

    main {
      width: min(900px, 92vw);
      margin: 0 auto;
      padding: 72px 0;
    }

    .eyebrow {
      color: var(--accent);
      font-size: 0.85rem;
      font-weight: 700;
      letter-spacing: 0.16em;
      text-transform: uppercase;
    }

    h1 {
      max-width: 720px;
      margin: 12px 0 16px;
      font-size: clamp(2.5rem, 7vw, 5rem);
      line-height: 1;
    }

    .intro {
      max-width: 680px;
      color: var(--muted);
      font-size: 1.15rem;
      line-height: 1.7;
    }

    .flow {
      display: grid;
      grid-template-columns: repeat(3, 1fr);
      gap: 16px;
      margin: 42px 0;
    }

    .card {
      min-height: 170px;
      padding: 24px;
      border: 1px solid var(--line);
      border-radius: 18px;
      background: rgba(17, 31, 51, 0.88);
      box-shadow: 0 18px 40px rgba(0, 0, 0, 0.25);
    }

    .number {
      display: inline-grid;
      width: 34px;
      height: 34px;
      place-items: center;
      border-radius: 50%;
      background: var(--accent);
      color: #062018;
      font-weight: 800;
    }

    h2 {
      margin: 18px 0 8px;
      font-size: 1.2rem;
    }

    .card p {
      margin: 0;
      color: var(--muted);
      line-height: 1.55;
    }

    .proof {
      padding: 24px;
      border-left: 4px solid var(--accent);
      border-radius: 0 14px 14px 0;
      background: rgba(85, 214, 190, 0.09);
    }

    .proof strong {
      color: var(--accent);
    }

    @media (max-width: 700px) {
      main {
        padding: 44px 0;
      }

      .flow {
        grid-template-columns: 1fr;
      }
    }
  </style>
</head>
<body>
  <main>
    <p class="eyebrow">Cloud Security Portfolio</p>
    <h1>Public delivery. Private origin.</h1>
    <p class="intro">
      My first AWS architecture serves a static page through Amazon CloudFront
      while the original object remains private in Amazon S3.
    </p>

    <section class="flow" aria-label="Request flow">
      <article class="card">
        <span class="number">1</span>
        <h2>Viewer</h2>
        <p>A browser requests the page through a public HTTPS endpoint.</p>
      </article>

      <article class="card">
        <span class="number">2</span>
        <h2>Amazon CloudFront</h2>
        <p>The distribution delivers cached content and authenticates to the origin.</p>
      </article>

      <article class="card">
        <span class="number">3</span>
        <h2>Amazon S3</h2>
        <p>The encrypted bucket stays private and permits the intended delivery path.</p>
      </article>
    </section>

    <section class="proof">
      <strong>Security proof:</strong>
      the CloudFront URL succeeds, while direct anonymous access to the S3 object is denied.
    </section>
  </main>
</body>
</html>
Capture the security evidence

The strongest evidence compares the permitted request with the blocked bypass attempt. You will repeat both checks in Microsoft Edge.

Keep screenshots safe

Keep account IDs outside each screenshot. Capture only the browser result plus the address needed to identify the delivery path.

  • Switch back to Microsoft Edge from earlier.
  • Open a new InPrivate window.

Before you test the delivery path, do you expect the CloudFront request to reach the private object?

  • Load your recorded CloudFront URL ending in /index.html.

You should see the portfolio page over HTTPS. This successful request proves CloudFront can use the intended permission path.

  • Capture a screenshot of the portfolio with the CloudFront address visible.
  • Open another tab in the same InPrivate window.

Before you test the origin, do you expect an anonymous request to bypass CloudFront?

  • Load the direct S3 object URL from earlier.

You should see AccessDenied or HTTP 403. This denial proves anonymous visitors cannot read the object directly.

  • Capture a screenshot of the denied S3 request without exposing sensitive values.

Before the final comparison, do you expect either browser result to change when you refresh both tabs?

  • Refresh the CloudFront tab.

You should still see the portfolio page.

  • Refresh the direct S3 object tab.

You should still see AccessDenied or HTTP 403.

  • Switch back to architecture-brief.md in Windows Notepad.
  • Confirm the Validation evidence section records CloudFront HTTPS request: PASS.
  • Confirm the Validation evidence section records Direct anonymous S3 request: EXPECTED DENY.

That is the proof complete. Your browser evidence now demonstrates both sides of the permission boundary.

Secret mission

Add and Verify Security Headers

Your private origin already blocks direct access. Now add CloudFront's managed SecurityHeadersPolicy to browser responses. Verify the returned security headers in Microsoft Edge DevTools.

Clean Up Your Resources

Clean Up Your Resources

Your deployed AWS resources can continue generating charges. Your options are to keep them active, pause delivery, or remove the project.

Cost warning

The AWS Budgets budget named cloud-security-lab-budget sends alerts at 80% of 20 USD. It does not create a hard spending cap.

Tutorial use is expected to stay under $1. Standard AWS charges can still apply when free allowances or credits are exceeded.

Resources you used:

  • A recurring AWS budget named cloud-security-lab-budget with Actual and Forecasted alerts.
  • An Amazon CloudFront standard distribution named secure-portfolio-cdn with Origin Access Control and the managed SecurityHeadersPolicy.
  • A private Amazon S3 bucket containing index.html and the CloudFront-scoped bucket policy.
  • The local Documents\cloud-security-portfolio folder containing index.html and architecture-brief.md.

Keep everything running

No cleanup is needed. Choose this option if you want to keep demonstrating the secure website.

  • Keep secure-portfolio-cdn enabled so its HTTPS URL continues serving your portfolio page.
  • Keep the private S3 bucket so CloudFront can continue retrieving index.html.
  • Monitor the alerts from cloud-security-lab-budget.
  • Limit how widely you share the CloudFront URL to control unexpected traffic.
  • Retain Documents\cloud-security-portfolio as your local copy of the project.

Pause - I'll come back to this later

Disable the CloudFront distribution to stop public delivery. Your private S3 bucket stays ready for when you return.

  • Return to Amazon CloudFront in the AWS Management Console from earlier.
  • Select secure-portfolio-cdn.
  • Choose Disable.
  • Confirm with Yes, Disable.
  • Wait for Last modified to show a new date and time.

The disabled distribution stops serving your HTTPS URL. It no longer accrues CloudFront charges.

  • Retain the private S3 bucket for a future restart.
  • Keep cloud-security-lab-budget active so its cost alerts remain available.
  • Keep Documents\cloud-security-portfolio unchanged on your computer.

Delete - I don't want to use this again

Full cleanup permanently removes the cloud resources and local files. The order matters because CloudFront must be deleted before its private S3 origin.

Delete the CloudFront distribution first:

  • Return to Amazon CloudFront in the AWS Management Console from earlier.
  • Select secure-portfolio-cdn.
  • Choose Disable if the distribution is still enabled.
  • Confirm with Yes, Disable.
  • Wait for Last modified to show a new date and time.

The new timestamp confirms that the disable operation has finished propagating. The distribution is now ready for deletion.

  • Select the disabled secure-portfolio-cdn distribution.
  • Choose Delete.
  • Confirm with Delete.
  • Confirm that secure-portfolio-cdn no longer appears in the distribution list.

Empty the private S3 bucket next:

  • Return to the S3 bucket list in the AWS Management Console from earlier.
  • Select the private bucket containing index.html.
  • Choose Empty.
  • Enter permanently delete in the confirmation field.
  • Confirm the empty operation.

The bucket now contains no objects. This clears index.html before the bucket itself is removed.

  • Return to the empty bucket's details.
  • Choose Delete.
  • Enter the bucket name in the confirmation field.
  • Choose Delete bucket.
  • Confirm that the bucket no longer appears in the S3 bucket list.

Remove the local project files:

  • Return to your Documents folder in Windows.
  • Select the cloud-security-portfolio folder.
  • Delete the selected folder.
  • Confirm that Documents\cloud-security-portfolio no longer appears in Documents.

Remove the cost budget only if you also want to retire this guardrail. Deleting the budget removes its alerts without deleting other AWS resources.

  • Return to Billing and Cost Management in the AWS Management Console from earlier.
  • Select Budgets.
  • Select cloud-security-lab-budget.
  • Use the budget deletion control to remove the selected budget.
  • Confirm that cloud-security-lab-budget no longer appears in the budget list.

Nice Work!

Nice Work!

You did it! Your secure portfolio page now loads over HTTPS through Amazon CloudFront while its Amazon S3 origin stays private.

You've learned how to:

  • Build a locally previewable cloud security portfolio page that shows the request flow from viewer to private origin.
  • Deliver the page through a CloudFront HTTPS URL. Keep the Amazon S3 origin private with Block Public Access, Bucket owner enforced, SSE-S3, and Origin Access Control.
  • Prove the security boundary by showing that the CloudFront path succeeds while direct anonymous S3 access returns AccessDenied. Record the design in architecture-brief.md. Preserve your validation evidence for interview discussion.
  • Complete the optional Secret Mission by attaching CloudFront's managed SecurityHeadersPolicy. Verify Strict-Transport-Security, X-Content-Type-Options, X-Frame-Options, and Referrer-Policy in Microsoft Edge DevTools.

Ready to quiz yourself?