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