buildonai-key-server v2.0.0 AGPL-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 jako pierwsza brama; w trybie AUTH_MODE=enforce dochodzi podpis 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

Domyślny port to 3040 — część zarezerwowanego zakresu ekosystemu BuildOnAI 3030–3050. Zmień przez KEY_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.

Trzy tryby auth

AUTH_MODE jest czytany raz przy starcie. Trzy prawidłowe wartości dają bezpieczną ścieżkę migracji z deploymentów niepodpisanych do podpisanych.

Tryb Zachowanie Kiedy używać
off Middleware no-op. Podpisy nie są sprawdzane. Jeden host, jeden użytkownik, brak niezaufanych wywołujących. Domyślne.
observe Nieprawidłowe podpisy przechodzą, ale powód jest logowany do logs/auth-observe.log. Migracja z unsigned do signed: włącz, napraw każdego wywołującego którego request byłby odrzucony, potem przejdź na enforce.
enforce Prawidłowe podpisy przechodzą. Reszta → 401 (lub 503 jeśli sam lookup zawiedzie). Multi-agent, współdzielony host, wszystko co ma być audytowalne.

Trzy-trybowy design jest celowy. Pojedynczy boolean AUTH=on/off zabija ścieżkę migracji — w dniu kiedy przełączasz, połowa agentów przestaje działać, bo ktoś zapomniał podpiąć klienta podpisującego. observe to bezpieczna próba generalna.

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 w keys/<service>/api-key.txt. chmod 600 na 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 do logs/audit.jsonl. Self-rotation co 50 MB.

logs/audit.log
[2026-05-31T08:00:01Z] IP=10.0.0.20 ENDPOINT=/api/verify RESULT=VALID agent=agent1
[2026-05-31T08:00:03Z] IP=10.0.0.30 ENDPOINT=/api/verify RESULT=VALID agent=agent2
[2026-05-31T08:00:18Z] IP=10.0.0.30 ENDPOINT=/api/verify RESULT=INVALID reason=nonce_replayed
[2026-05-31T14:22:05Z] IP=10.0.0.99 ENDPOINT=/keys/ssh/git-deploy RESULT=FORBIDDEN IP_not_whitelisted
[2026-05-31T14:22:18Z] IP=10.0.0.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. Usuwa SHARED_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
# Vault read w trybie AUTH_MODE=off (tylko IP allow-list):
curl -s 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 zawsze, podpis ed25519 dodatkowo w trybie AUTH_MODE=enforce.

API

Siedem endpointów. Cała powierzchnia.

Method Path Purpose
GET /health Sonda liveness
GET /api/agents/identity Lista zarejestrowanych agentów
GET /api/agents/identity/:agent Klucz publiczny + fingerprint dla agenta
POST /api/verify Weryfikacja podpisanego requesta (wywoływana przez gated services)
GET /keys/ssh/:name Pobierz prywatny klucz SSH z vaulta
GET /keys/api/:service Pobierz token API z vaulta
GET /audit Ostatnie 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