Prechádzať zdrojové kódy

release(v2.4.0): per-listener TLS + bearer-token auth

End-to-end TLS+auth for cross-droplet deployments. Local clients keep
the v2.3 plaintext loopback path with zero code change; new
listeners on LAN or public IPs opt in to TLS + auth.

## What landed across Stages A–G

- **A** (b944f5e) — config schema + back-compat shim
- **B** (1dd30be) — multi-listener gRPC startup
- **C** (9d6ad72) — TLS listener + self-signed cert generation via
  OpenSSL 3 EVP API
- **D** (98c213a) — BearerAuthProcessor via SetAuthMetadataProcessor
- **E** (96d21d6, subagent) — Client tls/auth fields + connect logic
- **F** (c68171e, subagent) — CLI helpers generate-auth-key +
  generate-tls-cert

## End-to-end test

`tests/load_test/test_v24_tls_auth.sh`:
  - Generates an auth key via the new CLI
  - Boots a server with TWO listeners: 127.0.0.1:9008 plaintext + no
    auth, AND 127.0.0.1:9444 TLS + auth.required
  - Compiles a 4-scenario test driver, runs it:
    PASS  plaintext_local (rc=0)
    PASS  tls_authed (rc=0)
    PASS  tls_wrong_token (rc=1)
    PASS  tls_no_token (rc=1)

ctest 14/14 still green. No regression.

## Defaults preserved for upgrade safety

A v2.3 config (bind_address + rpc_port, no listeners[]) parses
identically — synthesised into one plaintext listener with no auth.
Every existing consumer keeps working unchanged.
fszontagh 1 mesiac pred
rodič
commit
ee59b96431
3 zmenil súbory, kde vykonal 172 pridanie a 1 odobranie
  1. 0 0
      CLAUDE.md
  2. 1 1
      VERSION
  3. 171 0
      tests/load_test/test_v24_tls_auth.sh

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 0 - 0
CLAUDE.md


+ 1 - 1
VERSION

@@ -1 +1 @@
-2.3.3
+2.4.0

+ 171 - 0
tests/load_test/test_v24_tls_auth.sh

@@ -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

Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov