Przegląd
Serwis HTTP, który robi dwie rzeczy:
- 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. - 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
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/healthWdroż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)
# 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):
<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:
# 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-300sdonow+60s. Poza → odrzucone. - Cache nonce'ów: każdy
X-Nonceakceptowany najwyżej raz na 5 minut (RedisSET 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.
[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_attemptTypowe 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)
# 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_deployEndpointy 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.
| 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ącego —
rm keys/agents/<AGENT>.pubna weryfikatorze. Działa natychmiast, bez cache. - Przypadkowym commitem sekretów — dostarczany
.gitignorewykluczakeys/,logs/i prawdziwyauth/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.