Chapter 22 — Cloud and container security: automation
Lesson map
Lesson 22 published. Map, example, EX064.
flowchart TD
N0["Learning outcomes"]
N1["22.1 Shift checks earlier"]
N2["22.2 IaC smells"]
N3["Lab — pipeline checks"]
N4["22.3 Reading the result"]
N5["22.4 Explaining blocks"]
N6["22.5 Exceptions"]
N7["Measuring the checks"]
N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> N6 --> N7
N7 --> EX["Worked example"]
EX --> Q["Exercise EX064"]
Logical drawing
flowchart LR
N0["Learning outcomes"]
N1["22.1 Shift checks earlier"]
N2["22.2 IaC smells"]
N3["Lab — pipeline checks"]
N4["22.3 Reading the result"]
N5["22.4 Explaining blocks"]
N6["22.5 Exceptions"]
N7["Measuring the checks"]
N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> N6 --> N7 --> Q["EX064"]
[Learning outcomes]
|
v
[22.1 Shift checks earlier]
|
v
[22.2 IaC smells]
|
v
[Lab — pipeline checks]
|
v
[22.3 Reading the result]
|
v
[22.4 Explaining blocks]
|
v
[22.5 Exceptions]
|
v
[Measuring the checks]
|
v
[Worked example] --> [Exercise EX064]
Learning outcomes
You will (1) add security checks to the deployment pipeline; (2) block risky changes before merge; (3) keep checks explainable.
22.1 Shift checks earlier
Finding a public bucket after deployment means it was exposed for a time. Checking infrastructure code before merge stops it reaching production. Detecting a problem after deployment depends on a scheduled check running and someone reading the result, and the data may already have been exposed by then. A pipeline check runs on every proposed change before merge, and the developer sees the problem while the change is still fresh in mind. Detection after deployment remains necessary for changes that bypass the pipeline.
22.2 IaC smells
Rahman et al. (2019) studied infrastructure-as-code scripts and found recurring security smells, such as hard-coded secrets and admin by default. Cobalt's rules target similar patterns. Rahman et al. name seven such smells, including hard-coded secrets, admin by default, suspicious comments, empty passwords and invalid IP address binding. Cobalt's four rules are a deliberately small selection matched to its own risks: a public bucket, a mutable image tag, a container running as root and a secret written in a file. Each corresponds to a scenario or finding from earlier chapters.
Lab — pipeline checks
"""Flag risky settings in synthetic infrastructure-as-code records before merge (stdlib only)."""
changes = [
{"id": "PR-41", "bucket_public": False, "image_tag": "2.5", "run_as_root": False, "secret_in_file": False},
{"id": "PR-42", "bucket_public": True, "image_tag": "2.5", "run_as_root": False, "secret_in_file": False},
{"id": "PR-43", "bucket_public": False, "image_tag": "latest", "run_as_root": True, "secret_in_file": False},
{"id": "PR-44", "bucket_public": False, "image_tag": "1.2", "run_as_root": False, "secret_in_file": True},
{"id": "PR-45", "bucket_public": False, "image_tag": "3.0", "run_as_root": False, "secret_in_file": False}]
rules = {"bucket_public": lambda c: c["bucket_public"], "mutable tag": lambda c: c["image_tag"] == "latest",
"run_as_root": lambda c: c["run_as_root"], "secret_in_file": lambda c: c["secret_in_file"]}
blocked = 0
for c in changes:
hits = [n for n, f in rules.items() if f(c)]
blocked += bool(hits); print(f"{c['id']}: {'BLOCK ' + ', '.join(hits) if hits else 'pass'}")
print(f"{blocked}/{len(changes)} changes blocked")
Run with python3 lab_iac.py. Verified output:
PR-41: pass
PR-42: BLOCK bucket_public
PR-43: BLOCK mutable tag, run_as_root
PR-44: BLOCK secret_in_file
PR-45: pass
3/5 changes blocked
22.3 Reading the result
Of 5 proposed changes, 3 are blocked: PR-42 makes a bucket public, PR-43 uses the mutable latest tag and runs as root, and PR-44 puts a secret in a file. PR-41 and PR-45 pass. PR-43 triggers two rules at once, which shows the rules are evaluated independently and each hit is reported, so the developer can fix both in one round. PR-41 and PR-45 pin specific tags, keep buckets private, run as non-root and keep secrets out of files. The rule set is small enough that a developer can read all four rules and predict whether a change will pass.
22.4 Explaining blocks
Each blocked change gets a message stating the rule, why it matters and how to fix it. Developers fix most within the day. For PR-44, the message says that a secret was found in a configuration file, that anything in the repository is readable by everyone with access to it and stays in history, and that the secret should be stored in the cluster's secret store and referenced by name. Because the message explains the fix, developers rarely need to ask the platform team for help.
22.5 Exceptions
A rare legitimate need, such as a public bucket for website images, uses an approved exception label reviewed by Kwame. The exception label alone does not let a change through the pipeline. Kwame reviews the reason, confirms that the bucket will hold only public content such as website images, and records the exception with an owner and expiry date. The pipeline then skips that single rule for that single resource, while all other rules still apply to the change. Expired exceptions make the rule apply again automatically on the next change.
Measuring the checks
Priya tracks how many changes are blocked each week and how long fixes take. Early on, many changes are blocked; after a month the number falls as developers learn the rules. A rule that blocks often without finding real problems is reviewed, because false blocks make developers resent the checks.
She also confirms that checks run quickly, under a minute, so they do not slow the pipeline.
Detecting console changes
A nightly comparison of live settings with the repository flags any change made outside the pipeline.
Worked example
The developer of PR-43 asks why latest is blocked. Priya explains that latest can point to different content over time, so the inventory could not say what is running; pinning a version or digest fixes this.
Synthetic case. Changes are synthetic.
Errors and limits
Pipeline checks only cover changes made through the pipeline; console changes need detection too.
Exercises
- EX064 — Lab blocks. Which changes were blocked and why? Rubric: 3 = 42 public bucket; 43 latest and root; 44 secret; 2 = three; 1 = two; 0 = none.
- EX065 — Latest tag. Why is the latest tag blocked? Rubric: 3 = content changes; inventory cannot say what runs; 2 = partial; 1 = vague; 0 = none.
- EX066 — Pipeline limit. Which changes do pipeline checks miss? Rubric: 3 = console changes; 2 = partial; 1 = vague; 0 = none.
Synthesis
Block risky changes early with clear explanations and keep detection for the rest.
Continue studying — chapter 22 of 30
← Chapter 21 (draft) · Course chapter index · Chapter 23 (draft) →