fix(reboot): add windows99 vmx source locator
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 2m11s
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 2m11s
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled
This commit is contained in:
320
scripts/reboot-recovery/locate-windows99-vmx-source.sh
Executable file
320
scripts/reboot-recovery/locate-windows99-vmx-source.sh
Executable file
@@ -0,0 +1,320 @@
|
||||
#!/usr/bin/env bash
|
||||
set -u
|
||||
|
||||
MODE="check"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
TARGET_HOST="${WINDOWS99_HOST:-192.168.0.99}"
|
||||
CONNECT_TIMEOUT="${WINDOWS99_CONNECT_TIMEOUT:-3}"
|
||||
SSH_TIMEOUT="${WINDOWS99_SSH_TIMEOUT:-3}"
|
||||
REMOTE_LOCATOR_TIMEOUT="${WINDOWS99_REMOTE_LOCATOR_TIMEOUT:-60}"
|
||||
SSH_PORT="${WINDOWS99_SSH_PORT:-22}"
|
||||
MAX_AUTH_USERS="${WINDOWS99_MAX_AUTH_USERS:-}"
|
||||
MAX_AUTH_USERS_EXPLICIT=0
|
||||
if [[ -n "${WINDOWS99_MAX_AUTH_USERS:-}" ]]; then
|
||||
MAX_AUTH_USERS_EXPLICIT=1
|
||||
fi
|
||||
KNOWN_HOSTS_FILE="${WINDOWS99_KNOWN_HOSTS_FILE:-/tmp/awoooi-windows99-known_hosts}"
|
||||
LOCAL_LOCATOR_SCRIPT="${WINDOWS99_LOCAL_LOCATOR_SCRIPT:-${SCRIPT_DIR}/windows99-vmx-source-locator.ps1}"
|
||||
REMOTE_LOCATOR_COMMAND="${WINDOWS99_REMOTE_LOCATOR_COMMAND:-powershell -NoProfile -ExecutionPolicy Bypass -Command \"& ([scriptblock]::Create([Console]::In.ReadToEnd())) -Mode Check\"}"
|
||||
SSH_USERS=(ogt wooo ooo administrator Administrator)
|
||||
SSH_USERS_EXPLICIT=0
|
||||
|
||||
if [[ -n "${WINDOWS99_SSH_USERS:-}" ]]; then
|
||||
# shellcheck disable=SC2206
|
||||
SSH_USERS=(${WINDOWS99_SSH_USERS})
|
||||
SSH_USERS_EXPLICIT=1
|
||||
fi
|
||||
|
||||
is_positive_int() {
|
||||
[[ "$1" =~ ^[1-9][0-9]*$ ]]
|
||||
}
|
||||
|
||||
usage() {
|
||||
printf '%s\n' "usage: $0 [--check|--collect] [--host HOST] [--users 'u1 u2'] [--timeout SECONDS] [--max-auth-users N]"
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--check)
|
||||
MODE="check"
|
||||
;;
|
||||
--collect)
|
||||
MODE="collect"
|
||||
;;
|
||||
--host)
|
||||
shift
|
||||
TARGET_HOST="${1:-}"
|
||||
;;
|
||||
--users)
|
||||
shift
|
||||
# shellcheck disable=SC2206
|
||||
SSH_USERS=(${1:-})
|
||||
SSH_USERS_EXPLICIT=1
|
||||
;;
|
||||
--timeout)
|
||||
shift
|
||||
CONNECT_TIMEOUT="${1:-5}"
|
||||
SSH_TIMEOUT="${CONNECT_TIMEOUT}"
|
||||
;;
|
||||
--max-auth-users)
|
||||
shift
|
||||
MAX_AUTH_USERS="${1:-}"
|
||||
MAX_AUTH_USERS_EXPLICIT=1
|
||||
;;
|
||||
--help|-h)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
printf '%s\n' "error=unknown_argument:$1" >&2
|
||||
usage >&2
|
||||
exit 64
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if ! is_positive_int "${CONNECT_TIMEOUT}"; then
|
||||
CONNECT_TIMEOUT=3
|
||||
fi
|
||||
if ! is_positive_int "${SSH_TIMEOUT}"; then
|
||||
SSH_TIMEOUT=3
|
||||
fi
|
||||
if ! is_positive_int "${REMOTE_LOCATOR_TIMEOUT}"; then
|
||||
REMOTE_LOCATOR_TIMEOUT=60
|
||||
fi
|
||||
if ! is_positive_int "${MAX_AUTH_USERS}"; then
|
||||
MAX_AUTH_USERS="${#SSH_USERS[@]}"
|
||||
fi
|
||||
if [[ "${SSH_USERS_EXPLICIT}" == "1" && "${MAX_AUTH_USERS_EXPLICIT}" != "1" ]]; then
|
||||
MAX_AUTH_USERS="${#SSH_USERS[@]}"
|
||||
fi
|
||||
if ! is_positive_int "${MAX_AUTH_USERS}"; then
|
||||
MAX_AUTH_USERS=2
|
||||
fi
|
||||
|
||||
if [[ "${MODE}" != "check" && "${MODE}" != "collect" ]]; then
|
||||
printf '%s\n' "error=invalid_mode:${MODE}" >&2
|
||||
exit 64
|
||||
fi
|
||||
|
||||
PORT_TIMEOUT_WRAPPER="none"
|
||||
if command -v timeout >/dev/null 2>&1; then
|
||||
PORT_TIMEOUT_WRAPPER="timeout"
|
||||
elif command -v gtimeout >/dev/null 2>&1; then
|
||||
PORT_TIMEOUT_WRAPPER="gtimeout"
|
||||
fi
|
||||
|
||||
port_open() {
|
||||
local port="$1"
|
||||
if ! command -v nc >/dev/null 2>&1; then
|
||||
return 1
|
||||
fi
|
||||
if [[ "${PORT_TIMEOUT_WRAPPER}" == "timeout" ]]; then
|
||||
timeout "$((CONNECT_TIMEOUT + 1))s" nc -z -w "${CONNECT_TIMEOUT}" "${TARGET_HOST}" "${port}" >/dev/null 2>&1
|
||||
elif [[ "${PORT_TIMEOUT_WRAPPER}" == "gtimeout" ]]; then
|
||||
gtimeout "$((CONNECT_TIMEOUT + 1))s" nc -z -w "${CONNECT_TIMEOUT}" "${TARGET_HOST}" "${port}" >/dev/null 2>&1
|
||||
else
|
||||
nc -z -w "${CONNECT_TIMEOUT}" "${TARGET_HOST}" "${port}" >/dev/null 2>&1
|
||||
fi
|
||||
}
|
||||
|
||||
bool_for_port() {
|
||||
local port="$1"
|
||||
if port_open "${port}"; then
|
||||
printf '1'
|
||||
else
|
||||
printf '0'
|
||||
fi
|
||||
}
|
||||
|
||||
join_users() {
|
||||
local joined=""
|
||||
local user
|
||||
for user in "${SSH_USERS[@]}"; do
|
||||
if [[ -z "${joined}" ]]; then
|
||||
joined="${user}"
|
||||
else
|
||||
joined="${joined},${user}"
|
||||
fi
|
||||
done
|
||||
printf '%s' "${joined}"
|
||||
}
|
||||
|
||||
PORT_22_OPEN="$(bool_for_port 22)"
|
||||
PORT_3389_OPEN="$(bool_for_port 3389)"
|
||||
PORT_5985_OPEN="$(bool_for_port 5985)"
|
||||
PORT_5986_OPEN="$(bool_for_port 5986)"
|
||||
|
||||
SSH_BATCHMODE_AUTH_READY=0
|
||||
SSH_AUTHENTICATED_USER=""
|
||||
SSH_AUTH_PROBE_EXIT_STATUS="not_attempted"
|
||||
SSH_AUTH_PROBE_STDOUT_PRESENT=0
|
||||
SSH_AUTH_ATTEMPTED_USERS="$(join_users)"
|
||||
SSH_AUTH_PROBED_USERS=0
|
||||
SSH_TIMEOUT_WRAPPER="none"
|
||||
if command -v timeout >/dev/null 2>&1; then
|
||||
SSH_TIMEOUT_WRAPPER="timeout"
|
||||
elif command -v gtimeout >/dev/null 2>&1; then
|
||||
SSH_TIMEOUT_WRAPPER="gtimeout"
|
||||
fi
|
||||
|
||||
SSH_OPTS=(
|
||||
-o BatchMode=yes
|
||||
-o PreferredAuthentications=publickey
|
||||
-o PubkeyAuthentication=yes
|
||||
-o PasswordAuthentication=no
|
||||
-o KbdInteractiveAuthentication=no
|
||||
-o NumberOfPasswordPrompts=0
|
||||
-o ConnectTimeout="${SSH_TIMEOUT}"
|
||||
-o ConnectionAttempts=1
|
||||
-o GSSAPIAuthentication=no
|
||||
-o LogLevel=ERROR
|
||||
-o StrictHostKeyChecking=no
|
||||
-o UserKnownHostsFile="${KNOWN_HOSTS_FILE}"
|
||||
-p "${SSH_PORT}"
|
||||
)
|
||||
|
||||
run_ssh_timed() {
|
||||
local user="$1"
|
||||
local timeout_seconds="$2"
|
||||
local timeout_wrapper_seconds="$((timeout_seconds + 1))"
|
||||
shift 2
|
||||
if ! is_positive_int "${timeout_seconds}"; then
|
||||
timeout_seconds="${SSH_TIMEOUT}"
|
||||
timeout_wrapper_seconds="$((SSH_TIMEOUT + 1))"
|
||||
fi
|
||||
if [[ "${SSH_TIMEOUT_WRAPPER}" == "timeout" ]]; then
|
||||
timeout "${timeout_wrapper_seconds}s" ssh "${SSH_OPTS[@]}" -o ConnectTimeout="${timeout_seconds}" "${user}@${TARGET_HOST}" "$@"
|
||||
elif [[ "${SSH_TIMEOUT_WRAPPER}" == "gtimeout" ]]; then
|
||||
gtimeout "${timeout_wrapper_seconds}s" ssh "${SSH_OPTS[@]}" -o ConnectTimeout="${timeout_seconds}" "${user}@${TARGET_HOST}" "$@"
|
||||
else
|
||||
ssh "${SSH_OPTS[@]}" -o ConnectTimeout="${timeout_seconds}" "${user}@${TARGET_HOST}" "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
run_ssh() {
|
||||
local user="$1"
|
||||
shift
|
||||
run_ssh_timed "${user}" "${SSH_TIMEOUT}" "$@"
|
||||
}
|
||||
|
||||
if [[ "${PORT_22_OPEN}" == "1" ]]; then
|
||||
for user in "${SSH_USERS[@]}"; do
|
||||
if [[ "${SSH_AUTH_PROBED_USERS}" -ge "${MAX_AUTH_USERS}" ]]; then
|
||||
break
|
||||
fi
|
||||
SSH_AUTH_PROBED_USERS=$((SSH_AUTH_PROBED_USERS + 1))
|
||||
auth_output=""
|
||||
if auth_output="$(run_ssh "${user}" "echo AWOOOI_WINDOWS99_SSH_READY" 2>&1)"; then
|
||||
SSH_BATCHMODE_AUTH_READY=1
|
||||
SSH_AUTHENTICATED_USER="${user}"
|
||||
SSH_AUTH_PROBE_EXIT_STATUS=0
|
||||
if [[ -n "${auth_output}" ]]; then
|
||||
SSH_AUTH_PROBE_STDOUT_PRESENT=1
|
||||
fi
|
||||
break
|
||||
else
|
||||
SSH_AUTH_PROBE_EXIT_STATUS=$?
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
DRY_RUN="true"
|
||||
REMOTE_LOCATOR_ATTEMPTED=0
|
||||
REMOTE_LOCATOR_EXIT_STATUS="not_attempted"
|
||||
LOCATOR_COLLECTION_STATUS="blocked_ssh_publickey_auth_missing"
|
||||
SAFE_NEXT_STEP="select_existing_authorized_public_key_user_or_set_WINDOWS99_SSH_USERS_then_rerun_locator_no_password"
|
||||
REMOTE_LOCATOR_OUTPUT=""
|
||||
LOCAL_LOCATOR_SCRIPT_PRESENT=0
|
||||
if [[ -f "${LOCAL_LOCATOR_SCRIPT}" ]]; then
|
||||
LOCAL_LOCATOR_SCRIPT_PRESENT=1
|
||||
fi
|
||||
PROCESS_EXIT_STATUS=0
|
||||
|
||||
if [[ "${PORT_22_OPEN}" != "1" ]]; then
|
||||
LOCATOR_COLLECTION_STATUS="blocked_ssh_port_closed"
|
||||
SAFE_NEXT_STEP="enable_existing_ssh_management_channel_publickey_only_then_rerun_locator_no_secret"
|
||||
if [[ "${MODE}" == "collect" ]]; then
|
||||
PROCESS_EXIT_STATUS=75
|
||||
fi
|
||||
elif [[ "${SSH_BATCHMODE_AUTH_READY}" != "1" ]]; then
|
||||
LOCATOR_COLLECTION_STATUS="blocked_ssh_publickey_auth_missing"
|
||||
SAFE_NEXT_STEP="select_existing_authorized_public_key_user_or_set_WINDOWS99_SSH_USERS_then_rerun_locator_no_password"
|
||||
if [[ "${MODE}" == "collect" ]]; then
|
||||
PROCESS_EXIT_STATUS=75
|
||||
fi
|
||||
elif [[ "${MODE}" == "check" ]]; then
|
||||
LOCATOR_COLLECTION_STATUS="ready_ssh_batchmode_auth_probe_only"
|
||||
SAFE_NEXT_STEP="rerun_locator_with_collect_then_commit_no_secret_locator_artifact_and_scorecard_rerun"
|
||||
elif [[ "${LOCAL_LOCATOR_SCRIPT_PRESENT}" != "1" ]]; then
|
||||
DRY_RUN="false"
|
||||
LOCATOR_COLLECTION_STATUS="blocked_local_locator_script_missing"
|
||||
SAFE_NEXT_STEP="restore_windows99_vmx_source_locator_ps1_source_then_rerun_no_secret_no_remote_write"
|
||||
PROCESS_EXIT_STATUS=75
|
||||
else
|
||||
DRY_RUN="false"
|
||||
REMOTE_LOCATOR_ATTEMPTED=1
|
||||
if REMOTE_LOCATOR_OUTPUT="$(run_ssh_timed "${SSH_AUTHENTICATED_USER}" "${REMOTE_LOCATOR_TIMEOUT}" "${REMOTE_LOCATOR_COMMAND}" <"${LOCAL_LOCATOR_SCRIPT}" 2>&1)"; then
|
||||
REMOTE_LOCATOR_OUTPUT="${REMOTE_LOCATOR_OUTPUT//$'\r'/}"
|
||||
REMOTE_LOCATOR_EXIT_STATUS=0
|
||||
if grep -q '^AWOOOI_WINDOWS99_VMX_SOURCE_LOCATOR=1$' <<<"${REMOTE_LOCATOR_OUTPUT}" && grep -q '^MODE=Check$' <<<"${REMOTE_LOCATOR_OUTPUT}"; then
|
||||
LOCATOR_COLLECTION_STATUS="collected_windows99_vmx_source_locator_stdout"
|
||||
SAFE_NEXT_STEP="commit_no_secret_locator_artifact_then_rerun_reboot_auto_recovery_slo_scorecard"
|
||||
else
|
||||
LOCATOR_COLLECTION_STATUS="blocked_remote_locator_output_invalid"
|
||||
SAFE_NEXT_STEP="inspect_no_secret_locator_stdout_then_fix_in_memory_locator_script_and_rerun"
|
||||
PROCESS_EXIT_STATUS=75
|
||||
fi
|
||||
else
|
||||
REMOTE_LOCATOR_EXIT_STATUS=$?
|
||||
LOCATOR_COLLECTION_STATUS="blocked_remote_locator_command_failed"
|
||||
SAFE_NEXT_STEP="inspect_no_secret_locator_stdout_then_fix_in_memory_locator_script_and_rerun"
|
||||
PROCESS_EXIT_STATUS=75
|
||||
fi
|
||||
fi
|
||||
|
||||
printf '%s\n' "schema_version=windows99_vmx_source_locator_collector_v1"
|
||||
printf '%s\n' "dry_run=${DRY_RUN}"
|
||||
printf '%s\n' "target_host=${TARGET_HOST}"
|
||||
printf '%s\n' "target_host_alias=99"
|
||||
printf '%s\n' "target_vm_alias=111"
|
||||
printf '%s\n' "connect_timeout_seconds=${CONNECT_TIMEOUT}"
|
||||
printf '%s\n' "ssh_timeout_seconds=${SSH_TIMEOUT}"
|
||||
printf '%s\n' "remote_locator_timeout_seconds=${REMOTE_LOCATOR_TIMEOUT}"
|
||||
printf '%s\n' "port_timeout_wrapper=${PORT_TIMEOUT_WRAPPER}"
|
||||
printf '%s\n' "ssh_auth_probe_user_limit=${MAX_AUTH_USERS}"
|
||||
printf '%s\n' "ssh_timeout_wrapper=${SSH_TIMEOUT_WRAPPER}"
|
||||
printf '%s\n' "port_22_open=${PORT_22_OPEN}"
|
||||
printf '%s\n' "port_3389_open=${PORT_3389_OPEN}"
|
||||
printf '%s\n' "port_5985_open=${PORT_5985_OPEN}"
|
||||
printf '%s\n' "port_5986_open=${PORT_5986_OPEN}"
|
||||
printf '%s\n' "ssh_candidate_users=${SSH_AUTH_ATTEMPTED_USERS}"
|
||||
printf '%s\n' "ssh_auth_probed_users=${SSH_AUTH_PROBED_USERS}"
|
||||
printf '%s\n' "ssh_batchmode_auth_ready=${SSH_BATCHMODE_AUTH_READY}"
|
||||
printf '%s\n' "ssh_authenticated_user=${SSH_AUTHENTICATED_USER}"
|
||||
printf '%s\n' "ssh_auth_probe_exit_status=${SSH_AUTH_PROBE_EXIT_STATUS}"
|
||||
printf '%s\n' "ssh_auth_probe_stdout_present=${SSH_AUTH_PROBE_STDOUT_PRESENT}"
|
||||
printf '%s\n' "remote_locator_mode=in_memory_stdin_scriptblock"
|
||||
printf '%s\n' "local_locator_script_present=${LOCAL_LOCATOR_SCRIPT_PRESENT}"
|
||||
printf '%s\n' "remote_locator_attempted=${REMOTE_LOCATOR_ATTEMPTED}"
|
||||
printf '%s\n' "remote_locator_exit_status=${REMOTE_LOCATOR_EXIT_STATUS}"
|
||||
printf '%s\n' "locator_collection_status=${LOCATOR_COLLECTION_STATUS}"
|
||||
printf '%s\n' "safe_next_step=${SAFE_NEXT_STEP}"
|
||||
printf '%s\n' "secret_value_read=false"
|
||||
printf '%s\n' "password_prompt_allowed=false"
|
||||
printf '%s\n' "remote_write_performed=false"
|
||||
printf '%s\n' "host_reboot_performed=false"
|
||||
printf '%s\n' "vm_power_change_performed=false"
|
||||
printf '%s\n' "windows_registry_apply_performed=false"
|
||||
printf '%s\n' "scheduled_task_write_performed=false"
|
||||
printf '%s\n' "vmx_file_content_read=false"
|
||||
printf '%s\n' "raw_path_output=false"
|
||||
|
||||
if [[ "${REMOTE_LOCATOR_ATTEMPTED}" == "1" ]]; then
|
||||
printf '%s\n' "remote_locator_output_begin"
|
||||
printf '%s\n' "${REMOTE_LOCATOR_OUTPUT}"
|
||||
printf '%s\n' "remote_locator_output_end"
|
||||
fi
|
||||
|
||||
exit "${PROCESS_EXIT_STATUS}"
|
||||
@@ -0,0 +1,199 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import textwrap
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[3]
|
||||
BASH_SCRIPT = ROOT / "scripts" / "reboot-recovery" / "locate-windows99-vmx-source.sh"
|
||||
PS_SCRIPT = ROOT / "scripts" / "reboot-recovery" / "windows99-vmx-source-locator.ps1"
|
||||
|
||||
|
||||
def _write_executable(path: Path, text: str) -> None:
|
||||
path.write_text(textwrap.dedent(text).lstrip())
|
||||
path.chmod(0o755)
|
||||
|
||||
|
||||
def _run_locator(fake_bin: Path, *args: str) -> subprocess.CompletedProcess[str]:
|
||||
env = os.environ.copy()
|
||||
env["PATH"] = f"{fake_bin}:{env['PATH']}"
|
||||
env["WINDOWS99_KNOWN_HOSTS_FILE"] = str(fake_bin / "known_hosts")
|
||||
return subprocess.run(
|
||||
["bash", str(BASH_SCRIPT), *args],
|
||||
cwd=ROOT,
|
||||
env=env,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
|
||||
|
||||
def _key_values(stdout: str) -> dict[str, str]:
|
||||
values: dict[str, str] = {}
|
||||
for line in stdout.splitlines():
|
||||
if "=" not in line:
|
||||
continue
|
||||
key, value = line.split("=", 1)
|
||||
values[key] = value
|
||||
return values
|
||||
|
||||
|
||||
def test_locator_contract_forbids_secret_runtime_actions_and_vmx_content_reads() -> None:
|
||||
bash_text = BASH_SCRIPT.read_text()
|
||||
ps_text = PS_SCRIPT.read_text()
|
||||
|
||||
assert "BatchMode=yes" in bash_text
|
||||
assert "PreferredAuthentications=publickey" in bash_text
|
||||
assert "PubkeyAuthentication=yes" in bash_text
|
||||
assert "PasswordAuthentication=no" in bash_text
|
||||
assert "KbdInteractiveAuthentication=no" in bash_text
|
||||
assert "NumberOfPasswordPrompts=0" in bash_text
|
||||
assert "in_memory_stdin_scriptblock" in bash_text
|
||||
assert "[Console]::In.ReadToEnd()" in bash_text
|
||||
assert "Get-ChildItem" in ps_text
|
||||
assert "Test-Path" in ps_text
|
||||
for text in [bash_text, ps_text]:
|
||||
for forbidden in [
|
||||
"sshpass",
|
||||
"PasswordAuthentication=yes",
|
||||
"KbdInteractiveAuthentication=yes",
|
||||
"net use",
|
||||
"shutdown",
|
||||
"Restart-Computer",
|
||||
"Start-VM",
|
||||
"vmrun start",
|
||||
"Set-ItemProperty",
|
||||
"Register-ScheduledTask",
|
||||
"Set-Content",
|
||||
"Copy-Item",
|
||||
"Move-Item",
|
||||
"Remove-Item",
|
||||
"Get-Content",
|
||||
"Select-String",
|
||||
]:
|
||||
assert forbidden not in text
|
||||
|
||||
|
||||
def test_check_mode_reports_auth_probe_without_remote_locator(tmp_path: Path) -> None:
|
||||
fake_bin = tmp_path / "bin"
|
||||
fake_bin.mkdir()
|
||||
_write_executable(
|
||||
fake_bin / "nc",
|
||||
"""
|
||||
#!/usr/bin/env bash
|
||||
port="${!#}"
|
||||
if [[ "$port" == "22" || "$port" == "3389" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
exit 1
|
||||
""",
|
||||
)
|
||||
_write_executable(
|
||||
fake_bin / "ssh",
|
||||
"""
|
||||
#!/usr/bin/env bash
|
||||
printf '%s\n' 'AWOOOI_WINDOWS99_SSH_READY'
|
||||
exit 0
|
||||
""",
|
||||
)
|
||||
|
||||
result = _run_locator(fake_bin, "--check", "--max-auth-users", "1")
|
||||
|
||||
assert result.returncode == 0
|
||||
values = _key_values(result.stdout)
|
||||
assert values["schema_version"] == "windows99_vmx_source_locator_collector_v1"
|
||||
assert values["dry_run"] == "true"
|
||||
assert values["target_host_alias"] == "99"
|
||||
assert values["target_vm_alias"] == "111"
|
||||
assert values["ssh_batchmode_auth_ready"] == "1"
|
||||
assert values["remote_locator_attempted"] == "0"
|
||||
assert values["locator_collection_status"] == "ready_ssh_batchmode_auth_probe_only"
|
||||
assert values["secret_value_read"] == "false"
|
||||
assert values["password_prompt_allowed"] == "false"
|
||||
assert values["remote_write_performed"] == "false"
|
||||
assert values["host_reboot_performed"] == "false"
|
||||
assert values["vm_power_change_performed"] == "false"
|
||||
assert values["vmx_file_content_read"] == "false"
|
||||
assert values["raw_path_output"] == "false"
|
||||
|
||||
|
||||
def test_collect_mode_blocks_without_publickey_auth(tmp_path: Path) -> None:
|
||||
fake_bin = tmp_path / "bin"
|
||||
fake_bin.mkdir()
|
||||
_write_executable(
|
||||
fake_bin / "nc",
|
||||
"""
|
||||
#!/usr/bin/env bash
|
||||
[[ "${!#}" == "22" ]]
|
||||
""",
|
||||
)
|
||||
_write_executable(
|
||||
fake_bin / "ssh",
|
||||
"""
|
||||
#!/usr/bin/env bash
|
||||
exit 255
|
||||
""",
|
||||
)
|
||||
|
||||
result = _run_locator(fake_bin, "--collect", "--max-auth-users", "1")
|
||||
|
||||
assert result.returncode == 75
|
||||
values = _key_values(result.stdout)
|
||||
assert values["ssh_batchmode_auth_ready"] == "0"
|
||||
assert values["remote_locator_attempted"] == "0"
|
||||
assert values["locator_collection_status"] == "blocked_ssh_publickey_auth_missing"
|
||||
assert values["secret_value_read"] == "false"
|
||||
assert values["remote_write_performed"] == "false"
|
||||
|
||||
|
||||
def test_collect_mode_accepts_valid_remote_locator_stdout(tmp_path: Path) -> None:
|
||||
fake_bin = tmp_path / "bin"
|
||||
fake_bin.mkdir()
|
||||
_write_executable(
|
||||
fake_bin / "nc",
|
||||
"""
|
||||
#!/usr/bin/env bash
|
||||
[[ "${!#}" == "22" ]]
|
||||
""",
|
||||
)
|
||||
_write_executable(
|
||||
fake_bin / "ssh",
|
||||
"""
|
||||
#!/usr/bin/env bash
|
||||
joined="$*"
|
||||
if [[ "$joined" == *"echo AWOOOI_WINDOWS99_SSH_READY"* ]]; then
|
||||
printf '%s\n' 'AWOOOI_WINDOWS99_SSH_READY'
|
||||
exit 0
|
||||
fi
|
||||
cat >/dev/null
|
||||
printf '%s\n' 'EXPECTED_VMX alias=111 index=1 present=0 fingerprint=abc'
|
||||
printf '%s\n' 'AWOOOI_WINDOWS99_VMX_SOURCE_LOCATOR=1'
|
||||
printf '%s\n' 'schema_version=windows99_vmx_source_locator_v1'
|
||||
printf '%s\n' 'MODE=Check'
|
||||
printf '%s\n' 'target_host_alias=99'
|
||||
printf '%s\n' 'target_vm_alias=111'
|
||||
printf '%s\n' 'expected_vmx_path_count=1'
|
||||
printf '%s\n' 'expected_vmx_path_present_count=0'
|
||||
printf '%s\n' 'candidate_source_count=0'
|
||||
printf '%s\n' 'source_locator_status=blocked_missing_expected_vmx_source_no_candidate_found'
|
||||
printf '%s\n' 'file_content_read=false'
|
||||
printf '%s\n' 'remote_write_performed=false'
|
||||
exit 0
|
||||
""",
|
||||
)
|
||||
|
||||
result = _run_locator(fake_bin, "--collect", "--max-auth-users", "1")
|
||||
|
||||
assert result.returncode == 0
|
||||
values = _key_values(result.stdout)
|
||||
assert values["dry_run"] == "false"
|
||||
assert values["remote_locator_attempted"] == "1"
|
||||
assert values["remote_locator_exit_status"] == "0"
|
||||
assert values["locator_collection_status"] == (
|
||||
"collected_windows99_vmx_source_locator_stdout"
|
||||
)
|
||||
assert "remote_locator_output_begin" in result.stdout
|
||||
assert "source_locator_status=blocked_missing_expected_vmx_source_no_candidate_found" in result.stdout
|
||||
assert "remote_locator_output_end" in result.stdout
|
||||
136
scripts/reboot-recovery/windows99-vmx-source-locator.ps1
Normal file
136
scripts/reboot-recovery/windows99-vmx-source-locator.ps1
Normal file
@@ -0,0 +1,136 @@
|
||||
param(
|
||||
[ValidateSet("Check")]
|
||||
[string]$Mode = "Check",
|
||||
[string]$TargetAlias = "111",
|
||||
[string[]]$ExpectedVmxPaths = @(
|
||||
"D:\Documents\Virtual Machines\192.168.0.111_Ubuntu_64-bit\192.168.0.111_Ubuntu_64-bit.vmx"
|
||||
),
|
||||
[string[]]$DiscoveryRoot = @(
|
||||
"D:\Documents\Virtual Machines",
|
||||
"D:\Downloads",
|
||||
"D:\VMs",
|
||||
"E:\VMs",
|
||||
"C:\VMs",
|
||||
"C:\Users\Public\Documents\Virtual Machines"
|
||||
),
|
||||
[int]$MaxCandidateCount = 20
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
if ($MaxCandidateCount -lt 1) {
|
||||
$MaxCandidateCount = 20
|
||||
}
|
||||
|
||||
function Get-StringFingerprint {
|
||||
param([string]$Value)
|
||||
|
||||
$sha = [System.Security.Cryptography.SHA256]::Create()
|
||||
try {
|
||||
$bytes = [System.Text.Encoding]::UTF8.GetBytes($Value.ToLowerInvariant())
|
||||
return (($sha.ComputeHash($bytes) | ForEach-Object { $_.ToString("x2") }) -join "")
|
||||
} finally {
|
||||
$sha.Dispose()
|
||||
}
|
||||
}
|
||||
|
||||
function Write-BoolLine {
|
||||
param(
|
||||
[string]$Name,
|
||||
[bool]$Value
|
||||
)
|
||||
|
||||
Write-Output "$Name=$([int]$Value)"
|
||||
}
|
||||
|
||||
$normalizedExpected = @(
|
||||
$ExpectedVmxPaths |
|
||||
Where-Object { $_ -and $_.Trim() } |
|
||||
ForEach-Object { $_.Trim() } |
|
||||
Select-Object -Unique
|
||||
)
|
||||
$normalizedRoots = @(
|
||||
$DiscoveryRoot |
|
||||
Where-Object { $_ -and $_.Trim() } |
|
||||
ForEach-Object { $_.Trim() } |
|
||||
Select-Object -Unique
|
||||
)
|
||||
|
||||
$expectedPresentCount = 0
|
||||
$expectedIndex = 0
|
||||
foreach ($path in $normalizedExpected) {
|
||||
$expectedIndex += 1
|
||||
$present = Test-Path -LiteralPath $path
|
||||
if ($present) {
|
||||
$expectedPresentCount += 1
|
||||
}
|
||||
Write-Output "EXPECTED_VMX alias=$TargetAlias index=$expectedIndex present=$([int]$present) fingerprint=$(Get-StringFingerprint -Value $path)"
|
||||
}
|
||||
|
||||
$rootPresentCount = 0
|
||||
$rootIndex = 0
|
||||
$candidateFingerprints = New-Object System.Collections.Generic.List[string]
|
||||
$aliasPattern = "(^|[\\._\-\s])$([regex]::Escape($TargetAlias))([\\._\-\s]|$)"
|
||||
|
||||
foreach ($root in $normalizedRoots) {
|
||||
$rootIndex += 1
|
||||
$rootPresent = Test-Path -LiteralPath $root
|
||||
if ($rootPresent) {
|
||||
$rootPresentCount += 1
|
||||
}
|
||||
Write-Output "DISCOVERY_ROOT index=$rootIndex present=$([int]$rootPresent) fingerprint=$(Get-StringFingerprint -Value $root)"
|
||||
if (-not $rootPresent) {
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
$matches = Get-ChildItem -LiteralPath $root -Recurse -File -Filter "*.vmx" -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.FullName -match $aliasPattern } |
|
||||
Select-Object -First $MaxCandidateCount
|
||||
foreach ($match in @($matches)) {
|
||||
if ($candidateFingerprints.Count -ge $MaxCandidateCount) {
|
||||
break
|
||||
}
|
||||
$candidateFingerprints.Add((Get-StringFingerprint -Value $match.FullName))
|
||||
}
|
||||
} catch {
|
||||
Write-Output "DISCOVERY_ROOT_ERROR fingerprint=$(Get-StringFingerprint -Value $root) error_type=$($_.Exception.GetType().Name)"
|
||||
}
|
||||
}
|
||||
|
||||
$candidateFingerprintValues = @($candidateFingerprints | Select-Object -Unique)
|
||||
$candidateCount = $candidateFingerprintValues.Count
|
||||
$candidateStatus = "blocked_missing_expected_vmx_source_no_candidate_found"
|
||||
$safeNextStep = "provide_existing_vmx_source_or_backup_path_then_rerun_locator_check_mode_no_write"
|
||||
if ($expectedPresentCount -gt 0) {
|
||||
$candidateStatus = "ready_expected_vmx_source_present"
|
||||
$safeNextStep = "rerun_no_secret_vmware_verify_collector_and_reboot_slo_scorecard"
|
||||
} elseif ($candidateCount -gt 0) {
|
||||
$candidateStatus = "candidate_found_for_controlled_relink_check_mode"
|
||||
$safeNextStep = "record_scoped_relink_dry_run_and_rollback_then_apply_existing_vmx_source_copy_or_link"
|
||||
}
|
||||
|
||||
Write-Output "AWOOOI_WINDOWS99_VMX_SOURCE_LOCATOR=1"
|
||||
Write-Output "schema_version=windows99_vmx_source_locator_v1"
|
||||
Write-Output "MODE=$Mode"
|
||||
Write-Output "target_host_alias=99"
|
||||
Write-Output "target_vm_alias=$TargetAlias"
|
||||
Write-Output "expected_vmx_path_count=$($normalizedExpected.Count)"
|
||||
Write-Output "expected_vmx_path_present_count=$expectedPresentCount"
|
||||
Write-Output "discovery_root_count=$($normalizedRoots.Count)"
|
||||
Write-Output "discovery_root_present_count=$rootPresentCount"
|
||||
Write-Output "candidate_source_count=$candidateCount"
|
||||
Write-Output "candidate_source_fingerprint_count=$candidateCount"
|
||||
Write-Output "candidate_source_fingerprints=$($candidateFingerprintValues -join ',')"
|
||||
Write-Output "source_locator_status=$candidateStatus"
|
||||
Write-Output "safe_next_step=$safeNextStep"
|
||||
Write-Output "raw_path_output=false"
|
||||
Write-Output "path_fingerprint_only=true"
|
||||
Write-Output "file_content_read=false"
|
||||
Write-Output "secret_value_read=false"
|
||||
Write-Output "password_prompt_allowed=false"
|
||||
Write-Output "remote_write_performed=false"
|
||||
Write-Output "host_reboot_performed=false"
|
||||
Write-Output "vm_power_change_performed=false"
|
||||
Write-Output "windows_registry_apply_performed=false"
|
||||
Write-Output "scheduled_task_write_performed=false"
|
||||
Reference in New Issue
Block a user