Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions changelog.d/894-install-credential-file-channel.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
- **Install-time credentials now survive `apk add`.** The operator-chosen
admin password and private-WiFi key are delivered through root-only
staged secret files (`/tmp/tollgate-admin-password`,
`/tmp/tollgate-wifi-key`), consumed — unlinked on first read — by the
setup script: apk-tools on 25.12 does not hand the installer's
environment to package scripts, so the previously documented
`TOLLGATE_ADMIN_PASSWORD=… apk add …` flow silently degraded to a random
provisional credential the operator never chose (found end-to-end on the
vlab installer lane). Mode-600 is enforced with a `find -perm` gate —
the minimal busybox on real router images ships without `stat`, which
the first revision used and which refused every staging — and the
environment channel remains for manual re-runs. The installer-contract
lane (#892) asserts the whole contract: staged secret becomes root's
hash (sha256crypt-verified), nothing plaintext in the install output,
files consumed
([#894](https://github.com/OpenTollGate/tollgate-module-basic-go/pull/894)).
69 changes: 58 additions & 11 deletions packaging/files/etc/uci-defaults/99-tollgate-setup
Original file line number Diff line number Diff line change
Expand Up @@ -1257,17 +1257,55 @@ generate_admin_password() {

# The operator may CHOOSE the admin credential at install time instead of
# receiving a generated one they have to write down in the seconds it is on
# screen. This is the supported mechanism because uci-defaults run from the
# package manager with no controlling terminal: there is nowhere to hold a
# prompt, so the choice arrives in the environment.
#
# TOLLGATE_ADMIN_PASSWORD='…' apk add --allow-untrusted /tmp/tollgate-wrt.apk
#
# The value is taken as-is (no trimming — a deliberate leading/trailing space
# is the operator's business) and is NEVER echoed: it is already known to the
# operator, so printing it would only leak it into the install log.
# screen. uci-defaults run from the package manager with no controlling
# terminal, so there is nowhere to hold a prompt: the choice has to arrive
# through a side channel. There are two, because apk-tools on 25.12 does not
# hand the installer's environment to the package scripts (verified
# end-to-end on the vlab lane, #888):
#
# - the ROOT-ONLY STAGED FILE, which works through `apk add`:
#
# printf '%s' 'my-password' > /tmp/tollgate-admin-password
# printf '%s' 'my-wifi-key' > /tmp/tollgate-wifi-key
# chmod 600 /tmp/tollgate-admin-password /tmp/tollgate-wifi-key
# apk add --allow-untrusted /tmp/tollgate-wrt.apk
#
# Each file is CONSUMED — unlinked on first read — so a stale file can
# never re-apply an old password on a later boot, and the value never
# sits on a world-readable path (the operator's 600 is preserved; a file
# staged world-readable is refused).
#
# - the ENVIRONMENT, which works for a manual re-run of this script (and
# for installers that do preserve their environment):
#
# TOLLGATE_ADMIN_PASSWORD='…' /etc/uci-defaults/99-tollgate-setup
#
# The values are taken as-is (no trimming — a deliberate leading/trailing
# space is the operator's business) and are NEVER echoed: they are already
# known to the operator, so printing them would only leak them into the
# install log.
consume_install_secret() { # <path> — print a root-staged secret once, then unlink it
[ -r "$1" ] || return 0
# Mode gate via find -perm, not stat -c: the minimal busybox on real
# router images ships without the stat applet (verified on 25.12.5 x86),
# and an unreadable mode answer must never fall open.
if [ -z "$(find "$1" -maxdepth 0 -perm 0600 2>/dev/null)" ]; then
log "ERROR: staged install secret $1 must be mode 600 (root-only) — refusing to read it"
rm -f "$1"
return 0
fi
local value
value="$(head -n 1 "$1" 2>/dev/null)"
rm -f "$1"
[ -n "$value" ] && printf '%s' "$value"
}

operator_chosen_admin_password() {
printf '%s' "${TOLLGATE_ADMIN_PASSWORD:-}"
if [ -n "${TOLLGATE_ADMIN_PASSWORD:-}" ]; then
printf '%s' "$TOLLGATE_ADMIN_PASSWORD"
return 0
fi
consume_install_secret "${TOLLGATE_ADMIN_PASSWORD_FILE:-/tmp/tollgate-admin-password}"
}

# Marks a credential this package GENERATED rather than one the operator
Expand Down Expand Up @@ -2323,7 +2361,16 @@ setup_private_network() {
# Operator chose it (install-time prompt, B) — never generated-and-printed.
private_key="$TOLLGATE_PRIVATE_WIFI_PASSWORD"
log "Private WiFi key set from TOLLGATE_PRIVATE_WIFI_PASSWORD (not printed)"
elif [ -z "$private_key" ]; then
fi
if [ -z "$private_key" ]; then
# The apk-reliable channel, same contract as the admin password's
# (#888): consumed on read, mode-600 enforced, never echoed.
private_key="$(consume_install_secret "${TOLLGATE_WIFI_PASSWORD_FILE:-/tmp/tollgate-wifi-key}")"
if [ -n "$private_key" ]; then
log "Private WiFi key set from the staged install file (not printed)"
fi
fi
if [ -z "$private_key" ]; then
# Memorable password: 3 words + 2-digit number. Single
# /dev/urandom read, awk picks words — avoids forking per word.
# NOT printed: the owner sets their own in the board's first-run page
Expand Down