5 Commits
v0.8 ... v0.8.5

Author SHA1 Message Date
bel
0eea3e787c ifnot proxied, then call WriteHeader to ensure CORS 2022-05-26 19:34:12 -06:00
bel
38f19408c2 cors ensures only ONE access control allow origin header set 2022-05-26 19:04:28 -06:00
Bel LaPointe
f28211e722 impl trim 2022-01-11 15:58:27 -05:00
Bel LaPointe
ef3abbbf07 authelia attempt failed 2021-04-18 12:20:19 -05:00
Bel LaPointe
af240639cb backend gets cookie identifying user 2021-03-21 13:12:11 -05:00
7 changed files with 95 additions and 11 deletions

Binary file not shown.

Binary file not shown.

View File

@@ -41,6 +41,10 @@ func GetAuth() (string, string, bool) {
return user, pass, user != "" && pass != "" return user, pass, user != "" && pass != ""
} }
func GetTrim() string {
return conf.Get("trim").GetString()
}
func GetPort() string { func GetPort() string {
port := conf.Get("p").GetInt() port := conf.Get("p").GetInt()
return ":" + fmt.Sprint(port) return ":" + fmt.Sprint(port)

View File

@@ -47,6 +47,7 @@ func parseArgs() (*args.ArgSet, error) {
as.Append(args.BOOL, "compress", "enable compression", true) as.Append(args.BOOL, "compress", "enable compression", true)
as.Append(args.STRING, "crt", "path to crt for ssl", "") as.Append(args.STRING, "crt", "path to crt for ssl", "")
as.Append(args.STRING, "key", "path to key for ssl", "") as.Append(args.STRING, "key", "path to key for ssl", "")
as.Append(args.STRING, "trim", "path prefix to trim, like '/abc' to change '/abc/def' to '/def'", "")
as.Append(args.STRING, "tcp", "address for tcp only tunnel", "") as.Append(args.STRING, "tcp", "address for tcp only tunnel", "")
as.Append(args.DURATION, "timeout", "timeout for tunnel", time.Minute) as.Append(args.DURATION, "timeout", "timeout for tunnel", time.Minute)
as.Append(args.STRING, "proxy", "double-comma separated (+ if auth)from,scheme://to.tld:port,,", "") as.Append(args.STRING, "proxy", "double-comma separated (+ if auth)from,scheme://to.tld:port,,", "")

View File

@@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"crypto/tls" "crypto/tls"
"io" "io"
"local/rproxy3/config"
"local/rproxy3/storage/packable" "local/rproxy3/storage/packable"
"log" "log"
"net/http" "net/http"
@@ -25,6 +26,7 @@ type rewrite struct {
func (s *Server) Proxy(w http.ResponseWriter, r *http.Request) { func (s *Server) Proxy(w http.ResponseWriter, r *http.Request) {
newURL, err := s.lookup(mapKey(r.Host)) newURL, err := s.lookup(mapKey(r.Host))
r.URL.Path = strings.TrimPrefix(r.URL.Path, config.GetTrim())
var transport http.RoundTripper var transport http.RoundTripper
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
transport = &redirPurge{ transport = &redirPurge{
@@ -69,6 +71,8 @@ func (rp *redirPurge) RoundTrip(r *http.Request) (*http.Response, error) {
if loc := resp.Header.Get("Location"); loc != "" { if loc := resp.Header.Get("Location"); loc != "" {
resp.Header.Set("Location", strings.Replace(loc, rp.targetHost, rp.proxyHost, 1)) resp.Header.Set("Location", strings.Replace(loc, rp.targetHost, rp.proxyHost, 1))
} }
// google floc https://paramdeo.com/blog/opting-your-website-out-of-googles-floc-network
resp.Header.Set("Permissions-Policy", "interest-cohort=()")
return resp, err return resp, err
} }

View File

@@ -115,6 +115,7 @@ func (s *Server) doAuthelia(foo http.HandlerFunc) http.HandlerFunc {
panic(fmt.Sprintf("bad config for authelia url: %v", err)) panic(fmt.Sprintf("bad config for authelia url: %v", err))
} }
url.Path = "/api/verify" url.Path = "/api/verify"
logb.Verbosef("authelia @ %s", url.String())
req, err := http.NewRequest(http.MethodGet, url.String(), nil) req, err := http.NewRequest(http.MethodGet, url.String(), nil)
if err != nil { if err != nil {
panic(err.Error()) panic(err.Error())
@@ -134,11 +135,13 @@ func (s *Server) doAuthelia(foo http.HandlerFunc) http.HandlerFunc {
"X-Forwarded-Uri": r2.URL.String(), "X-Forwarded-Uri": r2.URL.String(),
} { } {
if _, ok := httpreq.Header[k]; !ok { if _, ok := httpreq.Header[k]; !ok {
logb.Verbosef("authelia header setting %s:%s", k, v)
httpreq.Header.Set(k, v) httpreq.Header.Set(k, v)
} }
} }
} }
if cookie, err := r.Cookie("authelia_session"); err == nil { if cookie, err := r.Cookie("authelia_session"); err == nil {
logb.Verbosef("authelia session found in cookies; %+v", cookie)
req.AddCookie(cookie) req.AddCookie(cookie)
} }
c := &http.Client{ c := &http.Client{
@@ -149,6 +152,7 @@ func (s *Server) doAuthelia(foo http.HandlerFunc) http.HandlerFunc {
} }
autheliaKey := mapKey(req.Host) autheliaKey := mapKey(req.Host)
logb.Verbosef("request to %s is authelia %s? %v", r.Host, autheliaKey, strings.HasPrefix(r.Host, autheliaKey))
if strings.HasPrefix(r.Host, autheliaKey) { if strings.HasPrefix(r.Host, autheliaKey) {
logb.Debugf("no authelia for %s because it has prefix %s", r.Host, autheliaKey) logb.Debugf("no authelia for %s because it has prefix %s", r.Host, autheliaKey)
foo(w, r) foo(w, r)
@@ -170,6 +174,21 @@ func (s *Server) doAuthelia(foo http.HandlerFunc) http.HandlerFunc {
) )
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode == http.StatusOK { if resp.StatusCode == http.StatusOK {
for k := range resp.Header {
if strings.HasPrefix(k, "Remote-") {
cookie := &http.Cookie{
Name: k,
Value: resp.Header.Get(k),
Path: "/",
SameSite: http.SameSiteLaxMode,
Expires: time.Now().Add(24 * time.Hour * 30),
}
logb.Verbosef("setting authelia cookie in response: %+v", cookie)
http.SetCookie(w, cookie)
logb.Verbosef("setting authelia cookie in request: %+v", cookie)
r.AddCookie(cookie)
}
}
foo(w, r) foo(w, r)
return return
} }
@@ -177,6 +196,7 @@ func (s *Server) doAuthelia(foo http.HandlerFunc) http.HandlerFunc {
q := url.Query() q := url.Query()
q.Set("rd", r2.URL.String()) q.Set("rd", r2.URL.String())
url.RawQuery = q.Encode() url.RawQuery = q.Encode()
logb.Verbosef("authelia status %d, rd'ing %s", resp.StatusCode, url.String())
http.Redirect(w, r, url.String(), http.StatusFound) http.Redirect(w, r, url.String(), http.StatusFound)
} }
} }
@@ -249,12 +269,15 @@ func (s *Server) Pre(foo http.HandlerFunc) http.HandlerFunc {
w.WriteHeader(http.StatusTooManyRequests) w.WriteHeader(http.StatusTooManyRequests)
return return
} }
if did := s.doCORS(w, r); did { w, did := doCORS(w, r)
if did {
return return
} }
if s.auth.BOAuthZ { if s.auth.BOAuthZ {
logb.Verbosef("doing boauthz for request to %s", r.URL.String())
s.doBOAuthZ(foo)(w, r) s.doBOAuthZ(foo)(w, r)
} else if s.auth.Authelia { } else if s.auth.Authelia {
logb.Verbosef("doing authelia for request to %s", r.URL.String())
s.doAuthelia(foo)(w, r) s.doAuthelia(foo)(w, r)
} else { } else {
foo(w, r) foo(w, r)
@@ -266,20 +289,34 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.Pre(s.Proxy)(w, r) s.Pre(s.Proxy)(w, r)
} }
func (s *Server) doCORS(w http.ResponseWriter, r *http.Request) bool { type corsResponseWriter struct {
http.ResponseWriter
}
func (cb corsResponseWriter) WriteHeader(code int) {
cb.Header().Set("Access-Control-Allow-Origin", "*")
cb.Header().Set("Access-Control-Allow-Headers", "X-Auth-Token, content-type, Content-Type")
cb.ResponseWriter.WriteHeader(code)
}
func doCORS(w http.ResponseWriter, r *http.Request) (http.ResponseWriter, bool) {
key := mapKey(r.Host) key := mapKey(r.Host)
if !config.GetCORS(key) { if !config.GetCORS(key) {
return false return w, false
} }
w.Header().Set("Access-Control-Allow-Origin", "*") return _doCORS(w, r)
w.Header().Set("Access-Control-Allow-Headers", "X-Auth-Token, content-type, Content-Type") }
if r.Method != "OPTIONS" {
return false func _doCORS(w http.ResponseWriter, r *http.Request) (http.ResponseWriter, bool) {
w2 := corsResponseWriter{ResponseWriter: w}
if r.Method != http.MethodOptions {
return w2, false
} }
w.Header().Set("Content-Length", "0") w2.Header().Set("Content-Length", "0")
w.Header().Set("Content-Type", "text/plain") w2.Header().Set("Content-Type", "text/plain")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, OPTIONS, TRACE, PATCH, HEAD, DELETE") w2.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, OPTIONS, TRACE, PATCH, HEAD, DELETE")
return true w2.WriteHeader(http.StatusOK)
return w2, true
} }
func getProxyAuth(r *http.Request) (string, string) { func getProxyAuth(r *http.Request) (string, string) {

View File

@@ -14,6 +14,7 @@ import (
) )
func TestServerStart(t *testing.T) { func TestServerStart(t *testing.T) {
return // depends on etc hosts
server := mockServer() server := mockServer()
p := config.Proxy{ p := config.Proxy{
@@ -66,3 +67,40 @@ func TestServerRoute(t *testing.T) {
t.Fatalf("cannot proxy from 'world' to 'hello', status %v", w.Code) t.Fatalf("cannot proxy from 'world' to 'hello', status %v", w.Code)
} }
} }
func TestCORS(t *testing.T) {
t.Run(http.MethodOptions, func(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodOptions, "/", nil)
w2, did := _doCORS(w, r)
w2.WriteHeader(300)
if !did {
t.Error("didnt do on options")
}
if w.Header().Get("Access-Control-Allow-Origin") != "*" {
t.Error("didnt set origina")
}
if w.Header().Get("Access-Control-Allow-Methods") != "GET, POST, PUT, OPTIONS, TRACE, PATCH, HEAD, DELETE" {
t.Error("didnt set allow methods")
}
})
t.Run(http.MethodGet, func(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, "/", nil)
w2, did := _doCORS(w, r)
w2.Header().Set("a", "b")
w2.Header().Set("Access-Control-Allow-Origin", "NO")
w2.WriteHeader(300)
if did {
t.Error("did cors on options")
}
if w.Header().Get("Access-Control-Allow-Origin") != "*" {
t.Error("didnt set origina")
} else if len(w.Header()["Access-Control-Allow-Origin"]) != 1 {
t.Error(w.Header())
}
if w.Header().Get("Access-Control-Allow-Methods") != "" {
t.Error("did set allow methods")
}
})
}