🛡️ Cloud Security Baseline: A Checklist You Can Audit
Twenty things that stop most cloud incidents, grouped by identity, network, data, servers, containers, logging and backups, with the pages of this series that explain each one, the public-storage settings on all four clouds, the posture tools that audit it for you, and a small auditor written and run for real
💡 How this page was made. The small auditor on this page was written and run for real on 6 October 2026 (Python, no downloads): it reads a description of cloud resources and prints findings. It is my own teaching tool, not a security product, it checks fourteen simple things, and the sample resources are made up. The facts about each cloud's settings and tools are from the providers' documentation, read on 6 October 2026; nothing was run on a cloud account. Almost every item here is explained in more depth on an earlier page, and each section says which: account pages, IAM, networking, EC2, containers, Kubernetes, first boot, backups and cost control.
What a baseline is
A baseline is the short list of things that must be true before you worry about anything fancy. Cloud security is shared: the provider secures the data centers, the hardware and the platform, and you secure what you put on it: the accounts, the access, the network rules, the data settings and the servers (AWS's Kubernetes documentation uses exactly the words "shared responsibility model"). Most incidents come from the customer's side, and from a small number of causes: an account without MFA, a key in a repository, a storage bucket opened to the world, a database reachable from the internet, a server nobody patched, and nobody watching the logs.
The checklist below has twenty items. Each is something you can look at and answer yes or no, which is what makes it auditable. Do them in order: the earlier ones matter most.
The twenty
Identity (the front door)
- Every human account has MFA, and the root or owner account is locked away. No daily use, no access keys for it (the AWS account and Azure account pages).
- Everyone has their own identity, and access goes through groups or roles, not shared logins. People sign in through SSO where you have it.
- Least privilege. No "allow everything on everything" policies, no Owner or Administrator for people who only need to read. Give the smallest role at the smallest scope (the IAM page).
- No long-lived keys for machines. Servers, containers and functions use an attached identity (instance role, managed identity, service account, instance principal). Keys that must exist are rotated and never in a repository.
- Old accounts, keys and roles are removed. Review them on a calendar: the clouds show "last used" information to help.
Network (the doors and windows)
- SSH (22) and RDP (3389) are never open to the whole internet. Allow your own address, a VPN or a bastion, or use a managed session service (the EC2 page; AWS's own best practices say the same).
- Databases, caches and internal services have no public path. They sit in private subnets and accept connections from the application tier only (the networking page).
- Rules are default-deny and written down. Open only what a service needs, from the sources that need it. No wide port ranges.
Data
- No public storage unless public is the point. Block public access at the account level where you can (the next section shows how on each cloud).
- Data is encrypted at rest and in transit, and you know who holds the keys. (Each cloud encrypts storage by default in most services: confirm it for the ones you use, and use your own keys where a rule requires it.)
- Secrets live in a secrets service, not in code, images, user data or tags (the first boot and Kubernetes pages explain why: user data is not secret, and a Kubernetes Secret is only base64).
Servers and containers
- Servers are patched, on a schedule you can name, and a reboot after kernel updates is part of it. New servers start from a recent image and update themselves at first boot.
- SSH uses keys, not passwords, and root login is off.
- The metadata service requires the authenticated version (IMDSv2 on AWS), so a flaw in one application cannot read the machine's credentials with a single request (the EC2 page).
- Containers run as non-root from images you pinned and scanned, and the Kubernetes API is not open to everyone (the containers and Kubernetes pages; ECR can scan on push, as its documentation says).
Watching and recovering
- Audit logging is on, goes somewhere the attacker cannot edit, and someone reads it. AWS CloudTrail records who called which API, Azure has the activity log, Google Cloud has Cloud Audit Logs, Oracle has the Audit service (named on the earlier pages).
- Alerts exist for the loud events: a root or owner sign-in, a new access key, a firewall rule opened to the world, a cost spike (the cost page).
- Backups exist, are kept apart from the originals, are immutable where the cloud allows it, and a restore has been tested (the backups page).
- Infrastructure is code (Terraform or similar), reviewed like code, so that a change has an author and a reason, and a lost environment can be rebuilt (the Terraform page).
- You know what to do on a bad day: who decides, how to revoke access (disable the key, the user, the role), how to preserve evidence (snapshot before you delete) and who to call. Write it down now, not during the incident.
A small auditor, run for real
Most of items 1 to 9 and 13 to 14 can be checked by a program reading a description of your resources. That is exactly what the cloud providers' posture tools do at scale (next section). To see the idea in its simplest form, here is a small auditor (59 lines) I wrote. It takes a JSON description (made-up here), applies fourteen rules, and prints findings, sorted by severity, and exits with status 1 if it finds anything, so it could fail a build:
#!/usr/bin/env python3
"""A tiny baseline auditor: reads a description of cloud resources (JSON) and prints findings."""
import json, sys, datetime as dt
def audit(data, today):
f = []
def add(sev, where, msg): f.append((sev, where, msg))
for sg in data.get("firewall_rules", []):
for r in sg["inbound"]:
world = r["source"] in ("0.0.0.0/0", "::/0")
if world and r["port"] in (22, 3389):
add("HIGH", sg["name"], "port %d is open to the whole internet" % r["port"])
elif world and r["port"] in (3306, 5432, 1433, 6379, 27017):
add("HIGH", sg["name"], "database port %d is open to the whole internet" % r["port"])
elif world and r["port"] not in (80, 443):
add("MEDIUM", sg["name"], "port %s is open to the whole internet" % r["port"])
for p in data.get("policies", []):
for st in p["statements"]:
if st["effect"] == "Allow" and st["action"] == "*" and st["resource"] == "*":
add("HIGH", p["name"], "allows every action on every resource")
if st["effect"] == "Allow" and st.get("principal") == "*":
add("HIGH", p["name"], "grants access to any principal (*)")
for u in data.get("users", []):
if u["admin"] and not u["mfa"]:
add("HIGH", u["name"], "administrator without multi-factor authentication")
if u.get("root_keys"):
add("HIGH", u["name"], "the root/owner account has access keys")
for k in u.get("keys", []):
age = (today - dt.date.fromisoformat(k["created"])).days
if age > 90:
add("MEDIUM", u["name"], "access key %s is %d days old (rotate or remove)" % (k["id"], age))
for b in data.get("buckets", []):
if b["public"]:
add("HIGH", b["name"], "bucket allows public access")
if not b["encrypted"]:
add("MEDIUM", b["name"], "bucket is not encrypted")
for i in data.get("instances", []):
if i["metadata_v1"]:
add("MEDIUM", i["name"], "metadata service still accepts the old unauthenticated version (IMDSv1)")
if i["password_ssh"]:
add("MEDIUM", i["name"], "SSH password logins are enabled")
if not i["disk_encrypted"]:
add("MEDIUM", i["name"], "disk is not encrypted")
if not i["backup"]:
add("MEDIUM", i["name"], "no backup policy is attached")
order = {"HIGH": 0, "MEDIUM": 1}
return sorted(f, key=lambda x: (order[x[0]], x[1]))
data = json.load(open(sys.argv[1]))
findings = audit(data, dt.date(2026, 10, 6))
for sev, where, msg in findings:
print("%-6s %-14s %s" % (sev, where, msg))
print("%d findings (%d high)" % (len(findings), sum(1 for x in findings if x[0] == "HIGH")))
sys.exit(1 if findings else 0)
Run on a deliberately bad description (an admin without MFA, a database open to the world, an "allow everything" policy, access keys on the root account, a public bucket, port 22 open to everyone, an old key, and a server with every weak setting) and then on a fixed one (as run):
user@lab:~$ python3 audit.py sample-bad.json HIGH alice administrator without multi-factor authentication HIGH db-sg database port 5432 is open to the whole internet HIGH dev-policy allows every action on every resource HIGH owner the root/owner account has access keys HIGH share-policy grants access to any principal (*) HIGH site-assets bucket allows public access HIGH web-sg port 22 is open to the whole internet MEDIUM alice access key AKIA-EXAMPLE1 is 219 days old (rotate or remove) MEDIUM reports bucket is not encrypted MEDIUM web1 metadata service still accepts the old unauthenticated version (IMDSv1) MEDIUM web1 SSH password logins are enabled MEDIUM web1 disk is not encrypted MEDIUM web1 no backup policy is attached 13 findings (7 high) exit status: 1 user@lab:~$ python3 audit.py sample-good.json 0 findings (0 high) exit status: 0
- The bad sample produces 13 findings, 7 of them high, each naming the resource and the problem in plain words. The 219-day-old key is computed against a fixed date.
- The fixed sample (SSH only from one address, the database only from the private subnet, no wildcard policy, MFA on, a private encrypted bucket, IMDSv1 off, a backed-up server) produces zero findings and exit status 0.
- What it is, and is not. It is a teaching tool: fourteen rules, over a data file that I wrote. A real audit has hundreds of checks against live resources, which is why you use the providers' tools. But the structure is the same: describe the resources, apply the rules, list what is wrong. And the same idea is how you test your own Terraform plans before applying them.
Public storage: the settings, on each cloud
Item 9 has caused more headlines than any other, so here is what each cloud's documentation says (read on 6 October 2026).
- AWS S3. New buckets and objects do not allow public access by default, but people can change bucket policies and ACLs to allow it. S3 Block Public Access has four settings (BlockPublicAcls, IgnorePublicAcls, BlockPublicPolicy, RestrictPublicBuckets) that override those policies and ACLs. They apply to a bucket, an access point, a whole account, or an organization, and S3 uses the most restrictive combination. AWS recommends turning on all four at the account level, and checking first that your applications work without public access. Two cautions from the documentation: turning a setting off makes a bucket with a public policy public again (the settings do not change the policies themselves), and a bucket policy with "Principal": "*" and no condition is counted as public. IAM Access Analyzer for S3 lists buckets that are public or shared.
- Azure Blob Storage. Anonymous access is prohibited by default. Two settings decide: the storage account setting (AllowBlobPublicAccess) and each container's access level. If the account disallows anonymous access, it overrides every container, whatever its own setting says. The documentation's advice: set the account property to false, and put the few containers that must be public in a separate account reserved for that. The CLI creates an account with --allow-blob-public-access false, and az storage account show --query allowBlobPublicAccess reads it. Azure Policy can audit (warn) or deny (block) accounts that allow anonymous access, and the Resource Graph can list the setting across a whole subscription. One exception: a static website container ($web) is always public. Disallowing takes up to about 30 seconds to apply, and it will break any client that was using anonymous reads, so look at the logs and metrics for anonymous requests first (the documentation shows the Log Analytics query).
- Google Cloud Storage. Public access prevention can be enforced on a bucket or inherited from the organization policy constraint storage.publicAccessPrevention, set at the organization, folder or project. When it is on, requests that try to add allUsers or allAuthenticatedUsers to a policy or ACL fail with 412 Precondition Failed, and existing grants to those principals stay in place but are overridden.
- Oracle Cloud Object Storage. I did not read Oracle's documentation on bucket visibility for this page. Use private buckets and check each bucket's visibility setting in the console or CLI.
The pattern: block it at the highest level (account, organization), and make exceptions deliberately.
The posture tools: an auditor you do not have to write
All four clouds sell or provide a service that continuously checks your configuration against best practices and lists the problems. The documentation (read on 6 October 2026):
- AWS Security Hub CSPM. Collects findings across accounts and services (including GuardDuty, Inspector and Macie) and runs automatic checks against standards: the AWS Foundational Security Best Practices, CIS, PCI DSS and NIST. It needs AWS Config to generate most control findings, works per Region (to fully meet the CIS checks you enable it in every supported Region), only sees findings generated after you enable it, and starts with a 30-day free trial, after which it is billed (by checks and findings).
- Microsoft Defender for Cloud. Has free Foundational CSPM: centralized policy, secure score (your posture summarized, improving as you fix recommendations) and multicloud coverage, since you can connect AWS and Google Cloud accounts to it. Further plans (Defender CSPM, Defender for Servers, Containers, Storage, Databases, Key Vault and others) are paid.
- Google Security Command Center. A risk-management service for preventing, detecting and responding to problems, with Security Health Analytics finding "misconfigurations, publicly exposed resources, leaked credentials". It has Standard, Premium and Enterprise tiers. (The page I read does not say what a free tier includes: check the current offering.)
- Oracle Cloud Guard. Monitors OCI resources for security weaknesses and risky activity, using detectors (activity, configuration, container security, instance security, threat) and responders that take corrective action automatically or after approval, driven by recipes that are either Oracle-managed or your own copies. The page I read does not say whether it is free.
Start by switching on the free or trial tier and reading the first list. Expect a long list on an old account: sort by severity, fix the highs first, and treat the rest as a backlog.
A routine
- On day one: items 1, 6, 9 and 17 (MFA, no open SSH, no public storage, a budget alert). They take an hour and prevent most of the disasters.
- Every week: look at the posture tool's new findings, the cost page, and who was added to what.
- Every month: review users, keys and roles that were not used; patch status of every server; failed sign-ins in the audit log.
- Every quarter: test a restore, review the firewall rules against what is actually running, and walk through the "bad day" plan with the people who would use it.
- After every incident or near miss: add the lesson to the list.
Common mistakes
- MFA only for some people, or the break-glass account with no MFA and no monitoring.
- "Temporary" wide-open rules that become permanent.
- Keys in repositories: public repositories are scanned by automated tools, so a leaked key should be treated as stolen.
- Public buckets for convenience, and buckets nobody remembers.
- Security tools switched on and never read.
- Logs kept in the same account as the thing they audit, with the same administrators.
- Patching "later".
- Treating a green score as the goal. The goal is that the specific things that hurt (the items above) are true. A score is a way to find the ones that are not.
- No plan for the bad day.
What this page did not cover
Compliance frameworks in depth, threat detection and response, web application firewalls and DDoS protection, key management and hardware security modules, zero trust network design, data classification and privacy law, penetration testing and supply-chain security for code and images. Each is a subject of its own.
The cheat sheet
- Identity: MFA everywhere, root locked away, own identity per person, groups and roles, least privilege, no machine keys, remove the unused.
- Network: no 22 or 3389 to the world, databases private, default-deny with written rules.
- Data: block public storage at account level (AWS Block Public Access, Azure AllowBlobPublicAccess=false, Google public access prevention), encrypt, secrets in a secrets service.
- Servers and containers: patch on a schedule, keys not passwords, IMDSv2, non-root pinned scanned images, a closed Kubernetes API.
- Watch and recover: audit logs on and protected, alerts for the loud events, immutable tested backups, infrastructure as code, a written bad-day plan.
- Tools: AWS Security Hub CSPM (needs Config, 30-day trial) | Defender for Cloud (free Foundational CSPM, secure score, multicloud) | Security Command Center (Standard, Premium, Enterprise) | Oracle Cloud Guard (detectors, responders, recipes).
- First hour: MFA, no open SSH, no public storage, a budget alert.
← Cloud Backups and Disaster Recovery: Protect, Copy, Restore · ↑ All tutorials · DNS and Domain Names Across the Clouds →