fix: install bws, isolate the private config, keep credentials out of logs
Three defects the private tier could not survive a fresh machine with. bws was never installed. The private/bws-secrets row promised seven API keys and shipped no way to fetch them: dotsecrets shells out to `bws secret get`, and nothing put that binary on the box. Added as a tarball channel with checksum verification, musl rather than gnu so it does not pin a glibc newer than an older LTS carries, plus core/unzip as a real @needs dependency since the release is a .zip and 24.04 minimal has no unzip. The releases API needs filtering by tag prefix: sdk-sm is a monorepo and `latest` usually points at a python SDK, not bws. Both tiers rendered their config to ~/.config/chezmoi/chezmoi.toml, so re-running the public installer overwrote the private config and took its seven promptStringOnce answers with it. Silently -- the templates degrade politely when data is missing, so the symptom was `git commit` not knowing who you are, days later. The private tier now renders to private.toml and the cmp alias carries the matching -c. `run` echoes its argv to stderr, and the private init argv ends in https://user:TOKEN@host -- into scrollback, any `dotup 2>log`, and any agent transcript. Traced through redact_url instead, in both the live and dry-run branches. The clone is also chmod -R go-rwx afterwards: it lands at the caller umask, and .git/config stores that same credential URL. Tests: 90 passing, 9 new covering all three.
This commit is contained in:
@@ -375,6 +375,10 @@ find_tool() {
|
||||
# empty, so a channel with nothing in it prints no header.
|
||||
norm() { printf '%s\n' "$*" | tr ' ' '\n' | grep . | tr '\n' ' ' | sed 's/ $//'; }
|
||||
|
||||
# Strip userinfo from a URL for tracing: https://user:token@host/p -> https://host/p
|
||||
# Used wherever a URL that may carry a credential is about to be printed.
|
||||
redact_url() { printf '%s\n' "$1" | sed 's#://[^/@]*@#://<redacted>@#'; }
|
||||
|
||||
run() {
|
||||
if [ "$DRYRUN" -eq 1 ]; then printf ' + %s\n' "$*"; return 0; fi
|
||||
printf '%s + %s%s\n' "$DIM" "$*" "$R" >&2
|
||||
@@ -570,6 +574,59 @@ install_bespoke() {
|
||||
run_sh "${SUDO:+$SUDO }rm -rf /usr/local/go && ${SUDO:+$SUDO }tar -xzf /tmp/go.tgz -C /usr/local" \
|
||||
|| { note_fail "$key" "tarball extract failed"; continue; }
|
||||
run_sh "rm -f /tmp/go.tgz" ;;
|
||||
private/bws-secrets)
|
||||
# The Bitwarden Secrets Manager CLI. Without it the private tier
|
||||
# fetches nothing: `dotsecrets` shells out to `bws secret get`, so
|
||||
# this row promising seven API keys and not installing the binary
|
||||
# was a promise it could not keep.
|
||||
#
|
||||
# User-level, ~/.local/bin, no sudo. It is a single static binary
|
||||
# and the private tier is per-user by definition.
|
||||
if have bws && [ "$DRYRUN" -eq 0 ]; then
|
||||
say " bws $(bws --version 2>/dev/null | awk '{print $2}') already present — leaving it"
|
||||
continue
|
||||
fi
|
||||
# musl, not gnu: the gnu build pins a glibc newer than some LTS
|
||||
# images carry, and this has to work on whatever a fresh VM is.
|
||||
case $(uname -s) in
|
||||
Darwin) t=macos-universal ;;
|
||||
*) case $(uname -m) in
|
||||
x86_64|amd64) t=x86_64-unknown-linux-musl ;;
|
||||
aarch64|arm64) t=aarch64-unknown-linux-musl ;;
|
||||
*) note_fail "$key" "no bws build for $(uname -m)"; continue ;;
|
||||
esac ;;
|
||||
esac
|
||||
if [ "$DRYRUN" -eq 1 ]; then v=2.X.Y
|
||||
else
|
||||
# Releases are tagged per-component in this monorepo, so the
|
||||
# `latest` endpoint points at whatever shipped last -- often a
|
||||
# python SDK, not bws. Filter by tag prefix instead.
|
||||
v=$(curl -fsSL 'https://api.github.com/repos/bitwarden/sdk-sm/releases?per_page=40' 2>/dev/null \
|
||||
| sed -n 's/.*"tag_name": *"bws-v\([0-9.]*\)".*/\1/p' | head -1)
|
||||
fi
|
||||
[ -n "$v" ] || { note_fail "$key" "could not resolve the current bws version"; continue; }
|
||||
b=https://github.com/bitwarden/sdk-sm/releases/download/bws-v$v
|
||||
run_sh "curl -fsSL '$b/bws-$t-$v.zip' -o /tmp/bws.zip" \
|
||||
|| { note_fail "$key" "download failed"; continue; }
|
||||
# Bitwarden publishes checksums; a binary that is about to hold the
|
||||
# key to every other credential is worth verifying.
|
||||
if [ "$DRYRUN" -eq 0 ] && have sha256sum; then
|
||||
if curl -fsSL "$b/bws-sha256-checksums-$v.txt" -o /tmp/bws.sums 2>/dev/null; then
|
||||
want=$(awk -v f="bws-$t-$v.zip" '$2==f || $2=="*"f {print $1}' /tmp/bws.sums | head -1)
|
||||
got=$(sha256sum /tmp/bws.zip | awk '{print $1}')
|
||||
if [ -n "$want" ] && [ "$want" != "$got" ]; then
|
||||
note_fail "$key" "checksum mismatch — refusing to install"
|
||||
rm -f /tmp/bws.zip /tmp/bws.sums; continue
|
||||
fi
|
||||
[ -n "$want" ] && say " checksum verified"
|
||||
else
|
||||
warn " bws: checksums unavailable — installing unverified"
|
||||
fi
|
||||
rm -f /tmp/bws.sums
|
||||
fi
|
||||
run_sh "mkdir -p \"\$HOME/.local/bin\" && unzip -oq /tmp/bws.zip -d /tmp/bws.d && install -m 755 \"\$(find /tmp/bws.d -type f -name bws | head -1)\" \"\$HOME/.local/bin/bws\"" \
|
||||
|| { note_fail "$key" "extract failed"; rm -rf /tmp/bws.zip /tmp/bws.d; continue; }
|
||||
run_sh "rm -rf /tmp/bws.zip /tmp/bws.d" ;;
|
||||
core/chezmoi)
|
||||
# Circular by nature: dotup arrives *via* chezmoi. Present already
|
||||
# in every case that matters; here for the one where it is not.
|
||||
@@ -658,6 +715,22 @@ cmd_install() {
|
||||
PRIV_SRC=${DOTUP_PRIVATE_SRC:-${XDG_DATA_HOME:-$HOME/.local/share}/dotfiles-private}
|
||||
BWS_TOKEN=${DOTUP_BWS_TOKEN:-${XDG_CONFIG_HOME:-$HOME/.config}/bitwarden/bws-token}
|
||||
|
||||
# The private tier gets its OWN config file, and this is load-bearing.
|
||||
#
|
||||
# chezmoi renders .chezmoi.toml.tmpl to the config path, and without -c that is
|
||||
# ~/.config/chezmoi/chezmoi.toml for BOTH tiers. The private template asks seven
|
||||
# [data] questions once (promptStringOnce) -- name, email, signing key, four
|
||||
# gitea addresses. Re-running the PUBLIC installer afterwards rewrites that same
|
||||
# file, and since the public tier's rendered config carries no [data] block, the
|
||||
# seven answers are simply gone.
|
||||
#
|
||||
# The failure is silent, which is what makes it worth a separate file rather
|
||||
# than a warning: the private templates degrade politely when their data is
|
||||
# missing -- config.local emits a comment telling you to re-run init instead of
|
||||
# failing the apply -- so the first symptom is `git commit` not knowing who you
|
||||
# are, days later, with nothing connecting it to the install you ran.
|
||||
PRIV_CFG=${DOTUP_PRIVATE_CFG:-${XDG_CONFIG_HOME:-$HOME/.config}/chezmoi/private.toml}
|
||||
|
||||
cmd_private() {
|
||||
rows=$(selected_private)
|
||||
[ -n "$rows" ] || return 0
|
||||
@@ -720,8 +793,28 @@ cmd_private() {
|
||||
|
||||
if [ "$want_repo" -eq 1 ]; then
|
||||
if [ -n "$p_repo" ]; then
|
||||
run chezmoi init --apply --source "$PRIV_SRC" "$p_repo" \
|
||||
|| err "private repo init failed"
|
||||
# NOT `run`. It echoes its whole argv to stderr, and this argv ends
|
||||
# in https://user:TOKEN@host/... -- which would put a live git
|
||||
# credential into terminal scrollback, any `dotup 2>log`, and any
|
||||
# agent or CI transcript capturing the run. Trace a redacted form
|
||||
# and execute quietly.
|
||||
mkdir -p "$(dirname "$PRIV_CFG")"
|
||||
if [ "$DRYRUN" -eq 1 ]; then
|
||||
printf ' + chezmoi init --apply --source %s -c %s %s\n' \
|
||||
"$PRIV_SRC" "$PRIV_CFG" "$(redact_url "$p_repo")"
|
||||
else
|
||||
printf '%s + chezmoi init --apply --source %s -c %s %s%s\n' \
|
||||
"$DIM" "$PRIV_SRC" "$PRIV_CFG" "$(redact_url "$p_repo")" "$R" >&2
|
||||
chezmoi init --apply --source "$PRIV_SRC" -c "$PRIV_CFG" "$p_repo" \
|
||||
|| err "private repo init failed"
|
||||
fi
|
||||
|
||||
# The clone lands with the caller's umask, which on a stock Ubuntu
|
||||
# is 022 -- world-readable. This tree holds ssh config, accepted
|
||||
# keys and machine identity, and its .git/config stores the
|
||||
# credential-bearing remote URL in plain text. On a shared or
|
||||
# multi-user box that is readable by anyone with an account.
|
||||
[ ! -d "$PRIV_SRC" ] || chmod -R go-rwx "$PRIV_SRC" 2>/dev/null || :
|
||||
else err "the blob carried no PRIVATE_REPO_URL"; fi
|
||||
fi
|
||||
unset p_repo
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
@needs agents/specify-cli core/uv
|
||||
@needs core/mermaid-cli core/node
|
||||
@needs core/neovim core/imagemagick core/mermaid-cli
|
||||
@needs private/bws-secrets core/unzip
|
||||
#
|
||||
# npm names carry scopes that the plugin's short name does not. `npm i -g
|
||||
# rpiv-btw` installs somebody else's package.
|
||||
@@ -63,6 +64,7 @@ core mermaid-cli safe -npm -npm mmdc — nvim renders mermaid fences with it
|
||||
core btop safe btop btop
|
||||
core htop safe htop htop
|
||||
core ncdu safe ncdu ncdu
|
||||
core unzip safe unzip unzip the bws release ships as a .zip, and 24.04 minimal has no unzip
|
||||
core tree safe tree tree
|
||||
core cmake safe cmake cmake
|
||||
core ninja safe ninja-build ninja package name differs from binary
|
||||
@@ -116,4 +118,4 @@ virt libvirt invasive libvirt-daemon-system - daemon + group membership
|
||||
virt virt-manager invasive virt-manager -
|
||||
@private one password, typed after the install finishes
|
||||
private private-repo private - - ~/.local/share/dotfiles-private — agent config, ssh config
|
||||
private bws-secrets private - - 7 API keys into ~/.config/zsh/secrets.zsh
|
||||
private bws-secrets private -tarball -tarball installs bws, then 7 API keys into ~/.config/zsh/secrets.zsh
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 17 and column 49.
|
Reference in New Issue
Block a user