updates
ober
425c0ae994b02773a0db939279645381cf87abf1
--- a/data/anti-patterns.sexp +++ b/data/anti-patterns.sexp @@ -5908,4 +5908,41 @@ "jerboa_check_balance" "jerboa_compile_check" "jerboa_security_scan")) - ) + (("advice" + . + "Download official release artifacts exclusively from versioned SourceHut tag URLs. Reserve alternate base URLs only for explicit local test fixtures or separate fork tooling, never as an automatic fallback.") + ("avoid" + . + "Do not add a mirror or fallback download path to the official Jerboa release installer.") + ("id" . "official-release-installer-mirror-fallback") + ("kinds" "script" "security") + ("pattern" + . + "download_from_bases|JERBOA_RELEASE_BASE.*mirror") + ("severity" . "high") + ("tags" "release" "installer" "sourcehut" "artifact" + "supply-chain") + ("title" + . + "Official release installer falls back to non-official mirror") + ("tools" + "jerboa_security_scan" + "jerboa_anti_pattern_lookup")) + (("advice" + . + "For mutexes use the (std misc thread) set together via (prefix (only (std misc thread) make-mutex mutex-lock! mutex-unlock!) mt:) and call mt:make-mutex/mt:mutex-lock!/mt:mutex-unlock! (gerbil-mutex, mutually compatible). To block the main thread use (std misc thread) thread-sleep! in a loop. For filtering lists write an explicit recursive loop (or filter) instead of prelude keep.") + ("avoid" + . + "Mixing prelude make-mutex (Chez #<mutex>) with (std misc thread) mutex-lock! (gerbil-mutex) -> runtime error '#<mutex> is not of type gerbil-mutex'. Using prelude 'keep' to filter -> returned (#t #t) instead of kept elements. Using (sleep (make-time 'time-duration 0 1)) -> 'not a time record of type time-duration'.") + ("id" . "jerboa-prelude-keep-sleep-mutex-traps") + ("kinds" "jerboa" "scheme" "debugging") + ("pattern" + . + "(make-mutex)|(mutex-lock!)|(keep )|(sleep \\(make-time)") + ("severity" . "high") + ("tags" "jerboa" "prelude" "mutex" "keep" "sleep" "thread" + "stdlib") + ("title" + . + "Jerboa prelude traps: keep, sleep, and make-mutex mismatch") + ("tools" "jerboa_eval" "jerboa_module_exports"))) --- a/docs/release-artifacts.md +++ b/docs/release-artifacts.md @@ -48,26 +48,33 @@ and Chez NOTICE text alongside the executable. ## End-User Install -Do not pipe the network installer to a shell. Download the versioned installer -and detached signature, authenticate it against a Jerboa release key obtained -through an independent trusted channel, inspect it, and run the local file. -For example, after substituting the reviewed release identity and locally -provisioned allowed-signers file: +Do not pipe the network installer to a shell. A secure install has one +irreducible bootstrap step: obtain the Jerboa release public key and its signer +identity through a channel independent of the download (for example, a +maintainer's verified fingerprint). Save that information as an OpenSSH +allowed-signers file. The project must publish the exact file contents, +fingerprint, and identity before a release can claim to offer an end-user +authenticated install. + +Once you have that small local trust file, the Linux AMD64 install is: ```sh -version=v0.2.4 +version=vX.Y.Z # exact release tag base="https://git.sr.ht/~lisp/jerboa/refs/download/$version" +trust_file="$HOME/.config/jerboa/release.allowed_signers" +identity='jerboa-release@<publisher-domain>' + +mkdir -p "$(dirname "$trust_file")" +# Create $trust_file from the independently verified release-key information. curl --fail --proto '=https' --proto-redir '=https' \ -o install.sh "$base/install.sh" curl --fail --proto '=https' --proto-redir '=https' \ -o install.sh.sig "$base/install.sh.sig" ssh-keygen -Y verify \ - -f /path/to/independently-provisioned/jerboa-release.allowed_signers \ - -I '<reviewed-release-identity>' -n file -s install.sh.sig < install.sh + -f "$trust_file" -I "$identity" -n file -s install.sh.sig < install.sh chmod 0755 install.sh -JERBOA_RELEASE_ALLOWED_SIGNERS=/path/to/independently-provisioned/jerboa-release.allowed_signers \ -JERBOA_RELEASE_SIGNER_IDENTITY='<reviewed-release-identity>' \ - ./install.sh --version "$version" +./install.sh --version "$version" \ + --allowed-signers "$trust_file" --signer-identity "$identity" ``` The installer requires an exact version; mutable `latest` resolution is not an @@ -89,7 +96,10 @@ JERBOA_INSTALL_DIR="$HOME/bin" ./install.sh --version "$version" The canonical installer source remains `support/install.sh`. Release signing stages that reviewed file as a versioned release asset and signs both it and the release manifest. No official installation path is enabled until the project -release identity has been provisioned independently to the consumer. +release identity has been provisioned independently to the consumer. The +installer accepts `--allowed-signers` and `--signer-identity` so its required +trust inputs are visible in the command rather than hidden in environment +variables. ## Build Locally @@ -436,7 +446,8 @@ https://git.sr.ht/~lisp/jerboa/refs/download/<tag>/<artifact> ``` The allowed-signers file must be committed or provisioned by the consuming -project, not downloaded beside the artifact. Override `JERBOA_RELEASE_REPO`, -`JERBOA_RELEASE_ORIGIN`, or `JERBOA_RELEASE_BASE` for mirrors or forks. Override -`JERBOA_RELEASE_TARGET` only for tests or unusual hosts where `uname` detection -is not enough. +project, not downloaded beside the artifact. Override `JERBOA_RELEASE_REPO` or +`JERBOA_RELEASE_ORIGIN` only for a fork. `JERBOA_RELEASE_BASE` is reserved for +local test fixtures; official artifacts are downloaded only from SourceHut. +Override `JERBOA_RELEASE_TARGET` only for tests or unusual hosts where `uname` +detection is not enough. deleted file mode 100644 --- a/foo +++ /dev/null --- a/support/install.sh +++ b/support/install.sh @@ -11,6 +11,7 @@ umask 077 usage() { cat >&2 <<'EOF' Usage: install.sh --version VERSION [--bindir DIR | --prefix DIR] + [--allowed-signers FILE --signer-identity ID] Installs the base Jerboa toolchain from authenticated release artifacts. @@ -18,6 +19,10 @@ Options: --version VERSION Install an exact tag such as v0.2.4 (required) --bindir DIR Install commands directly into DIR --prefix DIR Install commands into DIR/bin + --allowed-signers FILE + Local OpenSSH allowed-signers trust file + --signer-identity ID + Expected signer identity in the trust file -h, --help Show this help Required trust configuration: @@ -88,24 +93,6 @@ download() { fi } -# Try a list of base URLs until one serves the requested file. Sets $dst to -# the downloaded bytes and echoes the base that worked to stdout. Used to -# fall back from SourceHut (blocked by the go-away proxy for many anonymous -# clients) to a mirror without weakening signature verification. -download_from_bases() { - relpath=$1 - dst=$2 - shift 2 - for base in "$@"; do - url="${base%/}/${relpath}" - if download "$url" "$dst" 2>/dev/null; then - printf '%s\n' "$base" - return 0 - fi - done - return 1 -} - choose_tmp_parent() { if [ -n "${TMPDIR:-}" ] && [ -d "$TMPDIR" ] && [ -w "$TMPDIR" ]; then printf '%s\n' "$TMPDIR" @@ -146,12 +133,16 @@ install_links() { version=${JERBOA_VERSION:-} bindir=${JERBOA_INSTALL_DIR:-} prefix=${PREFIX:-} +allowed_signers=${JERBOA_RELEASE_ALLOWED_SIGNERS:-} +signer_identity=${JERBOA_RELEASE_SIGNER_IDENTITY:-} while [ "$#" -gt 0 ]; do case "$1" in --version) need_value "$1" "${2:-}"; version=$2; shift 2 ;; --bindir) need_value "$1" "${2:-}"; bindir=$2; shift 2 ;; --prefix) need_value "$1" "${2:-}"; prefix=$2; shift 2 ;; + --allowed-signers) need_value "$1" "${2:-}"; allowed_signers=$2; shift 2 ;; + --signer-identity) need_value "$1" "${2:-}"; signer_identity=$2; shift 2 ;; -h|--help) usage; exit 0 ;; *) error "unknown option: $1" ;; esac @@ -161,8 +152,6 @@ done printf '%s\n' "$version" | grep -Eq '^v[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*$' || \ error "version must be an exact tag like v0.2.4" -allowed_signers=${JERBOA_RELEASE_ALLOWED_SIGNERS:-} -signer_identity=${JERBOA_RELEASE_SIGNER_IDENTITY:-} ssh_namespace=${JERBOA_RELEASE_SSH_NAMESPACE:-file} [ -n "$allowed_signers" ] || error "JERBOA_RELEASE_ALLOWED_SIGNERS is required" [ -f "$allowed_signers" ] || error "allowed-signers file is missing: $allowed_signers" @@ -192,9 +181,8 @@ esac file="jerboa-${version}-${target}.tar.gz" -# Release base URL. SourceHut git-tag artifacts are the only release download -# channel. Override it with JERBOA_RELEASE_BASE when using an independently -# operated mirror. +# Release base URL. SourceHut git-tag artifacts are the only official release +# download channel. JERBOA_RELEASE_BASE exists solely for local test fixtures. sourcehut_base="${repo_url}/refs/download/${version}" release_bases=${JERBOA_RELEASE_BASE:-$sourcehut_base} @@ -206,13 +194,10 @@ signature="$manifest.sig" archive="$tmp/$file" echo "Authenticating Jerboa $version release manifest" -manifest_base=$(download_from_bases release-manifest.sha256 "$manifest" $release_bases) \ - || error "could not download release-manifest.sha256 from any configured base" -if ! download_from_bases release-manifest.sha256.sig "$signature" $release_bases >/dev/null; then - # Some mirrors co-locate the .sig next to the manifest; try the matched base first - download "${manifest_base%/}/release-manifest.sha256.sig" "$signature" \ - || error "could not download release-manifest.sha256.sig" -fi +download "${release_bases%/}/release-manifest.sha256" "$manifest" \ + || error "could not download release-manifest.sha256" +download "${release_bases%/}/release-manifest.sha256.sig" "$signature" \ + || error "could not download release-manifest.sha256.sig" if ! ssh-keygen -Y verify -f "$allowed_signers" -I "$signer_identity" \ -n "$ssh_namespace" -s "$signature" < "$manifest" >/dev/null; then error "release manifest signature is invalid or from an untrusted signer" @@ -228,8 +213,8 @@ expected=$(awk -v wanted="$file" ' is_sha256 "$expected" || error "signed manifest contains an invalid SHA-256" echo "Downloading authenticated artifact" -download_from_bases "$file" "$archive" $release_bases >/dev/null \ - || error "could not download $file from any configured base" +download "${release_bases%/}/$file" "$archive" \ + || error "could not download $file" actual=$(sha256_file "$archive") [ "$actual" = "$expected" ] || error "archive SHA-256 does not match the signed manifest" --- a/tests/test-supply-chain-policy.sh +++ b/tests/test-supply-chain-policy.sh @@ -10,7 +10,7 @@ fail() { } must_contain() { - grep -Fq "$2" "$1" || fail "$1 is missing: $2" + grep -Fq -- "$2" "$1" || fail "$1 is missing: $2" } tmp=$(mktemp -d "${TMPDIR:-/tmp}/jerboa-supply-test.XXXXXX") @@ -118,6 +118,8 @@ fi # The installer requires a consumer-controlled trust root and never trusts a # same-origin checksum or extracts arbitrary archive paths. must_contain support/install.sh 'JERBOA_RELEASE_ALLOWED_SIGNERS is required' +must_contain support/install.sh '--allowed-signers FILE' +must_contain support/install.sh '--signer-identity ID' must_contain support/install.sh 'ssh-keygen -Y verify' must_contain support/install.sh 'an exact --version is required' must_contain support/install.sh 'tar -xOzf' @@ -169,12 +171,12 @@ ssh-keygen -Y sign -q -f "$tmp/release-key" -n file "$release/release-manifest.s printf 'jerboa-release@example.test %s\n' "$(cat "$tmp/release-key.pub")" > "$tmp/allowed-signers" JERBOA_INSTALL_ALLOW_FILE_URL=1 \ -JERBOA_RELEASE_ALLOWED_SIGNERS="$tmp/allowed-signers" \ -JERBOA_RELEASE_SIGNER_IDENTITY=jerboa-release@example.test \ JERBOA_RELEASE_BASE="file://$release" \ JERBOA_RELEASE_TARGET="$target" \ JERBOA_INSTALL_DIR="$tmp/bin" \ - support/install.sh --version "$version" > "$tmp/install.out" + support/install.sh --version "$version" \ + --allowed-signers "$tmp/allowed-signers" \ + --signer-identity jerboa-release@example.test > "$tmp/install.out" "$tmp/bin/jerboa" | grep -Fq authenticated-test-binary || fail 'authenticated binary was not installed' [ "$(readlink "$tmp/bin/jmcp")" = jerboa ] || fail 'multicall link was not installed'