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:
1 parent
39659f0144
commit
1c2a5b71bd
6 files changed
+232
-9
No files matched your search
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user