Answer domain redirects from the edge, with or without Traefik
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ad56fa4e3a
commit
b46e0813fe
6 files changed
+89
-13
No files matched your search
+2
-2
@@ -505,14 +505,14 @@ func (b *Blacktip) newMapState(m model.Mapping, sig string) *mapState {
|
||||
for i := range ms.health {
|
||||
ms.health[i] = model.TargetHealth{Healthy: true, Detail: "not checked yet"}
|
||||
}
|
||||
if model.Web(m.Proto) {
|
||||
if model.Web(m.Proto) && !m.IsRedirect() { // a redirect has nothing behind it to relay to or check
|
||||
if b.tr != nil {
|
||||
ms.startRelay()
|
||||
} else {
|
||||
ms.initProxy()
|
||||
}
|
||||
}
|
||||
if m.Proto != model.ProtoUDP && m.HealthSeconds != -1 {
|
||||
if m.Proto != model.ProtoUDP && m.HealthSeconds != -1 && !m.IsRedirect() {
|
||||
go b.healthLoop(ctx, ms)
|
||||
}
|
||||
return ms
|
||||
|
||||
@@ -3,7 +3,7 @@ module git.veillax.com/Veillax/blacktip
|
||||
go 1.27.3
|
||||
|
||||
require (
|
||||
git.veillax.com/Veillax/reef v0.1.7
|
||||
git.veillax.com/Veillax/reef v0.1.10
|
||||
github.com/emersion/go-msgauth v0.7.0
|
||||
golang.org/x/crypto v0.58.0
|
||||
)
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
git.veillax.com/Veillax/reef v0.1.0 h1:O0OhEKFx2CsvACJ20sfzJNjGsngomtfDdKdxu4ROpCs=
|
||||
git.veillax.com/Veillax/reef v0.1.0/go.mod h1:ZBUd/BARO5kkpUNTP81TTY1aQXLHFR6bKDhCxckjHso=
|
||||
git.veillax.com/Veillax/reef v0.1.3 h1:RxSq2xwaup1+MieAkhlvOTZnYwPf19thFHEAe/7VxsI=
|
||||
git.veillax.com/Veillax/reef v0.1.3/go.mod h1:ZBUd/BARO5kkpUNTP81TTY1aQXLHFR6bKDhCxckjHso=
|
||||
git.veillax.com/Veillax/reef v0.1.5 h1:BydjLJsfGgU4zEIqUsePJRjPi9NgbFdmvq0M8dvJCgM=
|
||||
git.veillax.com/Veillax/reef v0.1.5/go.mod h1:ZBUd/BARO5kkpUNTP81TTY1aQXLHFR6bKDhCxckjHso=
|
||||
git.veillax.com/Veillax/reef v0.1.6 h1:NgZ4u2Z3hJ+BYD/zX9lov8fox4s2i8txtYNOpIsikVo=
|
||||
git.veillax.com/Veillax/reef v0.1.6/go.mod h1:ZBUd/BARO5kkpUNTP81TTY1aQXLHFR6bKDhCxckjHso=
|
||||
git.veillax.com/Veillax/reef v0.1.7 h1:5C2udonlJac2xvDOE5e+zDxej+AbZ9ttrNSE4owmwuU=
|
||||
git.veillax.com/Veillax/reef v0.1.7/go.mod h1:ZBUd/BARO5kkpUNTP81TTY1aQXLHFR6bKDhCxckjHso=
|
||||
git.veillax.com/Veillax/reef v0.1.10 h1:b15oD1vyrGOriCHhipQtJ3j7Cy5ForYWvfJkqifLQPg=
|
||||
git.veillax.com/Veillax/reef v0.1.10/go.mod h1:ZBUd/BARO5kkpUNTP81TTY1aQXLHFR6bKDhCxckjHso=
|
||||
github.com/Microsoft/go-winio v0.6.4 h1:kJF2pamtYCWXf0xAT8sAkwKC0pMl2HYBrRPHAqyUc+g=
|
||||
github.com/Microsoft/go-winio v0.6.4/go.mod h1:1xl5JXG4ze/m74GH6Dc4Mwxfc3VL5siRVQjIAlHH07U=
|
||||
github.com/emersion/go-msgauth v0.7.0 h1:vj2hMn6KhFtW41kshIBTXvp6KgYSqpA/ZN9Pv4g1INc=
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package blacktip
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"git.veillax.com/Veillax/reef/model"
|
||||
)
|
||||
|
||||
// A redirect mapping answers from the edge itself: no proxy, relay or target is needed.
|
||||
func TestRedirectMappingAnswersWithoutATarget(t *testing.T) {
|
||||
b := &Blacktip{logq: make(chan model.LogEntry, 8)}
|
||||
mk := func(m model.Mapping) *listener {
|
||||
return &listener{b: b, kind: model.ProtoHTTP, maps: []*mapState{{b: b, m: m}}}
|
||||
}
|
||||
cases := []struct {
|
||||
name string
|
||||
m model.Mapping
|
||||
uri string
|
||||
code int
|
||||
loc string
|
||||
}{
|
||||
{"temporary, no path", model.Mapping{Hostname: "old.test", RedirectTo: "https://new.test/home"}, "/a?b=1", http.StatusFound, "https://new.test/home"},
|
||||
{"permanent keeps path and query", model.Mapping{Hostname: "old.test", RedirectTo: "https://new.test", RedirectPermanent: true, RedirectKeepPath: true}, "/a/b?c=d", http.StatusMovedPermanently, "https://new.test/a/b?c=d"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
r := httptest.NewRequest("GET", "http://old.test"+c.uri, nil)
|
||||
w := httptest.NewRecorder()
|
||||
mk(c.m).ServeHTTP(w, r)
|
||||
if w.Code != c.code || w.Header().Get("Location") != c.loc {
|
||||
t.Errorf("%s: got %d %q, want %d %q", c.name, w.Code, w.Header().Get("Location"), c.code, c.loc)
|
||||
}
|
||||
}
|
||||
// a hostname nobody serves is still a 404
|
||||
w := httptest.NewRecorder()
|
||||
mk(cases[0].m).ServeHTTP(w, httptest.NewRequest("GET", "http://other.test/", nil))
|
||||
if w.Code != http.StatusNotFound {
|
||||
t.Errorf("unknown host: got %d", w.Code)
|
||||
}
|
||||
if e := <-b.logq; e.Result != "redirect" || e.Status != http.StatusFound {
|
||||
t.Errorf("access log entry: %+v", e)
|
||||
}
|
||||
}
|
||||
+30
@@ -244,6 +244,36 @@ func (t *traefikMgr) sync(ctx context.Context, maps []*mapState, binds map[strin
|
||||
}
|
||||
for _, ms := range maps {
|
||||
m := ms.m
|
||||
if m.IsRedirect() { // answered by Traefik itself: no relay, login or target
|
||||
rule, wildcard := hostRule(m.Hostname)
|
||||
var eps []string
|
||||
for _, ip := range t.bindsOf(m) {
|
||||
eps = append(eps, epName(ip, m.Port))
|
||||
}
|
||||
prio := 5000
|
||||
if wildcard {
|
||||
prio = 2000
|
||||
}
|
||||
to := strings.ReplaceAll(m.RedirectTo, "$", "$$") // "$" would otherwise start a group reference
|
||||
regex, repl := "^.*", to
|
||||
if m.RedirectKeepPath {
|
||||
regex, repl = "^https?://[^/]+(.*)", strings.TrimRight(to, "/")+"${1}"
|
||||
}
|
||||
mws["rd-"+m.ID] = map[string]any{"redirectRegex": map[string]any{"regex": regex, "replacement": repl, "permanent": m.RedirectPermanent}}
|
||||
router := map[string]any{"rule": rule, "entryPoints": eps, "service": "noop@internal", "middlewares": []string{"rd-" + m.ID}, "priority": prio}
|
||||
if m.Proto == model.ProtoHTTPS {
|
||||
tlsCfg := map[string]any{}
|
||||
if !t.cfg.NoACME {
|
||||
tlsCfg["certResolver"] = "le"
|
||||
if wildcard {
|
||||
tlsCfg["domains"] = []map[string]any{{"main": m.Hostname}}
|
||||
}
|
||||
}
|
||||
router["tls"] = tlsCfg
|
||||
}
|
||||
routers["m-"+m.ID] = router
|
||||
continue
|
||||
}
|
||||
if ms.relayPort == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -264,6 +264,16 @@ func (l *listener) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
b.logAccess(entry)
|
||||
}()
|
||||
|
||||
if ms.m.IsRedirect() {
|
||||
code := http.StatusFound
|
||||
if ms.m.RedirectPermanent {
|
||||
code = http.StatusMovedPermanently
|
||||
}
|
||||
entry.Result = "redirect"
|
||||
http.Redirect(rec, r, ms.m.RedirectLocation(r.URL.RequestURI()), code)
|
||||
return
|
||||
}
|
||||
|
||||
secure := l.kind == model.ProtoHTTPS
|
||||
scheme := "http"
|
||||
if secure {
|
||||
|
||||
Reference in new issue
Block a user