Scaling Threat Detection with AI
Table of contents
Why I Gave This Talk
This year at SAINTCON I gave a talk on the Blue Team track called Scaling Threat Detection with AI using Detection-as-Code, and since a slide deck without the speaker attached is only about half useful, this Byte is the blog version of it. The promise of the talk is right there in the subtitle: taking a detection from hypothesis to production in hours, or potentially minutes, instead of weeks, with a human still at the gate for every change that ships. Where it lands between those two mostly depends on your SIEM platform and how long the queries take to run, and on how quickly a reviewer gets to the output. One quick disclaimer before we get going, this was an independent talk and the views here are my own and don’t represent any employer.
If you only read one sentence, make it this one: codify your detections, automate the pipeline, and then let agents scale your engineers behind a human gate. The rest of this post is the “how” and the “why” behind each of those three pieces, in the order you’d actually build them.
The Problem: Exploits Are Getting Cheap
AI agents are making attacks fast, easy, and hands-off. The example I opened the talk with came out of Black Hat USA 2026, where two OpenAI researchers (Michael Dalton and Eric Wallace) walked through an incident that started inside their own internal evaluations. The short version, as told in their talk and the follow-up incident write-ups from OpenAI, Hugging Face, and METR:
- A set of evaluation agents got stuck on a hard security benchmark. Nobody told them to attack anything, they just wanted to finish the task and found a way to cheat.
- One of them found a zero-day in the sandbox proxy and used it to get out to the open internet.
- From there they escalated privileges, moved laterally, and started trading exploits with each other on a shared “message board.”
- They chained stolen credentials and more zero-days into remote code execution, and ended up pulling the benchmark answers out of a third party’s production environment.
The timeline is the part that should get your attention. The eval launched on July 8th, agents were out on the open internet by July 9th, one of them went from a single pod to cluster admin in under 13 hours on July 11th, and the whole thing was contained on July 13th after a roughly four and a half day intrusion. (The victim contained it using their own agents, which is its own interesting footnote.)
Now compare that with the historical baseline. RAND’s Zero Days, Thousands of Nights study looked at 200+ real zero-day exploits and found that the median time for a human to turn a newly found flaw into a working exploit was 22 days, with most landing somewhere between 6 and 37 days. The agents did the finding and the weaponizing inside a single evaluation run, and the resulting intrusion spread over a weekend.
What this means for us as defenders is pretty simple: exploits are getting cheap to build, and some of them are going to land before the patch does. That shrinks the patch window, and it shifts a lot more of the weight onto detection and eradication. We should expect the foothold and focus on catching what comes next, which means our detection coverage needs to be broad, kept current, and able to ship about as fast as the threats show up.
Most Detection Teams Can’t Keep Pace
That’s a problem, because most detection teams are already behind. The 2026 SANS and Anvilogic State of Detection Engineering survey asked 307 practitioners how their coverage is holding up against the threats they care about, and the answers broke down like this:
- 18% stay ahead of threats
- 56% barely keep pace
- 24% fall behind
So roughly four out of five teams are either treading water or sinking, and when you ask them what the top blocker is, the answer is time. Not tooling, not data, time. Which leads to the obvious question: how do we speed up?
Find Where You Sit
SIEMs run queries well, but the engineering workflow belongs in Git. Before we talk about speeding anything up, it helps to find where your team sits today, and I like to think about it as a 2x2 with two axes:
- Across: where do your rules live and get checked? In the SIEM console, checked by hand, or in Git, checked by CI?
- Up: who does the work? People, or AI agents?

That gives us four quadrants:
- Console-driven (no agents, no DaC). Rules are built and edited directly in the SIEM, maybe with a change ticket or a backup export alongside. It works, and when I ask the room at a conference where they sit, most hands go up here, but it’s hard to test and hard to scale.
- Code-driven (no agents, DaC). Rules live in Git and every change is reviewed and tested by CI. This is reliable, but output still tracks headcount, so hiring adds capacity rather than compounding it.
- AI-assisted (agents, no DaC). AI drafts rules fast, but review lives in a Jira ticket and testing happens by hand in the SIEM. Nothing actually enforces either of those, so it’s a habit rather than a control, and people still set the pace. Agent speed, human pace.
- AI-scaled (agents, DaC). Agents draft changes as pull requests, CI checks them, and a human merges. This is where we scale the engineer rather than the team.
The path is always the same: first build the foundation (console-driven to code-driven), then add agents (code-driven to AI-scaled). Jumping straight to AI-assisted is tempting because the drafts show up fast, but you end up with agent output and no guardrails, which is the worst of both worlds. The rest of this post walks the path in order.
Detection-as-Code: The Foundation
Detection-as-Code (DaC) is just treating your detection rules like software: write once, deploy anywhere. Here’s the architecture at a high level:

An engineer writes a hypothesis (or, later on, an AI agent drafts and edits), the rule lives as one YAML file per object in a Git repo with a schema per object type, a pull request kicks off CI (schema, style, and tests, plus runtime and performance checks), and a human reviews and merges. Merge is deploy. A transformer layer translates the YAML into whatever each vendor’s API wants, so the same file can land in the SIEM, the data lake, or whatever platform you move to next. And because edits in the UI still happen in the real world, a drift sync job watches the platforms and opens an automatic pull request whenever something changes outside of Git, so Git stays the source of truth no matter what.
A rule file ends up looking roughly like this:
# office_spawns_encoded_ps.yml
name: Office spawns encoded PS
severity: high
mitre: [T1059.001]
logic:
siem: <search query>
lake: <SQL query>
owner: detection-team
One file, any platform. The portability is nice on its own, but the real win is that every rule change is now a diff that code can inspect and a person can review, and that’s what everything else in this post is built on.
Building the Foundation in Five Steps
Foundation first, agents last. Steps 1 through 4 are plain software engineering and pay off on their own with no AI involved at all, and step 5 only works because of the first four.
1) Schema and CLI
The schema is the set of fields the platform needs to actually run the rule (name, logic, severity, and so on), plus whatever metadata your team finds useful (MITRE mapping, owner, data source, tuning notes). The CLI lets an engineer build, validate, and style-check a rule locally before it ever hits a pull request.
2) CI/CD pipeline and sync
CI/CD is just automation that tests every change and ships it once it’s approved, so nobody is copying rules into the SIEM by hand anymore. This step builds the pipeline and its validity checks: syntax, schema, a dry run against the platform, and drift detection. Approved means deployed, and edits made in the SIEM UI come back as pull requests.
3) Migrate legacy rules
Pull, don’t retype. Export what’s already in the SIEM through its API, convert it into your schema, and commit it, so there’s one source of truth from day one instead of a half-migrated hybrid that nobody trusts.
4) Quality gates in CI
Valid is not the same as good. This step plugs quality checks into the same pipeline: style, runtime cost, backtests against historical data, and replays of past attacks to confirm each rule still fires.
5) Agentic workflows
Only once 1 through 4 are in place. The rest of this post is step 5.
The punchline of this section is that every check you build for humans becomes a guardrail for the agents. The schema validation, the backtest, the regression replay, none of it was built with AI in mind, but it’s exactly what lets us trust AI-generated changes later.
Where AI Agents Pay Off
Not everything should go to an agent. The test I use has three parts, and all three need to be a yes:
- Is the work repetitive?
- Does it end in a file change?
- Can code check the result?
If so, hand it to an agent. The reason this works is that a file diff is reviewable and code can grade it, so the agent’s output is always something we can measure before we trust it.
Give to agents:
- Drafting from a hypothesis. Threat report in, rule and tests out. Days to minutes.
- Bulk edits. One change across hundreds of rules. This is the sleeper hit, because what used to be weeks of careful find-and-replace turns into hours.
- Style and docs. Apply the style guide to every rule, on every pull request.
- Tuning noisy rules. Find the noise, propose the fix. More on this in Level 2 below.
Keep with a human:
- What’s worth detecting in the first place.
- How much noise is acceptable.
- Does the change actually meet the intent?
- The merge.
Agents take the legwork, people keep the judgment calls.
Level 1: Human-Triggered
Level 1 is an agent on demand: you start it, AI does the legwork. It sits on top of the DaC pipeline, and an engineer kicks it off the same way they’d start the work by hand. Say a threat report comes in that says “attackers are abusing a remote-access tool.” The flow looks like this:
- Research and gaps (agent). What do we already cover? In the example, the install is already covered but service persistence is a gap.
- Log discovery (agent). Which logs have this? Service-creation events in the endpoint logs do.
- Build and open a PR (agent). The agent writes the rule, the tests, and the docs, runs the backtest, and opens a pull request with its work shown.
- Review (human). The engineer reviews the evidence: were the steps done right, and does the rule meet the intent?
That takes a hypothesis from days to minutes to get to a reviewable pull request. The agent did exactly what I would have done by hand, but because the PR shows its work, I’m reviewing instead of rebuilding. (Fun side effect: the slow part of the process is now the SIEM and data lake queries, not the people.)
Why Trust the PR? The Guardrails
Code checks the agent’s work, and agents can’t merge or deploy. Every agent change rides exactly the same pipeline as a human’s:

The agent edits a rule and opens a pull request. CI then runs the checks in order: style (naming, structure, docs), validate (schema, runs clean, tests pass), backtest (how often would this have fired over the last 30 days?), and regression (do the past attacks we replay still fire?). If anything fails it goes back to the agent to fix and retry, and only once everything passes does a human see it to review and merge.
Three guardrails make this trustworthy:
- The checks are code, not a prompt asking the model to please be careful.
- We measure before merge, so the reviewer sees numbers, not promises.
- Agents can’t ship. The merge button belongs to a human.
This matters most for bulk edits, because one bad change times hundreds of rules is a very bad day. A concrete example from a tuning run: a 30-day backtest on a rule showed 1,200 hits, the agent added a parent-process filter, and the re-run came back at 14 hits, all of them worth a look. With checks this strong I don’t need to be the one pressing go on every step, which is what makes Level 2 possible.
Level 2: AI-Driven
Level 2 runs on its own off the ticket queue: agents work together, and a human signs off at the end. Level 1 waited on me to start it. This one picks up tickets by itself, and I started with tuning because it’s the biggest reactive queue most teams have and the work is very repetitive.

It runs in four phases, and here’s a real-shaped example of a ticket that says a rule “fires 40 times a day”:
- Understand. Plain code does intake first (is this ticket actually actionable, or does it need a human to clarify?), then an evidence agent runs the heavy queries to find the false positives. In the example, 38 of the 40 daily hits came from one signed software updater.
- Plan. A planner agent proposes the fix, and a separate plan-reviewer agent asks whether there’s a simpler or safer option, sending it back to revise if needed. Here the narrowest fix was to exclude that signer, and only for the known install path.
- Implement and measure. A patcher agent edits the rule file, then the quality gates (code again, running the heavy queries) measure the result instead of guessing. A code-reviewer agent looks at the diff plus the measurements, and a serious finding goes back to the patcher or the planner. The example ended up as a 3-line change where 12 of 12 past attacks still fired, so no blind spot was added.
- Ship. Code opens the pull request into the DaC repo, and a human reviews the diff and the evidence together and merges, usually in minutes.
The bottom row of that diagram is the takeaway: no human trigger, agents check agents, code measures, and a person merges. The agents get no new privileges at any level. Level 1 and Level 2 both end at the same reviewed pull request a human would have opened.
Under the Hood: A Few Design Decisions
The most-asked question after the talk was “which framework?”, and honestly it’s the least important decision in the list. The ones that actually matter:
- Plain code sets the order of steps, not an AI. The flow can’t be talked into skipping a step, because there’s no prompt to talk to.
- Each agent does one small job. When something breaks, you know which one.
- Strong model for thinking, cheap model for chores. Planning and review get the best model, formatting and summarizing don’t need it.
- Tests decide pass or fail. An AI can flag a problem, but it can’t wave one through.
- A person approves the final change, and the change and all of its proof arrive together. Put the human gate at the PR only, because if you review every intermediate step you may as well have done the work yourself.
- Any agent framework works. LangGraph, Strands, the OpenAI and Claude SDKs are all fine. Keep the step order in your own code so you can switch tools later without a rewrite.
Here’s what that looks like stripped down to the bone:
# the flow lives in code, not a prompt
def tune(ticket):
if not intake.actionable(ticket):
return hand_back(ticket)
evidence = evidence_agent(ticket)
plan = planner_agent(evidence)
for attempt in range(3): # bounded
patch = patcher_agent(plan)
result = gates.run(patch) # code
if result.passed:
return open_pr(patch, result)
plan = planner_agent(evidence,
feedback=result)
return hand_back(ticket) # human
The loop is bounded, the gates are code, and the fallback is always a hand-back to a human. Agents fill in the steps, but the Python decides what happens next.
Lessons Learned
Four things I’d tell anyone starting this, at any team size.
1) Ship the MVP
A working pipeline for ten rules beats a perfect design for a thousand. Get the schema, the CLI, and one CI check running end to end, then grow it from there.
2) Eat your own dogfood
Use the tooling every single day yourself, because friction you personally feel gets fixed fast. Agents should work the same way: same repo, same pull requests, same CI checks as your engineers. No side doors.
3) Test your rules. Eval your agents.
This is the one I’d linger on. Every rule change should replay past real attacks, and each one must still fire. Every model or prompt change should run evals: past tickets with known good answers, scored per step and end to end. Grade the evidence too, meaning did the agent actually test, cite its sources, and stay in scope? And run nightly checks for drift and rule runtime creep. No evals means no model swaps, full stop.
4) No problem, no feature
Every tool described here started as a real, recurring pain. If you’re not sure whether to build something, wait until it hurts twice.
Wrapping Up
Codify your detections. Automate the pipeline. Then let agents scale your engineers, behind a human gate.
None of this requires a big team or a big budget to start, and steps 1 through 4 are worth doing even if you never add an agent. But once the foundation is there, the agents are the part that turns “we barely keep pace” into “hypothesis to production in hours, or potentially minutes,” and the human gate is what keeps that speed from becoming its own incident.
Sources from the talk: Dalton and Wallace at Black Hat USA 2026 (coverage from SiliconANGLE) and the OpenAI, Hugging Face, and METR incident write-ups from July and August 2026; SANS and Anvilogic, State of Detection Engineering, 2026; RAND, Zero Days, Thousands of Nights, 2017; MITRE ATT&CK T1059.001.
If you have any questions, want to compare notes on your own pipeline, or something is out-of-date here, feel free to email ivan@sabersecurity.io
Posted 10/28/2026
Threat Modeling AI Apps
Table of contents
Why Threat Modeling Still Matters
Threat modeling is just structured thinking about how a system can be abused, and at a practical level we’re trying to answer a few simple questions:
- What are we protecting?
- How can an attacker reach it?
- What can go wrong at each step?
- What controls do we have (or need)?
- If we can’t prevent it cleanly, how do we detect and respond quickly?
For detection engineers this is huge, because instead of writing detections from random ideas (or only from previous incidents), we can build detections from known attack paths in our own environment and get better coverage with fewer “interesting but not relevant” detection rules.
When Prevention Isn’t Possible
“Prevent everything” sounds great on paper, but production systems are messy.
A few common reasons preventive controls fall short:
- Business friction: hard blocks can break critical user workflows.
- Legacy constraints: older systems can’t support modern controls quickly.
- Third-party dependencies: we don’t fully control external services.
- Operational tradeoffs: strict controls can create reliability pain.
- Model uncertainty: LLM behavior is probabilistic, so hard prevention isn’t always deterministic.
This gets even more real in agentic AI systems - we should absolutely use guardrails, system prompts, policy checks, and least privilege, but teams still need to prepare for bypass attempts, indirect prompt injection, tool abuse, and data exfiltration patterns that slip past prevention. That’s where detection engineering acts as a compensating control.
Threat Modeling for Detection Engineering
A good model should create detection work, not just documentation.
One pattern that works well:

If you can fill in each stage you have something actionable.
Example:
- Threat: indirect prompt injection through untrusted retrieved content, such as malicious instructions embedded on a fetched website.
- Attack path: attacker plants malicious instructions on a compromised website, agent ingests it, and then performs unintended sensitive actions.
- Telemetry: HTTP request/fetch logs (URLs visited by the agent), retrieved content/payload logs, prompt and completion metadata, tool invocation audit logs, session identity and auth events.
- Detection theory: using the telemetry we have, how can we detect the attack narrative at various stages of the attacker’s path?
- Detection logic: alert when anomalous or malicious instruction-like content is retrieved and sensitive tool calls follow soon after.
- Response: revoke credentials/tokens if needed, quarantine source, investigate timeline for RCA and blast radius.
Prioritization That Actually Helps
I like a simple framework that’s easy to follow:
- Impact
- Likelihood
- Preventability gap
- Detectability with current telemetry
High impact plus high likelihood plus low preventability is the sweet spot for incentive to get detections out ASAP.
A Practical Threat Modeling Workflow
Keep it lightweight and repeatable - a model you actually use beats a perfect model you never update.
1) Define scope and critical assets
Pick one clear workflow or feature boundary.
For AI apps, common assets include:
- internal documents and user data
- API keys and service credentials
- tool/action permissions
- system prompts and policy context
- downstream systems (ticketing, email, CRM, code repos)
2) Diagram system and data flows
Map how data and actions move:
- user input
- retrieval (RAG/vector/search)
- model runtime
- tool calls
- external integrations
- logging/monitoring pipeline
This is where attack paths become obvious.
3) Identify trust boundaries
A trust boundary is any point in the system where the level of trust changes between two components. In other words, it’s where data or control crosses from one security context into another, like when an unauthenticated internet user’s input reaches your backend, or when your agent runtime calls a privileged tool that can read customer records. Every time data crosses a trust boundary, we should be asking: “what could go wrong if this input is malicious or this component is compromised?”
Trust boundaries are critical for detection engineering because they’re natural instrumentation points - if you can log and inspect traffic at each boundary crossing, you have visibility into where attacks transition from one stage to the next.
Typical boundaries for agentic systems include:
- internet user to app backend (untrusted input enters the system)
- app to retrieval/indexed content sources (content from external or shared sources could be poisoned)
- app to LLM provider (prompts and completions cross an external API boundary)
- agent runtime to privileged tools (the agent escalates from reasoning to action with real-world impact)
- app to customer data stores (access to sensitive data that could be exfiltrated)
4) Enumerate threats with STRIDE (plus AI-specific abuse)
STRIDE still works well, and for AI systems we layer on additional abuse patterns:

5) Decide controls (preventive and detective)
For each threat:
- what can we prevent now?
- what can we partially or are very limited in preventing?
- what must we detect and respond to?
Make detection outputs explicit and avoid leaving this as a vague “SOC will monitor it” note.
6) Prioritize and assign owners
Turn findings into backlog items with owners across:
- app engineering
- platform/security engineering
- detection engineering
- SOC/IR
Revisit the model when architecture changes, tools are added, or major incidents occur.
The Microsoft Threat Modeling Tool is a great option because it’s free, structured, and fast to adopt. It gives teams a good out-of-the-box solution to start visualizing an application’s architecture.
Practical Setup Flow
- Create a model and lock scope to one workflow.
- Build the DFD (entities, processes, stores, flows).
- Add trust boundaries.
- Generate threats (the tool does this automatically based on your DFD) and triage the results, since not every generated threat will be relevant to your environment.
- Tag each threat as
Prevent, Detect, Respond, or Accept.
- Export and convert high-priority findings into implementation tickets.
Keeping Signal High
- model reality, not ideal architecture
- split giant systems into smaller models
- involve app owners in triage
- prioritize high-impact abuse cases first
Worked Example: Agentic AI Support Assistant
Let’s use a simple scenario that mirrors a lot of real-world internal AI deployments.
Scenario
An internal support assistant has agentic capabilities:
- employees chat through a web app
- app uses an LLM and retrieval from internal KB docs
- agent can call tools:
search_kb
create_ticket
read_customer_record
send_email_summary
- app logs prompts, responses, tool calls, and auth events

Threat Focus: Prompt Injection Flavors
Direct prompt injection
User attempts to override policy directly in chat input (for example, asking the model to ignore controls or expose hidden instructions).
Detection ideas:
- detect jailbreak/bypass language patterns
- alert on high-risk prompt score plus sensitive tool invocation
- baseline repeated bypass attempts by user/session/source
Indirect prompt injection
Malicious instructions are embedded in retrieved documents and influence tool selection or output behavior.
Detection ideas:
- scan retrieved chunks for instruction-like markers
- correlate risky retrieval with privileged tool calls
- alert when post-retrieval behavior deviates from baseline
Context poisoning / memory abuse
Attacker attempts to persist malicious context so that future responses and agent decisions drift from intended behavior.
Detection ideas:
- monitor writes to memory/state with policy-aware rules
- alert on abrupt shifts in agent decision patterns
- version and diff memory/state artifacts for suspicious changes
Prompt manipulation nudges the agent into technically valid but abusive tool usage, like bulk reading customer records under the guise of a summary request.
Detection ideas:
- detect unusual tool chains (sequence + timing)
- alert on role-to-action mismatches
- detect argument volume anomalies for sensitive tools
Data exfiltration through model output
The model is coerced into disclosing sensitive context or data returned by tool calls in its response to the user.
Detection ideas:
- run DLP-like checks on model output streams
- alert on credential-like, token-like, or PII-heavy responses
- correlate exfil indicators with earlier sensitive tool access
Example Detection Backlog
-
Detect risky retrieval-to-tool chain in a short time window.
An attacker plants malicious instructions in a document or webpage that gets retrieved by the agent, and shortly after the agent invokes a sensitive tool it wouldn’t normally call in that context.
-
Detect bypass campaign behavior across users/sessions.
A user (or multiple users) repeatedly attempts prompt injection or jailbreak variations in a short period, probing for a technique that slips past guardrails.
-
Detect anomalous privileged tool usage by role.
A user’s session triggers tool calls that don’t match their role or typical usage pattern, like a help desk employee’s session bulk-reading customer records it normally never touches.
-
Detect sensitive data exfil indicators in model output.
The model’s response contains credential-like strings, PII patterns, or unusually large volumes of sensitive data that suggest the prompt coerced it into leaking context or tool-returned information.
-
Detect suspicious memory poisoning followed by behavior drift.
An attacker writes or modifies persistent agent memory/state in a way that causes future sessions to behave differently, such as silently skipping policy checks or routing actions to unintended targets.
-
Detect anomalous command execution on the underlying infrastructure.
An attacker chains prompt injection into tool calls that lead to suspicious command execution on the underlying host or platform, indicating activity like persistence mechanisms or data exfiltration.
Tie Each Detection to Response
Detection without response is only half a control, so map each detection rule to concrete actions:
- terminate suspicious sessions
- revoke or rotate impacted credentials
- temporarily disable high-risk tools (likely hard to do without a very high severity incident + CSO)
Closing Thoughts
Threat modeling for AI systems doesn’t need to be heavyweight to be useful, and done well it gives detection engineering a clear map of where attackers can push, where prevention is weak, and where monitoring needs to be strongest.
If you can’t fully prevent a path today, that doesn’t mean you’re stuck - it means we define the path, instrument it properly, and build response-ready detections that reduce blast radius.
Posted 01/19/2026
Hosting a static site in AWS for cheaper than a cup of coffee
Table of contents
The Components
These are the main pieces used to build this website. If you’re here, you probably already know what these three components are. If you don’t, here’s a brief overview and linked documentation if you need to read into it further. This is a very cheap, fast, and fairly easy way to host a static website.
Hugo
According to their site:
“Hugo is a fast and modern static site generator written in Go”.
A static site generator is a tool that takes simple text input + markup language, and generates an renderable HTML file. This makes building static content much faster and simpler, because you don’t have to build out content using HTML/CSS syntax. Although if you’re not happy with certain aspects of your site, editing the “theme” or template is as simple as browsing to the CSS or HTML file and tweaking something there if necessary. But for the most part, you’re just writing simple text (like I am now). The exception is when you want to use markup to format your text. I personally use this cheat sheet to reference markup occasionally.
AWS S3
AWS S3 is a service provided by Amazon Web Services (AWS). It’s basically just flat object storage, organized into “buckets” which is the name of the container of data. Its role here is just a place to store our Hugo-generated HTML/CSS and static objects somewhere where CloudFront can find them. It’s perfect for simpler static sites because it’s incredibly cheap (like less than a dollar a month for most sites cheap) and super easy to configure/deploy. You can read more about it here.
CloudFront
CloudFront is a CDN service, native to AWS as well. It’s serving two roles here:
- CDN to serve our code stored in S3
- Negotiating and building SSL
A CDN is a Content Delivery Network. It’s basically a service that caches our served site from S3 on servers distributed all over the globe, and those servers act as proxies between your browser and where the code is permanently hosted. So when you browse to this site, instead of browsing directly to the S3 bucket to request a webpage, you’re actually just communicating with CloudFront which is serving you the cached version. Why is this necessary? Well one of the downsides of S3 is it’s fairly slow to load. It also doesn’t support HTTPS if you’re hosting a static site in a bucket. So CloudFront can be used as a way to cache the site on a server that’s much closer to you, and also do all of the SSL building via a configured certificate. You can read more about it here.
Honorable Mentions (Not required, but kinda cool)
Zoho email service
Zoho is one of the few legitimate business suite services that has a completely free tier. Their Forever Free Plan costs nothing as long as you stay under their 5GB/25MB attachment limit, which is still fairly generous. Got cash to burn? They offer a “Mail Lite” plan for a whopping $1 a month, but it comes with a myriad of additional features. Most bloggers and small businesses even, will get by just fine with the free plan. Their mobile app makes it easy to check mail as well.
Route 53 DNS
If you’ve bought your domain already, there’s a good chance you’ve probably already setup or plan to use the domain registrar as your DNS resolver too. I was in that boat when I bought my domain from GoDaddy like many times before. Until I tried Route 53, which is the DNS service hosted on AWS. Using their DNS service made the whole setup much easier. This isn’t free (.50 cents per DNS Zone) but pretty dang close, as long as you’re not getting millions of resolutions for your site a month. Even then, it’s very affordable (less than a dollar a month).
All-in-all, all the above costs about .60 cents to a dollar to host per month (depending on site size and popularity). Yes. Less than a dollar per month for a modern, fast, static website. And the first year in AWS is free. This INCLUDES having a custom email address with your domain.
Step-by-step Guide
These guides from AWS are pretty helpful if you get stuck somewhere to help fill in any gaps.
Hosting a Static Site in S3
Using CloudFront to Serve HTTPS for your S3 bucket
Setup S3
-
Login to your AWS console and browse to the S3 portion. Create a bucket a bucket with the same name as the domain you’ve purchased (i.e. sabersecurity.io).
-
For the region, it’s generally best to keep this the same throughout your AWS account to avoid confusion/complexity. Just choose one that’s closest to you and stick to that.
-
Under the public access for the bucket, uncheck “Block all public access” (we want to allow our site to be reachable by the internet).
-
Leave the rest and hit create. Your bucket should be provisioned. Now click your bucket and head over to “Properties”.
-
Near the bottom, click edit on “Static website hosting”. Enable this, and set the index document to “index.html” then save.
-
Lastly for your bucket setup, go to the “Permissions” tab and click edit on your Bucket policy. We want to set the policy to allow it to be readable by anyone (replace my site with yours, leave the rest):
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::sabersecurity.io/*"
}
]
}
Request SSL Certificate
-
Browse to Certificate Manager in AWS to request a certificate for your site. This isn’t required but highly recommended if you want anyone to trust your website.
-
Click “Request”. Then public certificate. In the certificate details, for the FQDN make this identical to your site name (i.e. sabersecurity.io). Also add another name with your same domain but “www.” in front. Then click Request.
-
For Validation method, select DNS validation if you own your domain and have access to DNS settings in the registrar (or using Route 53).
- NOTE: If you have your domain on a different registrar than AWS, but want to use Route 53 for DNS, I recommend moving your designated name servers first before continuing. If you use GoDaddy, follow this guide. Otherwise just search “migrate nameservers from x to Route 53” on Google.
-
A message at the top will appear, if you click on it you can see the status of your cert request. We want to take the CNAME values provided and enter them into our DNS settings on our registrar/Route 53. If using Route 53, just click “Create Records in Route 53” and it will do this for you (this is why I recommend just using Route 53).
-
Once the CNAME records are added to your DNS settings, it will take a few min usually to verify your ownership of that domain, and the certificate be approved.
Setup CloudFront CDN
-
Browse to CloudFront in the AWS console. Click “Create Distribution”.
-
Fill out the following fields, leave the rest blank/default, then click Create:
-
Origin domain: Go back to your S3 bucket properties in a seperate tab, at the bottom where we enabeld static hosting copy the url to your site. It should look like http://sabersecurity.io.s3-website-us-east-1.amazonaws.com. Paste this url into the Origin domain field.
-
Viewer protocol policy: Redirect HTTP to HTTPS.
-
Alternate domain name (CNAME): input the same two CNAME records you used for your certificate request.
-
Custom SSL certificate: If your certificate request has been approved, you can click the drop-down and select it.
-
Default root object: index.html
-
You should now have a CloudFront distribution generated. It can take 20 or so minutes for it to be enabled.
-
Once enabled, click on the distribution ID, and copy the distribution domain. We want to create two A records in our domain’s DNS settings with this domain pointing to our purchased one. They should look something like this:
https://imgur.com/a/cPvAmPh
- NOTE: If using Route 53, you will have to make this an “Alias”, because usually A records are used for domain -> IP translation.
Create and upload your site
- At this point, your cloud infrastructure should be up and running, and is just waiting for something to serve to browsers. You can test it by saving a file as
index.html with the following contents and uploading it to your S3 bucket:
<!DOCTYPE html>
<html>
<head>
<title>Test</title>
</head>
<body>
<p>Your site is working!</p>
</body>
</html>
-
Once the file is uploaded, browse to your website URL and it should render the text in the code above. If it doesn’t work, you may have to do some additional troubleshooting and double checking the linked AWS articles above.
-
If it’s working, and you’re planning on using Hugo, you can now browse to the Themes page and pick out a theme that suits your website purpose.
-
I can’t do a better job than Hugo already has at documenting their simple setup process, so just follow that here (it’s super easy): https://gohugo.io/getting-started/quick-start/
-
Once your site is setup, and you’ve run Hugo -D to build it and generate the “Public” folder in your site directory, browse back to S3 and upload all the contents of that Public folder (not the folder itself). This should include the key index.html file that your CloudFront distribution is looking for.
- NOTE: If you ever make changes to your S3 contents, you will have to invalidate your CloudFront cache to serve the new content. This is because CloudFront only caches every 24hrs or so, and will not check your S3 bucket for changes for a while. To do that follow these steps. You can invalidate 1000 objects a month before you start gettings charged for it (even then it’s super cheap).
-
Once uploaded, it make take a second for everything to get cached and ready to go. But now you should be able to browse to your site URL and see your brand new shiny site in all its glory!
If you have any questions, or something is out-of-date here, feel free to email ivan@sabersecurity.io
Posted 11/10/2022