mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-10-10 21:06:14 -08:00
Add credential peers with table and JSON output while preserving credential list. Match authenticated remote credential keys to stored fingerprints and virtual route addresses. Keep instance results isolated and exclude unproven relay users. Enable SHA-256 for CLI-only builds and add regression and multi-instance tests. Accept omitted protobuf default lists in the existing multi-instance test. Refs #2529
300 lines
7.3 KiB
Bash
Executable File
300 lines
7.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
|
REPO_ROOT=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)
|
|
|
|
CORE_BIN=${CORE_BIN:-"$REPO_ROOT/target/debug/easytier-core"}
|
|
CLI_BIN=${CLI_BIN:-"$REPO_ROOT/target/debug/easytier-cli"}
|
|
TMPDIR_PATH=""
|
|
CORE_PID=""
|
|
PYTHON_BIN=${PYTHON_BIN:-python3}
|
|
|
|
print_section() {
|
|
printf '\n==> %s\n' "$1"
|
|
}
|
|
|
|
print_output() {
|
|
local title="$1"
|
|
local content="$2"
|
|
printf -- '---- %s ----\n' "$title"
|
|
printf '%s\n' "$content"
|
|
printf -- '---- end %s ----\n' "$title"
|
|
}
|
|
|
|
build_binaries() {
|
|
print_section "Building easytier-core and easytier-cli"
|
|
cargo build -p easytier --bin easytier-core --bin easytier-cli
|
|
}
|
|
|
|
ensure_binaries() {
|
|
if [[ "${SKIP_BUILD:-0}" != "1" ]] || [[ ! -x "$CORE_BIN" ]] || [[ ! -x "$CLI_BIN" ]]; then
|
|
build_binaries
|
|
fi
|
|
}
|
|
|
|
make_tmpdir() {
|
|
"$PYTHON_BIN" - <<'PY'
|
|
import tempfile
|
|
print(tempfile.mkdtemp(prefix="easytier-cli-e2e-"))
|
|
PY
|
|
}
|
|
|
|
cleanup_tmpdir() {
|
|
TMPDIR_TO_DELETE="$1" "$PYTHON_BIN" - <<'PY'
|
|
import os
|
|
import shutil
|
|
|
|
shutil.rmtree(os.environ["TMPDIR_TO_DELETE"], ignore_errors=True)
|
|
PY
|
|
}
|
|
|
|
alloc_port() {
|
|
"$PYTHON_BIN" - <<'PY'
|
|
import socket
|
|
|
|
sock = socket.socket()
|
|
sock.bind(("127.0.0.1", 0))
|
|
print(sock.getsockname()[1])
|
|
sock.close()
|
|
PY
|
|
}
|
|
|
|
wait_for_cli() {
|
|
local rpc_port="$1"
|
|
local attempts=0
|
|
while (( attempts < 50 )); do
|
|
if "$CLI_BIN" -p "127.0.0.1:${rpc_port}" -o json node >/dev/null 2>&1; then
|
|
return 0
|
|
fi
|
|
attempts=$((attempts + 1))
|
|
sleep 0.2
|
|
done
|
|
return 1
|
|
}
|
|
|
|
run_cmd() {
|
|
local __var_name="$1"
|
|
local title="$2"
|
|
shift 2
|
|
|
|
print_section "$title"
|
|
printf '+'
|
|
for arg in "$@"; do
|
|
printf ' %q' "$arg"
|
|
done
|
|
printf '\n'
|
|
|
|
local output
|
|
if ! output=$("$@" 2>&1); then
|
|
print_output "$title output" "$output"
|
|
return 1
|
|
fi
|
|
|
|
print_output "$title output" "$output"
|
|
printf -v "$__var_name" '%s' "$output"
|
|
}
|
|
|
|
assert_text_output() {
|
|
local text_output="$1"
|
|
grep -F '== e2e-inst-a (' <<<"$text_output" >/dev/null
|
|
grep -F '== e2e-inst-b (' <<<"$text_output" >/dev/null
|
|
}
|
|
|
|
assert_multi_instance_json() {
|
|
local json_payload="$1"
|
|
JSON_PAYLOAD="$json_payload" "$PYTHON_BIN" - <<'PY'
|
|
import json
|
|
import os
|
|
|
|
data = json.loads(os.environ["JSON_PAYLOAD"])
|
|
assert isinstance(data, list), data
|
|
assert len(data) == 2, data
|
|
|
|
names = {item["instance_name"] for item in data}
|
|
assert names == {"e2e-inst-a", "e2e-inst-b"}, names
|
|
|
|
for item in data:
|
|
assert item["instance_id"], item
|
|
assert isinstance(item["result"], dict), item
|
|
PY
|
|
}
|
|
|
|
assert_single_instance_json() {
|
|
local json_payload="$1"
|
|
JSON_PAYLOAD="$json_payload" "$PYTHON_BIN" - <<'PY'
|
|
import json
|
|
import os
|
|
|
|
data = json.loads(os.environ["JSON_PAYLOAD"])
|
|
assert isinstance(data, dict), data
|
|
assert data["config"].find('instance_name = "e2e-inst-a"') >= 0, data["config"]
|
|
PY
|
|
}
|
|
|
|
assert_whitelist_fanout() {
|
|
local json_payload="$1"
|
|
JSON_PAYLOAD="$json_payload" "$PYTHON_BIN" - <<'PY'
|
|
import json
|
|
import os
|
|
|
|
data = json.loads(os.environ["JSON_PAYLOAD"])
|
|
assert len(data) == 2, data
|
|
for item in data:
|
|
assert item["result"]["tcp_ports"] == ["80", "443"], item
|
|
assert item["result"].get("udp_ports", []) == [], item
|
|
PY
|
|
}
|
|
|
|
assert_single_instance_write() {
|
|
local json_payload="$1"
|
|
JSON_PAYLOAD="$json_payload" "$PYTHON_BIN" - <<'PY'
|
|
import json
|
|
import os
|
|
|
|
data = {item["instance_name"]: item["result"] for item in json.loads(os.environ["JSON_PAYLOAD"])}
|
|
assert data["e2e-inst-a"]["tcp_ports"] == ["80", "443"], data
|
|
assert data["e2e-inst-b"].get("tcp_ports", []) == [], data
|
|
PY
|
|
}
|
|
|
|
main() {
|
|
command -v "$PYTHON_BIN" >/dev/null 2>&1 || {
|
|
echo "python interpreter not found: $PYTHON_BIN" >&2
|
|
exit 1
|
|
}
|
|
|
|
ensure_binaries
|
|
|
|
TMPDIR_PATH=$(make_tmpdir)
|
|
print_section "Created temporary test directory"
|
|
printf '%s\n' "$TMPDIR_PATH"
|
|
|
|
local rpc_port
|
|
rpc_port=$(alloc_port)
|
|
print_section "Allocated RPC port"
|
|
printf '%s\n' "$rpc_port"
|
|
|
|
cleanup() {
|
|
if [[ -n "$CORE_PID" ]] && kill -0 "$CORE_PID" >/dev/null 2>&1; then
|
|
kill "$CORE_PID" >/dev/null 2>&1 || true
|
|
wait "$CORE_PID" >/dev/null 2>&1 || true
|
|
fi
|
|
if [[ -n "$TMPDIR_PATH" ]]; then
|
|
cleanup_tmpdir "$TMPDIR_PATH"
|
|
fi
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
cat >"$TMPDIR_PATH/inst-a.toml" <<'EOF'
|
|
instance_name = "e2e-inst-a"
|
|
listeners = []
|
|
|
|
[network_identity]
|
|
network_name = "e2e-net-a"
|
|
network_secret = ""
|
|
|
|
[flags]
|
|
no_tun = true
|
|
enable_ipv6 = false
|
|
EOF
|
|
|
|
cat >"$TMPDIR_PATH/inst-b.toml" <<'EOF'
|
|
instance_name = "e2e-inst-b"
|
|
listeners = []
|
|
|
|
[network_identity]
|
|
network_name = "e2e-net-b"
|
|
network_secret = ""
|
|
|
|
[flags]
|
|
no_tun = true
|
|
enable_ipv6 = false
|
|
EOF
|
|
|
|
"$CORE_BIN" --config-dir "$TMPDIR_PATH" --rpc-portal "127.0.0.1:${rpc_port}" \
|
|
>"$TMPDIR_PATH/core.log" 2>&1 &
|
|
CORE_PID=$!
|
|
print_section "Started easytier-core"
|
|
printf 'pid=%s\n' "$CORE_PID"
|
|
|
|
wait_for_cli "$rpc_port"
|
|
print_output "easytier-core startup log" "$(cat "$TMPDIR_PATH/core.log")"
|
|
|
|
local text_output
|
|
run_cmd text_output \
|
|
"Case 1: node fanout in table mode" \
|
|
"$CLI_BIN" -p "127.0.0.1:${rpc_port}" node
|
|
assert_text_output "$text_output"
|
|
|
|
local json_output
|
|
run_cmd json_output \
|
|
"Case 2: node fanout in JSON mode" \
|
|
"$CLI_BIN" -p "127.0.0.1:${rpc_port}" -o json node
|
|
assert_multi_instance_json "$json_output"
|
|
|
|
local single_output
|
|
run_cmd single_output \
|
|
"Case 3: explicit instance selector stays single-instance" \
|
|
"$CLI_BIN" -p "127.0.0.1:${rpc_port}" --instance-name e2e-inst-a -o json node
|
|
assert_single_instance_json "$single_output"
|
|
|
|
local set_whitelist_output
|
|
run_cmd set_whitelist_output \
|
|
"Case 4: whitelist set-tcp fans out to all instances" \
|
|
"$CLI_BIN" -p "127.0.0.1:${rpc_port}" whitelist set-tcp 80,443
|
|
|
|
local whitelist_output
|
|
run_cmd whitelist_output \
|
|
"Case 5: whitelist show confirms fanout write" \
|
|
"$CLI_BIN" -p "127.0.0.1:${rpc_port}" -o json whitelist show
|
|
assert_whitelist_fanout "$whitelist_output"
|
|
|
|
local clear_whitelist_output
|
|
run_cmd clear_whitelist_output \
|
|
"Case 6: explicit selector write only touches one instance" \
|
|
"$CLI_BIN" -p "127.0.0.1:${rpc_port}" --instance-name e2e-inst-b whitelist clear-tcp
|
|
|
|
local cleared_output
|
|
run_cmd cleared_output \
|
|
"Case 7: whitelist show confirms single-instance write isolation" \
|
|
"$CLI_BIN" -p "127.0.0.1:${rpc_port}" -o json whitelist show
|
|
assert_single_instance_write "$cleared_output"
|
|
|
|
local credential_table
|
|
run_cmd credential_table \
|
|
"Case 8: credential peers table fans out to all instances" \
|
|
"$CLI_BIN" -p "127.0.0.1:${rpc_port}" credential peers
|
|
assert_text_output "$credential_table"
|
|
|
|
local credential_json
|
|
run_cmd credential_json \
|
|
"Case 9: credential peers JSON preserves instance wrappers" \
|
|
"$CLI_BIN" -p "127.0.0.1:${rpc_port}" -o json credential peers
|
|
assert_multi_instance_json "$credential_json"
|
|
JSON_PAYLOAD="$credential_json" "$PYTHON_BIN" - <<'PY'
|
|
import json
|
|
import os
|
|
|
|
data = json.loads(os.environ["JSON_PAYLOAD"])
|
|
assert all(item["result"] == {"credentials": []} for item in data), data
|
|
PY
|
|
|
|
local credential_single
|
|
run_cmd credential_single \
|
|
"Case 10: credential peers explicit selector returns one instance" \
|
|
"$CLI_BIN" -p "127.0.0.1:${rpc_port}" --instance-name e2e-inst-a -o json credential peers
|
|
JSON_PAYLOAD="$credential_single" "$PYTHON_BIN" - <<'PY'
|
|
import json
|
|
import os
|
|
|
|
assert json.loads(os.environ["JSON_PAYLOAD"]) == {"credentials": []}
|
|
PY
|
|
|
|
print_section "Result"
|
|
echo "CLI multi-instance E2E passed"
|
|
}
|
|
|
|
main "$@"
|