Ephemeral exposures: short-lived site and port or hostname with a TTL, for shark expose
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
39659f0144
commit
bce44e21ce
7 files changed
+705
No files matched your search
@@ -0,0 +1,96 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user