|
|
@@ -0,0 +1,171 @@
|
|
|
+#!/usr/bin/env bash
|
|
|
+# v2.4 end-to-end TLS + auth smoke test.
|
|
|
+#
|
|
|
+# Spins up a server with two listeners — local plaintext on 127.0.0.1:9008
|
|
|
+# AND TLS + auth on 127.0.0.1:9444 — then drives the v2.4 client against
|
|
|
+# each path:
|
|
|
+# 1) v2.3-shape client (no TLS, no auth) → 9008 → write + read works
|
|
|
+# 2) v2.4 client (tls_enabled, valid auth_token) → 9444 → write + read works
|
|
|
+# 3) v2.4 client (tls_enabled, WRONG token) → 9444 → UNAUTHENTICATED
|
|
|
+# 4) v2.4 client (no token) → 9444 → UNAUTHENTICATED
|
|
|
+#
|
|
|
+# Build with the same recipe as the other load_test_* binaries — see the
|
|
|
+# inline g++ line.
|
|
|
+
|
|
|
+set -euo pipefail
|
|
|
+
|
|
|
+cd "$(dirname "$0")"
|
|
|
+
|
|
|
+ROOT=/data/smartbotic-database
|
|
|
+DIR=/tmp/sbdbv24-tls-auth-e2e
|
|
|
+
|
|
|
+rm -rf "$DIR"
|
|
|
+mkdir -p "$DIR/data"
|
|
|
+
|
|
|
+# Generate a real auth key with the new CLI helper.
|
|
|
+AUTH_KEY="$("$ROOT/build/cli/smartbotic-db-cli" generate-auth-key)"
|
|
|
+echo "Auth key: $AUTH_KEY"
|
|
|
+
|
|
|
+cat > "$DIR/config.json" <<EOF
|
|
|
+{
|
|
|
+ "storage": {
|
|
|
+ "data_directory": "$DIR/data",
|
|
|
+ "listeners": [
|
|
|
+ { "bind": "127.0.0.1", "port": 9008,
|
|
|
+ "tls": { "enabled": false }, "auth": { "required": false } },
|
|
|
+ { "bind": "127.0.0.1", "port": 9444,
|
|
|
+ "tls": { "enabled": true, "auto_self_signed_if_missing": true },
|
|
|
+ "auth": { "required": true, "keys": ["$AUTH_KEY"] } }
|
|
|
+ ],
|
|
|
+ "encryption": { "enabled": false },
|
|
|
+ "migrations": { "enabled": false },
|
|
|
+ "replication": { "enabled": false }
|
|
|
+ }
|
|
|
+}
|
|
|
+EOF
|
|
|
+
|
|
|
+echo ""
|
|
|
+echo "=== Phase 1: boot server with both listeners ==="
|
|
|
+"$ROOT/build/service/smartbotic-database" --config "$DIR/config.json" \
|
|
|
+ > "$DIR/server.log" 2>&1 &
|
|
|
+PID=$!
|
|
|
+trap "kill $PID 2>/dev/null || true" EXIT
|
|
|
+sleep 3
|
|
|
+
|
|
|
+grep -E "gRPC listener up|self-signed" "$DIR/server.log" | head -5
|
|
|
+
|
|
|
+# Build the four-shape test client inline.
|
|
|
+echo ""
|
|
|
+echo "=== Phase 2: build test driver ==="
|
|
|
+cat > "$DIR/test_driver.cpp" <<'CPP'
|
|
|
+#include <smartbotic/database/client.hpp>
|
|
|
+#include <iostream>
|
|
|
+#include <string>
|
|
|
+
|
|
|
+using namespace smartbotic::database;
|
|
|
+
|
|
|
+int main(int argc, char* argv[]) {
|
|
|
+ if (argc < 2) { std::cerr << "usage: scenario [args]\n"; return 99; }
|
|
|
+ std::string scenario = argv[1];
|
|
|
+ std::string auth_key = (argc > 2) ? argv[2] : "";
|
|
|
+ std::string cert_path = (argc > 3) ? argv[3] : "";
|
|
|
+
|
|
|
+ Client::Config cfg;
|
|
|
+ if (scenario == "plaintext_local") {
|
|
|
+ cfg.address = "localhost:9008";
|
|
|
+ } else if (scenario == "tls_authed") {
|
|
|
+ cfg.address = "localhost:9444";
|
|
|
+ cfg.tls_enabled = true;
|
|
|
+ cfg.tls_ca_cert_path = cert_path;
|
|
|
+ cfg.auth_token = auth_key;
|
|
|
+ } else if (scenario == "tls_wrong_token") {
|
|
|
+ cfg.address = "localhost:9444";
|
|
|
+ cfg.tls_enabled = true;
|
|
|
+ cfg.tls_ca_cert_path = cert_path;
|
|
|
+ cfg.auth_token = "wrong-token";
|
|
|
+ } else if (scenario == "tls_no_token") {
|
|
|
+ cfg.address = "localhost:9444";
|
|
|
+ cfg.tls_enabled = true;
|
|
|
+ cfg.tls_ca_cert_path = cert_path;
|
|
|
+ } else {
|
|
|
+ std::cerr << "unknown scenario: " << scenario << "\n";
|
|
|
+ return 99;
|
|
|
+ }
|
|
|
+ cfg.timeoutMs = 3000;
|
|
|
+
|
|
|
+ Client c(cfg);
|
|
|
+ if (!c.connect()) {
|
|
|
+ std::cerr << "connect failed\n";
|
|
|
+ return 2;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ nlohmann::json doc{{"hello", "world"}};
|
|
|
+ std::string id = c.insert("tls_test", doc, scenario);
|
|
|
+ auto got = c.get("tls_test", id);
|
|
|
+ if (!got) { std::cerr << "round-trip lost the doc\n"; return 3; }
|
|
|
+ std::cout << scenario << " OK: " << id << "\n";
|
|
|
+ return 0;
|
|
|
+ } catch (const std::exception& e) {
|
|
|
+ std::cout << scenario << " THREW: " << e.what() << "\n";
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+}
|
|
|
+CPP
|
|
|
+
|
|
|
+g++ -std=c++20 -O2 \
|
|
|
+ -I"$ROOT/client/include" \
|
|
|
+ "$DIR/test_driver.cpp" \
|
|
|
+ -L"$ROOT/build/client" -lsmartbotic-db-client \
|
|
|
+ $(pkg-config --libs grpc++) \
|
|
|
+ -lspdlog -lfmt -pthread \
|
|
|
+ -Wl,-rpath,"$ROOT/build/client" \
|
|
|
+ -o "$DIR/test_driver"
|
|
|
+
|
|
|
+# The self-signed cert lives at $DIR/data/tls/auto_self_signed.pem
|
|
|
+CERT="$DIR/data/tls/auto_self_signed.pem"
|
|
|
+if [[ ! -f "$CERT" ]]; then
|
|
|
+ echo "MISSING self-signed cert at $CERT — server boot didn't generate one"
|
|
|
+ cat "$DIR/server.log" | tail -20
|
|
|
+ exit 5
|
|
|
+fi
|
|
|
+
|
|
|
+echo ""
|
|
|
+echo "=== Phase 3: 4 scenarios ==="
|
|
|
+
|
|
|
+PASS=0
|
|
|
+FAIL=0
|
|
|
+
|
|
|
+run_case() {
|
|
|
+ local name="$1"; local expect_rc="$2"; shift 2
|
|
|
+ set +e
|
|
|
+ "$DIR/test_driver" "$@" > "$DIR/out_${name}.log" 2>&1
|
|
|
+ local rc=$?
|
|
|
+ set -e
|
|
|
+ if [[ "$rc" == "$expect_rc" ]]; then
|
|
|
+ echo " PASS $name (rc=$rc)"
|
|
|
+ PASS=$((PASS+1))
|
|
|
+ else
|
|
|
+ echo " FAIL $name (rc=$rc, expected $expect_rc)"
|
|
|
+ cat "$DIR/out_${name}.log" | head -5 | sed 's/^/ /'
|
|
|
+ FAIL=$((FAIL+1))
|
|
|
+ fi
|
|
|
+}
|
|
|
+
|
|
|
+run_case plaintext_local 0 plaintext_local
|
|
|
+run_case tls_authed 0 tls_authed "$AUTH_KEY" "$CERT"
|
|
|
+run_case tls_wrong_token 1 tls_wrong_token "$AUTH_KEY" "$CERT"
|
|
|
+run_case tls_no_token 1 tls_no_token "$AUTH_KEY" "$CERT"
|
|
|
+
|
|
|
+echo ""
|
|
|
+echo "=== Summary ==="
|
|
|
+echo " PASS: $PASS / 4"
|
|
|
+echo " FAIL: $FAIL"
|
|
|
+
|
|
|
+if [[ "$FAIL" -gt 0 ]]; then
|
|
|
+ echo ""
|
|
|
+ echo "--- server.log tail ---"
|
|
|
+ tail -30 "$DIR/server.log"
|
|
|
+fi
|
|
|
+
|
|
|
+exit $FAIL
|