9 Commits
v0.5 ... v0.8.1

Author SHA1 Message Date
Bel LaPointe
af240639cb backend gets cookie identifying user 2021-03-21 13:12:11 -05:00
Bel LaPointe
c623792c2f NOW authelia supported 2021-03-21 13:03:04 -05:00
Bel LaPointe
cebb518e05 impl authelia I think 2021-03-21 12:44:21 -05:00
Bel LaPointe
177e0d88da dont rewrite 2020-07-31 23:15:58 -06:00
Bel LaPointe
9b0bccd9ca CORS for DELETE 2020-07-25 19:32:59 -06:00
Bel LaPointe
1af274dc1d Add redirect things for dumb js apps 2020-07-25 02:28:57 -06:00
Bel LaPointe
ec1e0cdf2e Add nopath for vue things 2020-07-25 02:23:04 -06:00
Bel LaPointe
61811e8e61 Listen on second port and redirect to main 2020-02-14 14:57:26 -07:00
bel
c4c37068f3 New oauth2client 2019-12-31 11:21:15 -07:00
10 changed files with 221 additions and 18 deletions

0
Dockerfile Normal file → Executable file
View File

BIN
config/.config.go.un~ Normal file

Binary file not shown.

BIN
config/.new.go.un~ Normal file

Binary file not shown.

35
config/config.go Normal file → Executable file
View File

@@ -1,6 +1,7 @@
package config package config
import ( import (
"encoding/json"
"fmt" "fmt"
"log" "log"
"strings" "strings"
@@ -24,6 +25,11 @@ func parseProxy(s string) (string, Proxy) {
return key, p return key, p
} }
func GetAuthelia() (string, bool) {
authelia := conf.Get("authelia").GetString()
return authelia, authelia != ""
}
func GetBOAuthZ() (string, bool) { func GetBOAuthZ() (string, bool) {
boauthz := conf.Get("oauth").GetString() boauthz := conf.Get("oauth").GetString()
return boauthz, boauthz != "" return boauthz, boauthz != ""
@@ -40,6 +46,11 @@ func GetPort() string {
return ":" + fmt.Sprint(port) return ":" + fmt.Sprint(port)
} }
func GetAltPort() string {
port := conf.Get("ap").GetInt()
return ":" + fmt.Sprint(port)
}
func GetRate() (int, int) { func GetRate() (int, int) {
rate := conf.Get("r").GetInt() rate := conf.Get("r").GetInt()
burst := conf.Get("b").GetInt() burst := conf.Get("b").GetInt()
@@ -73,3 +84,27 @@ func GetTimeout() time.Duration {
timeout := conf.Get("timeout").GetDuration() timeout := conf.Get("timeout").GetDuration()
return timeout return timeout
} }
func GetCORS(key string) bool {
cors := conf.GetString("cors")
var m map[string]bool
if err := json.Unmarshal([]byte(cors), &m); err != nil {
return false
}
_, ok := m[key]
return ok
}
func GetNoPath(key string) bool {
nopath := conf.GetString("nopath")
var m map[string]bool
if err := json.Unmarshal([]byte(nopath), &m); err != nil {
return false
}
_, ok := m[key]
return ok
}
func GetCompression() bool {
return conf.GetBool("compression")
}

10
config/new.go Normal file → Executable file
View File

@@ -3,6 +3,7 @@ package config
import ( import (
"fmt" "fmt"
"local/args" "local/args"
"local/logb"
"log" "log"
"os" "os"
"strings" "strings"
@@ -26,6 +27,7 @@ func Refresh() error {
return err return err
} }
conf = as conf = as
logb.Set(logb.LevelFromString(as.GetString("level")))
return nil return nil
} }
@@ -39,14 +41,20 @@ func parseArgs() (*args.ArgSet, error) {
as.Append(args.STRING, "user", "username for basic auth", "") as.Append(args.STRING, "user", "username for basic auth", "")
as.Append(args.STRING, "pass", "password for basic auth", "") as.Append(args.STRING, "pass", "password for basic auth", "")
as.Append(args.INT, "p", "port for service", 51555) as.Append(args.INT, "p", "port for service", 51555)
as.Append(args.INT, "ap", "alt port for always http service", 51556)
as.Append(args.INT, "r", "rate per second for requests", 100) as.Append(args.INT, "r", "rate per second for requests", 100)
as.Append(args.INT, "b", "burst requests", 100) as.Append(args.INT, "b", "burst requests", 100)
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, "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 oauth)from,scheme://to.tld:port,oauth,,", "") as.Append(args.STRING, "proxy", "double-comma separated (+ if auth)from,scheme://to.tld:port,,", "")
as.Append(args.STRING, "oauth", "url for boauthz", "") as.Append(args.STRING, "oauth", "url for boauthz", "")
as.Append(args.STRING, "authelia", "url for authelia", "")
as.Append(args.STRING, "cors", "json dict key:true for keys to set CORS permissive headers, like {\"from\":true}", "{}")
as.Append(args.STRING, "nopath", "json dict key:true for keys to remove all path info from forwarded request, like -cors", "{}")
as.Append(args.STRING, "level", "log level", "info")
err := as.Parse() err := as.Parse()
return as, err return as, err

View File

@@ -9,10 +9,15 @@ import (
func New() *Server { func New() *Server {
port := config.GetPort() port := config.GetPort()
altport := config.GetAltPort()
r, b := config.GetRate() r, b := config.GetRate()
return &Server{ server := &Server{
db: storage.NewMap(), db: storage.NewMap(),
addr: port, addr: port,
altaddr: altport,
limiter: rate.NewLimiter(rate.Limit(r), b), limiter: rate.NewLimiter(rate.Limit(r), b),
} }
_, server.auth.BOAuthZ = config.GetBOAuthZ()
_, server.auth.Authelia = config.GetAuthelia()
return server
} }

View File

@@ -37,7 +37,7 @@ func (s *Server) Proxy(w http.ResponseWriter, r *http.Request) {
log.Printf("unknown host lookup %q", r.Host) log.Printf("unknown host lookup %q", r.Host)
return return
} }
r.Host = newURL.Host //r.Host = newURL.Host
proxy := httputil.NewSingleHostReverseProxy(newURL) proxy := httputil.NewSingleHostReverseProxy(newURL)
proxy.Transport = transport proxy.Transport = transport
proxy.ServeHTTP(w, r) proxy.ServeHTTP(w, r)
@@ -49,7 +49,7 @@ func (s *Server) lookup(host string) (*url.URL, error) {
return v.URL(), err return v.URL(), err
} }
func (s *Server) lookupBOAuthZ(host string) (bool, error) { func (s *Server) lookupAuth(host string) (bool, error) {
v := packable.NewString() v := packable.NewString()
err := s.db.Get(nsBOAuthZ, host, v) err := s.db.Get(nsBOAuthZ, host, v)
return v.String() == "true", err return v.String() == "true", err

View File

@@ -7,6 +7,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"local/logb"
"local/oauth2/oauth2client" "local/oauth2/oauth2client"
"local/rproxy3/config" "local/rproxy3/config"
"local/rproxy3/storage" "local/rproxy3/storage"
@@ -15,6 +16,7 @@ import (
"net" "net"
"net/http" "net/http"
"net/url" "net/url"
"path"
"strings" "strings"
"time" "time"
@@ -47,9 +49,14 @@ func (ls listenerScheme) String() string {
type Server struct { type Server struct {
db storage.DB db storage.DB
addr string addr string
altaddr string
username string username string
password string password string
limiter *rate.Limiter limiter *rate.Limiter
auth struct {
BOAuthZ bool
Authelia bool
}
} }
func (s *Server) Route(src string, dst config.Proxy) error { func (s *Server) Route(src string, dst config.Proxy) error {
@@ -65,20 +72,13 @@ func (s *Server) Route(src string, dst config.Proxy) error {
} }
func (s *Server) Run() error { func (s *Server) Run() error {
scheme := schemeHTTP go s.alt()
if _, _, ok := config.GetSSL(); ok { scheme := getScheme()
scheme = schemeHTTPS
}
if _, ok := config.GetTCP(); ok {
scheme = schemeTCP
}
log.Printf("Listening for %v on %v...\n", scheme, s.addr) log.Printf("Listening for %v on %v...\n", scheme, s.addr)
switch scheme { switch scheme {
case schemeHTTP: case schemeHTTP:
log.Printf("Serve http")
return http.ListenAndServe(s.addr, s) return http.ListenAndServe(s.addr, s)
case schemeHTTPS: case schemeHTTPS:
log.Printf("Serve https")
c, k, _ := config.GetSSL() c, k, _ := config.GetSSL()
httpsServer := &http.Server{ httpsServer := &http.Server{
Addr: s.addr, Addr: s.addr,
@@ -98,15 +98,107 @@ func (s *Server) Run() error {
} }
return httpsServer.ListenAndServeTLS(c, k) return httpsServer.ListenAndServeTLS(c, k)
case schemeTCP: case schemeTCP:
log.Printf("Serve tcp")
addr, _ := config.GetTCP() addr, _ := config.GetTCP()
return s.ServeTCP(addr) return s.ServeTCP(addr)
} }
return errors.New("did not load server") return errors.New("did not load server")
} }
func (s *Server) doAuth(foo http.HandlerFunc) http.HandlerFunc { func (s *Server) doAuthelia(foo http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
authelia, ok := config.GetAuthelia()
if !ok {
panic("howd i get here")
}
url, err := url.Parse(authelia)
if err != nil {
panic(fmt.Sprintf("bad config for authelia url: %v", err))
}
url.Path = "/api/verify"
req, err := http.NewRequest(http.MethodGet, url.String(), nil)
if err != nil {
panic(err.Error())
}
r2 := r.Clone(r.Context())
if r2.URL.Host == "" {
r2.URL.Host = r2.Host
}
if r2.URL.Scheme == "" {
r2.URL.Scheme = "https"
}
for _, httpreq := range []*http.Request{r, req} {
for k, v := range map[string]string{
"X-Original-Url": r2.URL.String(),
"X-Forwarded-Proto": r2.URL.Scheme,
"X-Forwarded-Host": r2.URL.Host,
"X-Forwarded-Uri": r2.URL.String(),
} {
if _, ok := httpreq.Header[k]; !ok {
httpreq.Header.Set(k, v)
}
}
}
if cookie, err := r.Cookie("authelia_session"); err == nil {
req.AddCookie(cookie)
}
c := &http.Client{
Timeout: time.Minute,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
autheliaKey := mapKey(req.Host)
if strings.HasPrefix(r.Host, autheliaKey) {
logb.Debugf("no authelia for %s because it has prefix %s", r.Host, autheliaKey)
foo(w, r)
return
}
resp, err := c.Do(req)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
logb.Debugf(
"authelia: %+v, %+v \n\t-> \n\t(%d) %+v, %+v",
req,
req.Cookies(),
resp.StatusCode,
resp.Header,
resp.Cookies(),
)
defer resp.Body.Close()
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: "/",
Domain: r2.Host,
SameSite: http.SameSiteLaxMode,
Secure: true,
HttpOnly: true,
Expires: time.Now().Add(24 * time.Hour * 30),
}
http.SetCookie(w, cookie)
}
}
foo(w, r)
return
}
url.Path = ""
q := url.Query()
q.Set("rd", r2.URL.String())
url.RawQuery = q.Encode()
http.Redirect(w, r, url.String(), http.StatusFound)
}
}
func (s *Server) doBOAuthZ(foo http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
key := mapKey(r.Host)
rusr, rpwd, ok := config.GetAuth() rusr, rpwd, ok := config.GetAuth()
if ok { if ok {
usr, pwd, ok := r.BasicAuth() usr, pwd, ok := r.BasicAuth()
@@ -116,8 +208,7 @@ func (s *Server) doAuth(foo http.HandlerFunc) http.HandlerFunc {
return return
} }
} }
key := mapKey(r.Host) ok, err := s.lookupAuth(key)
ok, err := s.lookupBOAuthZ(key)
if err != nil { if err != nil {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
return return
@@ -128,6 +219,9 @@ func (s *Server) doAuth(foo http.HandlerFunc) http.HandlerFunc {
return return
} }
} }
if config.GetNoPath(key) && path.Ext(r.URL.Path) == "" {
r.URL.Path = "/"
}
foo(w, r) foo(w, r)
} }
} }
@@ -170,7 +264,16 @@ func (s *Server) Pre(foo http.HandlerFunc) http.HandlerFunc {
w.WriteHeader(http.StatusTooManyRequests) w.WriteHeader(http.StatusTooManyRequests)
return return
} }
s.doAuth(foo)(w, r) if did := s.doCORS(w, r); did {
return
}
if s.auth.BOAuthZ {
s.doBOAuthZ(foo)(w, r)
} else if s.auth.Authelia {
s.doAuthelia(foo)(w, r)
} else {
foo(w, r)
}
} }
} }
@@ -178,6 +281,22 @@ 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 {
key := mapKey(r.Host)
if !config.GetCORS(key) {
return false
}
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Headers", "X-Auth-Token, content-type, Content-Type")
if r.Method != "OPTIONS" {
return false
}
w.Header().Set("Content-Length", "0")
w.Header().Set("Content-Type", "text/plain")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, OPTIONS, TRACE, PATCH, HEAD, DELETE")
return true
}
func getProxyAuth(r *http.Request) (string, string) { func getProxyAuth(r *http.Request) (string, string) {
proxyAuthHeader := r.Header.Get("Proxy-Authorization") proxyAuthHeader := r.Header.Get("Proxy-Authorization")
proxyAuthB64 := strings.TrimPrefix(proxyAuthHeader, "Basic ") proxyAuthB64 := strings.TrimPrefix(proxyAuthHeader, "Basic ")
@@ -189,3 +308,39 @@ func getProxyAuth(r *http.Request) (string, string) {
proxyAuthSplit := strings.Split(proxyAuth, ":") proxyAuthSplit := strings.Split(proxyAuth, ":")
return proxyAuthSplit[0], proxyAuthSplit[1] return proxyAuthSplit[0], proxyAuthSplit[1]
} }
func (s *Server) alt() {
switch getScheme() {
case schemeHTTP:
case schemeHTTPS:
default:
return
}
foo := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
r.URL.Scheme = getScheme().String()
if hostname := r.URL.Hostname(); hostname != "" {
r.URL.Host = r.URL.Hostname() + s.addr
} else if hostname := r.URL.Host; hostname != "" {
r.URL.Host = r.URL.Host + s.addr
} else {
u := url.URL{Host: r.Host}
r.URL.Host = u.Hostname() + s.addr
}
http.Redirect(w, r, r.URL.String(), http.StatusSeeOther)
})
log.Println("redirecting from", s.altaddr)
if err := http.ListenAndServe(s.altaddr, foo); err != nil {
panic(err)
}
}
func getScheme() listenerScheme {
scheme := schemeHTTP
if _, _, ok := config.GetSSL(); ok {
scheme = schemeHTTPS
}
if _, ok := config.GetTCP(); ok {
scheme = schemeTCP
}
return scheme
}

0
testdata/index.html vendored Normal file → Executable file
View File

0
testdata/ws.go vendored Normal file → Executable file
View File