package hammerhead import ( "encoding/json" "strings" "testing" ) // websites share a port by hostname, and a website on every address steps aside for one that names an address func TestWebsitesShareAPortAndWildcardsStepAside(t *testing.T) { s, err := New(t.TempDir(), Options{AdminEmail: "admin@test.local", AdminPassword: "password123", MasterKey: key}) if err != nil { t.Fatal(err) } defer s.Close() do := apiClient(t, s) var me struct{ Orgs []struct{ ID string } } _, b := do("GET", "/api/me", nil) json.Unmarshal(b, &me) org := me.Orgs[0].ID var edge, site struct{ ID string } _, b = do("POST", "/api/edges", map[string]any{"name": "e", "tunnelAddr": "203.0.113.5:51820"}) json.Unmarshal(b, &edge) _, b = do("POST", "/api/orgs/"+org+"/sites", map[string]any{"name": "home"}) json.Unmarshal(b, &site) if code, b := do("POST", "/api/orgs/"+org+"/domains", map[string]any{"name": "example.test", "edgeId": edge.ID, "ip": "203.0.113.10"}); code != 200 { t.Fatalf("domain: %d %s", code, b) } base := "/api/orgs/" + org + "/mappings" open := func(m map[string]any) (int, string) { m["edgeId"] = edge.ID m["enabled"] = true m["targets"] = []any{map[string]any{"siteId": site.ID, "host": "127.0.0.1", "port": 8080}} code, b := do("POST", base, m) return code, string(b) } want := func(code int, got int, body, what string) { t.Helper() if code != got { t.Errorf("%s: got %d, want %d (%s)", what, got, code, body) } } c, body := open(map[string]any{"proto": "https", "bindIp": "0.0.0.0", "port": 443, "hostname": "a.example.test"}) want(200, c, body, "first website on every address") c, body = open(map[string]any{"proto": "https", "bindIp": "203.0.113.10", "port": 443, "hostname": "b.example.test"}) want(200, c, body, "a website naming an address shares the port with the wildcard one") c, body = open(map[string]any{"proto": "https", "bindIp": "203.0.113.11", "port": 443, "hostname": "c.example.test"}) want(200, c, body, "a second named address") c, body = open(map[string]any{"proto": "https", "bindIp": "203.0.113.10", "port": 443, "hostname": "d.example.test"}) want(200, c, body, "another hostname on a named address") // still refused: the same hostname twice on one listener, a raw port on a website's port, and another protocol c, body = open(map[string]any{"proto": "https", "bindIp": "203.0.113.10", "port": 443, "hostname": "b.example.test"}) want(400, c, body, "the same hostname twice on one address") c, body = open(map[string]any{"proto": "tcp", "bindIp": "0.0.0.0", "port": 443}) want(400, c, body, "a raw port on top of websites") c, body = open(map[string]any{"proto": "http", "bindIp": "203.0.113.10", "port": 443, "hostname": "e.example.test"}) want(400, c, body, "http on an https port") // the HTTP to HTTPS redirect also uses port 80 at its address c, body = open(map[string]any{"proto": "tcp", "bindIp": "203.0.113.20", "port": 80}) want(200, c, body, "a raw port 80 on its own address") c, body = open(map[string]any{"proto": "https", "bindIp": "203.0.113.20", "port": 8443, "hostname": "f.example.test", "redirectHttp": true}) want(400, c, body, "a redirect onto a raw port 80") if !strings.Contains(body, "port 80") { t.Errorf("the error should name port 80: %s", body) } c, body = open(map[string]any{"proto": "https", "bindIp": "203.0.113.20", "port": 8443, "hostname": "f.example.test"}) want(200, c, body, "the same website without the redirect") c, body = open(map[string]any{"proto": "https", "bindIp": "203.0.113.21", "port": 443, "hostname": "g.example.test", "redirectHttp": true}) want(200, c, body, "a redirect where port 80 is free") c, body = open(map[string]any{"proto": "tcp", "bindIp": "203.0.113.21", "port": 80}) want(400, c, body, "a raw port 80 where a redirect lives") c, body = open(map[string]any{"proto": "http", "bindIp": "203.0.113.21", "port": 80, "hostname": "h.example.test"}) want(200, c, body, "an http website beside a redirect on port 80") }