add IP_FILTERLIST_BYPASS_HTTP_AUTH (#538)

* add IP_FILTERLIST_BYPASS_HTTP_AUTH

* refactor to separated ip whitelist
This commit is contained in:
Andrea Spacca 2023-03-12 13:34:41 +09:00 committed by GitHub
parent 54cacb5487
commit 1fb67f49ff
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 98 additions and 62 deletions

View file

@ -87,7 +87,7 @@ https://transfer.sh/1lDau/test.txt --> https://transfer.sh/inline/1lDau/test.txt
## Usage ## Usage
Parameter | Description | Value | Env Parameter | Description | Value | Env
--- |---------------------------------------------------------------------------------------------| --- |----------------------------- --- |---------------------------------------------------------------------------------------------|------------------------------|-----------------------------
listener | port to use for http (:80) | | LISTENER | listener | port to use for http (:80) | | LISTENER |
profile-listener | port to use for profiler (:6060) | | PROFILE_LISTENER | profile-listener | port to use for profiler (:6060) | | PROFILE_LISTENER |
force-https | redirect to https | false | FORCE_HTTPS force-https | redirect to https | false | FORCE_HTTPS
@ -98,6 +98,7 @@ tls-private-key | path to tls private key
http-auth-user | user for basic http auth on upload | | HTTP_AUTH_USER | http-auth-user | user for basic http auth on upload | | HTTP_AUTH_USER |
http-auth-pass | pass for basic http auth on upload | | HTTP_AUTH_PASS | http-auth-pass | pass for basic http auth on upload | | HTTP_AUTH_PASS |
http-auth-htpasswd | htpasswd file path for basic http auth on upload | | HTTP_AUTH_HTPASSWD | http-auth-htpasswd | htpasswd file path for basic http auth on upload | | HTTP_AUTH_HTPASSWD |
http-auth-ip-whitelist | comma separated list of ips allowed to upload without being challenged an http auth | | HTTP_AUTH_IP_WHITELIST |
ip-whitelist | comma separated list of ips allowed to connect to the service | | IP_WHITELIST | ip-whitelist | comma separated list of ips allowed to connect to the service | | IP_WHITELIST |
ip-blacklist | comma separated list of ips not allowed to connect to the service | | IP_BLACKLIST | ip-blacklist | comma separated list of ips not allowed to connect to the service | | IP_BLACKLIST |
temp-path | path to temp folder | system temp | TEMP_PATH | temp-path | path to temp folder | system temp | TEMP_PATH |

View file

@ -276,6 +276,12 @@ var globalFlags = []cli.Flag{
Value: "", Value: "",
EnvVar: "HTTP_AUTH_HTPASSWD", EnvVar: "HTTP_AUTH_HTPASSWD",
}, },
cli.StringFlag{
Name: "http-auth-ip-whitelist",
Usage: "comma separated list of ips allowed to upload without being challenged an http auth",
Value: "",
EnvVar: "HTTP_AUTH_IP_WHITELIST",
},
cli.StringFlag{ cli.StringFlag{
Name: "ip-whitelist", Name: "ip-whitelist",
Usage: "comma separated list of ips allowed to connect to the service", Usage: "comma separated list of ips allowed to connect to the service",
@ -450,6 +456,13 @@ func New() *Cmd {
options = append(options, server.HTTPAuthHtpasswd(httpAuthHtpasswd)) options = append(options, server.HTTPAuthHtpasswd(httpAuthHtpasswd))
} }
if httpAuthIPWhitelist := c.String("http-auth-ip-whitelist"); httpAuthIPWhitelist != "" {
ipFilterOptions := server.IPFilterOptions{}
ipFilterOptions.AllowedIPs = strings.Split(httpAuthIPWhitelist, ",")
ipFilterOptions.BlockByDefault = false
options = append(options, server.HTTPAUTHFilterOptions(ipFilterOptions))
}
applyIPFilter := false applyIPFilter := false
ipFilterOptions := server.IPFilterOptions{} ipFilterOptions := server.IPFilterOptions{}
if ipWhitelist := c.String("ip-whitelist"); ipWhitelist != "" { if ipWhitelist := c.String("ip-whitelist"); ipWhitelist != "" {

View file

@ -45,6 +45,7 @@
http-auth-user = mkOption { type = types.nullOr types.str; description = "user for basic http auth on upload"; }; http-auth-user = mkOption { type = types.nullOr types.str; description = "user for basic http auth on upload"; };
http-auth-pass = mkOption { type = types.nullOr types.str; description = "pass for basic http auth on upload"; }; http-auth-pass = mkOption { type = types.nullOr types.str; description = "pass for basic http auth on upload"; };
http-auth-htpasswd = mkOption { type = types.nullOr types.str; description = "htpasswd file path for basic http auth on upload"; }; http-auth-htpasswd = mkOption { type = types.nullOr types.str; description = "htpasswd file path for basic http auth on upload"; };
http-auth-ip-whitelist = mkOption { type = types.nullOr types.str; description = "comma separated list of ips allowed to upload without being challenged an http auth"; };
ip-whitelist = mkOption { type = types.nullOr types.str; description = "comma separated list of ips allowed to connect to the service"; }; ip-whitelist = mkOption { type = types.nullOr types.str; description = "comma separated list of ips allowed to connect to the service"; };
ip-blacklist = mkOption { type = types.nullOr types.str; description = "comma separated list of ips not allowed to connect to the service"; }; ip-blacklist = mkOption { type = types.nullOr types.str; description = "comma separated list of ips not allowed to connect to the service"; };
temp-path = mkOption { type = types.nullOr types.str; description = "path to temp folder"; }; temp-path = mkOption { type = types.nullOr types.str; description = "path to temp folder"; };

View file

@ -58,6 +58,7 @@ import (
"github.com/ProtonMail/gopenpgp/v2/constants" "github.com/ProtonMail/gopenpgp/v2/constants"
"github.com/dutchcoders/transfer.sh/server/storage" "github.com/dutchcoders/transfer.sh/server/storage"
"github.com/tg123/go-htpasswd" "github.com/tg123/go-htpasswd"
"github.com/tomasen/realip"
web "github.com/dutchcoders/transfer.sh-web" web "github.com/dutchcoders/transfer.sh-web"
"github.com/gorilla/mux" "github.com/gorilla/mux"
@ -1313,20 +1314,20 @@ func ipFilterHandler(h http.Handler, ipFilterOptions *IPFilterOptions) http.Hand
if ipFilterOptions == nil { if ipFilterOptions == nil {
h.ServeHTTP(w, r) h.ServeHTTP(w, r)
} else { } else {
WrapIPFilter(h, *ipFilterOptions).ServeHTTP(w, r) WrapIPFilter(h, ipFilterOptions).ServeHTTP(w, r)
} }
} }
} }
func (s *Server) basicAuthHandler(h http.Handler) http.HandlerFunc { func (s *Server) basicAuthHandler(h http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
if s.AuthUser == "" || s.AuthPass == "" || s.AuthHtpasswd == "" { if s.authUser == "" || s.authPass == "" || s.authHtpasswd == "" {
h.ServeHTTP(w, r) h.ServeHTTP(w, r)
return return
} }
if s.htpasswdFile == nil && s.AuthHtpasswd != "" { if s.htpasswdFile == nil && s.authHtpasswd != "" {
htpasswdFile, err := htpasswd.New(s.AuthHtpasswd, htpasswd.DefaultSystems, nil) htpasswdFile, err := htpasswd.New(s.authHtpasswd, htpasswd.DefaultSystems, nil)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -1335,20 +1336,29 @@ func (s *Server) basicAuthHandler(h http.Handler) http.HandlerFunc {
s.htpasswdFile = htpasswdFile s.htpasswdFile = htpasswdFile
} }
if s.authIPFilter == nil && s.authIPFilterOptions != nil {
s.authIPFilter = newIPFilter(s.authIPFilterOptions)
}
w.Header().Set("WWW-Authenticate", "Basic realm=\"Restricted\"") w.Header().Set("WWW-Authenticate", "Basic realm=\"Restricted\"")
var authorized bool
if s.authIPFilter != nil {
remoteIP := realip.FromRequest(r)
authorized = s.authIPFilter.Allowed(remoteIP)
}
username, password, authOK := r.BasicAuth() username, password, authOK := r.BasicAuth()
if !authOK { if !authOK && !authorized {
http.Error(w, "Not authorized", http.StatusUnauthorized) http.Error(w, "Not authorized", http.StatusUnauthorized)
return return
} }
var authorized bool if !authorized && username == s.authUser && password == s.authPass {
if username == s.AuthUser && password == s.AuthPass {
authorized = true authorized = true
} }
if s.htpasswdFile != nil && !authorized { if !authorized && s.htpasswdFile != nil {
authorized = s.htpasswdFile.Match(username, password) authorized = s.htpasswdFile.Match(username, password)
} }

View file

@ -45,7 +45,6 @@ type IPFilterOptions struct {
// ipFilter // ipFilter
type ipFilter struct { type ipFilter struct {
opts IPFilterOptions
//mut protects the below //mut protects the below
//rw since writes are rare //rw since writes are rare
mut sync.RWMutex mut sync.RWMutex
@ -60,13 +59,12 @@ type subnet struct {
allowed bool allowed bool
} }
func newIPFilter(opts IPFilterOptions) *ipFilter { func newIPFilter(opts *IPFilterOptions) *ipFilter {
if opts.Logger == nil { if opts.Logger == nil {
flags := log.LstdFlags flags := log.LstdFlags
opts.Logger = log.New(os.Stdout, "", flags) opts.Logger = log.New(os.Stdout, "", flags)
} }
f := &ipFilter{ f := &ipFilter{
opts: opts,
ips: map[string]bool{}, ips: map[string]bool{},
defaultAllowed: !opts.BlockByDefault, defaultAllowed: !opts.BlockByDefault,
} }
@ -189,7 +187,7 @@ func (f *ipFilter) Wrap(next http.Handler) http.Handler {
} }
// WrapIPFilter is equivalent to newIPFilter(opts) then Wrap(next) // WrapIPFilter is equivalent to newIPFilter(opts) then Wrap(next)
func WrapIPFilter(next http.Handler, opts IPFilterOptions) http.Handler { func WrapIPFilter(next http.Handler, opts *IPFilterOptions) http.Handler {
return newIPFilter(opts).Wrap(next) return newIPFilter(opts).Wrap(next)
} }

View file

@ -295,15 +295,26 @@ func TLSConfig(cert, pk string) OptionFn {
// HTTPAuthCredentials sets basic http auth credentials // HTTPAuthCredentials sets basic http auth credentials
func HTTPAuthCredentials(user string, pass string) OptionFn { func HTTPAuthCredentials(user string, pass string) OptionFn {
return func(srvr *Server) { return func(srvr *Server) {
srvr.AuthUser = user srvr.authUser = user
srvr.AuthPass = pass srvr.authPass = pass
} }
} }
// HTTPAuthHtpasswd sets basic http auth htpasswd file // HTTPAuthHtpasswd sets basic http auth htpasswd file
func HTTPAuthHtpasswd(htpasswdPath string) OptionFn { func HTTPAuthHtpasswd(htpasswdPath string) OptionFn {
return func(srvr *Server) { return func(srvr *Server) {
srvr.AuthHtpasswd = htpasswdPath srvr.authHtpasswd = htpasswdPath
}
}
// HTTPAUTHFilterOptions sets basic http auth ips whitelist
func HTTPAUTHFilterOptions(options IPFilterOptions) OptionFn {
for i, allowedIP := range options.AllowedIPs {
options.AllowedIPs[i] = strings.TrimSpace(allowedIP)
}
return func(srvr *Server) {
srvr.authIPFilterOptions = &options
} }
} }
@ -324,11 +335,13 @@ func FilterOptions(options IPFilterOptions) OptionFn {
// Server is the main application // Server is the main application
type Server struct { type Server struct {
AuthUser string authUser string
AuthPass string authPass string
AuthHtpasswd string authHtpasswd string
authIPFilterOptions *IPFilterOptions
htpasswdFile *htpasswd.File htpasswdFile *htpasswd.File
authIPFilter *ipFilter
logger *log.Logger logger *log.Logger