From 54cacb54876344c804dd1001ea7acf8677bbde0b Mon Sep 17 00:00:00 2001 From: Andrea Spacca Date: Sun, 12 Mar 2023 11:52:45 +0900 Subject: [PATCH] add http-auth-htpasswd (#537) * add http-auth-htpasswd * go mod tidy --- README.md | 89 +++++++++++++++++++++++----------------------- cmd/cmd.go | 10 ++++++ flake.nix | 1 + go.mod | 2 ++ go.sum | 4 +++ server/handlers.go | 24 +++++++++++-- server/server.go | 15 ++++++-- 7 files changed, 97 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index 5a31830..6447b97 100644 --- a/README.md +++ b/README.md @@ -86,50 +86,51 @@ https://transfer.sh/1lDau/test.txt --> https://transfer.sh/inline/1lDau/test.txt ## Usage -Parameter | Description | Value | Env ---- | --- | --- | --- -listener | port to use for http (:80) | | LISTENER | -profile-listener | port to use for profiler (:6060) | | PROFILE_LISTENER | -force-https | redirect to https | false | FORCE_HTTPS -tls-listener | port to use for https (:443) | | TLS_LISTENER | -tls-listener-only | flag to enable tls listener only | | TLS_LISTENER_ONLY | -tls-cert-file | path to tls certificate | | TLS_CERT_FILE | -tls-private-key | path to tls private key | | TLS_PRIVATE_KEY | -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 | -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 | -temp-path | path to temp folder | system temp | TEMP_PATH | -web-path | path to static web files (for development or custom front end) | | WEB_PATH | -proxy-path | path prefix when service is run behind a proxy | | PROXY_PATH | -proxy-port | port of the proxy when the service is run behind a proxy | | PROXY_PORT | -email-contact | email contact for the front end | | EMAIL_CONTACT | -ga-key | google analytics key for the front end | | GA_KEY | -provider | which storage provider to use | (s3, storj, gdrive or local) | -uservoice-key | user voice key for the front end | | USERVOICE_KEY | -aws-access-key | aws access key | | AWS_ACCESS_KEY | -aws-secret-key | aws access key | | AWS_SECRET_KEY | -bucket | aws bucket | | BUCKET | -s3-endpoint | Custom S3 endpoint. | | S3_ENDPOINT | -s3-region | region of the s3 bucket | eu-west-1 | S3_REGION | -s3-no-multipart | disables s3 multipart upload | false | S3_NO_MULTIPART | -s3-path-style | Forces path style URLs, required for Minio. | false | S3_PATH_STYLE | -storj-access | Access for the project | | STORJ_ACCESS | -storj-bucket | Bucket to use within the project | | STORJ_BUCKET | -basedir | path storage for local/gdrive provider | | BASEDIR | -gdrive-client-json-filepath | path to oauth client json config for gdrive provider | | GDRIVE_CLIENT_JSON_FILEPATH | -gdrive-local-config-path | path to store local transfer.sh config cache for gdrive provider| | GDRIVE_LOCAL_CONFIG_PATH | -gdrive-chunk-size | chunk size for gdrive upload in megabytes, must be lower than available memory (8 MB) | | GDRIVE_CHUNK_SIZE | -lets-encrypt-hosts | hosts to use for lets encrypt certificates (comma seperated) | | HOSTS | -log | path to log file| | LOG | -cors-domains | comma separated list of domains for CORS, setting it enable CORS | | CORS_DOMAINS | -clamav-host | host for clamav feature | | CLAMAV_HOST | -perform-clamav-prescan | prescan every upload through clamav feature (clamav-host must be a local clamd unix socket) | | PERFORM_CLAMAV_PRESCAN | -rate-limit | request per minute | | RATE_LIMIT | -max-upload-size | max upload size in kilobytes | | MAX_UPLOAD_SIZE | -purge-days | number of days after the uploads are purged automatically | | PURGE_DAYS | -purge-interval | interval in hours to run the automatic purge for (not applicable to S3 and Storj) | | PURGE_INTERVAL | -random-token-length | length of the random token for the upload path (double the size for delete path) | 6 | RANDOM_TOKEN_LENGTH | +Parameter | Description | Value | Env +--- |---------------------------------------------------------------------------------------------| --- |----------------------------- +listener | port to use for http (:80) | | LISTENER | +profile-listener | port to use for profiler (:6060) | | PROFILE_LISTENER | +force-https | redirect to https | false | FORCE_HTTPS +tls-listener | port to use for https (:443) | | TLS_LISTENER | +tls-listener-only | flag to enable tls listener only | | TLS_LISTENER_ONLY | +tls-cert-file | path to tls certificate | | TLS_CERT_FILE | +tls-private-key | path to tls private key | | TLS_PRIVATE_KEY | +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-htpasswd | htpasswd file path for basic http auth on upload | | HTTP_AUTH_HTPASSWD | +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 | +temp-path | path to temp folder | system temp | TEMP_PATH | +web-path | path to static web files (for development or custom front end) | | WEB_PATH | +proxy-path | path prefix when service is run behind a proxy | | PROXY_PATH | +proxy-port | port of the proxy when the service is run behind a proxy | | PROXY_PORT | +email-contact | email contact for the front end | | EMAIL_CONTACT | +ga-key | google analytics key for the front end | | GA_KEY | +provider | which storage provider to use | (s3, storj, gdrive or local) | +uservoice-key | user voice key for the front end | | USERVOICE_KEY | +aws-access-key | aws access key | | AWS_ACCESS_KEY | +aws-secret-key | aws access key | | AWS_SECRET_KEY | +bucket | aws bucket | | BUCKET | +s3-endpoint | Custom S3 endpoint. | | S3_ENDPOINT | +s3-region | region of the s3 bucket | eu-west-1 | S3_REGION | +s3-no-multipart | disables s3 multipart upload | false | S3_NO_MULTIPART | +s3-path-style | Forces path style URLs, required for Minio. | false | S3_PATH_STYLE | +storj-access | Access for the project | | STORJ_ACCESS | +storj-bucket | Bucket to use within the project | | STORJ_BUCKET | +basedir | path storage for local/gdrive provider | | BASEDIR | +gdrive-client-json-filepath | path to oauth client json config for gdrive provider | | GDRIVE_CLIENT_JSON_FILEPATH | +gdrive-local-config-path | path to store local transfer.sh config cache for gdrive provider | | GDRIVE_LOCAL_CONFIG_PATH | +gdrive-chunk-size | chunk size for gdrive upload in megabytes, must be lower than available memory (8 MB) | | GDRIVE_CHUNK_SIZE | +lets-encrypt-hosts | hosts to use for lets encrypt certificates (comma seperated) | | HOSTS | +log | path to log file | | LOG | +cors-domains | comma separated list of domains for CORS, setting it enable CORS | | CORS_DOMAINS | +clamav-host | host for clamav feature | | CLAMAV_HOST | +perform-clamav-prescan | prescan every upload through clamav feature (clamav-host must be a local clamd unix socket) | | PERFORM_CLAMAV_PRESCAN | +rate-limit | request per minute | | RATE_LIMIT | +max-upload-size | max upload size in kilobytes | | MAX_UPLOAD_SIZE | +purge-days | number of days after the uploads are purged automatically | | PURGE_DAYS | +purge-interval | interval in hours to run the automatic purge for (not applicable to S3 and Storj) | | PURGE_INTERVAL | +random-token-length | length of the random token for the upload path (double the size for delete path) | 6 | RANDOM_TOKEN_LENGTH | If you want to use TLS using lets encrypt certificates, set lets-encrypt-hosts to your domain, set tls-listener to :443 and enable force-https. diff --git a/cmd/cmd.go b/cmd/cmd.go index ba37b75..b7fe149 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -270,6 +270,12 @@ var globalFlags = []cli.Flag{ Value: "", EnvVar: "HTTP_AUTH_PASS", }, + cli.StringFlag{ + Name: "http-auth-htpasswd", + Usage: "htpasswd file http basic auth", + Value: "", + EnvVar: "HTTP_AUTH_HTPASSWD", + }, cli.StringFlag{ Name: "ip-whitelist", Usage: "comma separated list of ips allowed to connect to the service", @@ -440,6 +446,10 @@ func New() *Cmd { options = append(options, server.HTTPAuthCredentials(httpAuthUser, httpAuthPass)) } + if httpAuthHtpasswd := c.String("http-auth-htpasswd"); httpAuthHtpasswd != "" { + options = append(options, server.HTTPAuthHtpasswd(httpAuthHtpasswd)) + } + applyIPFilter := false ipFilterOptions := server.IPFilterOptions{} if ipWhitelist := c.String("ip-whitelist"); ipWhitelist != "" { diff --git a/flake.nix b/flake.nix index 2db0c68..94dca5c 100644 --- a/flake.nix +++ b/flake.nix @@ -44,6 +44,7 @@ tls-private-key = mkOption { type = types.nullOr types.str; description = "path to tls private key "; }; 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-htpasswd = mkOption { type = types.nullOr types.str; description = "htpasswd file path for basic http auth on upload"; }; 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"; }; temp-path = mkOption { type = types.nullOr types.str; description = "path to temp folder"; }; diff --git a/go.mod b/go.mod index 091efec..cc51eaf 100644 --- a/go.mod +++ b/go.mod @@ -19,6 +19,7 @@ require ( github.com/microcosm-cc/bluemonday v1.0.23 github.com/russross/blackfriday/v2 v2.1.0 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e + github.com/tg123/go-htpasswd v1.2.1 github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce github.com/urfave/cli v1.22.12 golang.org/x/crypto v0.6.0 @@ -33,6 +34,7 @@ require ( require ( cloud.google.com/go/compute v1.18.0 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect + github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/calebcase/tmpfile v1.0.3 // indirect github.com/cloudflare/circl v1.1.0 // indirect diff --git a/go.sum b/go.sum index 75842c9..3c87cf2 100644 --- a/go.sum +++ b/go.sum @@ -9,6 +9,8 @@ cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXW github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962 h1:KeNholpO2xKjgaaSyd+DyQRrsQjhbSeS7qe4nEw8aQw= +github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962/go.mod h1:kC29dT1vFpj7py2OvG1khBdQpo3kInWP+6QipLbdngo= github.com/ProtonMail/go-crypto v0.0.0-20230124153114-0acdc8ae009b/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwFc0ih8puEVAOFGELsn1zoIIYdxvML7mDxA= github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= @@ -179,6 +181,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/tg123/go-htpasswd v1.2.1 h1:i4wfsX1KvvkyoMiHZzjS0VzbAPWfxzI8INcZAKtutoU= +github.com/tg123/go-htpasswd v1.2.1/go.mod h1:erHp1B86KXdwQf1X5ZrLb7erXZnWueEQezb2dql4q58= github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce h1:fb190+cK2Xz/dvi9Hv8eCYJYvIGUTN2/KLq1pT6CjEc= github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4= github.com/urfave/cli v1.22.12 h1:igJgVw1JdKH+trcLWLeLwZjU9fEfPesQ+9/e4MQ44S8= diff --git a/server/handlers.go b/server/handlers.go index e32a22e..119fb0d 100644 --- a/server/handlers.go +++ b/server/handlers.go @@ -57,6 +57,7 @@ import ( "github.com/ProtonMail/go-crypto/openpgp/packet" "github.com/ProtonMail/gopenpgp/v2/constants" "github.com/dutchcoders/transfer.sh/server/storage" + "github.com/tg123/go-htpasswd" web "github.com/dutchcoders/transfer.sh-web" "github.com/gorilla/mux" @@ -1319,11 +1320,21 @@ func ipFilterHandler(h http.Handler, ipFilterOptions *IPFilterOptions) http.Hand func (s *Server) basicAuthHandler(h http.Handler) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - if s.AuthUser == "" || s.AuthPass == "" { + if s.AuthUser == "" || s.AuthPass == "" || s.AuthHtpasswd == "" { h.ServeHTTP(w, r) return } + if s.htpasswdFile == nil && s.AuthHtpasswd != "" { + htpasswdFile, err := htpasswd.New(s.AuthHtpasswd, htpasswd.DefaultSystems, nil) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + s.htpasswdFile = htpasswdFile + } + w.Header().Set("WWW-Authenticate", "Basic realm=\"Restricted\"") username, password, authOK := r.BasicAuth() @@ -1332,7 +1343,16 @@ func (s *Server) basicAuthHandler(h http.Handler) http.HandlerFunc { return } - if username != s.AuthUser || password != s.AuthPass { + var authorized bool + if username == s.AuthUser && password == s.AuthPass { + authorized = true + } + + if s.htpasswdFile != nil && !authorized { + authorized = s.htpasswdFile.Match(username, password) + } + + if !authorized { http.Error(w, "Not authorized", http.StatusUnauthorized) return } diff --git a/server/server.go b/server/server.go index fb0c9d0..62e4ab6 100644 --- a/server/server.go +++ b/server/server.go @@ -49,6 +49,7 @@ import ( "github.com/VojtechVitek/ratelimit/memory" gorillaHandlers "github.com/gorilla/handlers" "github.com/gorilla/mux" + "github.com/tg123/go-htpasswd" "golang.org/x/crypto/acme/autocert" web "github.com/dutchcoders/transfer.sh-web" @@ -299,6 +300,13 @@ func HTTPAuthCredentials(user string, pass string) OptionFn { } } +// HTTPAuthHtpasswd sets basic http auth htpasswd file +func HTTPAuthHtpasswd(htpasswdPath string) OptionFn { + return func(srvr *Server) { + srvr.AuthHtpasswd = htpasswdPath + } +} + // FilterOptions sets ip filtering func FilterOptions(options IPFilterOptions) OptionFn { for i, allowedIP := range options.AllowedIPs { @@ -316,8 +324,11 @@ func FilterOptions(options IPFilterOptions) OptionFn { // Server is the main application type Server struct { - AuthUser string - AuthPass string + AuthUser string + AuthPass string + AuthHtpasswd string + + htpasswdFile *htpasswd.File logger *log.Logger