diff --git a/app/config.go b/app/config.go index 69313ae..bb46f9d 100644 --- a/app/config.go +++ b/app/config.go @@ -12,15 +12,16 @@ import ( // Config represents the configuration of the server application. type Config struct { - Address string - Port string - Prefix string - Dir string - TLS *TLS - Log Logging - Realm string - Users map[string]*UserInfo - Cors Cors + Address string + Port string + Prefix string + Dir string + TLS *TLS + Log Logging + Realm string + Users map[string]*UserInfo + Authentication_Bypass_IP_Addresses []string + Cors Cors } // Logging allows definition for logging each CRUD method. @@ -99,6 +100,7 @@ func setDefaults() { viper.SetDefault("Prefix", "") viper.SetDefault("Dir", "/tmp") viper.SetDefault("Users", nil) + viper.SetDefault("Authentication_Bypass_IP_Addresses", nil) viper.SetDefault("TLS", nil) viper.SetDefault("Realm", "dave") viper.SetDefault("Log.Error", true) @@ -110,7 +112,14 @@ func setDefaults() { } // AuthenticationNeeded returns whether users are defined and authentication is required -func (cfg *Config) AuthenticationNeeded() bool { +func (cfg *Config) AuthenticationNeeded(requestIpAddress string) bool { + if cfg.Authentication_Bypass_IP_Addresses != nil { + for _, bypass_ip := range cfg.Authentication_Bypass_IP_Addresses { + if bypass_ip == requestIpAddress { + return false + } + } + } return cfg.Users != nil && len(cfg.Users) != 0 } diff --git a/app/security.go b/app/security.go index 63136da..3f61dec 100644 --- a/app/security.go +++ b/app/security.go @@ -3,11 +3,13 @@ package app import ( "context" "fmt" + "net" + "net/http" + "strings" + "github.com/pkg/errors" log "github.com/sirupsen/logrus" "golang.org/x/crypto/bcrypt" - "net/http" - "strings" ) type contextKey int @@ -39,8 +41,8 @@ func NewBasicAuthWebdavHandler(a *App) http.Handler { }) } -func authenticate(config *Config, username, password string) (*AuthInfo, error) { - if !config.AuthenticationNeeded() { +func authenticate(config *Config, username, password string, requestIpAddress string) (*AuthInfo, error) { + if !config.AuthenticationNeeded(requestIpAddress) { return &AuthInfo{Username: "", Authenticated: false}, nil } @@ -72,6 +74,11 @@ func AuthFromContext(ctx context.Context) *AuthInfo { } func handle(ctx context.Context, w http.ResponseWriter, req *http.Request, a *App) { + requestIpAddress, _, ipFetchError := net.SplitHostPort(req.RemoteAddr) + if ipFetchError != nil { + log.WithError(ipFetchError).Error("Error fetching remote address ??") + } + // handle a preflight if such a CORS request would be allowed if req.Method == "OPTIONS" { if a.Config.Cors.Origin == req.Header.Get("Origin") && @@ -83,7 +90,7 @@ func handle(ctx context.Context, w http.ResponseWriter, req *http.Request, a *Ap } // if there are no users, we don't need authentication here - if !a.Config.AuthenticationNeeded() { + if !a.Config.AuthenticationNeeded(requestIpAddress) { a.Handler.ServeHTTP(w, req.WithContext(ctx)) return } @@ -94,7 +101,7 @@ func handle(ctx context.Context, w http.ResponseWriter, req *http.Request, a *Ap return } - authInfo, err := authenticate(a.Config, username, password) + authInfo, err := authenticate(a.Config, username, password, requestIpAddress) if err != nil { ipAddr := req.Header.Get("X-Forwarded-For") if len(ipAddr) == 0 { @@ -120,7 +127,13 @@ func handle(ctx context.Context, w http.ResponseWriter, req *http.Request, a *Ap } func httpAuth(r *http.Request, config *Config) (string, string, bool) { - if config.AuthenticationNeeded() { + + requestIpAddress, _, ipFetchError := net.SplitHostPort(r.RemoteAddr) + if ipFetchError != nil { + log.WithError(ipFetchError).Error("Error fetching remote address ??") + } + + if config.AuthenticationNeeded(requestIpAddress) { username, password, ok := r.BasicAuth() return username, password, ok } diff --git a/app/security_test.go b/app/security_test.go index 847c24a..d290840 100644 --- a/app/security_test.go +++ b/app/security_test.go @@ -133,7 +133,8 @@ func TestAuthenticate(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := authenticate(tt.args.config, tt.args.username, tt.args.password) + requestIpAddress := "" // todo: test Authentication_Bypass_IP_Addresses, somehow.. + got, err := authenticate(tt.args.config, tt.args.username, tt.args.password, requestIpAddress) if (err != nil) != tt.wantErr { t.Errorf("authenticate() name = %v, error = %v, wantErr %v", tt.name, err, tt.wantErr) return diff --git a/examples/config-sample.yaml b/examples/config-sample.yaml index fb15ccc..e239ebe 100644 --- a/examples/config-sample.yaml +++ b/examples/config-sample.yaml @@ -51,6 +51,16 @@ users: password: "$2a$10$yITzSSNJZAdDZs8iVBQzkuZCzZ49PyjTiPIrmBUKUpB0pwX7eySvW" +# ---------------------------------- Authentication bypass IP Addresses -------- + +# A list of IP addresses that bypass authentication +# +#Authentication_Bypass_IP_Addresses: +# - '127.0.0.1' +# - '::1' +# - '984.49.158.188' + + # ---------------------------------- Logging ----------------------------------- # # Seperated loglevels for file / directory operations. All set to false per