buildonai-key-serverv2.1.1AGPL-3.0-only + Komercyjna

BuildOnAI Key Server

Uwierzytelnianie przez podpis ed25519 na każde zapytanie. Bez tokenów do wystawiania, bez sesji do utrzymania, bez JWT do obsługi. Klucz jest tożsamością.

Przegląd

Serwis HTTP, który robi dwie rzeczy:

  1. Weryfikuje podpisane ed25519 zapytania. Wywołujący podpisuje (method, path, timestamp, nonce, body-hash)swoim kluczem prywatnym. Serwer trzyma odpowiadający klucz publiczny w pliku i odpowiada { valid: true | false }. Bezstanowo. Bez session store. Bez wystawiania tokenów. Bez zarządzania wygasaniem.
  2. Wydaje sekrety na żądanie. Prywatne klucze SSH dla deployu/pushu, tokeny API dla wywołań wychodzących. Lista dozwolonych IP jest pierwszą bramą; każde wrażliwe żądanie do vaulta lub logu audytu wymaga też ważnego podpisu ed25519.

Zaprojektowany jako sidecar dla Consciousness Server i Cortex, ale działa samodzielnie dla każdego serwisu HTTP, który chce uwierzytelniania na bazie klucza bez stawiania Vaulta, OAuth czy issuera JWT.

Po co to istnieje

Jeśli prowadzisz grupę serwisów albo agentów komunikujących się ze sobą po HTTP, dzisiejsze opcje auth są złe na trzy różne sposoby:

  • Bearer tokeny w nagłówkach. Teraz masz: wystawianie tokenów, ich przechowywanie, rotację, odwoływanie, refresh flow, bazę danych tokenów do backupu. Połowa "auth" to teraz "token lifecycle".
  • mTLS między każdą parą. Czystszy model zaufania, ale każdy operator wpada w tę samą ścianę: certificate authority, certy pośrednie, rotacja i trudna historia o odwoływaniu.
  • Wspólny sekret w .env. Działa do dnia, w którym wycieknie — wtedy wszyscy dowiadują się o tym równocześnie.

Podpisane zapytania siedzą pośrodku. Klucz publiczny agenta to plik na dysku weryfikatora. Skasowanie pliku to odwołanie agenta. Podpis jest per-request, więc nie ma tokena do ukradnięcia który daje długoterminowy dostęp. Weryfikator nie trzyma stanu o tym kto co podpisał — tylko "czy ten podpis jest prawidłowy dla tej kanonicznej wiadomości".

Ten wzorzec jest dobrze znany (AWS SigV4, GitHub Apps JWT, SSH agent forwarding). BuildOnAI Key Server to samodzielna implementacja tego wzorca.

Instalacja

terminal
git clone https://github.com/build-on-ai/buildonai-key-server.git
cd buildonai-key-server
cp auth/allowed-clients.json.example auth/allowed-clients.json
# edytuj auth/allowed-clients.json: dodaj IP klientów którzy będą się łączyć
docker compose up -d
curl http://localhost:3040/health

Wdrożenie samodzielne: to repozytorium domyślnie wystawia port 3040. Gdy Key Server jest uruchomiony jako część Consciousness Servera, domyślny port hosta to13040. Port samodzielnego wdrożenia zmienisz przezKEY_SERVER_PORT.

Rejestracja agenta (ed25519)

terminal
# Na hoście agenta:
ssh-keygen -t ed25519 -C "agent1@$(hostname)" \
  -f ~/.ssh/buildonai-agent1 -N ""

# Skopiuj klucz PUBLICZNY na hosta key-server:
scp ~/.ssh/buildonai-agent1.pub \
    operator@key-server-host:/opt/buildonai-key-server/keys/agents/agent1.pub

# Sprawdź czy serwer go widzi:
curl http://key-server-host:3040/api/agents/identity
# {"agents": ["agent1"]}

Klucz prywatny zostaje na hoście agenta. Nigdy go nie opuszcza. Odwołanie to rm keys/agents/agent1.pub — działa natychmiast, nie ma cache.

Podpisane trasy

Wrażliwe trasy — odczyty vaulta i log audytu — wymagają zarówno dozwolonego IP wywołującego, jak i ważnego podpisu ed25519. Brak lub błędny podpis daje 401. Health, publiczna tożsamość i endpoint weryfikacji pozostają celowo dostępne dla dozwolonego IP.

Użyj dostarczonego helpera bin/sign-request albo zaimplementuj w kliencie udokumentowaną wiadomość kanoniczną. Nie ma trybu zgodności z niepodpisanymi żądaniami.

Pojęcia

Wiadomość kanoniczna

Agent, gated service i key-server muszą zrekonstruować te same bajty żeby podpisać i zweryfikować. Pięć pól łączonych literalnym\n (LF):

canonical message
<METHOD>\n<PATH>\n<X-Timestamp>\n<X-Nonce>\nSHA256(body)

SHA256(body) jest hex-encoded. PATH to ścieżka requesta bez query string. Dla GET bez body,SHA256("") = e3b0c442...52b855.

Weryfikacja podpisanego zapytania

Gated service (Consciousness Server, Twoje API, Twój odbiorca webhooków) wyciąga cztery nagłówki + body hash z przychodzącego requesta i POST-uje je do /api/verify:

terminal
# Gated service przesyła headery agenta + body hash do /api/verify.
curl -s -X POST http://key-server-host:3040/api/verify \
  -H 'Content-Type: application/json' \
  -d '{
    "agent_id": "agent1",
    "method": "POST",
    "path": "/api/notes/create",
    "timestamp": "2026-05-31T10:00:00Z",
    "nonce": "a7b3c1d9e2f64a18b3c1d9e2f64a18b3",
    "signature": "base64-ed25519-sig-tutaj",
    "body_sha256": "e3b0c44298fc1c149afbf4c8996fb924..."
  }'
# {"valid": true}

Anti-replay

  • Okno timestampu: now-300s do now+60s. Poza → odrzucone.
  • Cache nonce'ów: każdy X-Nonce akceptowany najwyżej raz na 5 minut (Redis SET NX EX 300).
  • Powiązanie z body: wiadomość kanoniczna zawiera SHA256(body) — przechwycony request nie da się odtworzyć z innym endpointem czy zmienionym body.

Układ vaulta

Układ na dysku pod keys/ jest celowo prosty —ls pokazuje co tam jest. Per-agent klucze publiczne ed25519 leżą w keys/agents/<AGENT>.pub. Klucze prywatne SSH w keys/ssh/<name>; tokeny API wkeys/<service>/api-key.txt. chmod 600na prywatne materiały.

Log audytu

Każde zapytanie — verify, vault read, sukces lub porażka — dodaje jedną linię do logs/audit.log plus wpis JSONL dologs/audit.jsonl. Self-rotation co 50 MB.

logs/audit.log
[2026-05-31T08:00:01Z] IP=192.0.2.20 ENDPOINT=/api/verify RESULT=VALID agent=agent1
[2026-05-31T08:00:03Z] IP=192.0.2.30 ENDPOINT=/api/verify RESULT=VALID agent=agent2
[2026-05-31T08:00:18Z] IP=192.0.2.30 ENDPOINT=/api/verify RESULT=INVALID reason=nonce_replayed
[2026-05-31T14:22:05Z] IP=192.0.2.99 ENDPOINT=/keys/ssh/git-deploy RESULT=FORBIDDEN IP_not_whitelisted
[2026-05-31T14:22:18Z] IP=192.0.2.20 ENDPOINT=/keys/ssh/../etc/passwd RESULT=REJECTED path_traversal_attempt

Typowe zastosowania

Inter-service auth w małym monorepo

Każdy serwis trzyma własny klucz prywatny ed25519 i podpisuje wychodzące requesty. Weryfikator to jeden kontener. UsuwaSHARED_SECRET env var per serwis, którego nikt nie rotuje.

Uwierzytelnianie webhooków

Twoje źródło podpisuje payload przed wysłaniem; odbiorca przesyła nagłówki do /api/verify. Kryptograficzny dowód pochodzenia bez współdzielonego sekretu HMAC per źródło. Skradziony plik klucza publicznego? Jest publiczny — nie ma współdzielonego sekretu do ukradnięcia.

Uwierzytelnianie urządzeń IoT

Każde urządzenie generuje własną parę kluczy ed25519 przy pierwszym uruchomieniu. Klucz publiczny rejestrowany out of band. Telemetria podpisana per request. Skradzione urządzenie → rm jego plik .pub, telemetria przestaje być akceptowana, jego zapisany klucz prywatny jest teraz bezużyteczny przeciw Tobie.

Vault read (opcjonalny, legacy)

dowolny IP-allow-listed host
# Helper wypisuje cztery nagłówki podpisu dla tego żądania.
eval curl -s $(bin/sign-request agent1 GET /keys/ssh/github-deploy) \
  http://localhost:3040/keys/ssh/github-deploy > ~/.ssh/id_deploy
chmod 600 ~/.ssh/id_deploy

Endpointy vaulta (/keys/ssh/<name>,/keys/api/<service>) dziedziczą ten sam model zaufania co /api/verify: IP allow-list i ważny podpis ed25519 dla każdego wrażliwego żądania.

API

Siedem endpointów. Cała powierzchnia.

MethodPathPurpose
GET/healthSonda liveness
GET/api/agents/identityLista zarejestrowanych agentów
GET/api/agents/identity/:agentKlucz publiczny + fingerprint dla agenta
POST/api/verifyWeryfikacja podpisanego requesta (wywoływana przez gated services)
GET/keys/ssh/:namePobierz prywatny klucz SSH z vaulta
GET/keys/api/:servicePobierz token API z vaulta
GET/auditOstatnie 100 wpisów w logu audytu

Model zagrożeń

Broni przed

  • Replayem w tej samej sieci LAN — nonce + okno timestampu.
  • Sfałszowanym pochodzeniem z obcego hosta — lista dozwolonych IP jako warstwa pierwsza.
  • Przechwyconym-i-zreplayowanym pod innym endpointem— wiadomość kanoniczna wiąże method + path + body hash.
  • Skompromitowanym agentem którego klucz prywatny jest w rękach atakującegorm keys/agents/<AGENT>.pub na weryfikatorze. Działa natychmiast, bez cache.
  • Przypadkowym commitem sekretów — dostarczany.gitignore wyklucza keys/,logs/ i prawdziwy auth/allowed-clients.json.

Nie broni przed (celowy zakres)

  • Podsłuchem treści requesta w sieci. ed25519 to schemat podpisu, nie szyfrowanie. Owiń za TLS (Caddy, nginx) jeśli requesty przenoszą wrażliwe payloady.
  • Atakującym z rootem na hoście. Może czytać klucze prywatne, vault kluczy publicznych i log audytu bezpośrednio z dysku.
  • Wysokowolumenowym DoS-em na /api/verify.Serwer nie rate-limituje niczego; postaw go za reverse proxy jeśli niezaufani wywołujący mogą do niego dojść.

Dla deploymentów poza zaufaną LAN/VPN, oczywiste upgrade'y to: terminować TLS na reverse proxy, postawić w prywatnej sieci, ustawić ścisłe permissions na keys/ i rotować klucze agentów w cyklu. Model zagrożeń napisany dla niskotarciowego użycia LAN; dostrajaj wraz z poszerzaniem się perymetru.

Dalsze kroki