API keys for server scope: edges, server alerts and orgs can be scripted

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
VeillaxandClaude Sonnet 5.5 committed 2026-10-27 16:54:14 -06:00
1 parent 39659f0144
commit 1c2a5b71bd
6 files changed
+232 -9

No files matched your search

+83
View File
@@ -0,0 +1,83 @@
package hammerhead
import (
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func keyCall(t *testing.T, s *Server, key, method, path, body string) (int, map[string]any) {
t.Helper()
req := httptest.NewRequest(method, path, strings.NewReader(body))
if key != "" {
req.Header.Set("Authorization", "Bearer "+key)
}
rec := httptest.NewRecorder()
s.Handler().ServeHTTP(rec, req)
var out map[string]any
json.Unmarshal(rec.Body.Bytes(), &out)
return rec.Code, out
}
func mintKey(t *testing.T, s *Server, org string) string {
t.Helper()
k := "hhk_" + randHex(4) + "_" + randHex(24)
parts := strings.SplitN(k, "_", 3)
s.mu.Lock()
s.st.APIKeys = append(s.st.APIKeys, &APIKey{ID: parts[1], OrgID: org, Name: "t", Hash: sha(parts[2])})
s.mu.Unlock()
return k
}
func TestServerKeysManageEdgesAndOrgs(t *testing.T) {
f := newFx(t)
server := mintKey(t, f.s, "")
orgKey := mintKey(t, f.s, f.org)
if c, _ := keyCall(t, f.s, server, "POST", "/api/edges", `{"name":"e2","tunnelAddr":"1.2.3.4:51820"}`); c != 200 {
t.Fatalf("server key should create an edge, got %d", c)
}
if c, _ := keyCall(t, f.s, orgKey, "POST", "/api/edges", `{"name":"e3","tunnelAddr":"1.2.3.4:51820"}`); c != 401 {
t.Fatalf("org key must not create edges, got %d", c)
}
if c, _ := keyCall(t, f.s, server, "POST", "/api/orgs", `{"name":"Second"}`); c != 200 {
t.Fatalf("server key should create an org, got %d", c)
}
// server key reaches any org's endpoints; an org key only its own
if c, _ := keyCall(t, f.s, server, "POST", "/api/orgs/"+f.org+"/sites", `{"name":"x"}`); c != 200 {
t.Fatalf("server key on org: %d", c)
}
if c, _ := keyCall(t, f.s, orgKey, "POST", "/api/orgs/nope/sites", `{"name":"x"}`); c != 404 {
t.Fatalf("org key on other org: %d", c)
}
// server alert channels
if c, _ := keyCall(t, f.s, server, "GET", "/api/admin/alerts", ""); c != 200 {
t.Fatalf("server key alerts: %d", c)
}
// keys can't mint keys at server level
if c, _ := keyCall(t, f.s, server, "POST", "/api/admin/apikeys", `{"name":"k"}`); c != 401 {
t.Fatalf("server key must not mint server keys, got %d", c)
}
if c, _ := keyCall(t, f.s, "", "GET", "/api/orgs", ""); c != 401 {
t.Fatalf("anonymous list orgs: %d", c)
}
req := httptest.NewRequest("GET", "/api/orgs", nil)
req.Header.Set("Authorization", "Bearer "+orgKey)
rec := httptest.NewRecorder()
f.s.Handler().ServeHTTP(rec, req)
var orgs []Org
json.Unmarshal(rec.Body.Bytes(), &orgs)
if rec.Code != http.StatusOK || len(orgs) != 1 || orgs[0].ID != f.org {
t.Fatalf("org key should list only its org: %d %s", rec.Code, rec.Body)
}
// org key lists edges its org may use
req = httptest.NewRequest("GET", "/api/edges", nil)
req.Header.Set("Authorization", "Bearer "+orgKey)
rec = httptest.NewRecorder()
f.s.Handler().ServeHTTP(rec, req)
if rec.Code != 200 || !strings.Contains(rec.Body.String(), "edge-1") {
t.Fatalf("org key edges: %d %s", rec.Code, rec.Body)
}
}