commit
b049886341
|
@ -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
2
go.sum
|
@ -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
21
main.go
|
@ -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()
|
||||||
|
|
||||||
|
|
76
utils.go
76
utils.go
|
@ -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
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue