Serve PAC files from memory

This commit is contained in:
Sergey Frolov 2017-08-17 14:26:57 -04:00 committed by Sergey Frolov
parent 9e92549830
commit f2019d57c4
6 changed files with 91 additions and 8 deletions

View file

@ -13,6 +13,7 @@ forwardproxy {
basicauth caddyuser2 秘密
ports 80 443
hide_ip
serve_pac proxy.pac
experimental_probe_resist secretlink-7qS4+3dqm.localhost
response_timeout 30
dial_timeout 30
@ -28,7 +29,11 @@ Default: no restrictions.
* hide_ip
If set, forwardproxy will not add user's IP to "Forwarded:" header.
Default: no hiding, "_Forwarded: for="useraddress"_" will be sent out.
* experimental_probe_resist secretlink.tld
* serve_pac path_to_pac_file.pac(optional)
Generate in memory and serve [Proxy Auto-Config](https://en.wikipedia.org/wiki/Proxy_auto-config) file on given path.
If no path is provided, PAC file will be served at /proxy.pac
Default: no PAC file generated by Caddy (you still can manually create and serve proxy.pac like a regular file)
* experimental_probe_resist secretlink.tld(optional)
EXPERIMENTAL, HERE BE DRAGONS.
Attempts to hide the fact that the site is a forwardproxy.
Proxy will no longer respond with _"407 Proxy Authentication Required"_ if credentials are incorrect or absent,

View file

@ -111,7 +111,7 @@ func (c *caddyTestServer) StartTestServer() {
func TestMain(m *testing.M) {
caddyForwardProxy = caddyTestServer{addr: "127.0.0.1:1984", root: "./test/forwardproxy",
directives: []string{"tls self_signed"},
proxyEnabled: true}
proxyEnabled: true, proxyDirectives: []string{"serve_pac"}}
caddyForwardProxy.StartTestServer()
caddyForwardProxyAuth = caddyTestServer{addr: "127.0.0.1:4891", root: "./test/forwardproxy",
@ -121,7 +121,9 @@ func TestMain(m *testing.M) {
caddyForwardProxyProbeResist = caddyTestServer{addr: "127.0.0.1:8888", root: "./test/forwardproxy",
directives: []string{"tls self_signed"}, HTTPRedirectPort: "8880",
proxyEnabled: true, proxyDirectives: []string{"basicauth test pass", "experimental_probe_resist test.localhost"}}
proxyEnabled: true, proxyDirectives: []string{"basicauth test pass",
"experimental_probe_resist test.localhost",
"serve_pac superhiddenfile.pac"}}
caddyForwardProxyProbeResist.StartTestServer()
caddyDummyProbeResist = caddyTestServer{addr: "127.0.0.1:9999", root: "./test/forwardproxy",

View file

@ -39,9 +39,11 @@ type ForwardProxy struct {
hideIP bool
whitelistedPorts []int
probeResistDomain string
pacFilePath string
probeResistEnabled bool
dialTimeout time.Duration // for initial tcp connection
hostname string // do not intercept requests to the hostname (except for hidden link)
port string // port on which chain with forwardproxy is listening on
}
var bufferPool sync.Pool
@ -185,7 +187,7 @@ func serveHiddenPage(w http.ResponseWriter, authErr error) (int, error) {
</body>
</html>`
const AuthFail = "Please authenticate yourself to the proxy."
const AuthOk = "Congratulations, you are succussfully authenticated to the proxy! Go browse all the things!"
const AuthOk = "Congratulations, you are successfully authenticated to the proxy! Go browse all the things!"
if authErr != nil {
w.Header().Set("Proxy-Authenticate", "Basic")
@ -197,6 +199,24 @@ func serveHiddenPage(w http.ResponseWriter, authErr error) (int, error) {
return 0, nil
}
func (fp *ForwardProxy) shouldServePacFile(r *http.Request) bool {
if len(fp.pacFilePath) > 0 && r.URL.Path == fp.pacFilePath {
return true
}
return false
}
const pacFile = `
function FindProxyForURL(url, host) {
return "HTTPS %s:%s";
}
`
func (fp *ForwardProxy) servePacFile(w http.ResponseWriter) (int, error) {
fmt.Fprintf(w, pacFile, fp.hostname, fp.port)
return 0, nil
}
func (fp *ForwardProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
var authErr error
if fp.authRequired {
@ -208,6 +228,9 @@ func (fp *ForwardProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) (int,
if isSubdomain(stripPort(r.Host), fp.hostname) && (r.Method != http.MethodConnect || authErr != nil) {
// Always pass non-CONNECT requests to hostname
// Pass CONNECT requests only if probe resistance is enabled and not authenticated
if fp.shouldServePacFile(r) {
return fp.servePacFile(w)
}
return fp.Next.ServeHTTP(w, r)
}
if authErr != nil {

View file

@ -29,6 +29,7 @@ import (
"net/url"
"testing"
"time"
"strings"
)
func dial(proxyAddr string, useTls bool) (net.Conn, error) {
@ -319,3 +320,28 @@ func TestConnectAuthWrong(t *testing.T) {
}
}
}
func TestPAC(t *testing.T) {
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
ResponseHeaderTimeout: 2 * time.Second,
}
client := &http.Client{Transport: tr, Timeout: 2 * time.Second}
resp, err := client.Get("https://" + caddyForwardProxy.addr + "/proxy.pac")
if err != nil {
t.Fatal(err)
}
splitAddr := strings.Split(caddyForwardProxy.addr, ":")
if err = responseExpected(resp, []byte(fmt.Sprintf(pacFile, splitAddr[0], splitAddr[1]))); err != nil {
t.Fatal(err)
}
resp, err = client.Get("https://" + caddyForwardProxyProbeResist.addr + "/superhiddenfile.pac")
if err != nil {
t.Fatal(err)
}
splitAddr = strings.Split(caddyForwardProxyProbeResist.addr, ":")
if err = responseExpected(resp, []byte(fmt.Sprintf(pacFile, splitAddr[0], splitAddr[1]))); err != nil {
t.Fatal(err)
}
}

View file

@ -19,6 +19,7 @@ import (
"errors"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
"log"
"net"
"net/http"
"strconv"
@ -29,8 +30,8 @@ import (
func setup(c *caddy.Controller) error {
httpserver.GetConfig(c).FallbackSite = true
fp := &ForwardProxy{dialTimeout: time.Second * 20, hostname: httpserver.GetConfig(c).Host(),
httpTransport: *http.DefaultTransport.(*http.Transport)}
fp := &ForwardProxy{dialTimeout: time.Second * 20, httpTransport: *http.DefaultTransport.(*http.Transport),
hostname: httpserver.GetConfig(c).Host(), port: httpserver.GetConfig(c).Port()}
fp.httpTransport.DialTLS = func(network, addr string) (net.Conn, error) {
return nil, &http.ProtocolError{ErrorString: "Proxy does not fetch TLS resources, use CONNECT instead"}
}
@ -94,6 +95,22 @@ func setup(c *caddy.Controller) error {
if len(args) == 1 {
fp.probeResistDomain = args[0]
}
case "serve_pac":
if len(args) > 1 {
return c.ArgErr()
}
if len(fp.pacFilePath) != 0 {
return errors.New("Parse error: serve_pac subdirective specified twice")
}
if len(args) == 1 {
fp.pacFilePath = args[0]
if !strings.HasPrefix(fp.pacFilePath, "/") {
fp.pacFilePath = "/" + fp.pacFilePath
}
} else {
fp.pacFilePath = "/proxy.pac"
}
log.Printf("Proxy Auto-Config will be served at %s%s\n", fp.hostname, fp.pacFilePath)
case "response_timeout":
if len(args) != 1 {
return c.ArgErr()
@ -123,8 +140,13 @@ func setup(c *caddy.Controller) error {
}
}
if fp.probeResistEnabled && !fp.authRequired {
return errors.New("Parse error: probing resistance requires authentication")
if fp.probeResistEnabled {
if !fp.authRequired {
return errors.New("Parse error: probing resistance requires authentication")
}
if len(fp.probeResistDomain) > 0 {
log.Printf("Secret domain used to connect to proxy: %s\n", fp.probeResistDomain)
}
}
fp.httpTransport.DialContext = (&net.Dialer{

View file

@ -91,6 +91,11 @@ func TestSetup(t *testing.T) {
testParsing([]string{"experimental_probe_resist local.host", "basicauth john doe"}, true)
testParsing([]string{"experimental_probe_resist local.host very.local.host", "basicauth john doe"}, false)
testParsing([]string{"serve_pac"}, true)
testParsing([]string{"serve_pac \"\""}, true)
testParsing([]string{"serve_pac proxyautoconfig.pac"}, true)
testParsing([]string{"serve_pac 1.pac 2.pac"}, false)
testParsing([]string{"response_timeout"}, false)
testParsing([]string{"response_timeout -1"}, false)
testParsing([]string{"response_timeout 1 2"}, false)