From 2e8e11a768b7ae39a1e56f24822ae45bd0dafcf7 Mon Sep 17 00:00:00 2001 From: Wes Morgan Date: Wed, 11 Nov 2026 10:36:02 -0700 Subject: [PATCH 1/3] Upgrade Leiningen to 2.13.1 Pin the 2.13.1 release tag to its commit, d109a01c ("Release 2.13.1"). Co-Authored-By: Claude Opus 5.5 --- src/docker_clojure/config.clj | 2 +- src/docker_clojure/dockerfile/lein.clj | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/docker_clojure/config.clj b/src/docker_clojure/config.clj index c20dc634..aa34bd22 100644 --- a/src/docker_clojure/config.clj +++ b/src/docker_clojure/config.clj @@ -116,7 +116,7 @@ :default :debian/bookworm}) (def build-tools - {"lein" "2.13.0" + {"lein" "2.13.1" "tools-deps" "1.12.6.1673"}) (def default-build-tool "tools-deps") diff --git a/src/docker_clojure/dockerfile/lein.clj b/src/docker_clojure/dockerfile/lein.clj index 51fa4996..6ce11767 100644 --- a/src/docker_clojure/dockerfile/lein.clj +++ b/src/docker_clojure/dockerfile/lein.clj @@ -35,7 +35,8 @@ ;; commit past us (belt-and-suspenders with `git verify-tag`). Requested by the ;; docker-library/official-images maintainers. (def release-commits - {"2.13.0" "d703e4802feb3e5c3fa9ae9f1874fb7a3a3e3030"}) + {"2.13.0" "d703e4802feb3e5c3fa9ae9f1874fb7a3a3e3030" + "2.13.1" "d109a01c2bc295c1ed218ed335c36481938b745b"}) (defn release-commit [version] (or (get release-commits version) From c0f2c1d05f0e073bb0071be798199006b94be5f1 Mon Sep 17 00:00:00 2001 From: Wes Morgan Date: Wed, 11 Nov 2026 10:36:03 -0700 Subject: [PATCH 2/3] Keep Leiningen's signing key in the repo Lein builds fetched the release signing key from keyserver.ubuntu.com and failed whenever it timed out, which happens often when building many images in parallel. Import a copy kept in resources/ instead, and still check it against the keyserver: fail with instructions if the keyserver's copy differs (e.g. the key was revoked, extended or gained a subkey), and carry on with the local copy if the keyserver can't be reached. Add `bb refresh-lein-key` to update the local copy from the keyserver. Co-Authored-By: Claude Opus 5.5 --- bb.edn | 2 + resources/lein-signing-key.asc | 51 ++++++++++++ resources/templates/lein.tmpl | 29 ++++++- src/docker_clojure/core.clj | 10 ++- src/docker_clojure/dockerfile.clj | 1 + src/docker_clojure/dockerfile/lein.clj | 17 ++-- src/docker_clojure/lein_key.clj | 104 ++++++++++++++++++++++++ test/docker_clojure/dockerfile_test.clj | 23 ++++++ test/docker_clojure/lein_key_test.clj | 36 ++++++++ 9 files changed, 266 insertions(+), 7 deletions(-) create mode 100644 resources/lein-signing-key.asc create mode 100644 src/docker_clojure/lein_key.clj create mode 100644 test/docker_clojure/lein_key_test.clj diff --git a/bb.edn b/bb.edn index 30e18e18..7fa9d607 100644 --- a/bb.edn +++ b/bb.edn @@ -12,6 +12,8 @@ manifest (apply dc/-main "manifest" *command-line-args*) build-images {:task (apply dc/-main "build-images" *command-line-args*)} changed-build-dirs (apply dc/-main "changed-build-dirs" *command-line-args*) + refresh-lein-key {:doc "Refresh resources/lein-signing-key.asc from the keyserver" + :task (dc/-main "refresh-lein-key")} test (clojure '-X:test) fmt-check {:doc "Check formatting with cljfmt" :task (fmt/check {:paths ["src" "test" "bb.edn" "deps.edn" "tests.edn"]})} diff --git a/resources/lein-signing-key.asc b/resources/lein-signing-key.asc new file mode 100644 index 00000000..43531e3b --- /dev/null +++ b/resources/lein-signing-key.asc @@ -0,0 +1,51 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +=/HwY +-----END PGP PUBLIC KEY BLOCK----- diff --git a/resources/templates/lein.tmpl b/resources/templates/lein.tmpl index e67fe478..eef8ee7d 100644 --- a/resources/templates/lein.tmpl +++ b/resources/templates/lein.tmpl @@ -3,15 +3,42 @@ ENV LEIN_INSTALL=/usr/local/bin/ WORKDIR /tmp +COPY {{gpg-key-file}} /tmp/{{gpg-key-file}} + # No standalone uberjar is published for this version, so build it from source. # Verify Leiningen's GPG-signed release tag and pin the expected commit, then # bootstrap leiningen-core's dependencies with Maven and run `lein uberjar`. +# +# The signing key comes from the copy in {{gpg-key-file}}. We also fetch it +# from keyserver.ubuntu.com: if the keyserver's copy differs (e.g. the key was +# revoked, extended or gained a subkey) the build fails so the change can be +# reviewed; if the keyserver can't be reached we carry on with the local copy. +# Only the fields that describe the key itself are compared, since gpg also +# records where and when each copy was imported. RUN set -eux; \ {% for dep in install-deps %}{{dep}} && \ {% endfor %}export GNUPGHOME="$(mktemp -d)" && \ export LEIN_ROOT=1 && \ mkdir -p $LEIN_INSTALL /usr/share/java /root/.lein && \ -gpg --batch --keyserver hkps://keyserver.ubuntu.com --recv-keys {{gpg-key}} && \ +key_summary() { gpg --batch --homedir "$1" --with-colons --list-keys {{gpg-key}} | awk -F: '/^(pub|sub|uid|fpr):/ { print $1, $2, $5, $6, $7, $8, $10, $12 }'; } && \ +gpg --batch --import /tmp/{{gpg-key-file}} && \ +[ "$(gpg --batch --with-colons --list-keys | awk -F: '$1 == "fpr" && prev == "pub" { print $10 } { prev = $1 }')" = "{{gpg-key}}" ] && \ +keyserver_home="$(mktemp -d)" && \ +if gpg --batch --homedir "$keyserver_home" --keyserver hkps://keyserver.ubuntu.com --recv-keys {{gpg-key}}; then \ + if [ "$(key_summary "$GNUPGHOME")" != "$(key_summary "$keyserver_home")" ]; then \ + printf '%s\n' \ + "ERROR: Leiningen's signing key {{gpg-key}} on keyserver.ubuntu.com no longer matches {{gpg-key-file}}." \ + "It may have been revoked, had its expiry changed, or gained a subkey or user ID. Review the change, then" \ + "refresh the local copy by running 'bb refresh-lein-key' in docker-clojure and regenerate the Dockerfiles." \ + "Local copy:" "$(key_summary "$GNUPGHOME")" \ + "Keyserver copy:" "$(key_summary "$keyserver_home")" >&2; \ + exit 1; \ + fi; \ +else \ + echo "WARNING: couldn't fetch {{gpg-key}} from keyserver.ubuntu.com; using the local copy in {{gpg-key-file}}." >&2; \ +fi && \ +gpgconf --homedir "$keyserver_home" --kill all && \ +rm -rf "$keyserver_home" /tmp/{{gpg-key-file}} && \ git clone --depth 1 --branch $LEIN_VERSION https://codeberg.org/leiningen/leiningen.git && \ cd leiningen && \ git verify-tag $LEIN_VERSION && \ diff --git a/src/docker_clojure/core.clj b/src/docker_clojure/core.clj index 9a0a2df4..8cc2ba77 100644 --- a/src/docker_clojure/core.clj +++ b/src/docker_clojure/core.clj @@ -8,6 +8,7 @@ [docker-clojure.config :as cfg] [docker-clojure.docker :as docker] [docker-clojure.dockerfile :as df] + [docker-clojure.lein-key :as lein-key] [docker-clojure.log :refer [log] :as logger] [docker-clojure.manifest :as manifest] [docker-clojure.util :refer [get-or-default]] @@ -144,6 +145,10 @@ (do (log "ERROR diffing target against" (str base ":") err) ::failed)))) +(defn cmd-refresh-lein-key [_] + (when-not (lein-key/refresh!) + ::failed)) + (def cmd-table [{:cmds ["clean"] :fn cmd-clean @@ -170,7 +175,10 @@ :fn cmd-changed-build-dirs :doc "List the build dirs worth building to verify staged target changes" :args->opts [:base] - :spec {:base {:desc "Git ref to compare staged target against (default: HEAD)"}}}]) + :spec {:base {:desc "Git ref to compare staged target against (default: HEAD)"}}} + {:cmds ["refresh-lein-key"] + :fn cmd-refresh-lein-key + :doc "Refresh the local copy of Leiningen's signing key from the keyserver"}]) (defn -main "Entrypoint for CLI." diff --git a/src/docker_clojure/dockerfile.clj b/src/docker_clojure/dockerfile.clj index e5260a18..99a6045c 100644 --- a/src/docker_clojure/dockerfile.clj +++ b/src/docker_clojure/dockerfile.clj @@ -17,6 +17,7 @@ build-tool)])) (defn all-prereqs [dir variant] + (lein/prereqs dir variant) (tools-deps/prereqs dir variant)) (defn copy-java? diff --git a/src/docker_clojure/dockerfile/lein.clj b/src/docker_clojure/dockerfile/lein.clj index 6ce11767..f644d8a9 100644 --- a/src/docker_clojure/dockerfile/lein.clj +++ b/src/docker_clojure/dockerfile/lein.clj @@ -1,9 +1,18 @@ (ns docker-clojure.dockerfile.lein (:require [docker-clojure.dockerfile.shared - :refer [install-distro-deps render-template + :refer [copy-resource-file! install-distro-deps render-template uninstall-distro-build-deps]])) -(defn prereqs [_ _] nil) +;; Leiningen release tags are signed with this key (Phil Hagelberg). +(def ^:const signing-key "9D13D9426A0814B3373CF5E3D8A8243577A7859F") + +;; Local copy of `signing-key` (exported with `--export-options export-minimal`) +;; so builds don't depend on the keyserver being up. Builds still check it +;; against keyserver.ubuntu.com and fail if the two have diverged. +(def ^:const signing-key-file "lein-signing-key.asc") + +(defn prereqs [dir _variant] + (copy-resource-file! dir signing-key-file)) ;; Leiningen no longer publishes a standalone uberjar, so we build it from ;; source: clone the GPG-signed release tag, bootstrap leiningen-core's deps @@ -27,9 +36,6 @@ ;; first use. (def ^:const bundled-clojure-version "1.12.5") -;; Leiningen release tags are signed with this key (Phil Hagelberg). -(def ^:const signing-key "9D13D9426A0814B3373CF5E3D8A8243577A7859F") - ;; The commit each release tag is expected to point at. After cloning we assert ;; HEAD matches, so a moved or re-pointed upstream tag can't slip a different ;; commit past us (belt-and-suspenders with `git verify-tag`). Requested by the @@ -51,6 +57,7 @@ :lein-commit (release-commit build-tool-version) :clojure-version bundled-clojure-version :gpg-key signing-key + :gpg-key-file signing-key-file :install-deps (install-deps variant) :uninstall-deps (uninstall-build-deps variant)})) diff --git a/src/docker_clojure/lein_key.clj b/src/docker_clojure/lein_key.clj new file mode 100644 index 00000000..8f47f291 --- /dev/null +++ b/src/docker_clojure/lein_key.clj @@ -0,0 +1,104 @@ +(ns docker-clojure.lein-key + "Refreshes the local copy of Leiningen's release signing key that lein builds + import (and check against the keyserver) instead of relying on the keyserver + being up." + (:require [clojure.java.io :as io] + [clojure.java.shell :refer [sh]] + [clojure.string :as str] + [docker-clojure.dockerfile.lein :as lein]) + (:import (java.nio.file Files) + (java.nio.file.attribute FileAttribute PosixFilePermissions))) + +(def keyserver "hkps://keyserver.ubuntu.com") + +(def key-file (io/file "resources" lein/signing-key-file)) + +(def ^:private fetch-attempts 3) + +(defn primary-fingerprints + "Returns the fingerprints of the primary keys in gpg `--with-colons` output. + Each `pub` record is followed by its `fpr` record; subkeys' `fpr` records + follow `sub` records instead." + [colons] + (->> (str/split-lines colons) + (map #(str/split % #":" -1)) + (partition 2 1) + (keep (fn [[prev cur]] + (when (and (= "pub" (first prev)) (= "fpr" (first cur))) + (nth cur 9)))))) + +(defn summary + "The parts of gpg `--with-colons` output that describe the key itself, the + same fields lein builds compare against the keyserver." + [colons] + (->> (str/split-lines colons) + (map #(str/split % #":" -1)) + (filter #(#{"pub" "sub" "uid" "fpr"} (first %))) + (map #(str/join " " (map (fn [i] (get % i "")) [0 1 4 5 6 7 9 11]))) + (str/join "\n"))) + +(defn- gpg [home & args] + (apply sh "gpg" "--batch" "--homedir" (str home) args)) + +(defn- temp-home [] + (Files/createTempDirectory + "lein-key" (into-array FileAttribute + [(PosixFilePermissions/asFileAttribute + (PosixFilePermissions/fromString "rwx------"))]))) + +(defn- delete-tree [dir] + (doseq [f (reverse (file-seq (io/file (str dir))))] + (io/delete-file f true))) + +(defn- fetch! + "Fetches the signing key into `home`, retrying since the keyserver is flaky. + Returns nil on success or the last error." + [home] + (loop [attempt 1] + (let [{:keys [exit err]} (gpg home "--keyserver" keyserver + "--recv-keys" lein/signing-key)] + (cond + (zero? exit) nil + (< attempt fetch-attempts) (do (println "Fetch attempt" attempt + "failed, retrying:" (str/trim err)) + (Thread/sleep (* 2000 attempt)) + (recur (inc attempt))) + :else err)))) + +(defn refresh! + "Fetches Leiningen's signing key from the keyserver, checks it's the pinned + key, and writes it (minus third-party signatures) to `key-file`. Returns true + on success." + [] + (let [home (temp-home)] + (try + (println "Fetching" lein/signing-key "from" keyserver "...") + (if-let [err (fetch! home)] + (do (println "ERROR fetching key:" (str/trim err)) + false) + (let [colons (:out (gpg home "--with-colons" "--list-keys")) + primary (primary-fingerprints colons)] + (if (not= [lein/signing-key] primary) + (do (println "ERROR: expected only key" lein/signing-key + "from the keyserver but got" (pr-str primary)) + false) + (let [{:keys [exit out err]} + (gpg home "--armor" "--export-options" "export-minimal" + "--export" lein/signing-key) + old (when (.exists key-file) (slurp key-file))] + (if-not (and (zero? exit) (str/includes? out "BEGIN PGP")) + (do (println "ERROR exporting key:" (str/trim err)) + false) + (do (spit key-file out) + (println (if (= old out) + (str key-file " is already up to date.") + (str "Updated " key-file "."))) + (println (str "\n" (summary colons) "\n")) + (when (not= old out) + (println "Review the change (e.g. a revocation or new" + "subkey) before committing it, then run" + "`bb dockerfiles`.")) + true)))))) + (finally + (sh "gpgconf" "--homedir" (str home) "--kill" "all") + (delete-tree home))))) diff --git a/test/docker_clojure/dockerfile_test.clj b/test/docker_clojure/dockerfile_test.clj index 44390108..6dd5a11d 100644 --- a/test/docker_clojure/dockerfile_test.clj +++ b/test/docker_clojure/dockerfile_test.clj @@ -12,6 +12,29 @@ (build-dir {:base-image-tag "openjdk:11-alpine" :build-tool "lein"}))))) +(deftest lein-signing-key-test + (testing "lein prereqs put the signing key in the build dir" + (let [dir (doto (java.io.File/createTempFile "lein-prereqs" "") + (.delete) + (.mkdirs)) + key (java.io.File. dir lein/signing-key-file)] + (try + (lein/prereqs (str dir) {}) + (is (str/starts-with? (slurp key) + "-----BEGIN PGP PUBLIC KEY BLOCK-----")) + (finally + (.delete key) + (.delete dir))))) + (testing "lein Dockerfile imports the local key and checks the keyserver" + (let [install (lein/install cfg/installer-hashes + {:distro :debian/bookworm + :build-tool-version "2.13.1"})] + (is (str/includes? install (str "COPY " lein/signing-key-file))) + (is (str/includes? install (str "gpg --batch --import /tmp/" + lein/signing-key-file))) + (is (str/includes? install (str "--recv-keys " lein/signing-key))) + (is (str/includes? install "bb refresh-lein-key"))))) + (deftest contents-test (testing "includes 'FROM base-image'" (is (str/includes? (contents cfg/installer-hashes diff --git a/test/docker_clojure/lein_key_test.clj b/test/docker_clojure/lein_key_test.clj new file mode 100644 index 00000000..5c273ab0 --- /dev/null +++ b/test/docker_clojure/lein_key_test.clj @@ -0,0 +1,36 @@ +(ns docker-clojure.lein-key-test + (:require [clojure.string :as str] + [clojure.test :refer [deftest is testing]] + [docker-clojure.lein-key :as lein-key])) + +(def colons + (str/join + "\n" + ["tru::1:1762870000:0:3:1:5" + "pub:-:4096:1:D8A8243577A7859F:1684357211:::-:::scESC::::::23::0:" + "fpr:::::::::9D13D9426A0814B3373CF5E3D8A8243577A7859F:" + "uid:-::::1684357211::972BED54DF6E1907FE129BFA8E51BA51411F22ED::Phil Hagelberg ::::::::::0:" + "sub:-:4096:1:4F1856267D44B221:1684357211::::::e::::::23:" + "fpr:::::::::B35D39C992B539C8EF362D9D4F1856267D44B221:"])) + +(deftest primary-fingerprints-test + (testing "returns primary key fingerprints but not subkey ones" + (is (= ["9D13D9426A0814B3373CF5E3D8A8243577A7859F"] + (lein-key/primary-fingerprints colons)))) + (testing "returns every primary key" + (is (= ["AAAA" "BBBB"] + (lein-key/primary-fingerprints + (str/join "\n" ["pub:-:::::" "fpr:::::::::AAAA:" + "sub:-:::::" "fpr:::::::::CCCC:" + "pub:-:::::" "fpr:::::::::BBBB:"])))))) + +(deftest summary-test + (testing "keeps the key-describing fields and drops import metadata" + (is (= (str/join + "\n" + ["pub - D8A8243577A7859F 1684357211 scESC" + "fpr 9D13D9426A0814B3373CF5E3D8A8243577A7859F " + "uid - 1684357211 972BED54DF6E1907FE129BFA8E51BA51411F22ED Phil Hagelberg " + "sub - 4F1856267D44B221 1684357211 e" + "fpr B35D39C992B539C8EF362D9D4F1856267D44B221 "]) + (lein-key/summary colons))))) From 81de2a4ab4c8b54c742cab1d06f34a8327d4280b Mon Sep 17 00:00:00 2001 From: Wes Morgan Date: Wed, 11 Nov 2026 10:36:03 -0700 Subject: [PATCH 3/3] Abbreviate smoke test commands in build logs The lein routing check passes its whole script and Clojure form as arguments, so logging each command printed ~25 lines per image. Log multi-line args as a line count and truncate long ones. Co-Authored-By: Claude Opus 5.5 --- src/docker_clojure/docker.clj | 12 +++++++++++- test/docker_clojure/docker_test.clj | 9 +++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/docker_clojure/docker.clj b/src/docker_clojure/docker.clj index 581b5c67..c49709ac 100644 --- a/src/docker_clojure/docker.clj +++ b/src/docker_clojure/docker.clj @@ -55,6 +55,16 @@ (and (= "lein" build-tool) (df/entrypoint? variant)) (conj ["sh" "-c" (lein-routing-check) "sh" (pr-str print-lein-commands)])))) +(defn abbreviate-arg + "Shortens a command arg for logging: multi-line scripts become a line count + and long args are truncated." + [arg] + (let [lines (str/split-lines arg)] + (cond + (< 1 (count lines)) (str "<" (count lines) "-line script>") + (< 60 (count arg)) (str (subs arg 0 40) "…") + :else arg))) + (defn smoke-test "Runs the variant's smoke test commands in `image-tag`. Returns true if they all succeed." @@ -63,7 +73,7 @@ (let [run-cmd (remove nil? (concat ["docker" "run" "--rm" platform-flag image-tag] cmd)) - _ (apply log "Running" run-cmd) + _ (apply log "Running" (map abbreviate-arg run-cmd)) {:keys [out err exit]} (apply sh run-cmd)] (if (zero? exit) true diff --git a/test/docker_clojure/docker_test.clj b/test/docker_clojure/docker_test.clj index 69af0280..aa8ae970 100644 --- a/test/docker_clojure/docker_test.clj +++ b/test/docker_clojure/docker_test.clj @@ -70,6 +70,15 @@ :debian/bookworm "tools-deps-bookworm" :debian-slim/bookworm-slim "tools-deps-bookworm-slim"))) +(deftest abbreviate-arg-test + (testing "leaves short args alone" + (is (= "version" (abbreviate-arg "version")))) + (testing "replaces multi-line scripts with their line count" + (is (= "<3-line script>" (abbreviate-arg "set -e\necho hi\nexit 0")))) + (testing "truncates long args" + (is (= (str (apply str (repeat 40 "x")) "…") + (abbreviate-arg (apply str (repeat 61 "x"))))))) + (deftest smoke-test-cmds-test (testing "every variant checks its build tools start" (are [build-tool jdk-version cmds]