Give Hammerhead its own edge with a built-in dashboard route; setup.sh installs it with Blacktip instead of Caddy
HAMMERHEAD_SELF_DOMAIN creates an edge for this machine, writes its token to <data>/self-edge.env and adds an https route for the dashboard to that edge's config, so no reverse proxy is needed. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9ad690def0
commit
536284ec75
8 files changed
+342
-74
No files matched your search
@@ -0,0 +1,98 @@
|
||||
package hammerhead
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.veillax.com/Veillax/hammerhead/internal/secretbox"
|
||||
"git.veillax.com/Veillax/reef/model"
|
||||
)
|
||||
|
||||
func edgeConfigOf(t *testing.T, s *Server, token string) model.EdgeConfig {
|
||||
t.Helper()
|
||||
ts := httptest.NewServer(s.Handler())
|
||||
defer ts.Close()
|
||||
req, _ := http.NewRequest("GET", ts.URL+"/edge/config?v=0", nil)
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
var cfg model.EdgeConfig
|
||||
if resp.StatusCode != 200 {
|
||||
t.Fatalf("edge config: %s", resp.Status)
|
||||
}
|
||||
json.NewDecoder(resp.Body).Decode(&cfg)
|
||||
return cfg
|
||||
}
|
||||
|
||||
func tokenFile(t *testing.T, dir string) string {
|
||||
b, err := os.ReadFile(filepath.Join(dir, "self-edge.env"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return strings.TrimSpace(strings.SplitN(strings.SplitN(string(b), "BLACKTIP_TOKEN=", 2)[1], "\n", 2)[0])
|
||||
}
|
||||
|
||||
var key = secretbox.GenerateKey()
|
||||
|
||||
func TestSelfEdgeServesTheDashboardWithoutAProxy(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
opts := Options{MasterKey: key, SelfDomain: "Hammerhead.Example.com", SelfTarget: "127.0.0.1:8080"}
|
||||
s, err := New(dir, opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
token := tokenFile(t, dir)
|
||||
cfg := edgeConfigOf(t, s, token)
|
||||
if len(cfg.Mappings) != 1 || len(cfg.Local) != 1 {
|
||||
t.Fatalf("mappings=%d local=%d", len(cfg.Mappings), len(cfg.Local))
|
||||
}
|
||||
m := cfg.Mappings[0]
|
||||
if m.Proto != model.ProtoHTTPS || m.Hostname != "hammerhead.example.com" || m.Port != 443 || !m.RedirectHTTP || !m.Enabled ||
|
||||
len(m.Targets) != 1 || m.Targets[0].Host != "127.0.0.1" || m.Targets[0].Port != 8080 || m.Targets[0].SiteID != cfg.Local[0] {
|
||||
t.Fatalf("unexpected dashboard route: %+v", m)
|
||||
}
|
||||
if m.Auth.Protected() {
|
||||
t.Fatal("the dashboard has its own login and must not sit behind resource auth")
|
||||
}
|
||||
edgeID := s.st.Settings.SelfEdgeID
|
||||
if info, _ := os.Stat(filepath.Join(dir, "self-edge.env")); info.Mode().Perm()&0o077 != 0 && os.PathSeparator == '/' {
|
||||
t.Fatalf("token file is readable by others: %v", info.Mode())
|
||||
}
|
||||
s.Close()
|
||||
|
||||
// a restart keeps the same edge and token instead of making a new one
|
||||
s2, err := New(dir, opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer s2.Close()
|
||||
if s2.st.Settings.SelfEdgeID != edgeID || tokenFile(t, dir) != token || len(s2.st.Edges) != 1 {
|
||||
t.Fatal("restart changed the self edge")
|
||||
}
|
||||
if len(edgeConfigOf(t, s2, token).Mappings) != 1 {
|
||||
t.Fatal("route missing after restart")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoSelfEdgeUnlessAsked(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
s, err := New(dir, Options{MasterKey: key})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer s.Close()
|
||||
if len(s.st.Edges) != 0 {
|
||||
t.Fatal("an edge appeared without HAMMERHEAD_SELF_DOMAIN")
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(dir, "self-edge.env")); err == nil {
|
||||
t.Fatal("token file written without HAMMERHEAD_SELF_DOMAIN")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user