The previous lesson ended by pointing at the bridge: immutable infrastructure and containers are the best way to apply a baseline, and yet classic hardening does not cover them. A container is not a virtual machine, it is not patched but rebuilt, and the cloud account that runs it has a surface of its own that no lynis looks at. This lesson closes the course's technical plan where most breaches happen today: not through a sophisticated exploit, but through a wrongly ticked box. We are going to configure, once and for all, account A-05, the attachments bucket A-02 and the backups A-03, and to build images that do not give the game away.
Contents
- Why the cloud changes the threat model
- Shared responsibility, on the technical side
- Identity is the new perimeter
- Bucket A-02, properly configured at last
- Network and data: private subnets, encryption and isolated backups
- Cloud logging and auditing
- Automated posture: CSPM and infrastructure as code
- Containers: what they isolate and what they do not
- Secure execution and image signing
- Orchestration and runtime detection
- What Nimbus should do first, on its budget
- Why the cloud changes the threat model
In your own data centre, attacking meant finding a vulnerable service and exploiting it. In the cloud, the dominant cause of breaches is not an exploit: it is a configuration error. The big public incidents of the last decade in cloud environments share a pattern that requires no offensive skill at all: a bucket marked as public, an access key published in a repository, a role with administrator permissions that only needed to read one file, a managed database exposed without authentication.
Three underlying changes explain the shift:
- Everything is an API. Creating a machine, opening a port or granting access to all the data are API calls, and a credential with broad permissions lets you make all of them. The control plane matters more than the data plane: whoever controls the account does not need to log into any server.
- Speed is both the advantage and the risk. A
terraform applystands up a complete infrastructure in minutes, and it also publishes a bucket in seconds. The same agility that makes Nimbus competitive multiplies the probability of error. - Everything is public by default in terms of network reach. There is no physical door and no corporate network around it: whatever is deployed is reachable from the Internet unless you configure otherwise.
The practical consequence inverts the order of work: in the cloud, reviewing the configuration pays off more than hunting for vulnerabilities, and that is why the CSPM in section 7 is, for an SME, more cost-effective than an additional vulnerability scanner.
- Shared responsibility, on the technical side
In 04-04 we saw the model as a contractual matter. Here it is a list of concrete tasks, and the question that orders it is: if this fails, who do you call? If the answer is you, it is yours.
| Service at Nimbus | The provider's responsibility | Nimbus's responsibility |
|---|---|---|
| Virtual machines | Hypervisor, hardware, physical network, zone availability | Operating system, patches, users, services, local firewall (all of 05-06) |
| Managed database | Engine, engine patches, automatic backups, replicas | Schema, users and roles, encryption enabled, private network, backup retention, RLS |
| Object storage (A-02, A-03) | Durability, encryption in transit, infrastructure | Access policy, public access block, encryption at rest, versioning, logging |
| Managed containers | Nodes, orchestrator, control plane | Image, process user, secrets, capabilities, network policies |
| The provider's identity service | The authentication service, availability | Users, roles, policies, MFA, rotation, permission review |
| All of them | — | The data, always. Without exception and in any service model |
Two conclusions worth keeping in mind. The first: the more managed the service, the fewer tasks you have, but the ones that remain are the most critical — the access configuration and the data. The second: the provider does not warn you when you get it wrong. Publishing a bucket generates no alert on their side: the checkbox exists, and using it is a legitimate customer decision. That asymmetry is exactly the gap where the configuration error lives.
- Identity is the new perimeter
With no network to defend, access control to the provider's API is the real perimeter. Five rules, ordered by impact:
- Mandatory MFA on the root account, with the credential held in custody and not used day to day. Root is only used for what no other identity can do.
- No long-lived access keys. A static key lives in a file, travels through chats, gets copied to a laptop and turns up in a repository: it is risk R-06. Instead, assumable roles with short-lived credentials (minutes or hours) for people, and the instance's or the container's own identity for services, with no secret to manage at all.
- Federation with the identity provider. Nobody has their own user in the cloud: you log in with the corporate identity, and offboarding an employee revokes their access to everything at once. It is the fix for a classic problem: the cloud account that outlives the person who used it.
- Real least privilege, achieved by observing what each role actually uses and trimming the rest.
- Periodic review of excessive permissions, with the provider's access analyser and with the six-monthly recertification of control C-18.
// BEFORE - the policy that "definitely works", which is why it is so common.
// It grants EVERYTHING on EVERYTHING: if this credential leaks, the account is lost.
{"Version": "2012-10-17", "Statement": [
{"Effect": "Allow", "Action": "s3:*", "Resource": "*"}
]}// AFTER - minimum permission for the Nimbus API role.
{"Version": "2012-10-17", "Statement": [
{
"Sid": "ReadWriteTenantAttachments",
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"], // no Delete, no List on the bucket
"Resource": "arn:aws:s3:::nimbus-adjuntos-prod/tenant/*",
"Condition": {
"Bool": {"aws:SecureTransport": "true"}, // over TLS only
"StringEquals": {"s3:x-amz-server-side-encryption": "aws:kms"}
}
},
{
"Sid": "DenyOutsideVpc",
"Effect": "Deny", // an explicit DENY always beats
"Action": "s3:*", // any Allow: even if another
"Resource": "*", // policy grants access by mistake
"Condition": {"StringNotEquals": {"aws:SourceVpce": "vpce-nimbus-s3"}}
}
]}Three decisions deserve justifying. s3:DeleteObject is not granted: the Nimbus API never needs to delete attachments, and not having it turns a compromise of the API into a leak — serious — instead of a destruction — irreversible. The Resource bounds the path, not just the bucket, so an application flaw cannot reach other prefixes. And the explicit deny by VPC endpoint is the most powerful: even if another policy grants access by mistake, if the request does not come over Nimbus's private network, it is denied. A stolen credential used from outside is useless.
- Bucket A-02, properly configured at last
This is the asset that 02-06 emptied over seven days. It is closed with seven controls applied together.
B=nimbus-adjuntos-prod
# 1) PUBLIC ACCESS BLOCK at account and bucket level. It is the checkbox that
# stops a future policy, written in a hurry, from making it public.
aws s3api put-public-access-block --bucket $B --public-access-block-configuration \
BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
# 2) ENCRYPTION at rest with a Nimbus-managed key (03-06: envelope encryption).
# bucket-key-enabled cuts the KMS cost without losing control of the key.
aws s3api put-bucket-encryption --bucket $B --server-side-encryption-configuration \
'{"Rules":[{"ApplyServerSideEncryptionByDefault":{"SSEAlgorithm":"aws:kms",
"KMSMasterKeyID":"arn:aws:kms:eu-west-1:...:key/nimbus-adjuntos"},
"BucketKeyEnabled":true}]}'
# 3) VERSIONING: a deletion leaves a recoverable previous version.
aws s3api put-bucket-versioning --bucket $B --versioning-configuration Status=Enabled
# 4) ACCESS LOGGING: without this, detection D-04 from 05-02 does not exist.
aws s3api put-bucket-logging --bucket $B --bucket-logging-status \
'{"LoggingEnabled":{"TargetBucket":"nimbus-logs-prod","TargetPrefix":"s3/adjuntos/"}}'
# 5) VERIFY that it is NO longer public. The configuration is not the proof: the test is.
curl -s -o /dev/null -w "%{http_code}\n" https://$B.s3.amazonaws.com/tenant/41/test.pdf
# Expected: 403. Any 200 means it is still exposed.The two remaining controls apply to the backups bucket A-03, and they are the ones 02-06 proved indispensable:
# nimbus-backups-prod - in an ACCOUNT SEPARATE from production
object_lock:
mode: COMPLIANCE # not even the administrator can shorten the retention:
retention_days: 35 # that is what turns "immutable" into something real
policy:
- effect: Deny
action: [s3:DeleteObject, s3:DeleteObjectVersion, s3:PutBucketVersioning]
principal: "*" # NOBODY deletes, not even with valid credentials
- effect: Allow
action: [s3:PutObject]
principal: "arn:aws:iam::PRODUCTION-ACCOUNT:role/nimbus-backup"
condition: { StringEquals: { "s3:x-amz-server-side-encryption": "aws:kms" } }
replication: { destination: secondary-region, account: BACKUP-ACCOUNT }COMPLIANCE mode is the difference between a backup and a hope. In governance mode, an administrator can lift the retention; in compliance mode, nobody can, not even the account owner, until it expires. It is the fourth digit of the 3-2-1-1-0 rule from 04-06 and it is, literally, what would have prevented the deletion on day 20 at 02:10.
And the seventh control, already familiar: downloads are served with 120-second signed URLs (03-07), generated after the authorisation has been checked in the application (05-05). There is never a permanent link and never an object readable without a credential.
- Network and data: private subnets, encryption and isolated backups
Picking up the design from 05-04 with managed services in mind:
- The database lives in a private subnet with no route to the Internet, inbound or outbound. It is not "a closed port": there is simply no path.
- Private endpoints (VPC endpoints) for talking to object storage and to the secrets manager. The traffic does not go out to the Internet and — most valuably — it enables the
aws:SourceVpcecondition from section 3, which cancels out the value of a stolen credential. - Encryption at rest and in transit by default across every service, with Nimbus-managed keys and rotation using
kid(03-06). The provider's encryption protects against physical theft of the disk; encryption with your own key also protects against logical access mistakes. - Blocking the metadata service, or requiring its session-based version (IMDSv2), which is the structural mitigation for the SSRF from 05-05: without it, an SSRF in the API hands over the instance role's credentials.
- The backups live in another account, with different credentials. It is the most expensive lesson of 02-06: Nimbus's backups were in the same account as production, and whoever compromised the account deleted them along with everything else. Backups in another account mean the attacker has to compromise two independent accounts, with two sets of credentials and two identity providers. The added cost is a few euros a month.
- Cloud logging and auditing
The provider's activity log (CloudTrail or equivalent) answers who called which API, when, from where and with what result. It is the most important source in a cloud environment and it has to be configured properly:
- Enabled in every region, not just the one you use. A common technique is to operate in a region nobody looks at.
- Shipped to a bucket in another account, with the same properties as in section 4: immutable and write-only. Logs are protected in the same way as backups.
- Integrity validation enabled, so that you can prove they were not tampered with.
- Complemented with the bucket access logs, the load balancer's and the VPC flow logs (05-04).
The minimum alerts, which connect directly with the catalogue from 05-02:
| Alert | Event | Severity | Why |
|---|---|---|---|
| Use of the root account | Any action with the root identity | S1 | It should never be used day to day |
| Policy or role change | PutRolePolicy, AttachUserPolicy |
S1 (D-07) | It is privilege escalation in the cloud |
| Change to a bucket's configuration | PutBucketPolicy, PutPublicAccessBlock |
S1 (D-06) | It is how a bucket becomes public |
| Logging disabled | StopLogging, DeleteTrail |
S1 (D-08) | There is only one reason: covering tracks |
| Deletion of backups or snapshots | DeleteBackup, DeleteDBSnapshot |
S1 (D-10) | Day 20 of 02-06 |
| Access from an unusual region or country | Calls from outside the expected list | S2 (D-02) | A compromised credential |
| MFA disabled on an account | DeactivateMFADevice |
S2 | Preparation for later access |
The first five are S1 because none of them has a frequent benign explanation. They are also dirt cheap to set up: they are queries over a log that already exists.
- Automated posture: CSPM and infrastructure as code
CSPM (Cloud Security Posture Management) is continuously evaluating the account's configuration against a set of best practices. prowler and ScoutSuite are free and good enough.
prowler aws --compliance cis_2.0_aws --severity critical high \
--output-formats html csv json --output-directory /var/log/prowlerProvider: aws Account: 4711-nimbus-prod Checks: 312 Duration: 6m
FAIL 14 · PASS 271 · MANUAL 27
CRITICAL s3_bucket_public_access
nimbus-web-assets -> Bucket publicly accessible (ACL: AllUsers READ)
CRITICAL iam_root_mfa_enabled
The root account does NOT have MFA enabled
HIGH iam_user_accesskey_unused_45_days
user 'deploy-legacy': access key created 2023-02-11, unused for 402 days
HIGH rds_instance_backup_enabled
nimbus-preprod-db: automatic backups DISABLED
HIGH cloudtrail_multi_region_enabled
Trail 'principal' only covers eu-west-1
MEDIUM s3_bucket_object_lock_enabled
nimbus-backups-prod: no object lockA commented reading, using the prioritisation criterion from 05-01. iam_root_mfa_enabled comes first, despite not being the noisiest: without MFA on root, every other control is optional for whoever gets hold of that credential, and enabling it takes five minutes. The public bucket comes immediately afterwards, and the first question is not how to close it but what it contains and since when it has been like that: if it holds personal data, this is no longer a configuration finding, it is a possible breach (04-05, 06-03). The access key unused for 402 days is the quietest finding and one of the most dangerous: a permanent, forgotten credential that nobody would miss if somebody used it; it is deleted, not rotated. And the disabled backups in preproduction point once more at A-22.
Shifting the control left. CSPM catches the error after it reaches production. checkov and tfsec catch it in the pull request, in the infrastructure code, before it exists:
# .github/workflows/iac.yml
- uses: bridgecrewio/checkov-action@master
with:
directory: infra/
framework: terraform
soft_fail: false # BLOCKS: a public bucket never reaches production
skip_check: CKV_AWS_18 # exception with justification and expiry (04-02)
output_format: sarifThe correct combination is checkov blocking in CI and prowler weekly against the account, because not everything is deployed as code: there is always something done by hand in the web console, and only CSPM sees that.
- Containers: what they isolate and what they do not
| Isolates reasonably well | Does not isolate |
|---|---|
| Processes, filesystem, network and hostnames (via namespaces) | The kernel: it is the same one as the host's. A kernel vulnerability affects everything |
| CPU and memory consumption (via cgroups) | A privileged container, or one with extra capabilities, which is equivalent to root on the host |
| Dependencies between applications | The Docker socket mounted inside: that is total control of the host |
The sentence to internalise: a container is process isolation, not a security boundary like a virtual machine's. If you need to separate workloads of very different trust levels, the correct boundary is the virtual machine or a separate account.
# BEFORE - the API's Dockerfile in 01-04, with six serious problems
FROM python:3.11 # full image: ~1 GB and hundreds of CVEs
WORKDIR /app
COPY . . # copies EVERYTHING: .git, .env, keys, tests
RUN pip install -r requirements.txt # no hashes: you install whatever is there today
ENV DATABASE_URL=postgresql://nimbus_api:Tr4mo...@db:5432/nimbus # SECRET
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0"] # runs as ROOT# AFTER - multi-stage, minimal, no secrets and no root
FROM python:3.11-slim@sha256:1c8f9a... AS build
# Pin by DIGEST and not by tag: `3.11-slim` changes content without notice, so
# the tag does not guarantee that you build the same thing twice.
WORKDIR /app
COPY requirements.txt .
RUN pip install --require-hashes --prefix=/installed -r requirements.txt
# --require-hashes: if a version was republished with different content, the build
# FAILS instead of silently installing it (04-04).
FROM gcr.io/distroless/python3-debian12@sha256:9d2e4b...
# Distroless: no shell, no package manager, no utilities. It cuts CVEs by around
# 90 % and, above all, leaves the attacker with no tools if they get in.
COPY --from=build /installed /usr/local
COPY --chown=nonroot:nonroot app/ /app/app/
# Only the application code is copied, thanks also to .dockerignore:
# .git, .env, tests and credentials never enter any layer.
USER nonroot # the process is NOT root inside the container
WORKDIR /app
EXPOSE 8000
ENTRYPOINT ["python", "-m", "uvicorn", "app.main:app", "--host", "0.0.0.0"]
# No ENV with secrets: the credential is injected at start-up from the manager
# (05-05). A secret in a layer stays in the image even if it is deleted later.The last comment deserves emphasis because it is the most common mistake and the least obvious: deleting a file in a later layer does not remove it from the image. It is still in the earlier layer and can be recovered with one command. That is why trivy --scanners secret (05-01) is part of the pipeline.
- Secure execution and image signing
Building the image well is half of it; the other half is how it is run.
# docker-compose.prod.yml / the orchestrator equivalent
services:
api:
image: registry.nimbus.example/api@sha256:6f1b... # by DIGEST, not by :latest
read_only: true # read-only filesystem: prevents writing binaries
tmpfs: [/tmp] # or persisting anything; /tmp in memory
cap_drop: ["ALL"] # ALL kernel capabilities are dropped
cap_add: [] # and none is given back: the API does not need them
security_opt:
- no-new-privileges:true # no child process can gain privileges
- apparmor=docker-default # mandatory access control profile (05-06)
user: "10001:10001" # unprivileged user, explicit
mem_limit: 512m # limits: one container does not take down its neighbour
pids_limit: 200 # nor exhaust the host's process tableread_only plus cap_drop: ALL plus no-new-privileges is the combination that raises the cost of an attack the most, and it costs nothing: most web applications work exactly as they are, and the ones that do not just need to declare their write paths as volumes.
Artefact signing with cosign (picking up 03-07), which answers the question "is this image the one we built?":
# Sign in CI, with no keys to look after (the pipeline's own identity)
cosign sign --yes registry.nimbus.example/api@sha256:6f1b...
# Verify BEFORE deploying; if it fails, the deployment stops
cosign verify registry.nimbus.example/api@sha256:6f1b... \
--certificate-identity-regexp "https://github.com/nimbus/.*" \
--certificate-oidc-issuer https://token.actions.githubusercontent.comWithout verification at deployment time, the signature is decorative. The complete chain looks like this, and it is the supply chain integrity from 04-04 put into practice:
flowchart LR
PR["Pull request\ncode + infra"] --> CK["checkov / tfsec\nBLOCKS insecure config"]
CK --> BU["Multi-stage build\ndistroless, no root"]
BU --> TR["trivy\nvulns + secrets\nBLOCKS above threshold"]
TR --> CS["cosign sign\npipeline identity"]
CS --> RG["Registry\nimage by digest"]
RG --> VE["cosign verify\nat deployment"]
VE -->|"valid signature"| PROD["Production\nread_only, cap_drop ALL"]
VE -->|"no signature"| STOP["Deployment stopped"]
PROD --> FA["Falco\nruntime detection"]
- Orchestration and runtime detection
Nimbus does not use Kubernetes yet, and it is worth saying clearly that Kubernetes is a security domain in its own right, not a deployment detail. If it arrives, these are the six minimum pieces: namespaces per environment and per criticality, with quotas; RBAC with dedicated service accounts per application and no use of the default account; network policies, because by default every pod talks to every other pod — this is the most frequent mistake and the equivalent of the flat network in 05-04; pod security standards at the restricted level, which enforces what section 9 describes; secrets management with an external provider, because native secrets are base64-encoded and not encrypted; and kube-bench, which evaluates the cluster against the CIS benchmark just as lynis evaluates a server.
Runtime detection with Falco. Image scanning looks at what is there; Falco looks at what happens, by observing system calls:
- rule: Shell opened inside a production container
desc: >
An API container should NEVER run a shell. Since the image is distroless,
this can only mean a compromise or a badly built deployment: either way,
you need to know about it.
condition: >
spawned_process and container
and container.image.repository = "registry.nimbus.example/api"
and proc.name in (bash, sh, dash, zsh)
output: "Shell in container (user=%user.name process=%proc.cmdline
container=%container.name image=%container.image.repository)"
priority: CRITICAL
tags: [container, mitre_execution]What is worth detecting at runtime, without generating noise: a shell inside a container, writes to system paths (/etc, /usr/bin) when the filesystem should be read-only, an outbound connection to an unexpected destination, reads of sensitive files such as /etc/shadow or the instance credentials, and the Docker socket being mounted. They all go to the same channel from 05-02 and share its alert quality rules.
- What Nimbus should do first, on its budget
| Priority | Action | Cost | Effort | Risk it reduces |
|---|---|---|---|---|
| 1 | MFA on root and federation with the identity provider | 0 € | 2 h | Total compromise of A-05 |
| 2 | Public access block on every bucket, and verified | 0 € | 2 h | R-04, the leak in 02-06 |
| 3 | Backups in a separate account with object lock in compliance mode | ~15 €/month | 6 h | R-02, day 20 |
| 4 | Remove long-lived access keys; short-lived roles | 0 € | 8 h | R-06 |
| 5 | Multi-region activity log to a separate account + the 5 S1 alerts | ~10 €/month | 8 h | The 20 days of blindness |
| 6 | Weekly prowler and review of the findings |
0 € | 4 h + 1 h/month | Configuration drift |
| 7 | Blocking checkov in the infrastructure CI |
0 € | 4 h | The error reaching production |
| 8 | Distroless Dockerfile, no root and no secrets, with trivy |
0 € | 12 h | The image's surface |
| 9 | Private endpoints and the SourceVpce condition |
~20 €/month | 6 h | The value of a stolen credential |
| 10 | Falco with five rules and cosign at deployment |
0 € | 16 h | Compromise at runtime |
The total comes to around 540 €/year and about 70 hours. That is less than 3 % of the 18,000 €/year budget and 16 % of Lucía's 440 hours, and it covers the three highest-scoring risks in the register from 04-01. It is, by a distance, the best cost/impact ratio in the whole of module 5, and the reason is the same one the lesson opened with: in the cloud, almost everything that fails is a checkbox, and checkboxes are free.
Common Mistakes and Tips
- Believing the provider protects you from your own configurations. It does not warn you when you publish a bucket: that is a legitimate customer action.
- Using long-lived access keys. They end up in a repository, in a chat or on a laptop. Short-lived roles and instance identity.
- Granting
Action: "*""temporarily". Nothing is more permanent than a temporary permission that works. - Leaving the backups in the same account as production. That is what turned the incident in 02-06 into a catastrophe.
- Trusting that a container isolates like a virtual machine. It shares the kernel; with extra capabilities or the Docker socket mounted, it is the whole host.
- Tags instead of digests.
:latestis not reproducible: you scan one image and deploy another. - Secrets in the
Dockerfile'sENV. They stay in the layer even if you delete them later. - Tip: start with the first two rows of the table in §11. Four hours, zero cost, and they cover the worst possible scenario.
- Tip: verify, do not configure. A
curlreturning 403 is worth more than a screenshot of the web console, and it is the evidence 04-03 asks for. - Tip:
checkovin CI beforeprowleron the account. It is cheaper to prevent the error than to discover it, although you will need both.
Exercises
Exercise 1 — Interpret a CSPM report
Working from the prowler output in §7, and knowing that nimbus-web-assets serves the public images of the marketing website:
- Rank the six findings by real priority, justifying each position.
- Does your assessment of the public bucket change once you know what it is used for? What would you check before deciding?
- Write the specific fix for the first three and state how you would verify each one.
Exercise 2 — Review a container deployment
Iván proposes this service for production. Identify every problem and rewrite it.
services:
api:
image: nimbus/api:latest
privileged: true
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /:/host
environment:
DATABASE_URL: "postgresql://nimbus_api:Tr4mo...@db:5432/nimbus"
ports: ["8000:8000"]Exercise 3 — Design the isolation of the backups
After reading the post-mortem of 02-06, Marta asks: "how do we guarantee this cannot happen again?". Design Nimbus's backup scheme so that an attacker with total control of the production account cannot destroy them, and state how you would prove it.
Solutions
Exercise 1
(1) Ranking by real priority:
| Rank | Finding | Justification |
|---|---|---|
| 1 | Root without MFA | It is the master key to A-05, on which almost every asset depends (01-04). A single password stands between an attacker and total control. It is fixed in 5 minutes |
| 2 | Public bucket | Current exposure, with an impact that depends on the contents (see point 2) |
| 3 | Access key unused for 402 days | The quietest finding: a permanent credential, forgotten and ownerless. If somebody used it, nobody would miss it. It is not rotated: it is deleted |
| 4 | CloudTrail in one region only | It is not exposure, it is partial blindness, and precisely in the regions nobody looks at, which is where a well-informed attacker operates |
| 5 | nimbus-backups-prod with no object lock |
It is the day-20 failure of 02-06, not fully fixed. It moves to first place if the backups are confirmed to still be in the production account |
| 6 | Backups disabled on nimbus-preprod-db |
Preproduction can tolerate loss... unless it holds real data, which is the open question about A-22. Settling that classification is more urgent than enabling the backup |
(2) Yes, it changes, but less than it seems. A marketing image bucket being publicly readable may be exactly what is intended, and in that case the finding is a false positive documented as an exception (04-02). Before deciding you have to check four things: what objects it actually contains — "web assets" buckets very frequently accumulate exports, one-off backups and customer PDFs somebody left there; whether, as well as public read, it has public write, which would be critical because it would let anyone alter the content served under Nimbus's domain; since when it has been like this and what the access logs say; and whether there is a better way of achieving the same thing, which there is: a CDN in front with the bucket private, which gives you control, caching and logging without direct exposure. The general rule: a public bucket is acceptable only if it is a dedicated bucket whose contents are entirely intended to be public and it is recorded as such in writing.
(3) Fixes and verification:
- Root without MFA: enable MFA with a hardware key, keep the credential and the backup second factor in physical custody with two people having access, and create an S1 alert on any use of root (§6). Verification:
prowlerre-runsiam_root_mfa_enabledwith a PASS result, and a test login asks for the second factor. - Public bucket: if the content has to be public, move it to a dedicated bucket behind a CDN and leave the original private; if not, apply the public access block from §4. In both cases, inventory the contents first. Verification: a
curlagainst a known object returning 403, andput-public-access-blockconfirmed. - Unused key: delete it, do not disable it — a disabled key gets re-enabled; first check in the activity log that no process used it in 402 days, and look for it in the repository history with
gitleaks(05-01), because a forgotten key is usually forgotten because it was embedded somewhere. Verification: the key no longer appears in the listing and there are no new authentication errors within 48 hours.
Exercise 2
Problems, from most to least serious:
| # | Problem | Consequence |
|---|---|---|
| 1 | /var/run/docker.sock mounted |
It is total control of the host. Whoever gets into the container can create another privileged container and break out. It is equivalent to handing over root on the machine |
| 2 | privileged: true |
It disables practically all the isolation: every capability and access to the devices |
| 3 | /:/host |
The host's entire filesystem, mounted and writable from the container |
| 4 | Credential in environment |
A secret in the clear, visible with docker inspect, in the orchestrator's logs and in the deployment history. It is R-06 |
| 5 | image: nimbus/api:latest |
Not reproducible: you scan one image and deploy another. And the signature is not verified |
| 6 | ports: ["8000:8000"] |
Published on every interface of the host, bypassing the load balancer; it is the queue panel pattern from 05-03 |
| 7 | Absences: no read_only, no cap_drop, no no-new-privileges, no user, no resource limits |
The container can write binaries, escalate privileges and exhaust the host |
services:
api:
image: registry.nimbus.example/api@sha256:6f1b... # digest + verified signature
read_only: true
tmpfs: [/tmp]
cap_drop: ["ALL"]
security_opt: [no-new-privileges:true, apparmor=docker-default]
user: "10001:10001"
mem_limit: 512m
pids_limit: 200
ports: ["127.0.0.1:8000:8000"] # local only; the load balancer is the front
secrets: [db_url] # injected at start-up from the manager
# no docker.sock, no host volumes, no privilegedExercise 3
The requirement restated: destroying the backups must require credentials the production account does not hold and an action no credential can perform. Four layers, each solving a different failure:
- A separate account, with its own identity provider and its own MFA. Compromising production gives no access whatsoever to the backup account. It is the layer that was missing in 02-06, where everything lived together.
- A one-way, least-privilege write flow. Production's
nimbus-backuprole has onlyPutObjecton the backups bucket: it cannot list, it cannot read and it cannot delete. That it cannot read is important and often overlooked: it stops an attacker in production also walking off with the complete history. - Object lock in
COMPLIANCEmode with 35 days of retention. Here is the core of the answer for Marta: not even an administrator of the backup account with MFA can shorten the retention or delete an object before it expires. It is not a question of permissions, which can always be changed; it is a property of the storage. - Replication to a second region and, for the critical set, an additional copy in cold storage with credentials different from all of the above.
It is complemented with detection: alert D-10 from 05-02 on any attempted deletion or retention change, and the silence alert — if the backup process stops writing for 24 hours, somebody must find out — because the stealthy way to destroy backups is not to delete them, but to prevent them being created and wait for the history to expire.
How it is proved, which is what Marta should demand instead of an explanation: (a) a real deletion attempt from the backup account with administrator credentials, which must fail with AccessDenied, with the output kept as evidence; (b) a timed restore test from 04-06, run from the backup account, verifying the hash, comparing rows and measuring the real RTO; (c) a compromise drill: revoke every production credential and check that the backups are still reachable and restorable; and (d) a quarterly review that object lock is still enabled, included in the infrastructure-as-code --check --diff (05-06). The right answer to Marta's question is not "we have configured this": it is "we tried to destroy them and we could not, and here is the dated console output".
Conclusion
You have closed the course's technical plan at the point where most companies break today. You know why the cloud changes the threat model — the configuration error replaces the exploit as the dominant cause — and the three underlying reasons: everything is an API, speed multiplies error and reach is public by default. Hence the inversion of priorities: reviewing the configuration pays off more than hunting for vulnerabilities. You apply the shared responsibility model service by service with the question that orders it — "if this fails, who do you call?" — and with the two conclusions that matter: the more managed the service, the fewer tasks and the more critical they are; and the provider never warns you when you get it wrong.
You have a command of identity as the new perimeter: MFA on root, an end to long-lived keys, assumable roles with short-lived credentials, federation with the identity provider and a review of excessive permissions; with the minimal policy that does not grant Delete, bounds the prefix and adds an explicit deny by VPC endpoint that cancels out the value of a stolen credential. You have configured bucket A-02 once and for all: public access block, encryption with your own key, versioning, access logging and verification with a curl that must return 403; and the A-03 backups in a separate account with object lock in COMPLIANCE mode, which is what turns "immutable" into something real and what would have prevented the deletion on day 20. You know how to set up private subnets, private endpoints, encryption by default, blocking of the metadata service as a structural mitigation for SSRF, and backups that require compromising two independent accounts.
You know how to configure the activity log multi-region, immutable, in another account and with integrity validation, and the seven minimum alerts, five of which are S1 because none has a benign explanation. You can handle CSPM with prowler — reading its report with the criterion from 05-01, starting with root MFA and asking what the public bucket contains before closing it — and blocking checkov in CI so the error never reaches production, with the conclusion that you need both. You understand what a container isolates and what it does not, and you have the Dockerfile before and after: multi-stage, distroless, pinned by digest, --require-hashes, no secrets in layers and no root; with the warning that deleting a file in a later layer does not remove it from the image. You run with read_only, cap_drop: ALL, no-new-privileges and limits, you sign with cosign and you verify before deploying, because without verification the signature is decorative. And you know how to frame Kubernetes as a domain of its own and Falco for detecting what happens at runtime. All of it prioritised in a table that comes to around 540 € and 70 hours and covers the three highest-scoring risks in the register: in the cloud, almost everything that fails is a checkbox, and checkboxes are free.
With that, Module 5 is complete. Nimbus now knows how to find what it has exposed and prioritise it (05-01), see the attacker move with twelve detections that would have cut the incident in 02-06 short on day 0 (05-02), test itself with an authorised pentest and get value from its report (05-03), segment its network and verify that the segmentation really works (05-04), defend its own code with centralised authorisation and a CI that says no (05-05), harden its systems with an automated baseline verified every week (05-06) and close down its cloud and its containers (05-07). One by one, every finding we had been carrying since 01-04 has fallen: the exposed PostgreSQL, the Redis with no authentication, the open SSH, the python -m http.server and the consultancy's permanent access.
And here is the limit of everything above. None of this maintains itself. Tools find and fix, but the monthly scan stops running when Lucía has a bad week; the baseline degrades if nobody looks at the --check --diff; the CSP fills up with exceptions; the security.txt expires; and the next employee to join will know nothing of any of this unless somebody teaches them. What is more, having done the work is not enough: you have to be able to prove it — to a customer demanding guarantees, to an audit, to the AEPD (the Spanish Data Protection Agency) if a breach ever has to be notified. In Module 6: Best Practices and Regulations we move from execution to what sustains it over time: best practices that turn one-off decisions into habits (06-01), regulations and standards such as ISO 27001, NIS2 and the ENS (06-02), data protection and the GDPR genuinely applied to a SaaS handling health data (06-03), compliance and auditing with the evidence this module has been generating (06-04), training and awareness, because people are still the main vector (06-05), and the ethics and responsible disclosure left pending by 05-03 (06-06). We stop asking whether it is protected and start asking whether it can be proved and whether it will last.
Fundamentals of Information Security Course
Module 1: Introduction to Information Security
- Basic Concepts of Information Security
- Types of Threats and Vulnerabilities
- Principles of Information Security
- Assets, Attack Surface and Threat Actors
Module 2: Cybersecurity
- Definition and Scope of Cybersecurity
- Types of Cyber Attacks
- Social Engineering and Phishing
- Protection Measures in Cybersecurity
- Identity, Authentication and Access Control
- Cybersecurity Incident Case Studies
Module 3: Cryptography
- Introduction to Cryptography
- Symmetric Cryptography
- Asymmetric Cryptography
- Hash Functions, HMAC and Password Storage
- Cryptographic Protocols
- Key Management, Certificates and PKI
- Applications of Cryptography
Module 4: Risk Management and Protection Measures
- Risk Assessment
- Security Policies
- Security Controls
- Third-Party and Supply Chain Risk
- Incident Response Plan
- Disaster Recovery and Business Continuity
Module 5: Security Tools and Techniques
- Vulnerability Analysis Tools
- Monitoring and Detection Techniques
- Penetration Testing
- Network Security
- Application Security
- System Hardening and Endpoint Security
- Cloud and Container Security
Module 6: Best Practices and Regulations
- Best Practices in Information Security
- Security Regulations and Standards
- Personal Data Protection and GDPR in Practice
- Compliance and Auditing
- Training and Awareness
- Ethics, Legal Aspects and Responsible Disclosure
