Skip to content

Commit 988281d

Browse files
committed
fix(dappnode): retry incomplete wallet provisioning
1 parent 1162575 commit 988281d

2 files changed

Lines changed: 84 additions & 5 deletions

File tree

dappnode/entrypoint.sh

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ CURRENT_STATE_DIR="${CURRENT_STATE_DIR:-$CONFIG_DIR/.interfold}"
1313
# Current Interfold releases resolve a relative `key_file: key` beside a discovered
1414
# /data/config.yaml to this path for the default node profile.
1515
PASSWORD_FILE="${PASSWORD_FILE:-$CURRENT_STATE_DIR/config/_default/key}"
16+
CREDENTIALS_READY_FILE="${CREDENTIALS_READY_FILE:-$(dirname "$PASSWORD_FILE")/credentials.provisioned}"
1617

1718
log() { printf '[%s] %s\n' "$(date '+%H:%M:%S')" "$1"; }
1819
fail() {
@@ -130,6 +131,43 @@ validate_persisted_password_file() {
130131
[ -r "$PASSWORD_FILE" ] || fail "persisted password file is not readable: $PASSWORD_FILE"
131132
}
132133

134+
validate_credentials_ready_file() {
135+
[ -f "$CREDENTIALS_READY_FILE" ] || fail "credential readiness marker is not a regular file: $CREDENTIALS_READY_FILE"
136+
[ ! -L "$CREDENTIALS_READY_FILE" ] || fail "credential readiness marker must not be a symbolic link: $CREDENTIALS_READY_FILE"
137+
chmod 400 "$CREDENTIALS_READY_FILE" || fail "could not restrict credential readiness marker permissions"
138+
}
139+
140+
mark_credentials_ready() {
141+
mkdir -p "$(dirname "$CREDENTIALS_READY_FILE")"
142+
: > "$CREDENTIALS_READY_FILE"
143+
chmod 400 "$CREDENTIALS_READY_FILE"
144+
}
145+
146+
wallet_identity_available() {
147+
interfold wallet get --config "$CONFIG_FILE" >/dev/null 2>&1
148+
}
149+
150+
credentials_are_ready() {
151+
if [ -e "$CREDENTIALS_READY_FILE" ]; then
152+
validate_credentials_ready_file
153+
return 0
154+
fi
155+
156+
if wallet_identity_available; then
157+
mark_credentials_ready
158+
return 0
159+
fi
160+
161+
return 1
162+
}
163+
164+
provision_wallet() {
165+
jq -jr '.private_key, "\n"' "$SECRETS_FILE" \
166+
| interfold wallet set --private-key-stdin --config "$CONFIG_FILE" \
167+
|| fail "wallet command failed"
168+
mark_credentials_ready
169+
}
170+
133171
configure_credentials() {
134172
validate_secret_file
135173

@@ -138,6 +176,10 @@ configure_credentials() {
138176
jq -er '.password' "$SECRETS_FILE" | tr -d '\n' | cmp -s - "$PASSWORD_FILE" \
139177
|| fail "uploaded password does not match the persisted credential key"
140178
log "Using the matching persisted encryption password."
179+
if ! credentials_are_ready; then
180+
log "Encrypted wallet identity is incomplete; retrying wallet provisioning."
181+
provision_wallet
182+
fi
141183
rm -f "$SECRETS_FILE"
142184
log "Existing encrypted wallet/network identity was preserved."
143185
return
@@ -147,9 +189,7 @@ configure_credentials() {
147189
jq -jr '.password, "\n"' "$SECRETS_FILE" \
148190
| interfold password set --password-stdin --config "$CONFIG_FILE" \
149191
|| fail "password command failed"
150-
jq -jr '.private_key, "\n"' "$SECRETS_FILE" \
151-
| interfold wallet set --private-key-stdin --config "$CONFIG_FILE" \
152-
|| fail "wallet command failed"
192+
provision_wallet
153193

154194
# DAppNode copies fileUpload content into this container before startup.
155195
# Wallet command derives both Ethereum and libp2p identities. Remove the
@@ -163,8 +203,10 @@ if [ -e "$SECRETS_FILE" ]; then
163203
elif [ -s "$PASSWORD_FILE" ]; then
164204
# Backward-compatible restart/upgrade path: DAppNode file uploads are copied
165205
# when configuring a container, while encrypted credentials persist in
166-
# /data. Interfold itself will fail startup if wallet/network state is absent.
206+
# /data. If an older complete install predates the readiness marker, stamp it
207+
# after the wallet decrypts successfully.
167208
validate_persisted_password_file
209+
credentials_are_ready || fail "credential upload is required to complete wallet provisioning: $SECRETS_FILE"
168210
log "No credential upload present; using persisted credential state."
169211
else
170212
fail "credentials file is required for first startup: $SECRETS_FILE"

dappnode/tests/test-hardening.sh

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ make_mock_interfold() {
3434
'case "${1:-} ${2:-} ${3:-}" in' \
3535
' "password set --password-stdin"*) operation=password ;;' \
3636
' "wallet set --private-key-stdin"*) operation=wallet ;;' \
37+
' "wallet get --config"*) operation=wallet-get ;;' \
3738
' "start "*) operation=start ;;' \
3839
' *) operation=unexpected ;;' \
3940
'esac' \
@@ -51,6 +52,13 @@ make_mock_interfold() {
5152
' ;;' \
5253
' wallet)' \
5354
' IFS= read -r secret' \
55+
' printf "%s" "$secret" > "$WALLET_FILE"' \
56+
' chmod 400 "$WALLET_FILE"' \
57+
' ;;' \
58+
' wallet-get)' \
59+
' [ "${WALLET_GET_FAIL:-}" != "1" ] || exit 43' \
60+
' [ -s "$WALLET_FILE" ] || exit 44' \
61+
' printf "0x3333333333333333333333333333333333333333\n"' \
5462
' ;;' \
5563
' start)' \
5664
' : ;;' \
@@ -93,6 +101,8 @@ run_entrypoint() {
93101
TEMPLATE_FILE="$ROOT_DIR/config.template.yaml" \
94102
SECRETS_FILE="$case_dir/secrets/secrets.json" \
95103
PASSWORD_FILE="$case_dir/data/password" \
104+
CREDENTIALS_READY_FILE="$case_dir/data/credentials.provisioned" \
105+
WALLET_FILE="$case_dir/data/wallet" \
96106
CALL_LOG="$case_dir/calls" \
97107
ARGV_LOG="$case_dir/argv" \
98108
RPC_URL="ws://127.0.0.1:8545" \
@@ -141,6 +151,12 @@ fi
141151
assert_contains "$failure_dir/calls" 'wallet'
142152
assert_not_contains "$failure_dir/calls" 'start'
143153
[ -e "$failure_dir/secrets/secrets.json" ] || fail "failed setup removed recovery input"
154+
[ ! -e "$failure_dir/data/credentials.provisioned" ] || fail "failed setup created readiness marker"
155+
run_entrypoint "$failure_dir"
156+
[ "$(tr '\n' ' ' < "$failure_dir/calls")" = "wallet-get wallet start " ] \
157+
|| fail "wallet retry did not resume incomplete provisioning"
158+
[ ! -e "$failure_dir/secrets/secrets.json" ] || fail "retried setup did not remove plaintext credentials"
159+
[ -e "$failure_dir/data/credentials.provisioned" ] || fail "retried setup did not create readiness marker"
144160

145161
# Existing state may only be reused with the password that encrypted it.
146162
mismatch_dir="$TEST_ROOT/password-mismatch"
@@ -156,6 +172,7 @@ fi
156172
matching_dir="$TEST_ROOT/password-match"
157173
mkdir -p "$matching_dir/data" "$matching_dir/secrets"
158174
printf '%s' 'correct horse battery staple' > "$matching_dir/data/password"
175+
printf '%s' 'ready' > "$matching_dir/data/credentials.provisioned"
159176
write_secrets "$matching_dir/secrets/secrets.json"
160177
run_entrypoint "$matching_dir"
161178
[ "$(tr '\n' ' ' < "$matching_dir/calls")" = "start " ] || fail "matching persisted state was re-provisioned"
@@ -191,21 +208,41 @@ restart_dir="$TEST_ROOT/restart"
191208
mkdir -p "$restart_dir/data"
192209
printf '%s' 'persisted-password' > "$restart_dir/data/password"
193210
chmod 400 "$restart_dir/data/password"
211+
printf '%s' 'ready' > "$restart_dir/data/credentials.provisioned"
194212
run_entrypoint "$restart_dir"
195213
[ "$(tr '\n' ' ' < "$restart_dir/calls")" = "start " ] || fail "persisted restart unexpectedly re-provisioned credentials"
196214

215+
old_restart_dir="$TEST_ROOT/old-restart"
216+
mkdir -p "$old_restart_dir/data"
217+
printf '%s' 'persisted-password' > "$old_restart_dir/data/password"
218+
printf '%s' 'wallet' > "$old_restart_dir/data/wallet"
219+
run_entrypoint "$old_restart_dir"
220+
[ "$(tr '\n' ' ' < "$old_restart_dir/calls")" = "wallet-get start " ] \
221+
|| fail "old complete state was not marked and started"
222+
[ -e "$old_restart_dir/data/credentials.provisioned" ] || fail "old complete state was not marked ready"
223+
224+
incomplete_restart_dir="$TEST_ROOT/incomplete-restart"
225+
mkdir -p "$incomplete_restart_dir/data"
226+
printf '%s' 'persisted-password' > "$incomplete_restart_dir/data/password"
227+
if run_entrypoint "$incomplete_restart_dir"; then
228+
fail "password-only state without upload was accepted"
229+
fi
230+
assert_contains "$incomplete_restart_dir/calls" 'wallet-get'
231+
assert_not_contains "$incomplete_restart_dir/calls" 'start'
232+
197233
# The 0.1.8 state bridge moves the complete custom-config namespace in one
198234
# rename, preserving the unversioned DB/event log for current releases.
199235
upgrade_dir="$TEST_ROOT/legacy-upgrade"
200236
mkdir -p "$upgrade_dir/data/.enclave/config/_default" "$upgrade_dir/data/.enclave/data/_default/db" \
201237
"$upgrade_dir/data/.enclave/data/_default/log.0"
202238
printf '%s' 'persisted-password' > "$upgrade_dir/data/.enclave/config/_default/key"
203239
printf '%s' 'legacy-state' > "$upgrade_dir/data/.enclave/data/_default/db/sentinel"
240+
printf '%s' 'wallet' > "$upgrade_dir/data/wallet"
204241
run_entrypoint "$upgrade_dir" \
205242
PASSWORD_FILE="$upgrade_dir/data/.interfold/config/_default/key"
206243
[ ! -e "$upgrade_dir/data/.enclave" ] || fail "legacy state namespace remained after upgrade"
207244
assert_contains "$upgrade_dir/data/.interfold/data/_default/db/sentinel" 'legacy-state'
208-
[ "$(tr '\n' ' ' < "$upgrade_dir/calls")" = "start " ] || fail "legacy state upgrade did not start"
245+
[ "$(tr '\n' ' ' < "$upgrade_dir/calls")" = "wallet-get start " ] || fail "legacy state upgrade did not start"
209246

210247
ambiguous_dir="$TEST_ROOT/ambiguous-upgrade"
211248
mkdir -p "$ambiguous_dir/data/.enclave" "$ambiguous_dir/data/.interfold"

0 commit comments

Comments
 (0)