Merge pull request #8 from Snawoot/autotls

AutoTLS
This commit is contained in:
Snawoot 2022-09-05 01:24:27 +03:00 committed by GitHub
commit 3074510fea
Failed to generate hash of commit
5 changed files with 110 additions and 13 deletions

View File

@ -26,6 +26,7 @@ You can say thanks to the author by donations to these wallets:
* Supports CONNECT method and forwarding of HTTPS connections * Supports CONNECT method and forwarding of HTTPS connections
* Supports `Basic` proxy authentication * Supports `Basic` proxy authentication
* Supports TLS operation mode (HTTP(S) proxy over TLS) * Supports TLS operation mode (HTTP(S) proxy over TLS)
* Native ACME support (can issue TLS certificates automatically using Let's Encrypt or BuyPass)
* Supports client authentication with client TLS certificates * Supports client authentication with client TLS certificates
* Supports HTTP/2 * Supports HTTP/2
* Resilient to DPI (including active probing, see `hidden_domain` option for authentication providers) * Resilient to DPI (including active probing, see `hidden_domain` option for authentication providers)
@ -69,15 +70,33 @@ sudo snap install dumbproxy
Just run program and it'll start accepting connections on port 8080 (default). Just run program and it'll start accepting connections on port 8080 (default).
Example: run proxy on port 1234 with `Basic` authentication with username `admin` and password `123456`: ### Example: plain proxy
Run proxy on port 1234 with `Basic` authentication with username `admin` and password `123456`:
```sh ```sh
dumbproxy -bind-address :1234 -auth 'static://?username=admin&password=123456' dumbproxy -bind-address :1234 -auth 'static://?username=admin&password=123456'
``` ```
### Example: HTTP proxy over TLS (LetsEncrypt automatic certs)
Run HTTPS proxy (HTTP proxy over TLS) with automatic certs from LetsEncrypt on port 443 with `Basic` authentication with username `admin` and password `123456`:
```sh
dumbproxy -bind-address :443 -auth 'static://?username=admin&password=123456' -autocert
```
### Example: HTTP proxy over TLS (BuyPass automatic certs)
Run HTTPS proxy (HTTP proxy over TLS) with automatic certs from BuyPass on port 443 with `Basic` authentication with username `admin` and password `123456`:
```sh
dumbproxy -bind-address :443 -auth 'static://?username=admin&password=123456' -autocert -autocert-acme 'https://api.buypass.com/acme/directory' -autocert-email YOUR-EMAIL@EXAMPLE.ORG -autocert-http :80
```
## Using HTTP-over-TLS proxy ## Using HTTP-over-TLS proxy
It's quite trivial to set up program which supports proxies to use dumbproxy in plain HTTP mode. However, using HTTP proxy over TLS connection with browsers is little bit tricky. Note that TLS must be enabled (`-cert` and `-key` options) for this to work. It's quite trivial to set up program which supports proxies to use dumbproxy in plain HTTP mode. However, using HTTP proxy over TLS connection with browsers is little bit tricky. Note that TLS must be enabled (`-cert` and `-key` options or `-autocert` option) for this to work.
### Routing all browsers on Windows via HTTPS proxy ### Routing all browsers on Windows via HTTPS proxy
@ -149,6 +168,18 @@ Authentication parameters are passed as URI via `-auth` parameter. Scheme of URI
$ ~/go/bin/dumbproxy -h $ ~/go/bin/dumbproxy -h
-auth string -auth string
auth parameters (default "none://") auth parameters (default "none://")
-autocert
issue TLS certificates automatically
-autocert-acme string
custom ACME endpoint (default "https://acme-v02.api.letsencrypt.org/directory")
-autocert-dir string
path to autocert cache (default "/home/user/.dumbproxy/autocert")
-autocert-email string
email used for ACME registration
-autocert-http string
listen address for HTTP-01 challenges handler of ACME
-autocert-whitelist value
restrict autocert domains to this comma-separated list
-bind-address string -bind-address string
HTTP proxy listen address (default ":8080") HTTP proxy listen address (default ":8080")
-cafile string -cafile string

View File

@ -5,12 +5,13 @@ import (
"crypto/subtle" "crypto/subtle"
"encoding/base64" "encoding/base64"
"errors" "errors"
"golang.org/x/crypto/bcrypt"
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
"strconv" "strconv"
"strings" "strings"
"golang.org/x/crypto/bcrypt"
) )
const AUTH_REQUIRED_MSG = "Proxy authentication required.\n" const AUTH_REQUIRED_MSG = "Proxy authentication required.\n"

5
go.mod
View File

@ -2,4 +2,7 @@ module github.com/Snawoot/dumbproxy
go 1.13 go 1.13
require golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 require (
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b // indirect
)

22
go.sum
View File

@ -1,7 +1,15 @@
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM=
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw= golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b h1:ZmngSVLe/wycRns9MKikG9OWIEjGcGAkacif7oYQaUY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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-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-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.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

58
main.go
View File

@ -7,10 +7,16 @@ import (
"log" "log"
"net/http" "net/http"
"os" "os"
"path/filepath"
"strings"
"time" "time"
"golang.org/x/crypto/acme"
"golang.org/x/crypto/acme/autocert"
) )
var ( var (
home, _ = os.UserHomeDir()
version = "undefined" version = "undefined"
) )
@ -26,6 +32,23 @@ func arg_fail(msg string) {
os.Exit(2) os.Exit(2)
} }
type CSVArg []string
func (a *CSVArg) Set(s string) error {
*a = strings.Split(s, ",")
return nil
}
func (a *CSVArg) String() string {
if a == nil {
return "<nil>"
}
if *a == nil {
return "<empty>"
}
return strings.Join(*a, ",")
}
type CLIArgs struct { type CLIArgs struct {
bind_address string bind_address string
auth string auth string
@ -35,7 +58,13 @@ type CLIArgs struct {
list_ciphers bool list_ciphers bool
ciphers string ciphers string
disableHTTP2 bool disableHTTP2 bool
showVersion bool showVersion bool
autocert bool
autocertWhitelist CSVArg
autocertDir string
autocertACME string
autocertEmail string
autocertHTTP string
} }
func list_ciphers() { func list_ciphers() {
@ -58,6 +87,12 @@ func parse_args() CLIArgs {
flag.StringVar(&args.ciphers, "ciphers", "", "colon-separated list of enabled ciphers") flag.StringVar(&args.ciphers, "ciphers", "", "colon-separated list of enabled ciphers")
flag.BoolVar(&args.disableHTTP2, "disable-http2", false, "disable HTTP2") flag.BoolVar(&args.disableHTTP2, "disable-http2", false, "disable HTTP2")
flag.BoolVar(&args.showVersion, "version", false, "show program version and exit") flag.BoolVar(&args.showVersion, "version", false, "show program version and exit")
flag.BoolVar(&args.autocert, "autocert", false, "issue TLS certificates automatically")
flag.Var(&args.autocertWhitelist, "autocert-whitelist", "restrict autocert domains to this comma-separated list")
flag.StringVar(&args.autocertDir, "autocert-dir", filepath.Join(home, ".dumbproxy", "autocert"), "path to autocert cache")
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.autocertHTTP, "autocert-http", "", "listen address for HTTP-01 challenges handler of ACME")
flag.Parse() flag.Parse()
return args return args
} }
@ -70,7 +105,6 @@ func run() int {
return 0 return 0
} }
if args.list_ciphers { if args.list_ciphers {
list_ciphers() list_ciphers()
return 0 return 0
@ -116,6 +150,26 @@ func run() int {
cfg.CipherSuites = makeCipherList(args.ciphers) cfg.CipherSuites = makeCipherList(args.ciphers)
server.TLSConfig = cfg server.TLSConfig = cfg
err = server.ListenAndServeTLS("", "") err = server.ListenAndServeTLS("", "")
} else if args.autocert {
m := &autocert.Manager{
Cache: autocert.DirCache(args.autocertDir),
Prompt: autocert.AcceptTOS,
Client: &acme.Client{DirectoryURL: args.autocertACME},
Email: args.autocertEmail,
}
if args.autocertWhitelist != nil {
m.HostPolicy = autocert.HostWhitelist([]string(args.autocertWhitelist)...)
}
if args.autocertHTTP != "" {
go func() {
log.Fatalf("HTTP-01 ACME challenge server stopped: %v",
http.ListenAndServe(args.autocertHTTP, m.HTTPHandler(nil)))
}()
}
cfg := m.TLSConfig()
cfg.CipherSuites = makeCipherList(args.ciphers)
server.TLSConfig = cfg
err = server.ListenAndServeTLS("", "")
} else { } else {
err = server.ListenAndServe() err = server.ListenAndServe()
} }