Harden PVE setup token extraction (#1312)

This commit is contained in:
rcourtman 2026-03-25 11:09:19 +00:00
parent c12f5fb5a4
commit 4d4344911a
2 changed files with 100 additions and 4 deletions

View file

@ -4308,6 +4308,66 @@ resolve_setup_auth_token() {
fi
}
extract_json_string_field() {
local json_input="$1"
local field_name="$2"
printf '%%s\n' "$json_input" | sed -n 's/.*"'$field_name'"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -n1
}
extract_pve_token_value() {
local token_output="$1"
local token_value=""
token_value=$(extract_json_string_field "$token_output" "value")
if [ -n "$token_value" ]; then
printf '%%s\n' "$token_value"
return 0
fi
printf '%%s\n' "$token_output" | awk -F'[|]' '
function trim(value) {
gsub(/^[[:space:]]+|[[:space:]]+$/, "", value)
return value
}
{
key = trim($2)
value = trim($3)
if (key == "value" && value != "") {
print value
exit
}
}
'
}
create_pve_token() {
if TOKEN_OUTPUT=$(pveum user token add pulse-monitor@pam "$TOKEN_NAME" --privsep 0 --output-format json 2>&1); then
TOKEN_CREATE_RC=0
else
TOKEN_CREATE_RC=$?
fi
if [ "$TOKEN_CREATE_RC" -eq 0 ]; then
TOKEN_VALUE=$(extract_pve_token_value "$TOKEN_OUTPUT")
return 0
fi
if echo "$TOKEN_OUTPUT" | grep -Eqi 'unknown option|unknown command|no such option|unable to parse option|output-format'; then
if TOKEN_OUTPUT=$(pveum user token add pulse-monitor@pam "$TOKEN_NAME" --privsep 0 2>&1); then
TOKEN_CREATE_RC=0
else
TOKEN_CREATE_RC=$?
fi
if [ "$TOKEN_CREATE_RC" -eq 0 ]; then
TOKEN_VALUE=$(extract_pve_token_value "$TOKEN_OUTPUT")
fi
fi
return "$TOKEN_CREATE_RC"
}
attempt_auto_registration() {
resolve_setup_auth_token
@ -4407,8 +4467,7 @@ fi
if [ "$TOKEN_ROTATION_SKIPPED" != true ]; then
# Create token and capture value (shown once by Proxmox)
TOKEN_OUTPUT=$(pveum user token add pulse-monitor@pam "$TOKEN_NAME" --privsep 0 2>&1)
TOKEN_CREATE_RC=$?
create_pve_token
if [ "$TOKEN_CREATE_RC" -ne 0 ]; then
echo "❌ Failed to create token '$TOKEN_NAME'"
echo "$TOKEN_OUTPUT"
@ -4416,8 +4475,6 @@ if [ "$TOKEN_ROTATION_SKIPPED" != true ]; then
echo "Manual registration may be required."
echo ""
else
TOKEN_VALUE=$(echo "$TOKEN_OUTPUT" | grep "│ value" | awk -F'│' '{print $3}' | tr -d ' ' | tail -1)
if [ -z "$TOKEN_VALUE" ]; then
echo ""
echo "================================================================"

View file

@ -67,6 +67,33 @@ echo "STATE AUTO_REG_SUCCESS=${AUTO_REG_SUCCESS} TOKEN_ROTATION_SKIPPED=${TOKEN_
assertContains(t, trace, "pveum user token add pulse-monitor@pam pulse-sentinel-url --privsep 0")
assertContains(t, trace, "curl -s -X POST")
})
t.Run("json_output_auto_registers_without_legacy_fallback", func(t *testing.T) {
output, trace := runSetupHarness(t, harness, mocks, map[string]string{
"MOCK_PVE_TOKEN_FORMAT": "json",
})
assertContains(t, output, "API token generated successfully")
assertContains(t, output, "Node registered successfully")
assertContains(t, output, "STATE AUTO_REG_SUCCESS=true TOKEN_ROTATION_SKIPPED=false TOKEN_VALUE=mocked-pve-secret")
assertContains(t, trace, "pveum user token add pulse-monitor@pam pulse-sentinel-url --privsep 0 --output-format json")
assertNotContains(t, trace, "pveum user token add pulse-monitor@pam pulse-sentinel-url --privsep 0\n")
})
t.Run("legacy_output_falls_back_when_json_format_is_unsupported", func(t *testing.T) {
output, trace := runSetupHarness(t, harness, mocks, map[string]string{
"MOCK_PVE_JSON_UNSUPPORTED": "1",
})
assertContains(t, output, "API token generated successfully")
assertContains(t, output, "Node registered successfully")
assertContains(t, output, "STATE AUTO_REG_SUCCESS=true TOKEN_ROTATION_SKIPPED=false TOKEN_VALUE=mocked-pve-secret")
assertContains(t, trace, "pveum user token add pulse-monitor@pam pulse-sentinel-url --privsep 0 --output-format json")
assertContains(t, trace, "pveum user token add pulse-monitor@pam pulse-sentinel-url --privsep 0")
assertContains(t, trace, "curl -s -X POST")
})
}
func TestSetupScriptTokenLifecycleIntegration_PBS(t *testing.T) {
@ -228,6 +255,18 @@ case "$*" in
;;
"user token remove pulse-monitor@pam pulse-sentinel-url")
;;
"user token add pulse-monitor@pam pulse-sentinel-url --privsep 0 --output-format json")
if [ "${MOCK_PVE_JSON_UNSUPPORTED:-0}" = "1" ]; then
echo "unknown option: output-format" >&2
exit 1
fi
if [ "${MOCK_PVE_TOKEN_FORMAT:-table}" = "json" ]; then
echo '{"tokenid":"pulse-monitor@pam!pulse-sentinel-url","value":"mocked-pve-secret"}'
exit 0
fi
# Some versions support the flag but still keep older text rendering in wrappers.
printf '\342\224\202 value \342\224\202 mocked-pve-secret \342\224\202\n'
;;
"user token add pulse-monitor@pam pulse-sentinel-url --privsep 0")
# Emit the box-drawing format parsed by the setup script ( value secret )
printf '\342\224\202 value \342\224\202 mocked-pve-secret \342\224\202\n'