package e2e import ( "context" "net" "strconv" "testing" "time" "git.veillax.com/Veillax/bamboo" "git.veillax.com/Veillax/blacktip" ) type exposed struct { ID, Host, Hostname, URL, SiteToken string Port int } // createExpose retries until an edge is online and the call succeeds. func (w *world) createExpose(c *client, in map[string]any) exposed { w.t.Helper() var out exposed for i := 0; i < 40; i++ { if code, _ := c.do("POST", "/api/orgs/-/expose", in, &out); code == 200 { return out } time.Sleep(150 * time.Millisecond) } c.must("POST", "/api/orgs/-/expose", in, &out) return out } func (w *world) agentFor(e exposed) context.CancelFunc { ctx, cancel := context.WithCancel(context.Background()) w.t.Cleanup(cancel) b, err := bamboo.New(bamboo.Config{Hammerhead: w.ts.URL, Token: e.SiteToken, DataDir: w.t.TempDir()}) if err != nil { w.t.Fatal(err) } go b.Run(ctx) return cancel } // An ephemeral exposure carries traffic through the tunnel, and is gone after DELETE. func TestEphemeralExpose(t *testing.T) { w := setup(t, blacktip.TraefikConfig{}) var key struct{ Key string } w.admin.must("POST", "/api/orgs/"+w.org+"/apikeys", map[string]string{"name": "shark"}, &key) kc := newClient(t, w.ts.URL) kc.key = key.Key w.admin.must("POST", "/api/orgs/"+w.org+"/domains", map[string]any{"name": "example.test", "edgeId": w.edge}, nil) // ----- tcp ----- svc := tcpBackend(t, "SVC") e := w.createExpose(kc, map[string]any{"proto": "tcp", "targetPort": svc, "ttlSeconds": 60}) stop := w.agentFor(e) addr := net.JoinHostPort("127.0.0.1", strconv.Itoa(e.Port)) if got := tcpAsk(t, addr, "hi"); got != "SVC:hi" { t.Fatalf("through the tunnel: %q", got) } kc.must("POST", "/api/orgs/-/expose/"+e.ID+"/renew", nil, nil) kc.must("DELETE", "/api/orgs/-/expose/"+e.ID, nil, nil) stop() closed := false for i := 0; i < 40 && !closed; i++ { c, err := net.DialTimeout("tcp", addr, 300*time.Millisecond) if err != nil { closed = true break } c.Close() time.Sleep(150 * time.Millisecond) } if !closed { t.Error("the port is still open after DELETE") } kc.expect(404, "GET", "/api/orgs/-/expose/"+e.ID, nil) // ----- http, with a password ----- web := webBackend(t, "app", 200) port := freePort(t) h := w.createExpose(kc, map[string]any{"proto": "http", "name": "demo", "port": port, "targetPort": web, "auth": map[string]any{"password": "hunter22"}}) w.agentFor(h) _, get := edgeClient("http", port) var r webResp for i := 0; i < 60; i++ { if r = get("GET", h.Hostname, "/x"); r.status == 302 { break } time.Sleep(150 * time.Millisecond) } if r.status != 302 { t.Fatalf("protected exposure should send visitors to the login, got %+v", r) } }