settings: allow transparent backends that don't set all values
This commit is contained in:
@@ -72,6 +72,14 @@ backends:
|
||||
# http2-enabled: true
|
||||
# tls-skip-verify: true
|
||||
|
||||
# Example HTTPS transparent backend with host/SNI override, HTTP/2, and subdirectory
|
||||
#"ssl.example.com":
|
||||
# url: "https://ssl.example.com/subdirectory/"
|
||||
# host: ssl.example.com
|
||||
# http2-enabled: true
|
||||
# ip-header: "-"
|
||||
# transparent: true
|
||||
|
||||
# List of strings you can replace to alter the presentation on challenge/error templates
|
||||
# Can use other languages.
|
||||
# Note raw HTML is allowed, be careful with it.
|
||||
|
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/traits"
|
||||
"maps"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/textproto"
|
||||
@@ -41,6 +42,8 @@ type RequestData struct {
|
||||
State StateInterface
|
||||
CookiePrefix string
|
||||
|
||||
ExtraHeaders http.Header
|
||||
|
||||
r *http.Request
|
||||
|
||||
fp map[string]string
|
||||
@@ -61,18 +64,18 @@ func CreateRequestData(r *http.Request, state StateInterface) (*http.Request, *R
|
||||
data.Time = time.Now().UTC()
|
||||
data.State = state
|
||||
|
||||
data.ExtraHeaders = make(http.Header)
|
||||
|
||||
data.fp = make(map[string]string, 2)
|
||||
|
||||
if fp := utils.GetTLSFingerprint(r); fp != nil {
|
||||
if ja3nPtr := fp.JA3N(); ja3nPtr != nil {
|
||||
ja3n := ja3nPtr.String()
|
||||
data.fp["ja3n"] = ja3n
|
||||
r.Header.Set("X-TLS-Fingerprint-JA3N", ja3n)
|
||||
}
|
||||
if ja4Ptr := fp.JA4(); ja4Ptr != nil {
|
||||
ja4 := ja4Ptr.String()
|
||||
data.fp["ja4"] = ja4
|
||||
r.Header.Set("X-TLS-Fingerprint-JA4", ja4)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,4 +260,14 @@ func (d *RequestData) RequestHeaders(headers http.Header) {
|
||||
headers.Set(fmt.Sprintf("X-Away-Challenge-%s-State", c.Name), d.ChallengeState[id].String())
|
||||
}
|
||||
}
|
||||
|
||||
if ja4, ok := d.fp["fp4"]; ok {
|
||||
headers.Set("X-TLS-Fingerprint-JA4", ja4)
|
||||
}
|
||||
|
||||
if ja3n, ok := d.fp["ja3n"]; ok {
|
||||
headers.Set("X-TLS-Fingerprint-JA3N", ja3n)
|
||||
}
|
||||
|
||||
maps.Copy(headers, d.ExtraHeaders)
|
||||
}
|
||||
|
@@ -33,7 +33,7 @@ func ServeChallengeScript(w http.ResponseWriter, r *http.Request, reg *Registrat
|
||||
"Random": utils.CacheBust(),
|
||||
"Challenge": reg.Name,
|
||||
"ChallengeScript": script,
|
||||
"Strings": data.State.Options().Strings,
|
||||
"Strings": data.State.Strings(),
|
||||
})
|
||||
if err != nil {
|
||||
//TODO: log
|
||||
|
@@ -3,7 +3,7 @@ package challenge
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"git.gammaspectra.live/git/go-away/lib/policy"
|
||||
"git.gammaspectra.live/git/go-away/lib/settings"
|
||||
"git.gammaspectra.live/git/go-away/utils"
|
||||
"github.com/google/cel-go/cel"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
@@ -114,7 +114,7 @@ type StateInterface interface {
|
||||
|
||||
Settings() policy.StateSettings
|
||||
|
||||
Options() settings.Settings
|
||||
Strings() utils.Strings
|
||||
|
||||
GetBackend(host string) http.Handler
|
||||
}
|
||||
|
12
lib/http.go
12
lib/http.go
@@ -157,7 +157,7 @@ func (state *State) handleRequest(w http.ResponseWriter, r *http.Request) {
|
||||
return backend
|
||||
}
|
||||
|
||||
cleanupRequest := func(r *http.Request, fromChallenge bool) {
|
||||
cleanupRequest := func(r *http.Request, fromChallenge bool, ruleName string, ruleAction policy.RuleAction) {
|
||||
if fromChallenge {
|
||||
r.Header.Del("Referer")
|
||||
}
|
||||
@@ -175,7 +175,8 @@ func (state *State) handleRequest(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
r.URL.RawQuery = q.Encode()
|
||||
|
||||
data.RequestHeaders(r.Header)
|
||||
data.ExtraHeaders.Set("X-Away-Rule", ruleName)
|
||||
data.ExtraHeaders.Set("X-Away-Action", string(ruleAction))
|
||||
|
||||
// delete cookies set by go-away to prevent user tracking that way
|
||||
cookies := r.Cookies()
|
||||
@@ -189,7 +190,7 @@ func (state *State) handleRequest(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
for _, rule := range state.rules {
|
||||
next, err := rule.Evaluate(lg, w, r, func() http.Handler {
|
||||
cleanupRequest(r, true)
|
||||
cleanupRequest(r, true, rule.Name, rule.Action)
|
||||
return getBackend()
|
||||
})
|
||||
if err != nil {
|
||||
@@ -208,10 +209,7 @@ func (state *State) handleRequest(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// default pass
|
||||
_, _ = action.Pass{}.Handle(lg, w, r, func() http.Handler {
|
||||
r.Header.Set("X-Away-Rule", "DEFAULT")
|
||||
r.Header.Set("X-Away-Action", "PASS")
|
||||
|
||||
cleanupRequest(r, false)
|
||||
cleanupRequest(r, false, "DEFAULT", policy.RuleActionPASS)
|
||||
return getBackend()
|
||||
})
|
||||
}
|
||||
|
@@ -4,7 +4,6 @@ import (
|
||||
"crypto/ed25519"
|
||||
"git.gammaspectra.live/git/go-away/lib/challenge"
|
||||
"git.gammaspectra.live/git/go-away/lib/policy"
|
||||
"git.gammaspectra.live/git/go-away/lib/settings"
|
||||
"git.gammaspectra.live/git/go-away/utils"
|
||||
"github.com/google/cel-go/cel"
|
||||
"log/slog"
|
||||
@@ -99,8 +98,8 @@ func (state *State) Settings() policy.StateSettings {
|
||||
return state.settings
|
||||
}
|
||||
|
||||
func (state *State) Options() settings.Settings {
|
||||
return state.opt
|
||||
func (state *State) Strings() utils.Strings {
|
||||
return state.opt.Strings
|
||||
}
|
||||
|
||||
func (state *State) GetBackend(host string) http.Handler {
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
"git.gammaspectra.live/git/go-away/lib/challenge"
|
||||
"git.gammaspectra.live/git/go-away/utils"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
@@ -27,6 +28,10 @@ type Backend struct {
|
||||
// GoDNS Resolve URL using the Go DNS server
|
||||
// Only relevant when running with CGO enabled
|
||||
GoDNS bool `yaml:"go-dns"`
|
||||
|
||||
// Transparent Do not add extra headers onto this backend
|
||||
// This prevents GoAway headers from being set, or other state
|
||||
Transparent bool `yaml:"transparent"`
|
||||
}
|
||||
|
||||
func (b Backend) Create() (*httputil.ReverseProxy, error) {
|
||||
@@ -53,10 +58,10 @@ func (b Backend) Create() (*httputil.ReverseProxy, error) {
|
||||
transport.TLSClientConfig.ServerName = b.Host
|
||||
}
|
||||
|
||||
if b.IpHeader != "" || b.Host != "" {
|
||||
if b.IpHeader != "" || b.Host != "" || !b.Transparent {
|
||||
director := proxy.Director
|
||||
proxy.Director = func(req *http.Request) {
|
||||
if b.IpHeader != "" {
|
||||
if b.IpHeader != "" && !b.Transparent {
|
||||
if ip := utils.GetRemoteAddress(req.Context()); ip != nil {
|
||||
req.Header.Set(b.IpHeader, ip.Addr().Unmap().String())
|
||||
}
|
||||
@@ -64,6 +69,13 @@ func (b Backend) Create() (*httputil.ReverseProxy, error) {
|
||||
if b.Host != "" {
|
||||
req.Host = b.Host
|
||||
}
|
||||
|
||||
if !b.Transparent {
|
||||
data := challenge.RequestDataFromContext(req.Context())
|
||||
if data != nil {
|
||||
data.RequestHeaders(req.Header)
|
||||
}
|
||||
}
|
||||
director(req)
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,9 @@
|
||||
package settings
|
||||
|
||||
import "maps"
|
||||
import (
|
||||
"git.gammaspectra.live/git/go-away/utils"
|
||||
"maps"
|
||||
)
|
||||
|
||||
type Settings struct {
|
||||
Bind Bind `yaml:"bind"`
|
||||
@@ -10,7 +13,7 @@ type Settings struct {
|
||||
BindDebug string `yaml:"bind-debug"`
|
||||
BindMetrics string `yaml:"bind-metrics"`
|
||||
|
||||
Strings Strings `yaml:"strings"`
|
||||
Strings utils.Strings `yaml:"strings"`
|
||||
|
||||
// Links to add to challenge/error pages like privacy/impressum.
|
||||
Links []Link `yaml:"links"`
|
||||
|
@@ -1,13 +1,10 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"maps"
|
||||
"git.gammaspectra.live/git/go-away/utils"
|
||||
)
|
||||
|
||||
type Strings map[string]string
|
||||
|
||||
var DefaultStrings = make(Strings).set(map[string]string{
|
||||
var DefaultStrings = utils.NewStrings(map[string]string{
|
||||
"title_challenge": "Checking you are not a bot",
|
||||
"title_error": "Oh no!",
|
||||
|
||||
@@ -39,17 +36,3 @@ var DefaultStrings = make(Strings).set(map[string]string{
|
||||
"status_challenge_done_took": "Done! Took",
|
||||
"status_error": "Error:",
|
||||
})
|
||||
|
||||
func (s Strings) set(v map[string]string) Strings {
|
||||
maps.Copy(s, v)
|
||||
return s
|
||||
}
|
||||
|
||||
func (s Strings) Get(value string) template.HTML {
|
||||
v, ok := (s)[value]
|
||||
if !ok {
|
||||
// fallback
|
||||
return template.HTML("string:" + value)
|
||||
}
|
||||
return template.HTML(v)
|
||||
}
|
||||
|
10
lib/state.go
10
lib/state.go
@@ -99,18 +99,18 @@ func NewState(p policy.Policy, opt settings.Settings, settings policy.StateSetti
|
||||
}
|
||||
}
|
||||
|
||||
if templates["challenge-"+state.Options().ChallengeTemplate+".gohtml"] == nil {
|
||||
if templates["challenge-"+state.opt.ChallengeTemplate+".gohtml"] == nil {
|
||||
|
||||
if data, err := os.ReadFile(state.Options().ChallengeTemplate); err == nil && len(data) > 0 {
|
||||
name := path.Base(state.Options().ChallengeTemplate)
|
||||
if data, err := os.ReadFile(state.opt.ChallengeTemplate); err == nil && len(data) > 0 {
|
||||
name := path.Base(state.opt.ChallengeTemplate)
|
||||
err := initTemplate(name, string(data))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error loading template %s: %w", state.Options().ChallengeTemplate, err)
|
||||
return nil, fmt.Errorf("error loading template %s: %w", state.opt.ChallengeTemplate, err)
|
||||
}
|
||||
state.opt.ChallengeTemplate = name
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("no template defined for %s", state.Options().ChallengeTemplate)
|
||||
return nil, fmt.Errorf("no template defined for %s", state.opt.ChallengeTemplate)
|
||||
}
|
||||
|
||||
state.networks = make(map[string]cidranger.Ranger)
|
||||
|
@@ -59,9 +59,9 @@ func (state *State) ChallengePage(w http.ResponseWriter, r *http.Request, status
|
||||
input["Random"] = utils.CacheBust()
|
||||
|
||||
input["Path"] = state.UrlPath()
|
||||
input["Links"] = state.Options().Links
|
||||
input["Strings"] = state.Options().Strings
|
||||
for k, v := range state.Options().ChallengeTemplateOverrides {
|
||||
input["Links"] = state.opt.Links
|
||||
input["Strings"] = state.opt.Strings
|
||||
for k, v := range state.opt.ChallengeTemplateOverrides {
|
||||
input[k] = v
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ func (state *State) ChallengePage(w http.ResponseWriter, r *http.Request, status
|
||||
maps.Copy(input, params)
|
||||
|
||||
if _, ok := input["Title"]; !ok {
|
||||
input["Title"] = state.Options().Strings.Get("title_challenge")
|
||||
input["Title"] = state.opt.Strings.Get("title_challenge")
|
||||
}
|
||||
|
||||
if data.GetOptBool(challenge.RequestOptCacheMetaTags, false) {
|
||||
@@ -95,7 +95,7 @@ func (state *State) ChallengePage(w http.ResponseWriter, r *http.Request, status
|
||||
|
||||
buf := bytes.NewBuffer(make([]byte, 0, 8192))
|
||||
|
||||
err := templates["challenge-"+state.Options().ChallengeTemplate+".gohtml"].Execute(buf, input)
|
||||
err := templates["challenge-"+state.opt.ChallengeTemplate+".gohtml"].Execute(buf, input)
|
||||
if err != nil {
|
||||
state.ErrorPage(w, r, http.StatusInternalServerError, err, "")
|
||||
} else {
|
||||
@@ -116,13 +116,13 @@ func (state *State) ErrorPage(w http.ResponseWriter, r *http.Request, status int
|
||||
"Error": err.Error(),
|
||||
"Path": state.UrlPath(),
|
||||
"Theme": "",
|
||||
"Title": template.HTML(string(state.Options().Strings.Get("title_error")) + " " + http.StatusText(status)),
|
||||
"Title": template.HTML(string(state.opt.Strings.Get("title_error")) + " " + http.StatusText(status)),
|
||||
"Challenge": "",
|
||||
"Redirect": redirect,
|
||||
"Links": state.Options().Links,
|
||||
"Strings": state.Options().Strings,
|
||||
"Links": state.opt.Links,
|
||||
"Strings": state.opt.Strings,
|
||||
}
|
||||
for k, v := range state.Options().ChallengeTemplateOverrides {
|
||||
for k, v := range state.opt.ChallengeTemplateOverrides {
|
||||
input[k] = v
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ func (state *State) ErrorPage(w http.ResponseWriter, r *http.Request, status int
|
||||
}
|
||||
}
|
||||
|
||||
err2 := templates["challenge-"+state.Options().ChallengeTemplate+".gohtml"].Execute(buf, input)
|
||||
err2 := templates["challenge-"+state.opt.ChallengeTemplate+".gohtml"].Execute(buf, input)
|
||||
if err2 != nil {
|
||||
// nested errors!
|
||||
panic(err2)
|
||||
|
26
utils/strings.go
Normal file
26
utils/strings.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"maps"
|
||||
)
|
||||
|
||||
type Strings map[string]string
|
||||
|
||||
func (s Strings) set(v map[string]string) Strings {
|
||||
maps.Copy(s, v)
|
||||
return s
|
||||
}
|
||||
|
||||
func (s Strings) Get(value string) template.HTML {
|
||||
v, ok := (s)[value]
|
||||
if !ok {
|
||||
// fallback
|
||||
return template.HTML("string:" + value)
|
||||
}
|
||||
return template.HTML(v)
|
||||
}
|
||||
|
||||
func NewStrings[T ~map[string]string](v T) Strings {
|
||||
return make(Strings).set(v)
|
||||
}
|
Reference in New Issue
Block a user