Cloud breaches rarely involve the provider being hacked. They involve a public storage bucket, an access key in a repository, a role that could do everything, or a logging service nobody turned on. The provider secures the data centre and the hypervisor; everything you configure on top is yours to get right, and the configuration surface is enormous. This module gives you the model for splitting that responsibility, the identity and access patterns that prevent most cloud incidents, the handful of misconfigurations that account for most of the rest, and the logging and automated checks that catch drift before an attacker does. Examples use AWS terminology; the ideas map directly to Azure and Google Cloud.
- Explain the shared responsibility model and list what is yours for compute, managed services and serverless
- Design cloud IAM around roles, least privilege, and short-lived credentials, with guardrails at the organisation level
- Recognise and fix the high-impact misconfigurations: public storage, open security groups, unencrypted data, missing MFA
- Turn on and centralise the logs that every cloud investigation depends on
- Run continuous configuration checks and treat findings as alerts
Identity: roles, not keys; guardrails, not hope
Cloud IAM is the control plane for everything else, so it gets the strictest treatment. Humans authenticate through single sign-on with phishing-resistant MFA and assume roles for specific tasks; nobody uses the root or owner account for daily work, and its credentials live in a safe with hardware MFA. Workloads use roles attached to the compute (instance profiles, pod identity, function roles) that issue rotating temporary credentials; long-lived access keys exist only where nothing else is possible, with an expiry date and an owner.
{
"Version": "2012-10-17",
"Statement": [
{"Effect": "Allow", "Action": "*", "Resource": "*"},
{
"Effect": "Deny",
"Action": [
"iam:*", "organizations:*", "cloudtrail:StopLogging", "cloudtrail:DeleteTrail",
"guardduty:Delete*", "config:Delete*", "kms:ScheduleKeyDeletion"
],
"Resource": "*"
}
]
}Above individual accounts sit organisation-level guardrails: service control policies (or Azure Policy, GCP organisation policies) that apply to every account regardless of what its administrators do — deny leaving the organisation, deny disabling audit logs, deny public storage, restrict regions. Separate accounts or subscriptions per environment (production, staging, sandbox) so a compromised sandbox key cannot touch production at all. Finally, use the access analysers the provider offers: they list roles and resources reachable from outside your organisation and permissions that have never been used, which is a ready-made least-privilege backlog.
"Action": "*", "Resource": "*" on a workload role is the most common serious finding in cloud reviews. Start from what the workload actually calls (the access analyser will tell you) and write that.
The misconfigurations that cause most incidents
A short list accounts for a large share of real cloud breaches. Learn them, then automate checking for them.
- Public object storage: a bucket or blob container readable by everyone, often holding backups or exports. Turn on the account-level public access block and make exceptions explicit and reviewed.
- Open security groups: SSH, RDP, databases or the Kubernetes API reachable from
0.0.0.0/0. Covered in the network module; scanners find these within minutes. - Leaked credentials: access keys in code, in images, in CI logs. Prefer roles; scan for keys; alert on use from new locations.
- Over-privileged roles: wildcard policies, cross-account trust to anyone, roles assumable without conditions.
- No MFA on the root or owner account, or on console users with production access.
- Unencrypted storage and snapshots: volumes, databases and backups without provider-managed or customer-managed keys; public snapshots.
- Metadata service v1 on instances, which turns any SSRF into stolen role credentials. Require v2 (token-based) everywhere.
- Logging off: no audit trail, no flow logs, no object access logs, so there is nothing to investigate with.
# buckets without the public access block (should be none; also set it at the account level)
for b in $(aws s3api list-buckets --query 'Buckets[].Name' --output text); do
aws s3api get-public-access-block --bucket "$b" >/dev/null 2>&1 || echo "NO PUBLIC ACCESS BLOCK: $b"
done
# security group rules open to the world on anything other than 80/443
aws ec2 describe-security-group-rules \
--query "SecurityGroupRules[?CidrIpv4=='0.0.0.0/0' && !IsEgress && FromPort!=\`80\` && FromPort!=\`443\`].[GroupId,FromPort,ToPort]" --output table
# instances not requiring IMDSv2
aws ec2 describe-instances \
--query "Reservations[].Instances[?MetadataOptions.HttpTokens!='required'].[InstanceId,MetadataOptions.HttpTokens]" --output tableEncryption at rest is cheap and should be the default for every volume, database, queue and bucket, using provider-managed keys at minimum and customer-managed keys where you need to control access and rotation or prove it to an auditor. Encryption in transit is TLS to every endpoint, including the provider's own APIs, which policy can require (aws:SecureTransport).
Logs are the investigation
When something goes wrong in the cloud, the questions are always the same: which identity did what, from where, to which resource, when. The API audit trail (CloudTrail, Azure Activity Log, Cloud Audit Logs) answers them, but only if it is enabled in every region and account, delivered to a central bucket the source account cannot delete from, and retained long enough — a year is a reasonable floor, since intrusions are often discovered months later. Add flow logs for network questions and data access logs for the storage that holds anything sensitive.
resource "aws_cloudtrail" "org" {
name = "org-trail"
s3_bucket_name = aws_s3_bucket.audit_logs.id
is_organization_trail = true
is_multi_region_trail = true
include_global_service_events = true
enable_log_file_validation = true
kms_key_id = aws_kms_key.audit.arn
event_selector {
read_write_type = "All"
include_management_events = true
data_resource {
type = "AWS::S3::Object"
values = ["arn:aws:s3:::acme-customer-exports/"]
}
}
}Logs you never look at are still valuable in an incident, but logs that raise alerts are valuable before one. The provider's threat detection services (GuardDuty, Defender for Cloud, Security Command Center) consume these logs and flag credential use from unusual locations, crypto-mining patterns, calls from known-bad addresses and privilege escalation sequences. Turning them on is one API call and is usually the best-value security control in the account.
Continuous checks and infrastructure as code
Configuration drifts: a rule is widened during an incident, a bucket is made public for a demo, a role gains a wildcard to make a deploy work. Continuous configuration checks compare the live estate against a baseline (the CIS cloud benchmarks are the usual one) and report or auto-remediate: the provider's config services and open-source tools such as Prowler do this across hundreds of rules. Treat a new critical finding as an alert with an owner, not as a monthly report.
# assess an account against the CIS benchmark and write an HTML report
prowler aws --compliance cis_2.0_aws --output-formats html --output-directory ./prowler-out
# catch the misconfiguration before it exists: scan the Terraform plan in CI
checkov -d infra/ --framework terraform --check CKV_AWS_18,CKV_AWS_19,CKV_AWS_23,CKV_AWS_53,CKV_AWS_54,CKV_AWS_55,CKV_AWS_56The most durable fix is upstream: because your infrastructure is code, the security rules can be tests on that code. Policy checks in the pipeline reject a public bucket or an open port before the plan is applied, the same way unit tests reject broken logic. Combine that with guardrails at the organisation level, roles instead of keys, encryption by default, central logging and threat detection, and the cloud stops being the place where one checkbox takes the company down.