CI / changes (push) Successful in 13s
CI / commitlint (push) Has been skipped
CI / openapi (push) Successful in 27s
CI / web (push) Successful in 1m1s
CI / go (push) Successful in 1m16s
CI / bird2 (push) Successful in 17s
CI / release (push) Successful in 4m4s
Introduced a new API endpoint for retrieving the install context of the firewall client, which includes the bundle seed, configuration status, and suggested control plane URL. Updated the frontend to utilize this new endpoint, enhancing the user experience by dynamically displaying relevant information. Additionally, added type definitions for the install context and integrated it into the existing firewall management flow.
179 lines
5.5 KiB
Go
179 lines
5.5 KiB
Go
package httpapi
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"evobgp/internal/authkey"
|
|
"evobgp/internal/store"
|
|
)
|
|
|
|
func TestFirewallEnrollAndBlocklist(t *testing.T) {
|
|
srv, err := New(Options{SeedDemo: true, BundleSeedHex: testBundleSeed})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer srv.Close()
|
|
tenant, _, _, _, _ := srv.Store().DemoIDs()
|
|
mustSetTestAPIKeys(t, srv, "opkey|"+tenant+"|operator")
|
|
|
|
ts := httptest.NewServer(srv.Handler())
|
|
defer ts.Close()
|
|
client := ts.Client()
|
|
|
|
tok := "evobgp_fw_testtoken123456789012345678901234"
|
|
enrollBody := `{"name":"web-01","hostname":"web-01.local","client_token":"` + tok + `","client_version":"test/1"}`
|
|
reqEnroll, _ := http.NewRequest(http.MethodPost, ts.URL+"/v1/firewall/enroll", strings.NewReader(enrollBody))
|
|
reqEnroll.Header.Set("Content-Type", "application/json")
|
|
reqEnroll.Header.Set("X-EvoBGP-Seed", testBundleSeed)
|
|
respEnroll, err := client.Do(reqEnroll)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer func() { _ = respEnroll.Body.Close() }()
|
|
if respEnroll.StatusCode != http.StatusCreated {
|
|
b, _ := io.ReadAll(respEnroll.Body)
|
|
t.Fatalf("enroll status=%d body=%s", respEnroll.StatusCode, b)
|
|
}
|
|
var enroll map[string]any
|
|
if err := json.NewDecoder(respEnroll.Body).Decode(&enroll); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
clientID, _ := enroll["client_id"].(string)
|
|
if clientID == "" {
|
|
t.Fatal("missing client_id")
|
|
}
|
|
|
|
reqBlock, _ := http.NewRequest(http.MethodGet, ts.URL+"/v1/firewall/blocklist", nil)
|
|
reqBlock.Header.Set("Authorization", "Bearer "+tok)
|
|
respBlock, err := client.Do(reqBlock)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer func() { _ = respBlock.Body.Close() }()
|
|
if respBlock.StatusCode != http.StatusForbidden {
|
|
t.Fatalf("pending blocklist want 403 got %d", respBlock.StatusCode)
|
|
}
|
|
|
|
reqApprove, _ := http.NewRequest(http.MethodPost, ts.URL+"/v1/firewall/clients/"+clientID+"/approve", nil)
|
|
reqApprove.Header.Set("Authorization", "Bearer opkey")
|
|
respApprove, err := client.Do(reqApprove)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer func() { _ = respApprove.Body.Close() }()
|
|
if respApprove.StatusCode != http.StatusOK {
|
|
b, _ := io.ReadAll(respApprove.Body)
|
|
t.Fatalf("approve status=%d body=%s", respApprove.StatusCode, b)
|
|
}
|
|
|
|
_, err = srv.Store().CreateFirewallRule(tenant, nil, &store.FirewallRuleCreate{Action: "accept", Comment: "default"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
reqBlock2, _ := http.NewRequest(http.MethodGet, ts.URL+"/v1/firewall/blocklist", nil)
|
|
reqBlock2.Header.Set("Authorization", "Bearer "+tok)
|
|
respBlock2, err := client.Do(reqBlock2)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer func() { _ = respBlock2.Body.Close() }()
|
|
if respBlock2.StatusCode != http.StatusOK {
|
|
b, _ := io.ReadAll(respBlock2.Body)
|
|
t.Fatalf("blocklist status=%d body=%s", respBlock2.StatusCode, b)
|
|
}
|
|
var bl map[string]any
|
|
if err := json.NewDecoder(respBlock2.Body).Decode(&bl); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if total, _ := bl["total"].(float64); total != 0 {
|
|
t.Fatalf("accept-only want empty blocklist, total=%v", total)
|
|
}
|
|
}
|
|
|
|
func TestFirewallEnrollBadSeed(t *testing.T) {
|
|
srv, err := New(Options{SeedDemo: true, BundleSeedHex: testBundleSeed})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer srv.Close()
|
|
ts := httptest.NewServer(srv.Handler())
|
|
defer ts.Close()
|
|
|
|
body := `{"name":"x","client_token":"evobgp_fw_` + strings.Repeat("a", 40) + `"}`
|
|
req, _ := http.NewRequest(http.MethodPost, ts.URL+"/v1/firewall/enroll", strings.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("X-EvoBGP-Seed", "deadbeef")
|
|
resp, err := ts.Client().Do(req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer func() { _ = resp.Body.Close() }()
|
|
if resp.StatusCode != http.StatusForbidden {
|
|
t.Fatalf("want 403 got %d", resp.StatusCode)
|
|
}
|
|
}
|
|
|
|
func TestFirewallInstallContext(t *testing.T) {
|
|
srv, err := New(Options{SeedDemo: true, BundleSeedHex: testBundleSeed})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer srv.Close()
|
|
tenant, _, _, _, _ := srv.Store().DemoIDs()
|
|
mustSetTestAPIKeys(t, srv, "opkey|"+tenant+"|operator,vwkey|"+tenant+"|viewer")
|
|
|
|
ts := httptest.NewServer(srv.Handler())
|
|
defer ts.Close()
|
|
client := ts.Client()
|
|
|
|
reqOp, _ := http.NewRequest(http.MethodGet, ts.URL+"/v1/firewall/install-context", nil)
|
|
reqOp.Header.Set("Authorization", "Bearer opkey")
|
|
respOp, err := client.Do(reqOp)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer func() { _ = respOp.Body.Close() }()
|
|
if respOp.StatusCode != http.StatusOK {
|
|
b, _ := io.ReadAll(respOp.Body)
|
|
t.Fatalf("operator install-context status=%d body=%s", respOp.StatusCode, b)
|
|
}
|
|
var ctx map[string]any
|
|
if err := json.NewDecoder(respOp.Body).Decode(&ctx); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if seed, _ := ctx["bundle_seed"].(string); seed != testBundleSeed {
|
|
t.Fatalf("bundle_seed=%q want %q", seed, testBundleSeed)
|
|
}
|
|
if configured, _ := ctx["bundle_seed_configured"].(bool); !configured {
|
|
t.Fatal("bundle_seed_configured want true")
|
|
}
|
|
if url, _ := ctx["install_sh_url"].(string); !strings.HasSuffix(url, "/v1/firewall/install.sh") {
|
|
t.Fatalf("install_sh_url=%q", url)
|
|
}
|
|
|
|
reqVw, _ := http.NewRequest(http.MethodGet, ts.URL+"/v1/firewall/install-context", nil)
|
|
reqVw.Header.Set("Authorization", "Bearer vwkey")
|
|
respVw, err := client.Do(reqVw)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer func() { _ = respVw.Body.Close() }()
|
|
if respVw.StatusCode != http.StatusForbidden {
|
|
t.Fatalf("viewer install-context want 403 got %d", respVw.StatusCode)
|
|
}
|
|
}
|
|
|
|
func TestFirewallTokenHashMatchesAuthkey(t *testing.T) {
|
|
tok := "evobgp_fw_sample"
|
|
h := authkey.HashToken(tok)
|
|
if len(h) != 32 {
|
|
t.Fatalf("hash len %d", len(h))
|
|
}
|
|
}
|