Merge pull request #10 from Snawoot/htpasswd_util

htpasswd-alike util
This commit is contained in:
Snawoot 2022-09-07 00:28:15 +03:00 committed by GitHub
commit b049886341
Failed to generate hash of commit
4 changed files with 98 additions and 7 deletions

View File

@ -157,7 +157,7 @@ Authentication parameters are passed as URI via `-auth` parameter. Scheme of URI
* `username` - login. * `username` - login.
* `password` - password. * `password` - password.
* `hidden_domain` - if specified and is not an empty string, proxy will respond with "407 Proxy Authentication Required" only on specified domain. All unauthenticated clients will receive "400 Bad Request" status. This option is useful to prevent DPI active probing from discovering that service is a proxy, hiding proxy authentication prompt when no valid auth header was provided. Hidden domain is used for generating 407 response code to trigger browser authorization request in cases when browser has no prior knowledge proxy authentication is required. In such cases user has to navigate to any hidden domain page via plaintext HTTP, authenticate themselves and then browser will remember authentication. * `hidden_domain` - if specified and is not an empty string, proxy will respond with "407 Proxy Authentication Required" only on specified domain. All unauthenticated clients will receive "400 Bad Request" status. This option is useful to prevent DPI active probing from discovering that service is a proxy, hiding proxy authentication prompt when no valid auth header was provided. Hidden domain is used for generating 407 response code to trigger browser authorization request in cases when browser has no prior knowledge proxy authentication is required. In such cases user has to navigate to any hidden domain page via plaintext HTTP, authenticate themselves and then browser will remember authentication.
* `basicfile` - use htpasswd-like file with login and password pairs for authentication. Such file can be created/updated like this: `touch /etc/dumbproxy.htpasswd && htpasswd -bBC 4 /etc/dumbproxy.htpasswd username password`. `path` parameter in URL for this provider must point to a local file with login and bcrypt-hashed password lines. Example: `basicfile://?path=/etc/dumbproxy.htpasswd`. * `basicfile` - use htpasswd-like file with login and password pairs for authentication. Such file can be created/updated with command like this: `dumbproxy -passwd /etc/dumbproxy.htpasswd username password` or with `htpasswd` utility from Apache HTTPD utils. `path` parameter in URL for this provider must point to a local file with login and bcrypt-hashed password lines. Example: `basicfile://?path=/etc/dumbproxy.htpasswd`.
* `path` - location of file with login and password pairs. File format is similar to htpasswd files. Each line must be in form `<username>:<bcrypt hash of password>`. Empty lines and lines starting with `#` are ignored. * `path` - location of file with login and password pairs. File format is similar to htpasswd files. Each line must be in form `<username>:<bcrypt hash of password>`. Empty lines and lines starting with `#` are ignored.
* `hidden_domain` - same as in `static` provider * `hidden_domain` - same as in `static` provider
* `reload` - interval for conditional password file reload, if it was modified since last load. Use negative duration to disable autoreload. Default: `15s`. * `reload` - interval for conditional password file reload, if it was modified since last load. Use negative duration to disable autoreload. Default: `15s`.
@ -195,6 +195,10 @@ $ ~/go/bin/dumbproxy -h
key for TLS certificate key for TLS certificate
-list-ciphers -list-ciphers
list ciphersuites list ciphersuites
-passwd string
update given htpasswd file and add/set password for username. Username and password can be passed as positional arguments or requested interactively
-passwd-cost int
bcrypt password cost (for -passwd mode) (default 4)
-timeout duration -timeout duration
timeout for network operations (default 10s) timeout for network operations (default 10s)
-verbosity int -verbosity int

2
go.sum
View File

@ -16,8 +16,10 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=

21
main.go
View File

@ -13,6 +13,7 @@ import (
"golang.org/x/crypto/acme" "golang.org/x/crypto/acme"
"golang.org/x/crypto/acme/autocert" "golang.org/x/crypto/acme/autocert"
"golang.org/x/crypto/bcrypt"
) )
var ( var (
@ -65,12 +66,9 @@ type CLIArgs struct {
autocertACME string autocertACME string
autocertEmail string autocertEmail string
autocertHTTP string autocertHTTP string
} passwd string
passwdCost int
func list_ciphers() { positionalArgs []string
for _, cipher := range tls.CipherSuites() {
fmt.Println(cipher.Name)
}
} }
func parse_args() CLIArgs { func parse_args() CLIArgs {
@ -93,7 +91,11 @@ func parse_args() CLIArgs {
flag.StringVar(&args.autocertACME, "autocert-acme", autocert.DefaultACMEDirectory, "custom ACME endpoint") flag.StringVar(&args.autocertACME, "autocert-acme", autocert.DefaultACMEDirectory, "custom ACME endpoint")
flag.StringVar(&args.autocertEmail, "autocert-email", "", "email used for ACME registration") flag.StringVar(&args.autocertEmail, "autocert-email", "", "email used for ACME registration")
flag.StringVar(&args.autocertHTTP, "autocert-http", "", "listen address for HTTP-01 challenges handler of ACME") flag.StringVar(&args.autocertHTTP, "autocert-http", "", "listen address for HTTP-01 challenges handler of ACME")
flag.StringVar(&args.passwd, "passwd", "", "update given htpasswd file and add/set password for username. "+
"Username and password can be passed as positional arguments or requested interactively")
flag.IntVar(&args.passwdCost, "passwd-cost", bcrypt.MinCost, "bcrypt password cost (for -passwd mode)")
flag.Parse() flag.Parse()
args.positionalArgs = flag.Args()
return args return args
} }
@ -110,6 +112,13 @@ func run() int {
return 0 return 0
} }
if args.passwd != "" {
if err := passwd(args.passwd, args.passwdCost, args.positionalArgs...); err != nil {
log.Fatalf("can't set password: %v", err)
}
return 0
}
logWriter := NewLogWriter(os.Stderr) logWriter := NewLogWriter(os.Stderr)
defer logWriter.Close() defer logWriter.Close()

View File

@ -16,6 +16,9 @@ import (
"strings" "strings"
"sync" "sync"
"time" "time"
"golang.org/x/crypto/bcrypt"
"golang.org/x/crypto/ssh/terminal"
) )
const COPY_BUF = 128 * 1024 const COPY_BUF = 128 * 1024
@ -192,6 +195,62 @@ func makeCipherList(ciphers string) []uint16 {
return cipherIDList return cipherIDList
} }
func list_ciphers() {
for _, cipher := range tls.CipherSuites() {
fmt.Println(cipher.Name)
}
}
func passwd(filename string, cost int, args ...string) error {
var (
username, password, password2 string
err error
)
if len(args) > 0 {
username = args[0]
} else {
username, err = prompt("Enter username: ", false)
if err != nil {
return fmt.Errorf("can't get username: %w", err)
}
}
if len(args) > 1 {
password = args[1]
} else {
password, err = prompt("Enter password: ", true)
if err != nil {
return fmt.Errorf("can't get password: %w", err)
}
password2, err = prompt("Repeat password: ", true)
if err != nil {
return fmt.Errorf("can't get password (repeat): %w", err)
}
if password != password2 {
return fmt.Errorf("passwords do not match")
}
}
hash, err := bcrypt.GenerateFromPassword([]byte(password), cost)
if err != nil {
return fmt.Errorf("can't generate password hash: %w", err)
}
f, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
if err != nil {
return fmt.Errorf("can't open file: %w", err)
}
defer f.Close()
_, err = f.WriteString(fmt.Sprintf("%s:%s\n", username, hash))
if err != nil {
return fmt.Errorf("can't write to file: %w", err)
}
return nil
}
func fileModTime(filename string) (time.Time, error) { func fileModTime(filename string) (time.Time, error) {
f, err := os.Open(filename) f, err := os.Open(filename)
if err != nil { if err != nil {
@ -206,3 +265,20 @@ func fileModTime(filename string) (time.Time, error) {
return fi.ModTime(), nil return fi.ModTime(), nil
} }
func prompt(prompt string, secure bool) (string, error) {
var input string
fmt.Print(prompt)
if secure {
b, err := terminal.ReadPassword(int(os.Stdin.Fd()))
if err != nil {
return "", err
}
input = string(b)
fmt.Println()
} else {
fmt.Scanln(&input)
}
return input, nil
}