R-1300: SECURITY — cloudflared 2026.10.3 on main (same change as tag v0.345.1); build.sh may push from the pushed tag v<VERSION>
gates / gates (push) Successful in 1m19s
gates / gates (push) Successful in 1m19s
infra.CloudflaredImage 2026.9.3 -> 2026.10.3 (Cloudflare's eMudhra notice, 2026-10-20) and TestEnsureCloudflared_ANewImageRecreatesTheTunnel (red-proved). build.sh: a --push may build from the tag v<VERSION> as the server has it, pointing at HEAD (operator's yes 2026-10-26); four new cases in test_build_push_once.sh, red-proved both ways. MinAgent 0.158.1 unchanged.
This commit is contained in:
+19
-6
@@ -153,14 +153,27 @@ if [[ "${ACTION}" == "--push" || "${ACTION}" == "--multiarch" ]]; then
|
||||
fi
|
||||
head_sha=$(cd "${REPO_DIR}" && git rev-parse HEAD 2>/dev/null || echo unknown)
|
||||
main_sha=$(cd "${REPO_DIR}" && git rev-parse origin/main 2>/dev/null || echo unknown)
|
||||
# The one other source that can be fetched again: the tag v<VERSION> AS THE SERVER HAS IT, pointing at HEAD. That
|
||||
# is a patch release built off a released version while main holds unreleased work (v0.345.1, R-1300, cloudflared's
|
||||
# security bump, 2026-10-26 — operator's yes in that session). Only the tag that names THIS version counts; a tag
|
||||
# only on this clone, a tag on another commit, or a server that cannot be asked is a refusal. Pinned by
|
||||
# scripts/test_build_push_once.sh („tag" cases).
|
||||
if [[ "${head_sha}" != "${main_sha}" ]]; then
|
||||
error "${REPO_DIR} is not at origin/main — refusing to push."
|
||||
error " HEAD ${head_sha}"
|
||||
error " origin/main ${main_sha}"
|
||||
error "An unpushed change does not exist; an image built from one cannot be fetched again."
|
||||
exit 1
|
||||
tag_sha=$(cd "${REPO_DIR}" && GIT_TERMINAL_PROMPT=0 git ls-remote origin "refs/tags/v${VERSION}" "refs/tags/v${VERSION}^{}" 2>/dev/null \
|
||||
| awk '$2 ~ /\^\{\}$/ {peeled=$1} $2 !~ /\^\{\}$/ {plain=$1} END {print (peeled != "" ? peeled : plain)}') || tag_sha="" # no origin / unreachable: the error below, not set -e
|
||||
if [[ -n "${tag_sha}" && "${tag_sha}" == "${head_sha}" ]]; then
|
||||
info "Source tree is clean and at the pushed tag v${VERSION} (not origin/main)."
|
||||
else
|
||||
error "${REPO_DIR} is not at origin/main, nor at a pushed tag v${VERSION} — refusing to push."
|
||||
error " HEAD ${head_sha}"
|
||||
error " origin/main ${main_sha}"
|
||||
error " v${VERSION} on origin ${tag_sha:-<none>}"
|
||||
error "An unpushed change does not exist; an image built from one cannot be fetched again."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
info "Source tree is clean and at origin/main."
|
||||
fi
|
||||
info "Source tree is clean and at origin/main."
|
||||
else
|
||||
error "${REPO_DIR} is not a git checkout — refusing to push a build whose source cannot be identified."
|
||||
exit 1
|
||||
|
||||
@@ -20,10 +20,12 @@ import (
|
||||
)
|
||||
|
||||
// Pinned image tags — NEVER ":latest" (a floating tag breaks reproducible golden bakes and lets the
|
||||
// deployed version drift). Verified to resolve on Docker Hub before baking.
|
||||
// deployed version drift). Verified to resolve on Docker Hub before baking. cloudflared follows Cloudflare's security
|
||||
// notices (felhom.eu `01` „Tunnel placement"): 2026.10.3 since the notice of 2026-10-20 — before 2026.10.2 the image
|
||||
// trusts eight eMudhra (emSign) intermediates with weak keys.
|
||||
const (
|
||||
TraefikImage = "traefik:v3.7.13"
|
||||
CloudflaredImage = "cloudflare/cloudflared:2026.9.3"
|
||||
CloudflaredImage = "cloudflare/cloudflared:2026.10.3"
|
||||
FileBrowserImage = "gtstef/filebrowser:1.5.6-stable"
|
||||
// FileBrowserImportMount is the in-container mount point NAME for the canonical drop-zone
|
||||
// (R-75): the bind lands at /srv/<this>. ASCII and space-free on purpose — it appears in a
|
||||
|
||||
@@ -326,3 +326,33 @@ func TestEnsureForwardedHeaders_R1033_RewritesAnOldFile(t *testing.T) {
|
||||
t.Fatal("the rewritten file must carry the https-only redirect")
|
||||
}
|
||||
}
|
||||
|
||||
// A controller update that changes only cloudflared's IMAGE recreates the running container on the new image (the
|
||||
// 2026.10.3 security bump rides this: no other step moves a household's tunnel). The control: the same render again
|
||||
// is a no-op.
|
||||
func TestEnsureCloudflared_ANewImageRecreatesTheTunnel(t *testing.T) {
|
||||
state := stubDocker(t)
|
||||
touch(t, filepath.Join(state, "running-cloudflared"), "")
|
||||
m, calls := tunnelTestManager(t, "")
|
||||
dir := t.TempDir()
|
||||
cur, _ := infra.RenderCloudflared(infra.CloudflaredData{CFTunnelToken: "tok-test", Tunnel: true})
|
||||
old := strings.Replace(cur["docker-compose.yml"].Content, "image: "+infra.CloudflaredImage, "image: cloudflare/cloudflared:2026.9.3", 1)
|
||||
if old == cur["docker-compose.yml"].Content {
|
||||
t.Fatal("the render has no `image:` line to age — the test would prove nothing")
|
||||
}
|
||||
touch(t, filepath.Join(dir, "docker-compose.yml"), old)
|
||||
|
||||
if err := m.ensureCloudflared(dir, true); err != nil {
|
||||
t.Fatalf("ensureCloudflared: %v", err)
|
||||
}
|
||||
cmp, _ := os.ReadFile(filepath.Join(dir, "docker-compose.yml"))
|
||||
if !strings.Contains(string(cmp), "image: "+infra.CloudflaredImage) {
|
||||
t.Fatalf("the compose must carry %s:\n%s", infra.CloudflaredImage, cmp)
|
||||
}
|
||||
if len(*calls) != 1 || (*calls)[0].args != "up -d" {
|
||||
t.Fatalf("want one `up -d` (compose recreates on the new image), got %+v", *calls)
|
||||
}
|
||||
if err := m.ensureCloudflared(dir, true); err != nil || len(*calls) != 1 {
|
||||
t.Fatalf("an unchanged image must not recreate the tunnel again; err %v calls %+v", err, *calls)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,6 +141,28 @@ run_build_in absent "$CLEAN_REPO" 0.999.0 --push
|
||||
check "ahead of origin/main -> refuses" 1 "not at origin/main" "FAKE-DOCKER-REACHED"
|
||||
rm -rf "$CLEAN_REPO"
|
||||
|
||||
# ── OR AT THE PUSHED TAG v<VERSION> (v0.345.1, 2026-10-26) ───────────────────────────────────────
|
||||
# A patch release off a released commit while main holds unreleased work. The tag must be on the SERVER, name THIS version,
|
||||
# and point at HEAD. RED-PROOF: with the tag branch accepting any tag the three refusals fail; with it removed the
|
||||
# first case fails.
|
||||
TAG_REPO="$(scratch_repo)"; TAG_ORIGIN="$(mktemp -d)"
|
||||
git init -q --bare "$TAG_ORIGIN" 2>/dev/null; git -C "$TAG_REPO" remote add origin "$TAG_ORIGIN"
|
||||
echo y > "$TAG_REPO/controller/f2"
|
||||
git -C "$TAG_REPO" add -A >/dev/null; git -C "$TAG_REPO" commit -qm fix >/dev/null
|
||||
git -C "$TAG_REPO" tag -a v0.999.1 -m t; git -C "$TAG_REPO" push -q origin v0.999.1 2>/dev/null
|
||||
run_build_in absent "$TAG_REPO" 0.999.1 --push
|
||||
check "at the pushed tag v<VERSION> -> proceeds" "$rc" "at the pushed tag v0.999.1" "refusing to push"
|
||||
run_build_in absent "$TAG_REPO" 0.999.2 --push
|
||||
check "a pushed tag of ANOTHER version -> refuses" 1 "nor at a pushed tag v0.999.2" "FAKE-DOCKER-REACHED"
|
||||
git -C "$TAG_REPO" tag v0.999.3
|
||||
run_build_in absent "$TAG_REPO" 0.999.3 --push
|
||||
check "a tag only on this clone -> refuses" 1 "nor at a pushed tag v0.999.3" "FAKE-DOCKER-REACHED"
|
||||
echo z > "$TAG_REPO/controller/f3"
|
||||
git -C "$TAG_REPO" add -A >/dev/null; git -C "$TAG_REPO" commit -qm later >/dev/null
|
||||
run_build_in absent "$TAG_REPO" 0.999.1 --push
|
||||
check "the pushed tag on another commit -> refuses" 1 "nor at a pushed tag v0.999.1" "FAKE-DOCKER-REACHED"
|
||||
rm -rf "$TAG_REPO" "$TAG_ORIGIN"
|
||||
|
||||
NOT_A_REPO="$(mktemp -d)"; mkdir -p "$NOT_A_REPO/controller"
|
||||
run_build_in absent "$NOT_A_REPO" 0.999.0 --push
|
||||
check "not a git checkout -> refuses" 1 "not a git checkout" "FAKE-DOCKER-REACHED"
|
||||
|
||||
Reference in New Issue
Block a user