carlos / platform Public

fix/scanner-freshclam-apparmor

Squashed from fix/scanner-freshclam-apparmor into main.
Paul Campbell pushed by claude agent, operated by paul@keymail.dev 592b667a7bb6c35fd8d6134161928e36ad94681a parent 1a17848
4 files changed, +31 −16
  • docs/superpowers/plans/2026-10-05-scanner-host-rollout.md +9 −7
  • docs/superpowers/specs/2026-10-04-scanner-host-rollout-design.md +4 −4
  • infra/modules/scanner-host/scanner-init.sh.tftpl +6 −5
  • infra/modules/scanner-host/tests/scanner_host.tftest.hcl +12 −0

CI passed — run details

diff --git a/docs/superpowers/plans/2026-10-05-scanner-host-rollout.md b/docs/superpowers/plans/2026-10-05-scanner-host-rollout.md
index 37b079565..2eb8e2512 100644
--- a/docs/superpowers/plans/2026-10-05-scanner-host-rollout.md
+++ b/docs/superpowers/plans/2026-10-05-scanner-host-rollout.md
@@ -1217,14 +1217,16 @@ Expected: the push succeeds. If the ancestor check fails, stop and tell Paul; ne
---
-### Task 6 (GATED): release v0.23.0
+### Task 6 (GATED): release v0.24.1
+
+> **2026-10-06:** v0.23.0 had already been cut (3ca43d3) before the scanner merged, so the scanner first shipped in v0.24.0. v0.24.0's first boot failed on AppArmor: freshclam may only read its config under `/etc/clamav`. The fix ships in v0.24.1, which is the pin used below.
Carloku pins modules to release tags, so the modules reach it through a release. The release carries everything on main since v0.22.2, not only this work, so **whether and when to cut it is Paul's call**. Ask before starting.
- [ ] **Step 1:** List what the release contains: `git log --oneline v0.22.2..origin/main`. Show Paul. Wait for his go.
- [ ] **Step 2:** Follow `README.md` "Releasing the platform" exactly (steps 1 to 9 and the public-side verification), with memory `platform-release-recipe` for the traps. Release notes go through `copy-review`. The operators' section says: object-storage scanning needs a scanner host (`infra/modules/scanner-host`) and `store_scanning` on the console's `region-host`; a console must run this release before it is pointed at a scanner host, because older consoles record an over-limit archive as infected.
-- [ ] **Step 3:** Push the tag to the GitHub mirror: `git push https://github.com/carlosframework/platform.git v0.23.0`.
-- [ ] **Step 4:** Do **not** roll boxes as part of this plan. The box binary is unaffected; only consoles need the new build (Tasks 8 and 12). If Paul wants the fleet on v0.23.0 too, that is the README's last step and his separate go.
+- [ ] **Step 3:** Push the tag to the GitHub mirror: `git push https://github.com/carlosframework/platform.git v0.24.1`.
+- [ ] **Step 4:** Do **not** roll boxes as part of this plan. The box binary is unaffected; only consoles need the new build (Tasks 8 and 12). If Paul wants the fleet on v0.24.1 too, that is the README's last step and his separate go.
---
@@ -1235,7 +1237,7 @@ Carloku pins modules to release tags, so the modules reach it through a release.
- `~/github.com/carlosframework/carloku-infrastructure/scanner.tf` (create)
- `~/github.com/carlosframework/carloku-infrastructure/outputs.tf` (modify)
-- [ ] **Step 1: Branch and edit.** In that repo: `git checkout -b scanner-host origin/main`. In `region-ie.tf`, change `host_ie`'s source to `?ref=v0.23.0` (that module block only; the other three regions stay on v0.21.0) and add, after `store_bucket_prefix`:
+- [ ] **Step 1: Branch and edit.** In that repo: `git checkout -b scanner-host origin/main`. In `region-ie.tf`, change `host_ie`'s source to `?ref=v0.24.1` (that module block only; the other three regions stay on v0.21.0) and add, after `store_bucket_prefix`:
```hcl
# Object-storage virus scanning (platform spec 2026-10-04): lets THIS box's
@@ -1251,7 +1253,7 @@ Create `scanner.tf`:
# plane. Reachable from the IE host's security group only. The module's README
# has the readiness and acceptance checks; run them before wiring the console.
module "scanner" {
- source = "git::https://github.com/carlosframework/platform.git//infra/modules/scanner-host?ref=v0.23.0"
+ source = "git::https://github.com/carlosframework/platform.git//infra/modules/scanner-host?ref=v0.24.1"
name = "carlos-flagship-scanner"
ami_id = module.host_ie.ami_id
client_security_group_id = module.host_ie.security_group_id
@@ -1354,7 +1356,7 @@ Expected: `active` three times, `1`, `0`, and nothing from `systemd-analyze veri
### Task 8 (GATED): Carloku, the console on the new build
-- [ ] **Step 1:** A clean checkout of the `v0.23.0` tag (scratch clone under `~/.claude/tmp/carlos-build/`, as memory `platform-roll-recipe` describes). `git status --porcelain` must print nothing.
+- [ ] **Step 1:** A clean checkout of the `v0.24.1` tag (scratch clone under `~/.claude/tmp/carlos-build/`, as memory `platform-roll-recipe` describes). `git status --porcelain` must print nothing.
- [ ] **Step 2:** Show Paul the command. On his go, from `carloku-infrastructure`: `GOFLAGS=-buildvcs=false scripts/roll-console.sh <checkout> flagship`.
Expected: it builds arm64, ships, promotes, adopts, restarts, and its own check prints the running executable as `.../versions/<sha>/console` for the SHA it built. **If that check fails, stop: nothing gets wired.**
- [ ] **Step 3:** `carlos system antivirus status --console https://console.carloku.com`
@@ -1364,7 +1366,7 @@ Expected: it answers, and says no engine is wired. (Wiring is Task 9.)
### Task 9 (GATED): Carloku, key and wiring
-- [ ] **Step 1: Preconditions, both.** Task 7's readiness and four acceptance checks are recorded as passed. Task 8's executable check passed for the build with the Task 1 change: `git merge-base --is-ancestor <task-1-commit-on-main> v0.23.0` exits 0.
+- [ ] **Step 1: Preconditions, both.** Task 7's readiness and four acceptance checks are recorded as passed. Task 8's executable check passed for the build with the Task 1 change: `git merge-base --is-ancestor <task-1-commit-on-main> v0.24.1` exits 0.
- [ ] **Step 2: Find the console's environment file.**
diff --git a/docs/superpowers/specs/2026-10-04-scanner-host-rollout-design.md b/docs/superpowers/specs/2026-10-04-scanner-host-rollout-design.md
index 0739dc3fd..539f33128 100644
--- a/docs/superpowers/specs/2026-10-04-scanner-host-rollout-design.md
+++ b/docs/superpowers/specs/2026-10-04-scanner-host-rollout-design.md
@@ -339,19 +339,19 @@ before they are wired** (§6.2). A console without it, wired to a clamd with
- The module change, `scanner-host` and the §4.3 client change land on amadan main through a branch and
`amadan branch merge -squash`, then the GitHub mirror is pushed forward
- (fast-forward checked first), then **v0.23.0** is cut per the README's
+ (fast-forward checked first), then **v0.24.1** is cut per the README's
"Releasing the platform" recipe and pushed to the mirror.
- **Carloku** (`carloku-infrastructure`): move **only `host_ie`'s** `?ref=` to
- v0.23.0, set `store_scanning = true`, add `module "scanner"` with
+ v0.24.1, set `store_scanning = true`, add `module "scanner"` with
`client_security_group_id = module.host_ie.security_group_id`. The other three
regions stay on v0.21.0. Every apply is `-target`ed
(`module.host_ie.aws_iam_role_policy.host_store_scan`, `module.scanner`), and
the plan is read for any `user_data` line before it runs, because a
`user_data` change stops and starts a box. Any other `host_ie` diff the
- v0.21.0 to v0.23.0 bump shows is listed in the PR and not applied.
+ v0.21.0 to v0.24.1 bump shows is listed in the PR and not applied.
- **Tito** (`teamtito/carlos-infrastructure`): a branch from the
`store-boundary-201` line (main cannot plan while the Canada box is only on
- `region-ca`), `module "scanner"` at `?ref=v0.23.0`, the `StoreScan`
+ `region-ca`), `module "scanner"` at `?ref=v0.24.1`, the `StoreScan`
statement. Targeted applies. Tito pins `region-host` to a SHA; that pin does
not move.
diff --git a/infra/modules/scanner-host/scanner-init.sh.tftpl b/infra/modules/scanner-host/scanner-init.sh.tftpl
index 2d8bad14a..0d4a3572f 100644
--- a/infra/modules/scanner-host/scanner-init.sh.tftpl
+++ b/infra/modules/scanner-host/scanner-init.sh.tftpl
@@ -131,17 +131,18 @@ systemctl daemon-reload
# ---- 4. first signatures ---------------------------------------------------
say "4/6 first signatures"
-# NotifyClamd would complain that no clamd is running yet. It is not.
-sed '/^NotifyClamd/d' /etc/clamav/freshclam.conf > /run/freshclam-first.conf
+# The packaged config, unchanged. Debian's AppArmor profile confines freshclam
+# to its config under /etc/clamav, so a copy anywhere else cannot be read. With
+# clamd not running yet, the NotifyClamd step only warns, and freshclam still
+# exits 0.
# ClamAV's mirror often rate-limits a new address. Three tries, a minute apart.
tries=1
-until freshclam --config-file=/run/freshclam-first.conf; do
- [ $tries -lt 3 ] || fail "first freshclam failed 3 times, 60s apart (rate-limited by ClamAV's mirror?)"
+until freshclam; do
+ [ $tries -lt 3 ] || fail "first freshclam failed 3 times, 60s apart (rate-limited by ClamAV's mirror? an AppArmor denial shows in: journalctl -k | grep freshclam)"
say "freshclam try $tries failed, retrying in 60s"
tries=$((tries + 1))
sleep 60
done
-rm -f /run/freshclam-first.conf
for db in main daily bytecode; do
ls /var/lib/clamav/$db.c[vl]d >/dev/null 2>&1 || fail "no $db signature database after freshclam"
done
diff --git a/infra/modules/scanner-host/tests/scanner_host.tftest.hcl b/infra/modules/scanner-host/tests/scanner_host.tftest.hcl
index 18047639f..4a2ae416c 100644
--- a/infra/modules/scanner-host/tests/scanner_host.tftest.hcl
+++ b/infra/modules/scanner-host/tests/scanner_host.tftest.hcl
@@ -237,6 +237,18 @@ run "logging_cannot_kill_init" {
}
}
+run "freshclam_uses_its_packaged_config" {
+ command = plan
+ assert {
+ condition = !strcontains(local.init_script, "freshclam --config-file") && !strcontains(local.init_script, "/run/freshclam")
+ error_message = "Debian's AppArmor profile confines freshclam to its config under /etc/clamav: a config passed from anywhere else cannot be read and freshclam fails at once"
+ }
+ assert {
+ condition = strcontains(local.init_script, "until freshclam; do")
+ error_message = "the first signatures must come from plain freshclam with the packaged config"
+ }
+}
+
run "another_vpc_needs_a_subnet" {
command = plan
override_data {