package httpapi import ( "encoding/json" "net/http" "net/http/httptest" "strings" "testing" ) func TestPostSpeaker_defaultsFromEndpointIP(t *testing.T) { srv, err := New(Options{InsecureDev: true, SeedDemo: true, BundleSeedHex: testBundleSeed}) if err != nil { t.Fatal(err) } defer srv.Close() tenant, _, _, _, _ := srv.Store().DemoIDs() mustSetTestAPIKeys(t, srv, "edkey|"+tenant+"|editor") body := `{"endpoint":"https://203.0.113.55:8443","role":"replica"}` req := httptest.NewRequest(http.MethodPost, "/v1/speakers", strings.NewReader(body)) req.Header.Set("Authorization", "Bearer edkey") req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() srv.Handler().ServeHTTP(rec, req) if rec.Code != http.StatusCreated { t.Fatalf("status %d body %s", rec.Code, rec.Body.String()) } var out map[string]any if err := json.Unmarshal(rec.Body.Bytes(), &out); err != nil { t.Fatal(err) } if out["agent_secret"] == nil || out["agent_secret"] == "" { t.Fatal("expected agent_secret on create") } if out["node_token"] == nil || out["node_token"] == "" { t.Fatal("expected node_token on replica create") } if out["node_ipv4"] != "203.0.113.55" { t.Fatalf("node_ipv4: %#v", out["node_ipv4"]) } if out["bird_bgp_source_ipv4"] != "203.0.113.55" { t.Fatalf("bird_bgp_source_ipv4: %#v", out["bird_bgp_source_ipv4"]) } install, _ := out["install"].(map[string]any) if install == nil { t.Fatal("expected install on replica create") } cmd, _ := install["docker_commands"].(string) if !strings.Contains(cmd, "traefik") || !strings.Contains(cmd, "dnschallenge") { t.Fatalf("docker_commands missing traefik dns challenge: %s", cmd[:min(200, len(cmd))]) } } func TestDeleteSpeaker(t *testing.T) { srv, err := New(Options{InsecureDev: true, SeedDemo: true, BundleSeedHex: testBundleSeed}) if err != nil { t.Fatal(err) } defer srv.Close() tenant, _, _, _, demoSpk := srv.Store().DemoIDs() mustSetTestAPIKeys(t, srv, "edkey|"+tenant+"|editor") req := httptest.NewRequest(http.MethodDelete, "/v1/speakers/"+demoSpk, nil) req.Header.Set("Authorization", "Bearer edkey") rec := httptest.NewRecorder() srv.Handler().ServeHTTP(rec, req) if rec.Code != http.StatusNoContent { t.Fatalf("status %d body %s", rec.Code, rec.Body.String()) } if _, err := srv.Store().GetSpeaker(tenant, demoSpk); err == nil { t.Fatal("speaker should be deleted") } } func TestGetBundleSigningPublicKey(t *testing.T) { srv, err := New(Options{InsecureDev: true, SeedDemo: true, BundleSeedHex: testBundleSeed}) if err != nil { t.Fatal(err) } defer srv.Close() tenant, _, _, _, _ := srv.Store().DemoIDs() mustSetTestAPIKeys(t, srv, "vwkey|"+tenant+"|viewer") req := httptest.NewRequest(http.MethodGet, "/v1/bundle/signing-public-key", nil) req.Header.Set("Authorization", "Bearer vwkey") rec := httptest.NewRecorder() srv.Handler().ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("status %d body %s", rec.Code, rec.Body.String()) } var out map[string]any _ = json.Unmarshal(rec.Body.Bytes(), &out) if out["public_key_base64"] == nil || out["public_key_base64"] == "" { t.Fatalf("missing public_key_base64: %#v", out) } } func TestPostSpeaker_installCommandsAndMetaObject(t *testing.T) { srv, err := New(Options{InsecureDev: true, SeedDemo: true, BundleSeedHex: testBundleSeed}) if err != nil { t.Fatal(err) } defer srv.Close() tenant, _, _, _, _ := srv.Store().DemoIDs() mustSetTestAPIKeys(t, srv, "edkey|"+tenant+"|editor") body := `{ "endpoint":"https://bgp-dc2.example.com", "role":"replica", "meta_json":{"agent_domain":"bgp-dc2.example.com","node_ipv4":"203.0.113.10"}, "letsencrypt_email":"ops@example.com", "cf_dns_api_token":"cf-token-xyz", "panel_ip_whitelist":"203.0.113.1/32", "control_plane_url":"https://cp.example.com" }` req := httptest.NewRequest(http.MethodPost, "/v1/speakers", strings.NewReader(body)) req.Header.Set("Authorization", "Bearer edkey") req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() h := srv.Handler() h.ServeHTTP(rec, req) if rec.Code != http.StatusCreated { t.Fatalf("status %d body %s", rec.Code, rec.Body.String()) } var out map[string]any if err := json.Unmarshal(rec.Body.Bytes(), &out); err != nil { t.Fatal(err) } id, _ := out["id"].(string) if id == "" { t.Fatal("missing id") } secret, _ := out["agent_secret"].(string) token, _ := out["node_token"].(string) pub, _ := out["bundle_pubkey_base64"].(string) if secret == "" || token == "" || pub == "" { t.Fatalf("missing one-shot secrets: %#v", out) } install, _ := out["install"].(map[string]any) cmd, _ := install["docker_commands"].(string) for _, want := range []string{ "traefik", "dnschallenge=true", "dnschallenge.provider=cloudflare", "CF_DNS_API_TOKEN", "cf-token-xyz", "Host(`bgp-dc2.example.com`)", secret, token, "https://cp.example.com", `"179:179/tcp"`, } { if !strings.Contains(cmd, want) { t.Errorf("docker_commands missing %q", want) } } get := httptest.NewRequest(http.MethodGet, "/v1/speakers/"+id, nil) get.Header.Set("Authorization", "Bearer edkey") grec := httptest.NewRecorder() h.ServeHTTP(grec, get) if grec.Code != http.StatusOK { t.Fatalf("GET status %d body %s", grec.Code, grec.Body.String()) } got := grec.Body.String() if strings.Contains(got, secret) || strings.Contains(got, token) || strings.Contains(got, "docker_commands") { t.Fatalf("GET must not leak install secrets: %s", got) } } func TestPostSpeaker_masterSkipsInstall(t *testing.T) { srv, err := New(Options{InsecureDev: true, SeedDemo: true, BundleSeedHex: testBundleSeed}) if err != nil { t.Fatal(err) } defer srv.Close() tenant, _, _, _, _ := srv.Store().DemoIDs() mustSetTestAPIKeys(t, srv, "edkey|"+tenant+"|editor") body := `{"endpoint":"https://127.0.0.1:8080","role":"master"}` req := httptest.NewRequest(http.MethodPost, "/v1/speakers", strings.NewReader(body)) req.Header.Set("Authorization", "Bearer edkey") req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() srv.Handler().ServeHTTP(rec, req) if rec.Code != http.StatusCreated { t.Fatalf("status %d body %s", rec.Code, rec.Body.String()) } var out map[string]any _ = json.Unmarshal(rec.Body.Bytes(), &out) if out["node_token"] != nil { t.Fatalf("master must not mint node_token: %#v", out["node_token"]) } if out["install"] != nil { t.Fatalf("master must not include install: %#v", out["install"]) } }