Begin refactor for Caddy 2
All the tests pass. I tried to keep as much as possible the same, but a few things don't translate well to Caddy 2, notably one test in probe_resist_test.go on L200, I had to change that test case since I didn't quite understand why it was the way it was before. Does not yet have v2 Caddyfile support.
This commit is contained in:
parent
247c0bafaa
commit
e92fe979d0
13 changed files with 2168 additions and 1359 deletions
29
acl.go
29
acl.go
|
|
@ -6,6 +6,11 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
type ACLRule struct {
|
||||
Subjects []string `json:"subjects,omitempty"`
|
||||
Allow bool `json:"allow,omitempty"`
|
||||
}
|
||||
|
||||
type aclDecision uint8
|
||||
|
||||
const (
|
||||
|
|
@ -65,7 +70,7 @@ func (a *aclAllRule) tryMatch(ip net.IP, domain string) aclDecision {
|
|||
return aclDecisionDeny
|
||||
}
|
||||
|
||||
func newAclRule(ruleSubject string, allow bool) (aclRule, error) {
|
||||
func newACLRule(ruleSubject string, allow bool) (aclRule, error) {
|
||||
if ruleSubject == "all" {
|
||||
return &aclAllRule{allow: allow}, nil
|
||||
}
|
||||
|
|
@ -94,3 +99,25 @@ func newAclRule(ruleSubject string, allow bool) (aclRule, error) {
|
|||
}
|
||||
return &aclDomainRule{domain: ruleSubject, subdomainsAllowed: subdomainsAllowed, allow: allow}, nil
|
||||
}
|
||||
|
||||
// isValidDomainLite shamelessly rejects non-LDH names. returns nil if domains seems valid
|
||||
func isValidDomainLite(domain string) error {
|
||||
for i := 0; i < len(domain); i++ {
|
||||
c := domain[i]
|
||||
if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '_' || '0' <= c && c <= '9' ||
|
||||
c == '-' || c == '.' {
|
||||
continue
|
||||
}
|
||||
return errors.New("character " + string(c) + " is not allowed")
|
||||
}
|
||||
sections := strings.Split(domain, ".")
|
||||
for _, s := range sections {
|
||||
if len(s) == 0 {
|
||||
return errors.New("empty section between dots in domain name or trailing dot")
|
||||
}
|
||||
if len(s) > 63 {
|
||||
return errors.New("domain name section is too long")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
69
acl_test.go
69
acl_test.go
|
|
@ -9,15 +9,14 @@ import (
|
|||
test port blocking working
|
||||
test blacklist allowed
|
||||
test blacklist refused with correct status
|
||||
|
||||
*/
|
||||
|
||||
func TestWhitelistAllowing(t *testing.T) {
|
||||
useTls := true
|
||||
for _, httpProxyVer := range testHttpProxyVersions {
|
||||
const useTLS, dialLocal = true, true
|
||||
for _, httpProxyVer := range testHTTPProxyVersions {
|
||||
for _, resource := range testResources {
|
||||
response, err := getViaProxy(caddyTestTarget.addr, resource, caddyForwardProxyWhiteListing.addr, httpProxyVer,
|
||||
"", useTls)
|
||||
"", useTLS, dialLocal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err = responseExpected(response, caddyTestTarget.contents[resource]); err != nil {
|
||||
|
|
@ -28,11 +27,11 @@ func TestWhitelistAllowing(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestWhitelistBlocking(t *testing.T) {
|
||||
useTls := true
|
||||
for _, httpProxyVer := range testHttpProxyVersions {
|
||||
const useTLS, dialLocal = true, false
|
||||
for _, httpProxyVer := range testHTTPProxyVersions {
|
||||
for _, resource := range testResources {
|
||||
response, err := getViaProxy(caddyHTTPTestTarget.addr, resource, caddyForwardProxyWhiteListing.addr, httpProxyVer,
|
||||
"", useTls)
|
||||
"", useTLS, dialLocal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
} else if response.StatusCode != http.StatusForbidden {
|
||||
|
|
@ -41,10 +40,10 @@ func TestWhitelistBlocking(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
for _, httpProxyVer := range testHttpProxyVersions {
|
||||
for _, httpProxyVer := range testHTTPProxyVersions {
|
||||
for _, resource := range testResources {
|
||||
response, err := getViaProxy("google.com:6451", resource, caddyForwardProxyWhiteListing.addr, httpProxyVer,
|
||||
"", useTls)
|
||||
"", useTLS, dialLocal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
} else if response.StatusCode != http.StatusForbidden {
|
||||
|
|
@ -55,11 +54,11 @@ func TestWhitelistBlocking(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestLocalhostDefaultForbidden(t *testing.T) {
|
||||
useTls := true
|
||||
for _, httpProxyVer := range testHttpProxyVersions {
|
||||
const useTLS, dialLocal = true, false
|
||||
for _, httpProxyVer := range testHTTPProxyVersions {
|
||||
for _, resource := range testResources {
|
||||
response, err := getViaProxy("localhost:6451", resource, caddyForwardProxyNoBlacklistOverride.addr, httpProxyVer,
|
||||
"", useTls)
|
||||
"", useTLS, dialLocal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
} else if response.StatusCode != http.StatusForbidden {
|
||||
|
|
@ -68,10 +67,10 @@ func TestLocalhostDefaultForbidden(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
for _, httpProxyVer := range testHttpProxyVersions {
|
||||
for _, httpProxyVer := range testHTTPProxyVersions {
|
||||
for _, resource := range testResources {
|
||||
response, err := getViaProxy("127.0.0.1:808", resource, caddyForwardProxyNoBlacklistOverride.addr, httpProxyVer,
|
||||
"", useTls)
|
||||
"", useTLS, dialLocal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
} else if response.StatusCode != http.StatusForbidden {
|
||||
|
|
@ -80,10 +79,10 @@ func TestLocalhostDefaultForbidden(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
for _, httpProxyVer := range testHttpProxyVersions {
|
||||
for _, httpProxyVer := range testHTTPProxyVersions {
|
||||
for _, resource := range testResources {
|
||||
response, err := getViaProxy("[::1]:8080", resource, caddyForwardProxyNoBlacklistOverride.addr, httpProxyVer,
|
||||
"", useTls)
|
||||
"", useTLS, dialLocal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
} else if response.StatusCode != http.StatusForbidden {
|
||||
|
|
@ -94,11 +93,11 @@ func TestLocalhostDefaultForbidden(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestLocalNetworksDefaultForbidden(t *testing.T) {
|
||||
useTls := true
|
||||
for _, httpProxyVer := range testHttpProxyVersions {
|
||||
const useTLS, dialLocal = true, false
|
||||
for _, httpProxyVer := range testHTTPProxyVersions {
|
||||
for _, resource := range testResources {
|
||||
response, err := getViaProxy("10.0.0.0:80", resource, caddyForwardProxyNoBlacklistOverride.addr, httpProxyVer,
|
||||
"", useTls)
|
||||
"", useTLS, dialLocal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
} else if response.StatusCode != http.StatusForbidden {
|
||||
|
|
@ -107,10 +106,10 @@ func TestLocalNetworksDefaultForbidden(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
for _, httpProxyVer := range testHttpProxyVersions {
|
||||
for _, httpProxyVer := range testHTTPProxyVersions {
|
||||
for _, resource := range testResources {
|
||||
response, err := getViaProxy("127.222.34.1:443", resource, caddyForwardProxyNoBlacklistOverride.addr, httpProxyVer,
|
||||
"", useTls)
|
||||
"", useTLS, dialLocal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
} else if response.StatusCode != http.StatusForbidden {
|
||||
|
|
@ -119,10 +118,10 @@ func TestLocalNetworksDefaultForbidden(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
for _, httpProxyVer := range testHttpProxyVersions {
|
||||
for _, httpProxyVer := range testHTTPProxyVersions {
|
||||
for _, resource := range testResources {
|
||||
response, err := getViaProxy("172.16.0.1:8080", resource, caddyForwardProxyNoBlacklistOverride.addr, httpProxyVer,
|
||||
"", useTls)
|
||||
"", useTLS, dialLocal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
} else if response.StatusCode != http.StatusForbidden {
|
||||
|
|
@ -131,10 +130,10 @@ func TestLocalNetworksDefaultForbidden(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
for _, httpProxyVer := range testHttpProxyVersions {
|
||||
for _, httpProxyVer := range testHTTPProxyVersions {
|
||||
for _, resource := range testResources {
|
||||
response, err := getViaProxy("192.168.192.168:888", resource, caddyForwardProxyNoBlacklistOverride.addr, httpProxyVer,
|
||||
"", useTls)
|
||||
"", useTLS, dialLocal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
} else if response.StatusCode != http.StatusForbidden {
|
||||
|
|
@ -145,11 +144,11 @@ func TestLocalNetworksDefaultForbidden(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestBlacklistBlocking(t *testing.T) {
|
||||
useTls := true
|
||||
for _, httpProxyVer := range testHttpProxyVersions {
|
||||
const useTLS, dialLocal = true, false
|
||||
for _, httpProxyVer := range testHTTPProxyVersions {
|
||||
for _, resource := range testResources {
|
||||
response, err := getViaProxy(blacklistedDomain, resource, caddyForwardProxyBlackListing.addr, httpProxyVer,
|
||||
"", useTls)
|
||||
"", useTLS, dialLocal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
} else if response.StatusCode != http.StatusForbidden {
|
||||
|
|
@ -158,10 +157,10 @@ func TestBlacklistBlocking(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
for _, httpProxyVer := range testHttpProxyVersions {
|
||||
for _, httpProxyVer := range testHTTPProxyVersions {
|
||||
for _, resource := range testResources {
|
||||
response, err := getViaProxy(blacklistedIPv4, resource, caddyForwardProxyBlackListing.addr, httpProxyVer,
|
||||
"", useTls)
|
||||
"", useTLS, dialLocal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
} else if response.StatusCode != http.StatusForbidden {
|
||||
|
|
@ -170,10 +169,10 @@ func TestBlacklistBlocking(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
for _, httpProxyVer := range testHttpProxyVersions {
|
||||
for _, httpProxyVer := range testHTTPProxyVersions {
|
||||
for _, resource := range testResources {
|
||||
response, err := getViaProxy("["+blacklistedIPv6+"]:80", resource, caddyForwardProxyBlackListing.addr, httpProxyVer,
|
||||
"", useTls)
|
||||
"", useTLS, dialLocal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
} else if response.StatusCode != http.StatusForbidden {
|
||||
|
|
@ -184,11 +183,11 @@ func TestBlacklistBlocking(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestBlacklistAllowing(t *testing.T) {
|
||||
useTls := true
|
||||
for _, httpProxyVer := range testHttpProxyVersions {
|
||||
const useTLS, dialLocal = true, true
|
||||
for _, httpProxyVer := range testHTTPProxyVersions {
|
||||
for _, resource := range testResources {
|
||||
response, err := getViaProxy(caddyTestTarget.addr, resource, caddyForwardProxyBlackListing.addr, httpProxyVer,
|
||||
"", useTls)
|
||||
"", useTLS, dialLocal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err = responseExpected(response, caddyTestTarget.contents[resource]); err != nil {
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/caddyserver/caddy/caddy/caddymain"
|
||||
|
||||
_ "github.com/caddyserver/forwardproxy"
|
||||
)
|
||||
|
||||
func main() {
|
||||
caddymain.EnableTelemetry = false
|
||||
caddymain.Run()
|
||||
}
|
||||
422
common_test.go
422
common_test.go
|
|
@ -1,19 +1,27 @@
|
|||
package forwardproxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"os"
|
||||
"strings"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/caddy"
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp/fileserver"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddypki"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
||||
)
|
||||
|
||||
var credentialsEmpty = ""
|
||||
|
|
@ -36,10 +44,10 @@ GET/CONNECT -- get gets, connect connects and gets
|
|||
Auth/NoAuth
|
||||
Empty/Correct/Wrong -- tries different credentials
|
||||
*/
|
||||
var testResources = []string{"", "/pic.png"}
|
||||
var testHttpProxyVersions = []string{"HTTP/2.0", "HTTP/1.1"}
|
||||
var testHttpTargetVersions = []string{"HTTP/1.1"}
|
||||
var httpVersionToAlpn = map[string]string{
|
||||
var testResources = []string{"/", "/pic.png"}
|
||||
var testHTTPProxyVersions = []string{"HTTP/2.0", "HTTP/1.1"}
|
||||
var testHTTPTargetVersions = []string{"HTTP/1.1"}
|
||||
var httpVersionToALPN = map[string]string{
|
||||
"HTTP/1.1": "http/1.1",
|
||||
"HTTP/2.0": "h2",
|
||||
}
|
||||
|
|
@ -49,15 +57,15 @@ var blacklistedIPv4 = "8.8.8.8"
|
|||
var blacklistedIPv6 = "2001:4860:4860::8888"
|
||||
|
||||
type caddyTestServer struct {
|
||||
*caddy.Instance
|
||||
addr string // could be http or https
|
||||
addr string
|
||||
tls bool
|
||||
|
||||
HTTPRedirectPort string // used in probe-resist tests to simulate default Caddy's http->https redirect
|
||||
root string // expected to have index.html and pic.png
|
||||
directives []string
|
||||
proxyEnabled bool
|
||||
proxyDirectives []string
|
||||
contents map[string][]byte
|
||||
httpRedirPort string // used in probe-resist tests to simulate default Caddy's http->https redirect
|
||||
|
||||
root string // expected to have index.html and pic.png
|
||||
directives []string
|
||||
proxyHandler *Handler
|
||||
contents map[string][]byte
|
||||
}
|
||||
|
||||
var (
|
||||
|
|
@ -78,40 +86,61 @@ var (
|
|||
caddyHTTPTestTarget caddyTestServer // serves plain http on 6480
|
||||
)
|
||||
|
||||
func (c *caddyTestServer) marshal() []byte {
|
||||
mainBlock := []string{c.addr + " {",
|
||||
"root " + c.root}
|
||||
mainBlock = append(mainBlock, c.directives...)
|
||||
if c.proxyEnabled {
|
||||
if len(c.proxyDirectives) == 0 {
|
||||
mainBlock = append(mainBlock, "forwardproxy")
|
||||
} else {
|
||||
forwardProxyBlock := []string{"forwardproxy {"}
|
||||
forwardProxyBlock = append(forwardProxyBlock, strings.Join(c.proxyDirectives, "\n"))
|
||||
forwardProxyBlock = append(forwardProxyBlock, "}")
|
||||
mainBlock = append(mainBlock, strings.Join(forwardProxyBlock, "\n"))
|
||||
}
|
||||
}
|
||||
mainBlock = append(mainBlock, "}")
|
||||
if len(c.HTTPRedirectPort) > 0 {
|
||||
// TODO: this is not good enough, since `func redirPlaintextHost(cfg *SiteConfig) *SiteConfig`
|
||||
// https://github.com/caddyserver/caddy/blob/master/caddyhttp/httpserver/https.go#L142 can change in future
|
||||
// and we won't know.
|
||||
redirectBlock := []string{"http://*:" + c.HTTPRedirectPort + " {",
|
||||
"redir https://" + c.addr + "{uri}",
|
||||
"header / Connection close",
|
||||
"}"}
|
||||
mainBlock = append(mainBlock, redirectBlock...)
|
||||
}
|
||||
return []byte(strings.Join(mainBlock, "\n"))
|
||||
}
|
||||
|
||||
func (c *caddyTestServer) StartTestServer() {
|
||||
var err error
|
||||
c.Instance, err = caddy.Start(caddy.CaddyfileInput{Contents: c.marshal(), ServerTypeName: "http"})
|
||||
func (c *caddyTestServer) server() *caddyhttp.Server {
|
||||
host, port, err := net.SplitHostPort(c.addr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
handlerJSON := func(h caddyhttp.MiddlewareHandler) json.RawMessage {
|
||||
return caddyconfig.JSONModuleObject(h, "handler", h.(caddy.Module).CaddyModule().ID.Name(), nil)
|
||||
}
|
||||
|
||||
// create the routes
|
||||
var routes caddyhttp.RouteList
|
||||
if c.proxyHandler != nil {
|
||||
if host != "" {
|
||||
if c.tls {
|
||||
// cheap hack for our tests to get TLS certs for the hostnames that
|
||||
// it needs TLS certs for: create an empty route with a single host
|
||||
// matcher for that hostname, and auto HTTPS will do the rest
|
||||
hostMatcherJSON, err := json.Marshal(caddyhttp.MatchHost{host})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
matchersRaw := caddyhttp.RawMatcherSets{
|
||||
caddy.ModuleMap{"host": hostMatcherJSON},
|
||||
}
|
||||
routes = append(routes, caddyhttp.Route{MatcherSetsRaw: matchersRaw})
|
||||
}
|
||||
|
||||
// tell the proxy which hostname to serve the proxy on; this must
|
||||
// be distinct from the host matcher, since the proxy basically
|
||||
// does its own host matching
|
||||
c.proxyHandler.Hosts = caddyhttp.MatchHost{host}
|
||||
}
|
||||
routes = append(routes, caddyhttp.Route{
|
||||
HandlersRaw: []json.RawMessage{handlerJSON(c.proxyHandler)},
|
||||
})
|
||||
}
|
||||
if c.root != "" {
|
||||
routes = append(routes, caddyhttp.Route{
|
||||
HandlersRaw: []json.RawMessage{
|
||||
handlerJSON(&fileserver.FileServer{Root: c.root}),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
srv := &caddyhttp.Server{
|
||||
Listen: []string{":" + port},
|
||||
Routes: routes,
|
||||
}
|
||||
if c.tls {
|
||||
srv.TLSConnPolicies = caddytls.ConnectionPolicies{{}}
|
||||
} else {
|
||||
srv.AutoHTTPS = &caddyhttp.AutoHTTPSConfig{Disabled: true}
|
||||
}
|
||||
|
||||
if c.contents == nil {
|
||||
c.contents = make(map[string][]byte)
|
||||
}
|
||||
|
|
@ -122,93 +151,216 @@ func (c *caddyTestServer) StartTestServer() {
|
|||
c.contents[""] = index
|
||||
c.contents["/"] = index
|
||||
c.contents["/index.html"] = index
|
||||
|
||||
c.contents["/pic.png"], err = ioutil.ReadFile(c.root + "/pic.png")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return srv
|
||||
}
|
||||
|
||||
// For simulating/mimicing Caddy's built-in auto-HTTPS redirects. Super hacky but w/e.
|
||||
func (c *caddyTestServer) redirServer() *caddyhttp.Server {
|
||||
return &caddyhttp.Server{
|
||||
Listen: []string{":" + c.httpRedirPort},
|
||||
Routes: caddyhttp.RouteList{
|
||||
{
|
||||
Handlers: []caddyhttp.MiddlewareHandler{
|
||||
caddyhttp.StaticResponse{
|
||||
StatusCode: caddyhttp.WeakString(strconv.Itoa(http.StatusPermanentRedirect)),
|
||||
Headers: http.Header{
|
||||
"Location": []string{"https://" + c.addr + "/{http.request.uri}"},
|
||||
"Connection": []string{"close"},
|
||||
},
|
||||
Close: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
caddyForwardProxy = caddyTestServer{addr: "127.0.19.84:1984", root: "./test/forwardproxy",
|
||||
directives: []string{"tls self_signed"},
|
||||
proxyEnabled: true, proxyDirectives: []string{"serve_pac",
|
||||
"acl {\nallow all\n}"}}
|
||||
caddyForwardProxy.StartTestServer()
|
||||
|
||||
caddyForwardProxyAuth = caddyTestServer{addr: "127.0.0.1:4891", root: "./test/forwardproxy",
|
||||
directives: []string{"tls self_signed"},
|
||||
proxyEnabled: true, proxyDirectives: []string{"basicauth test pass",
|
||||
"acl {\nallow all\n}"}}
|
||||
caddyForwardProxyAuth.StartTestServer()
|
||||
|
||||
caddyHTTPForwardProxyAuth = caddyTestServer{addr: "127.0.69.73:6973", root: "./test/forwardproxy",
|
||||
directives: []string{"tls off"},
|
||||
proxyEnabled: true, proxyDirectives: []string{"basicauth test pass",
|
||||
"acl {\nallow all\n}"}}
|
||||
caddyHTTPForwardProxyAuth.StartTestServer()
|
||||
|
||||
caddyForwardProxyProbeResist = caddyTestServer{addr: "127.0.88.88:8888", root: "./test/forwardproxy",
|
||||
directives: []string{"tls self_signed"}, HTTPRedirectPort: "8880",
|
||||
proxyEnabled: true, proxyDirectives: []string{"basicauth test pass",
|
||||
"probe_resistance test.localhost",
|
||||
"serve_pac superhiddenfile.pac",
|
||||
"acl {\nallow all\n}"}}
|
||||
caddyForwardProxyProbeResist.StartTestServer()
|
||||
|
||||
caddyDummyProbeResist = caddyTestServer{addr: "127.0.99.99:9999", root: "./test/forwardproxy",
|
||||
directives: []string{"tls self_signed"}, HTTPRedirectPort: "9980",
|
||||
proxyEnabled: false}
|
||||
caddyDummyProbeResist.StartTestServer()
|
||||
|
||||
// 127.0.0.1 and localhost are both used to avoid Caddy matching and routing proxy requests internally
|
||||
caddyTestTarget = caddyTestServer{addr: "127.0.64.51:6451", root: "./test/index",
|
||||
directives: []string{},
|
||||
proxyEnabled: false}
|
||||
caddyTestTarget.StartTestServer()
|
||||
|
||||
caddyHTTPTestTarget = caddyTestServer{addr: "localhost:6480", root: "./test/index",
|
||||
directives: []string{"tls off"},
|
||||
proxyEnabled: false}
|
||||
caddyHTTPTestTarget.StartTestServer()
|
||||
|
||||
caddyAuthedUpstreamEnter = caddyTestServer{addr: "127.0.65.25:6585", root: "./test/upstreamingproxy",
|
||||
directives: []string{"tls self_signed"},
|
||||
proxyEnabled: true, proxyDirectives: []string{"upstream https://test:pass@127.0.0.1:4891",
|
||||
"basicauth upstreamtest upstreampass"}}
|
||||
caddyAuthedUpstreamEnter.StartTestServer()
|
||||
|
||||
caddyForwardProxyWhiteListing = caddyTestServer{addr: "127.0.87.76:8776", root: "./test/forwardproxy",
|
||||
directives: []string{"tls self_signed"},
|
||||
proxyEnabled: true, proxyDirectives: []string{"acl {\nallow 127.0.64.51\n deny all\n}",
|
||||
"ports 6451"}}
|
||||
caddyForwardProxyWhiteListing.StartTestServer()
|
||||
|
||||
caddyForwardProxyBlackListing = caddyTestServer{addr: "127.0.66.76:6676", root: "./test/forwardproxy",
|
||||
directives: []string{"tls self_signed"},
|
||||
proxyEnabled: true, proxyDirectives: []string{"acl {\ndeny " + blacklistedIPv4 + "/30\n" +
|
||||
"deny " + blacklistedIPv6 + "\nallow all\n}"},
|
||||
caddyForwardProxy = caddyTestServer{
|
||||
addr: "127.0.19.84:1984",
|
||||
root: "./test/forwardproxy",
|
||||
tls: true,
|
||||
proxyHandler: &Handler{
|
||||
PACPath: defaultPACPath,
|
||||
ACL: []ACLRule{{Allow: true, Subjects: []string{"all"}}},
|
||||
},
|
||||
}
|
||||
caddyForwardProxyBlackListing.StartTestServer()
|
||||
|
||||
caddyForwardProxyNoBlacklistOverride = caddyTestServer{addr: "127.0.66.79:6679", root: "./test/forwardproxy",
|
||||
directives: []string{"tls self_signed"},
|
||||
proxyEnabled: true, proxyDirectives: []string{}}
|
||||
caddyForwardProxyNoBlacklistOverride.StartTestServer()
|
||||
caddyForwardProxyAuth = caddyTestServer{
|
||||
addr: "127.0.0.1:4891",
|
||||
root: "./test/forwardproxy",
|
||||
tls: true,
|
||||
proxyHandler: &Handler{
|
||||
PACPath: defaultPACPath,
|
||||
ACL: []ACLRule{{Subjects: []string{"all"}, Allow: true}},
|
||||
BasicauthUser: "test",
|
||||
BasicauthPass: "pass",
|
||||
},
|
||||
}
|
||||
|
||||
caddyHTTPForwardProxyAuth = caddyTestServer{
|
||||
addr: "127.0.69.73:6973",
|
||||
root: "./test/forwardproxy",
|
||||
proxyHandler: &Handler{
|
||||
PACPath: defaultPACPath,
|
||||
ACL: []ACLRule{{Subjects: []string{"all"}, Allow: true}},
|
||||
BasicauthUser: "test",
|
||||
BasicauthPass: "pass",
|
||||
},
|
||||
}
|
||||
|
||||
caddyForwardProxyProbeResist = caddyTestServer{
|
||||
addr: "127.0.88.88:8888",
|
||||
root: "./test/forwardproxy",
|
||||
tls: true,
|
||||
proxyHandler: &Handler{
|
||||
PACPath: "/superhiddenfile.pac",
|
||||
ACL: []ACLRule{{Subjects: []string{"all"}, Allow: true}},
|
||||
ProbeResistance: &ProbeResistance{Domain: "test.localhost"},
|
||||
BasicauthUser: "test",
|
||||
BasicauthPass: "pass",
|
||||
},
|
||||
httpRedirPort: "8880",
|
||||
}
|
||||
|
||||
caddyDummyProbeResist = caddyTestServer{
|
||||
addr: "127.0.99.99:9999",
|
||||
root: "./test/forwardproxy",
|
||||
tls: true,
|
||||
httpRedirPort: "9980",
|
||||
}
|
||||
|
||||
caddyTestTarget = caddyTestServer{
|
||||
addr: "127.0.64.51:6451",
|
||||
root: "./test/index",
|
||||
}
|
||||
|
||||
caddyHTTPTestTarget = caddyTestServer{
|
||||
addr: "localhost:6480",
|
||||
root: "./test/index",
|
||||
}
|
||||
|
||||
caddyAuthedUpstreamEnter = caddyTestServer{
|
||||
addr: "127.0.65.25:6585",
|
||||
root: "./test/upstreamingproxy",
|
||||
tls: true,
|
||||
proxyHandler: &Handler{
|
||||
Upstream: "https://test:pass@127.0.0.1:4891",
|
||||
BasicauthUser: "upstreamtest",
|
||||
BasicauthPass: "upstreampass",
|
||||
},
|
||||
}
|
||||
|
||||
caddyForwardProxyWhiteListing = caddyTestServer{
|
||||
addr: "127.0.87.76:8776",
|
||||
root: "./test/forwardproxy",
|
||||
tls: true,
|
||||
proxyHandler: &Handler{
|
||||
ACL: []ACLRule{
|
||||
{Subjects: []string{"127.0.0.1"}, Allow: true},
|
||||
{Subjects: []string{"all"}, Allow: false},
|
||||
},
|
||||
WhitelistedPorts: []int{6451},
|
||||
},
|
||||
}
|
||||
|
||||
caddyForwardProxyBlackListing = caddyTestServer{
|
||||
addr: "127.0.66.76:6676",
|
||||
root: "./test/forwardproxy",
|
||||
tls: true,
|
||||
proxyHandler: &Handler{
|
||||
ACL: []ACLRule{
|
||||
{Subjects: []string{blacklistedIPv4 + "/30"}, Allow: false},
|
||||
{Subjects: []string{blacklistedIPv6}, Allow: false},
|
||||
{Subjects: []string{"all"}, Allow: true},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
caddyForwardProxyNoBlacklistOverride = caddyTestServer{
|
||||
addr: "127.0.66.76:6679",
|
||||
root: "./test/forwardproxy",
|
||||
tls: true,
|
||||
proxyHandler: &Handler{},
|
||||
}
|
||||
|
||||
// done configuring all the servers; now build the HTTP app
|
||||
httpApp := caddyhttp.App{
|
||||
Servers: map[string]*caddyhttp.Server{
|
||||
"caddyForwardProxy": caddyForwardProxy.server(),
|
||||
"caddyForwardProxyAuth": caddyForwardProxyAuth.server(),
|
||||
"caddyHTTPForwardProxyAuth": caddyHTTPForwardProxyAuth.server(),
|
||||
"caddyForwardProxyProbeResist": caddyForwardProxyProbeResist.server(),
|
||||
"caddyDummyProbeResist": caddyDummyProbeResist.server(),
|
||||
"caddyTestTarget": caddyTestTarget.server(),
|
||||
"caddyHTTPTestTarget": caddyHTTPTestTarget.server(),
|
||||
"caddyAuthedUpstreamEnter": caddyAuthedUpstreamEnter.server(),
|
||||
"caddyForwardProxyWhiteListing": caddyForwardProxyWhiteListing.server(),
|
||||
"caddyForwardProxyBlackListing": caddyForwardProxyBlackListing.server(),
|
||||
"caddyForwardProxyNoBlacklistOverride": caddyForwardProxyNoBlacklistOverride.server(),
|
||||
|
||||
// HTTP->HTTPS redirect simulation servers for those which have a redir port configured
|
||||
"caddyForwardProxyProbeResist_redir": caddyForwardProxyProbeResist.redirServer(),
|
||||
"caddyDummyProbeResist_redir": caddyDummyProbeResist.redirServer(),
|
||||
},
|
||||
GracePeriod: caddy.Duration(1 * time.Second), // keep tests fast
|
||||
}
|
||||
httpAppJSON, err := json.Marshal(httpApp)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// ensure we always use internal issuer and not a public CA
|
||||
tlsApp := caddytls.TLS{
|
||||
Automation: &caddytls.AutomationConfig{
|
||||
Policies: []*caddytls.AutomationPolicy{
|
||||
{
|
||||
IssuerRaw: json.RawMessage(`{"module": "internal"}`),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
tlsAppJSON, err := json.Marshal(tlsApp)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// configure the default CA so that we don't try to install trust, just for our tests
|
||||
falseBool := false
|
||||
pkiApp := caddypki.PKI{
|
||||
CAs: map[string]*caddypki.CA{
|
||||
"local": {InstallTrust: &falseBool},
|
||||
},
|
||||
}
|
||||
pkiAppJSON, err := json.Marshal(pkiApp)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// build final config
|
||||
cfg := &caddy.Config{
|
||||
Admin: &caddy.AdminConfig{Disabled: true},
|
||||
AppsRaw: caddy.ModuleMap{
|
||||
"http": httpAppJSON,
|
||||
"tls": tlsAppJSON,
|
||||
"pki": pkiAppJSON,
|
||||
},
|
||||
}
|
||||
|
||||
// start the engines
|
||||
err = caddy.Run(cfg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
retCode := m.Run()
|
||||
|
||||
caddyForwardProxy.Stop()
|
||||
caddyForwardProxyAuth.Stop()
|
||||
caddyHTTPForwardProxyAuth.Stop()
|
||||
caddyForwardProxyProbeResist.Stop()
|
||||
caddyDummyProbeResist.Stop()
|
||||
caddyTestTarget.Stop()
|
||||
caddyHTTPTestTarget.Stop()
|
||||
caddyAuthedUpstreamEnter.Stop()
|
||||
caddyForwardProxyWhiteListing.Stop()
|
||||
caddyForwardProxyBlackListing.Stop()
|
||||
caddyForwardProxyNoBlacklistOverride.Stop()
|
||||
caddy.Stop()
|
||||
|
||||
os.Exit(retCode)
|
||||
}
|
||||
|
|
@ -216,11 +368,7 @@ func TestMain(m *testing.M) {
|
|||
// This is a sanity check confirming that target servers actually directly serve what they are expected to.
|
||||
// (And that they don't serve what they should not)
|
||||
func TestTheTest(t *testing.T) {
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
ResponseHeaderTimeout: 2 * time.Second,
|
||||
}
|
||||
client := &http.Client{Transport: tr, Timeout: 2 * time.Second}
|
||||
client := &http.Client{Transport: testTransport, Timeout: 2 * time.Second}
|
||||
|
||||
// Request index
|
||||
resp, err := client.Get("http://" + caddyTestTarget.addr)
|
||||
|
|
@ -307,9 +455,8 @@ func httpdump(r interface{}) string {
|
|||
b, err := httputil.DumpRequest(v, true)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
} else {
|
||||
return string(b)
|
||||
}
|
||||
return string(b)
|
||||
case *http.Response:
|
||||
if v == nil {
|
||||
return "httpdump: nil"
|
||||
|
|
@ -317,10 +464,27 @@ func httpdump(r interface{}) string {
|
|||
b, err := httputil.DumpResponse(v, true)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
} else {
|
||||
return string(b)
|
||||
}
|
||||
return string(b)
|
||||
default:
|
||||
return "httpdump: wrong type"
|
||||
}
|
||||
}
|
||||
|
||||
var testTransport = &http.Transport{
|
||||
ResponseHeaderTimeout: 2 * time.Second,
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
// always dial localhost for testing purposes
|
||||
return new(net.Dialer).DialContext(ctx, network, localDialAddr(addr))
|
||||
},
|
||||
DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
// always dial localhost for testing purposes
|
||||
conn, err := new(net.Dialer).DialContext(ctx, network, localDialAddr(addr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tls.Client(conn, &tls.Config{InsecureSkipVerify: true}), nil
|
||||
},
|
||||
}
|
||||
|
||||
const defaultPACPath = "/proxy.pac"
|
||||
|
|
|
|||
939
forwardproxy.go
939
forwardproxy.go
File diff suppressed because it is too large
Load diff
|
|
@ -17,54 +17,65 @@ package forwardproxy
|
|||
import (
|
||||
"bufio"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
_ "github.com/caddyserver/caddy/caddyhttp/header"
|
||||
_ "github.com/caddyserver/caddy/caddyhttp/httpserver"
|
||||
_ "github.com/caddyserver/caddy/caddyhttp/redirect"
|
||||
_ "github.com/caddyserver/caddy/caddyhttp/root"
|
||||
"github.com/caddyserver/forwardproxy/httpclient"
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
func dial(proxyAddr, httpProxyVer string, useTls bool) (net.Conn, error) {
|
||||
if useTls {
|
||||
return tls.Dial("tcp", proxyAddr, &tls.Config{InsecureSkipVerify: true,
|
||||
NextProtos: []string{httpVersionToAlpn[httpProxyVer]}})
|
||||
} else {
|
||||
return net.Dial("tcp", proxyAddr)
|
||||
// localDialAddr changes the host portion of addr to be loopback,
|
||||
// which is useful since we're just testing.
|
||||
func localDialAddr(addr string) string {
|
||||
_, port, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return net.JoinHostPort("127.0.0.1", port)
|
||||
}
|
||||
|
||||
func getViaProxy(targetHost, resource, proxyAddr, httpProxyVer, proxyCredentials string, useTls bool) (*http.Response, error) {
|
||||
proxyConn, err := dial(proxyAddr, httpProxyVer, useTls)
|
||||
func dial(proxyAddr, httpProxyVer string, useTLS bool) (net.Conn, error) {
|
||||
// always dial localhost for testing purposes
|
||||
dialAddr := localDialAddr(proxyAddr)
|
||||
if useTLS {
|
||||
return tls.Dial("tcp", dialAddr, &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
NextProtos: []string{httpVersionToALPN[httpProxyVer]},
|
||||
})
|
||||
}
|
||||
return net.Dial("tcp", dialAddr)
|
||||
}
|
||||
|
||||
func getViaProxy(targetHost, resource, proxyAddr, httpProxyVer, proxyCredentials string, useTLS, dialLocal bool) (*http.Response, error) {
|
||||
proxyConn, err := dial(proxyAddr, httpProxyVer, useTLS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return getResourceViaProxyConn(proxyConn, targetHost, resource, httpProxyVer, proxyCredentials)
|
||||
return getResourceViaProxyConn(proxyConn, targetHost, resource, httpProxyVer, proxyCredentials, dialLocal)
|
||||
}
|
||||
|
||||
// if connect is not successful - that response is returned, otherwise the requested resource
|
||||
func connectAndGetViaProxy(targetHost, resource, proxyAddr, httpTargetVer, proxyCredentials, httpProxyVer string, useTls bool) (*http.Response, error) {
|
||||
proxyConn, err := dial(proxyAddr, httpProxyVer, useTls)
|
||||
func connectAndGetViaProxy(targetHost, resource, proxyAddr, httpTargetVer, proxyCredentials, httpProxyVer string, useTLS, dialLocal bool) (*http.Response, error) {
|
||||
proxyConn, err := dial(proxyAddr, httpProxyVer, useTLS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req := http.Request{Header: make(http.Header)}
|
||||
req := &http.Request{Header: make(http.Header)}
|
||||
if len(proxyCredentials) > 0 {
|
||||
req.Header.Set("Proxy-Authorization", proxyCredentials)
|
||||
}
|
||||
req.Host = targetHost
|
||||
if dialLocal {
|
||||
req.Host = localDialAddr(targetHost)
|
||||
} else {
|
||||
req.Host = targetHost
|
||||
}
|
||||
req.URL, err = url.Parse("https://" + req.Host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -85,7 +96,7 @@ func connectAndGetViaProxy(targetHost, resource, proxyAddr, httpTargetVer, proxy
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err = clientConn.RoundTrip(&req)
|
||||
resp, err = clientConn.RoundTrip(req)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
|
@ -94,7 +105,7 @@ func connectAndGetViaProxy(targetHost, resource, proxyAddr, httpTargetVer, proxy
|
|||
req.ProtoMajor = 1
|
||||
req.ProtoMinor = 1
|
||||
req.Write(proxyConn)
|
||||
resp, err = http.ReadResponse(bufio.NewReader(proxyConn), &req)
|
||||
resp, err = http.ReadResponse(bufio.NewReader(proxyConn), req)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
|
@ -109,18 +120,22 @@ func connectAndGetViaProxy(targetHost, resource, proxyAddr, httpTargetVer, proxy
|
|||
return resp, err
|
||||
}
|
||||
|
||||
return getResourceViaProxyConn(proxyConn, targetHost, resource, httpTargetVer, proxyCredentials)
|
||||
return getResourceViaProxyConn(proxyConn, targetHost, resource, httpTargetVer, proxyCredentials, dialLocal)
|
||||
}
|
||||
|
||||
func getResourceViaProxyConn(proxyConn net.Conn, targetHost, resource, httpTargetVer, proxyCredentials string) (*http.Response, error) {
|
||||
func getResourceViaProxyConn(proxyConn net.Conn, targetHost, resource, httpTargetVer, proxyCredentials string, dialLocal bool) (*http.Response, error) {
|
||||
var err error
|
||||
|
||||
req := http.Request{Header: make(http.Header)}
|
||||
req := &http.Request{Header: make(http.Header)}
|
||||
if len(proxyCredentials) > 0 {
|
||||
req.Header.Set("Proxy-Authorization", proxyCredentials)
|
||||
}
|
||||
req.Host = targetHost
|
||||
req.URL, err = url.Parse("http://" + req.Host + resource)
|
||||
if dialLocal {
|
||||
req.Host = localDialAddr(targetHost)
|
||||
} else {
|
||||
req.Host = targetHost
|
||||
}
|
||||
req.URL, err = url.Parse("http://" + targetHost + resource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -137,14 +152,14 @@ func getResourceViaProxyConn(proxyConn net.Conn, targetHost, resource, httpTarge
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return clientConn.RoundTrip(&req)
|
||||
return clientConn.RoundTrip(req)
|
||||
case "HTTP/1.1":
|
||||
req.ProtoMajor = 1
|
||||
req.ProtoMinor = 1
|
||||
t := http.Transport{Dial: func(network, addr string) (net.Conn, error) {
|
||||
return proxyConn, nil
|
||||
}}
|
||||
return t.RoundTrip(&req)
|
||||
return t.RoundTrip(req)
|
||||
default:
|
||||
panic("proxy ver: " + httpTargetVer)
|
||||
}
|
||||
|
|
@ -165,30 +180,26 @@ func responseExpected(res *http.Response, expectedResponse []byte) error {
|
|||
panic(err)
|
||||
}
|
||||
if nTotal == responseLen {
|
||||
return errors.New(fmt.Sprintf("nTotal == responseLen, but haven't seen io.EOF. Expected response: %s\nGot: %s\n",
|
||||
expectedResponse, response))
|
||||
return fmt.Errorf("nTotal == responseLen, but haven't seen io.EOF. Expected response: %s\nGot: %s",
|
||||
expectedResponse, response)
|
||||
}
|
||||
}
|
||||
response = response[:nTotal]
|
||||
if len(expectedResponse) != len(response) {
|
||||
return errors.New(fmt.Sprintf("Expected length: %d. Got thus far: %d. Expected response: %s\nGot: %s\n",
|
||||
len(expectedResponse), len(response), expectedResponse, response))
|
||||
return fmt.Errorf("expected length: %d. Got thus far: %d. Expected response: %s\nGot: %s",
|
||||
len(expectedResponse), len(response), expectedResponse, response)
|
||||
}
|
||||
for i := range response {
|
||||
if response[i] != expectedResponse[i] {
|
||||
return errors.New(fmt.Sprintf("Response mismatch at character #%d. Expected response: %s\nGot: %s\n",
|
||||
i, expectedResponse, response))
|
||||
return fmt.Errorf("response mismatch at character #%d. Expected response: %s\nGot: %s",
|
||||
i, expectedResponse, response)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestPassthrough(t *testing.T) {
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
ResponseHeaderTimeout: 2 * time.Second,
|
||||
}
|
||||
client := &http.Client{Transport: tr, Timeout: 2 * time.Second}
|
||||
client := &http.Client{Transport: testTransport, Timeout: 2 * time.Second}
|
||||
resp, err := client.Get("https://" + caddyForwardProxy.addr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
@ -212,10 +223,10 @@ func TestPassthrough(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestGETNoAuth(t *testing.T) {
|
||||
useTls := true
|
||||
for _, httpProxyVer := range testHttpProxyVersions {
|
||||
const useTLS, dialLocal = true, false
|
||||
for _, httpProxyVer := range testHTTPProxyVersions {
|
||||
for _, resource := range testResources {
|
||||
response, err := getViaProxy(caddyHTTPTestTarget.addr, resource, caddyForwardProxy.addr, httpProxyVer, credentialsEmpty, useTls)
|
||||
response, err := getViaProxy(caddyHTTPTestTarget.addr, resource, caddyForwardProxy.addr, httpProxyVer, credentialsEmpty, useTLS, dialLocal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err = responseExpected(response, caddyHTTPTestTarget.contents[resource]); err != nil {
|
||||
|
|
@ -226,10 +237,10 @@ func TestGETNoAuth(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestGETAuthCorrect(t *testing.T) {
|
||||
useTls := true
|
||||
for _, httpProxyVer := range testHttpProxyVersions {
|
||||
const useTLS, dialLocal = true, false
|
||||
for _, httpProxyVer := range testHTTPProxyVersions {
|
||||
for _, resource := range testResources {
|
||||
response, err := getViaProxy(caddyHTTPTestTarget.addr, resource, caddyForwardProxyAuth.addr, httpProxyVer, credentialsCorrect, useTls)
|
||||
response, err := getViaProxy(caddyHTTPTestTarget.addr, resource, caddyForwardProxyAuth.addr, httpProxyVer, credentialsCorrect, useTLS, dialLocal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err = responseExpected(response, caddyHTTPTestTarget.contents[resource]); err != nil {
|
||||
|
|
@ -240,11 +251,11 @@ func TestGETAuthCorrect(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestGETAuthWrong(t *testing.T) {
|
||||
useTls := true
|
||||
const useTLS, dialLocal = true, false
|
||||
for _, wrongCreds := range credentialsWrong {
|
||||
for _, httpProxyVer := range testHttpProxyVersions {
|
||||
for _, httpProxyVer := range testHTTPProxyVersions {
|
||||
for _, resource := range testResources {
|
||||
response, err := getViaProxy(caddyHTTPTestTarget.addr, resource, caddyForwardProxyAuth.addr, httpProxyVer, wrongCreds, useTls)
|
||||
response, err := getViaProxy(caddyHTTPTestTarget.addr, resource, caddyForwardProxyAuth.addr, httpProxyVer, wrongCreds, useTLS, dialLocal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -258,11 +269,11 @@ func TestGETAuthWrong(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestProxySelfGet(t *testing.T) {
|
||||
useTls := true
|
||||
const useTLS, dialLocal = true, false
|
||||
// GETNoAuth to self
|
||||
for _, httpTargetVer := range testHttpTargetVersions {
|
||||
for _, httpTargetVer := range testHTTPTargetVersions {
|
||||
for _, resource := range testResources {
|
||||
response, err := getViaProxy(caddyForwardProxy.addr, resource, caddyForwardProxy.addr, httpTargetVer, credentialsEmpty, useTls)
|
||||
response, err := getViaProxy(caddyForwardProxy.addr, resource, caddyForwardProxy.addr, httpTargetVer, credentialsEmpty, useTLS, dialLocal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err = responseExpected(response, caddyForwardProxy.contents[resource]); err != nil {
|
||||
|
|
@ -272,9 +283,9 @@ func TestProxySelfGet(t *testing.T) {
|
|||
}
|
||||
|
||||
// GETAuthCorrect to self
|
||||
for _, httpTargetVer := range testHttpTargetVersions {
|
||||
for _, httpTargetVer := range testHTTPTargetVersions {
|
||||
for _, resource := range testResources {
|
||||
response, err := getViaProxy(caddyForwardProxyAuth.addr, resource, caddyForwardProxyAuth.addr, httpTargetVer, credentialsCorrect, useTls)
|
||||
response, err := getViaProxy(caddyForwardProxyAuth.addr, resource, caddyForwardProxyAuth.addr, httpTargetVer, credentialsCorrect, useTLS, dialLocal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err = responseExpected(response, caddyForwardProxyAuth.contents[resource]); err != nil {
|
||||
|
|
@ -289,11 +300,11 @@ func TestProxySelfGet(t *testing.T) {
|
|||
// Low priority since this is a functionality issue, not security, and it would be easily caught in the wild.
|
||||
|
||||
func TestConnectNoAuth(t *testing.T) {
|
||||
useTls := true
|
||||
for _, httpProxyVer := range testHttpProxyVersions {
|
||||
for _, httpTargetVer := range testHttpTargetVersions {
|
||||
const useTLS, dialLocal = true, true
|
||||
for _, httpProxyVer := range testHTTPProxyVersions {
|
||||
for _, httpTargetVer := range testHTTPTargetVersions {
|
||||
for _, resource := range testResources {
|
||||
response, err := connectAndGetViaProxy(caddyTestTarget.addr, resource, caddyForwardProxy.addr, httpTargetVer, credentialsEmpty, httpProxyVer, useTls)
|
||||
response, err := connectAndGetViaProxy(caddyTestTarget.addr, resource, caddyForwardProxy.addr, httpTargetVer, credentialsEmpty, httpProxyVer, useTLS, dialLocal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err = responseExpected(response, caddyTestTarget.contents[resource]); err != nil {
|
||||
|
|
@ -305,11 +316,11 @@ func TestConnectNoAuth(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestConnectAuthCorrect(t *testing.T) {
|
||||
useTls := true
|
||||
for _, httpProxyVer := range testHttpProxyVersions {
|
||||
for _, httpTargetVer := range testHttpTargetVersions {
|
||||
const useTLS, dialLocal = true, true
|
||||
for _, httpProxyVer := range testHTTPProxyVersions {
|
||||
for _, httpTargetVer := range testHTTPTargetVersions {
|
||||
for _, resource := range testResources {
|
||||
response, err := connectAndGetViaProxy(caddyTestTarget.addr, resource, caddyForwardProxyAuth.addr, httpTargetVer, credentialsCorrect, httpProxyVer, useTls)
|
||||
response, err := connectAndGetViaProxy(caddyTestTarget.addr, resource, caddyForwardProxyAuth.addr, httpTargetVer, credentialsCorrect, httpProxyVer, useTLS, dialLocal)
|
||||
if err != nil {
|
||||
t.Fatal(httpProxyVer, httpTargetVer, err)
|
||||
} else if err = responseExpected(response, caddyTestTarget.contents[resource]); err != nil {
|
||||
|
|
@ -321,18 +332,18 @@ func TestConnectAuthCorrect(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestConnectAuthWrong(t *testing.T) {
|
||||
useTls := true
|
||||
const useTLS, dialLocal = true, false
|
||||
for _, wrongCreds := range credentialsWrong {
|
||||
for _, httpProxyVer := range testHttpProxyVersions {
|
||||
for _, httpTargetVer := range testHttpTargetVersions {
|
||||
for _, httpProxyVer := range testHTTPProxyVersions {
|
||||
for _, httpTargetVer := range testHTTPTargetVersions {
|
||||
for _, resource := range testResources {
|
||||
response, err := connectAndGetViaProxy(caddyTestTarget.addr, resource, caddyForwardProxyAuth.addr, httpTargetVer, wrongCreds, httpProxyVer, useTls)
|
||||
response, err := connectAndGetViaProxy(caddyTestTarget.addr, resource, caddyForwardProxyAuth.addr, httpTargetVer, wrongCreds, httpProxyVer, useTLS, dialLocal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if response.StatusCode != http.StatusProxyAuthRequired {
|
||||
t.Fatalf("Expected response: 407 StatusProxyAuthRequired, Got: %d %s\n",
|
||||
response.StatusCode, response.Status)
|
||||
t.Fatalf("Expected response: 407 StatusProxyAuthRequired, Got: %d %s (wrongCreds=%s httpProxyVer=%s httpTargetVer=%s resource=%s)",
|
||||
response.StatusCode, response.Status, wrongCreds, httpProxyVer, httpTargetVer, resource)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -341,17 +352,12 @@ 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}
|
||||
client := &http.Client{Transport: testTransport, 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 {
|
||||
if err = responseExpected(resp, []byte(fmt.Sprintf(pacFile, caddyForwardProxy.addr))); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
|
@ -359,20 +365,19 @@ func TestPAC(t *testing.T) {
|
|||
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 {
|
||||
if err = responseExpected(resp, []byte(fmt.Sprintf(pacFile, caddyForwardProxyProbeResist.addr))); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCONNECTViaUpstream(t *testing.T) {
|
||||
useTls := true
|
||||
const useTLS, dialLocal = true, true
|
||||
for range make([]byte, 5) { // do several times to test http2 connection reuse
|
||||
for _, httpProxyVer := range testHttpProxyVersions {
|
||||
for _, httpTargetVer := range testHttpTargetVersions {
|
||||
for _, httpProxyVer := range testHTTPProxyVersions {
|
||||
for _, httpTargetVer := range testHTTPTargetVersions {
|
||||
for _, resource := range testResources {
|
||||
response, err := connectAndGetViaProxy(caddyTestTarget.addr, resource, caddyAuthedUpstreamEnter.addr,
|
||||
httpTargetVer, credentialsUpstreamCorrect, httpProxyVer, useTls)
|
||||
httpTargetVer, credentialsUpstreamCorrect, httpProxyVer, useTLS, dialLocal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err = responseExpected(response, caddyTestTarget.contents[resource]); err != nil {
|
||||
|
|
@ -385,12 +390,12 @@ func TestCONNECTViaUpstream(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestGETViaUpstream(t *testing.T) {
|
||||
useTls := true
|
||||
const useTLS, dialLocal = true, true
|
||||
for range make([]byte, 5) { // do several times to test http2 connection reuse
|
||||
for _, httpProxyVer := range testHttpProxyVersions {
|
||||
for _, httpProxyVer := range testHTTPProxyVersions {
|
||||
for _, resource := range testResources {
|
||||
response, err := getViaProxy(caddyHTTPTestTarget.addr, resource, caddyAuthedUpstreamEnter.addr, httpProxyVer,
|
||||
credentialsUpstreamCorrect, useTls)
|
||||
credentialsUpstreamCorrect, useTLS, dialLocal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err = responseExpected(response, caddyHTTPTestTarget.contents[resource]); err != nil {
|
||||
|
|
@ -403,11 +408,7 @@ func TestGETViaUpstream(t *testing.T) {
|
|||
|
||||
func TestUpstreamPassthrough(t *testing.T) {
|
||||
// Usptreaming proxy still hosts things as expected
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
ResponseHeaderTimeout: 2 * time.Second,
|
||||
}
|
||||
client := &http.Client{Transport: tr, Timeout: 2 * time.Second}
|
||||
client := &http.Client{Transport: testTransport, Timeout: 2 * time.Second}
|
||||
resp, err := client.Get("https://" + caddyAuthedUpstreamEnter.addr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
|
|||
6
go.mod
6
go.mod
|
|
@ -1,8 +1,8 @@
|
|||
module github.com/caddyserver/forwardproxy
|
||||
|
||||
go 1.12
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/mholt/caddy v1.0.0
|
||||
golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6
|
||||
github.com/caddyserver/caddy/v2 v2.0.0-rc.3
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e
|
||||
)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// httpclient is used by the upstreaming forwardproxy to establish connections to http(s) upstreams.
|
||||
// Package httpclient is used by the upstreaming forwardproxy to establish connections to http(s) upstreams.
|
||||
// it implements x/net/proxy.Dialer interface
|
||||
package httpclient
|
||||
|
||||
|
|
@ -33,7 +33,7 @@ import (
|
|||
|
||||
// HTTPConnectDialer allows to configure one-time use HTTP CONNECT client
|
||||
type HTTPConnectDialer struct {
|
||||
ProxyUrl url.URL
|
||||
ProxyURL url.URL
|
||||
DefaultHeader http.Header
|
||||
|
||||
// TODO: If spkiFp is set, use it as SPKI fingerprint to confirm identity of the
|
||||
|
|
@ -52,47 +52,47 @@ type HTTPConnectDialer struct {
|
|||
cachedH2RawConn net.Conn
|
||||
}
|
||||
|
||||
// NewHTTPClient creates a client to issue CONNECT requests and tunnel traffic via HTTPS proxy.
|
||||
// proxyUrlStr must provide Scheme and Host, may provide credentials and port.
|
||||
// NewHTTPConnectDialer creates a client to issue CONNECT requests and tunnel traffic via HTTPS proxy.
|
||||
// proxyURLStr must provide Scheme and Host, may provide credentials and port.
|
||||
// Example: https://username:password@golang.org:443
|
||||
func NewHTTPConnectDialer(proxyUrlStr string) (*HTTPConnectDialer, error) {
|
||||
proxyUrl, err := url.Parse(proxyUrlStr)
|
||||
func NewHTTPConnectDialer(proxyURLStr string) (*HTTPConnectDialer, error) {
|
||||
proxyURL, err := url.Parse(proxyURLStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if proxyUrl.Host == "" {
|
||||
return nil, errors.New("misparsed `url=" + proxyUrlStr +
|
||||
if proxyURL.Host == "" {
|
||||
return nil, errors.New("misparsed `url=" + proxyURLStr +
|
||||
"`, make sure to specify full url like https://username:password@hostname.com:443/")
|
||||
}
|
||||
|
||||
switch proxyUrl.Scheme {
|
||||
switch proxyURL.Scheme {
|
||||
case "http":
|
||||
if proxyUrl.Port() == "" {
|
||||
proxyUrl.Host = net.JoinHostPort(proxyUrl.Host, "80")
|
||||
if proxyURL.Port() == "" {
|
||||
proxyURL.Host = net.JoinHostPort(proxyURL.Host, "80")
|
||||
}
|
||||
case "https":
|
||||
if proxyUrl.Port() == "" {
|
||||
proxyUrl.Host = net.JoinHostPort(proxyUrl.Host, "443")
|
||||
if proxyURL.Port() == "" {
|
||||
proxyURL.Host = net.JoinHostPort(proxyURL.Host, "443")
|
||||
}
|
||||
case "":
|
||||
return nil, errors.New("specify scheme explicitly (https://)")
|
||||
default:
|
||||
return nil, errors.New("scheme " + proxyUrl.Scheme + " is not supported")
|
||||
return nil, errors.New("scheme " + proxyURL.Scheme + " is not supported")
|
||||
}
|
||||
|
||||
client := &HTTPConnectDialer{
|
||||
ProxyUrl: *proxyUrl,
|
||||
ProxyURL: *proxyURL,
|
||||
DefaultHeader: make(http.Header),
|
||||
SpkiFP: nil,
|
||||
EnableH2ConnReuse: true,
|
||||
}
|
||||
|
||||
if proxyUrl.User != nil {
|
||||
if proxyUrl.User.Username() != "" {
|
||||
password, _ := proxyUrl.User.Password()
|
||||
if proxyURL.User != nil {
|
||||
if proxyURL.User.Username() != "" {
|
||||
password, _ := proxyURL.User.Password()
|
||||
client.DefaultHeader.Set("Proxy-Authorization", "Basic "+
|
||||
base64.StdEncoding.EncodeToString([]byte(proxyUrl.User.Username()+":"+password)))
|
||||
base64.StdEncoding.EncodeToString([]byte(proxyURL.User.Username()+":"+password)))
|
||||
}
|
||||
}
|
||||
return client, nil
|
||||
|
|
@ -191,24 +191,24 @@ func (c *HTTPConnectDialer) DialContext(ctx context.Context, network, address st
|
|||
var err error
|
||||
var rawConn net.Conn
|
||||
negotiatedProtocol := ""
|
||||
switch c.ProxyUrl.Scheme {
|
||||
switch c.ProxyURL.Scheme {
|
||||
case "http":
|
||||
rawConn, err = c.Dialer.DialContext(ctx, network, c.ProxyUrl.Host)
|
||||
rawConn, err = c.Dialer.DialContext(ctx, network, c.ProxyURL.Host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "https":
|
||||
if c.DialTLS != nil {
|
||||
rawConn, negotiatedProtocol, err = c.DialTLS(network, c.ProxyUrl.Host)
|
||||
rawConn, negotiatedProtocol, err = c.DialTLS(network, c.ProxyURL.Host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
tlsConf := tls.Config{
|
||||
NextProtos: []string{"h2", "http/1.1"},
|
||||
ServerName: c.ProxyUrl.Hostname(),
|
||||
ServerName: c.ProxyURL.Hostname(),
|
||||
}
|
||||
tlsConn, err := tls.Dial(network, c.ProxyUrl.Host, &tlsConf)
|
||||
tlsConn, err := tls.Dial(network, c.ProxyURL.Host, &tlsConf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -220,7 +220,7 @@ func (c *HTTPConnectDialer) DialContext(ctx context.Context, network, address st
|
|||
rawConn = tlsConn
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("scheme " + c.ProxyUrl.Scheme + " is not supported")
|
||||
return nil, errors.New("scheme " + c.ProxyURL.Scheme + " is not supported")
|
||||
}
|
||||
|
||||
switch negotiatedProtocol {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package forwardproxy
|
|||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"testing"
|
||||
|
|
@ -12,27 +13,36 @@ import (
|
|||
)
|
||||
|
||||
func TestHttpClient(t *testing.T) {
|
||||
_test := func(proxyUrl string) {
|
||||
for _, httpProxyVer := range testHttpProxyVersions {
|
||||
for _, httpTargetVer := range testHttpTargetVersions {
|
||||
const dialLocal = false
|
||||
_test := func(urlSchemeAndCreds, urlAddress string) {
|
||||
for _, httpProxyVer := range testHTTPProxyVersions {
|
||||
for _, httpTargetVer := range testHTTPTargetVersions {
|
||||
for _, resource := range testResources {
|
||||
dialer, err := httpclient.NewHTTPConnectDialer(proxyUrl)
|
||||
// always dial localhost for testing purposes
|
||||
proxyURL := fmt.Sprintf("%s@%s", urlSchemeAndCreds, localDialAddr(urlAddress))
|
||||
|
||||
dialer, err := httpclient.NewHTTPConnectDialer(proxyURL)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
dialer.DialTLS = func(network string, address string) (net.Conn, string, error) {
|
||||
conn, err := tls.Dial(network, address, &tls.Config{InsecureSkipVerify: true,
|
||||
NextProtos: []string{httpVersionToAlpn[httpProxyVer]}})
|
||||
// always dial localhost for testing purposes
|
||||
conn, err := tls.Dial(network, localDialAddr(address), &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
NextProtos: []string{httpVersionToALPN[httpProxyVer]},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return conn, conn.ConnectionState().NegotiatedProtocol, nil
|
||||
}
|
||||
conn, err := dialer.Dial("tcp", caddyTestTarget.addr)
|
||||
|
||||
// always dial localhost for testing purposes
|
||||
conn, err := dialer.Dial("tcp", localDialAddr(caddyTestTarget.addr))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
response, err := getResourceViaProxyConn(conn, caddyTestTarget.addr, resource, httpTargetVer, credentialsCorrect)
|
||||
response, err := getResourceViaProxyConn(conn, caddyTestTarget.addr, resource, httpTargetVer, credentialsCorrect, dialLocal)
|
||||
if err != nil {
|
||||
t.Fatal(httpProxyVer, httpTargetVer, err)
|
||||
} else if err = responseExpected(response, caddyTestTarget.contents[resource]); err != nil {
|
||||
|
|
@ -43,8 +53,8 @@ func TestHttpClient(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
_test("https://" + credentialsCorrectPlain + "@" + caddyForwardProxyAuth.addr)
|
||||
_test("http://" + credentialsCorrectPlain + "@" + caddyHTTPForwardProxyAuth.addr)
|
||||
_test("https://"+credentialsCorrectPlain, caddyForwardProxyAuth.addr)
|
||||
_test("http://"+credentialsCorrectPlain, caddyHTTPForwardProxyAuth.addr)
|
||||
}
|
||||
|
||||
func TestHttpClientH2Multiplexing(t *testing.T) {
|
||||
|
|
@ -52,14 +62,18 @@ func TestHttpClientH2Multiplexing(t *testing.T) {
|
|||
// but it was manually inspected in Wireshark when this code was committed
|
||||
httpProxyVer := "HTTP/2.0"
|
||||
httpTargetVer := "HTTP/1.1"
|
||||
const dialLocal = false
|
||||
|
||||
dialer, err := httpclient.NewHTTPConnectDialer("https://" + credentialsCorrectPlain + "@" + caddyForwardProxyAuth.addr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
dialer.DialTLS = func(network string, address string) (net.Conn, string, error) {
|
||||
conn, err := tls.Dial(network, address, &tls.Config{InsecureSkipVerify: true,
|
||||
NextProtos: []string{httpVersionToAlpn[httpProxyVer]}})
|
||||
// always dial localhost for testing purposes
|
||||
conn, err := tls.Dial(network, localDialAddr(address), &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
NextProtos: []string{httpVersionToALPN[httpProxyVer]},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
|
@ -74,11 +88,12 @@ func TestHttpClientH2Multiplexing(t *testing.T) {
|
|||
_test := func() {
|
||||
defer wg.Done()
|
||||
for _, resource := range testResources {
|
||||
conn, err := dialer.Dial("tcp", caddyTestTarget.addr)
|
||||
// always dial localhost for testing purposes
|
||||
conn, err := dialer.Dial("tcp", localDialAddr(caddyTestTarget.addr))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
response, err := getResourceViaProxyConn(conn, caddyTestTarget.addr, resource, httpTargetVer, credentialsCorrect)
|
||||
response, err := getResourceViaProxyConn(conn, caddyTestTarget.addr, resource, httpTargetVer, credentialsCorrect, dialLocal)
|
||||
if err != nil {
|
||||
t.Fatal(httpProxyVer, httpTargetVer, err)
|
||||
} else if err = responseExpected(response, caddyTestTarget.contents[resource]); err != nil {
|
||||
|
|
|
|||
|
|
@ -5,16 +5,17 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGETAuthCorrectProbeResist(t *testing.T) {
|
||||
useTls := true
|
||||
for _, httpProxyVer := range testHttpProxyVersions {
|
||||
const useTLS, dialLocal = true, true
|
||||
for _, httpProxyVer := range testHTTPProxyVersions {
|
||||
for _, resource := range testResources {
|
||||
response, err := getViaProxy(caddyTestTarget.addr, resource, caddyForwardProxyProbeResist.addr, httpProxyVer, credentialsCorrect, useTls)
|
||||
response, err := getViaProxy(caddyTestTarget.addr, resource, caddyForwardProxyProbeResist.addr, httpProxyVer, credentialsCorrect, useTLS, dialLocal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err = responseExpected(response, caddyTestTarget.contents[resource]); err != nil {
|
||||
|
|
@ -25,21 +26,21 @@ func TestGETAuthCorrectProbeResist(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestGETAuthWrongProbeResist(t *testing.T) {
|
||||
useTls := true
|
||||
const useTLS, dialLocal = true, false
|
||||
for _, wrongCreds := range credentialsWrong {
|
||||
for _, httpProxyVer := range testHttpProxyVersions {
|
||||
for _, httpProxyVer := range testHTTPProxyVersions {
|
||||
for _, resource := range testResources {
|
||||
responseProbeResist, err := getViaProxy(caddyTestTarget.addr, resource, caddyForwardProxyProbeResist.addr, httpProxyVer, wrongCreds, useTls)
|
||||
responseProbeResist, err := getViaProxy(caddyTestTarget.addr, resource, caddyForwardProxyProbeResist.addr, httpProxyVer, wrongCreds, useTLS, dialLocal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// get response from reference server without forwardproxy and compare them
|
||||
responseReference, err := getViaProxy(caddyTestTarget.addr, resource, caddyDummyProbeResist.addr, httpProxyVer, wrongCreds, useTls)
|
||||
responseReference, err := getViaProxy(caddyTestTarget.addr, resource, caddyDummyProbeResist.addr, httpProxyVer, wrongCreds, useTLS, dialLocal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// as a sanity check, get 407 from simple authenticated forwardproxy
|
||||
responseForwardProxy, err := getViaProxy(caddyTestTarget.addr, resource, caddyForwardProxyAuth.addr, httpProxyVer, wrongCreds, useTls)
|
||||
responseForwardProxy, err := getViaProxy(caddyTestTarget.addr, resource, caddyForwardProxyAuth.addr, httpProxyVer, wrongCreds, useTLS, dialLocal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -55,17 +56,17 @@ func TestGETAuthWrongProbeResist(t *testing.T) {
|
|||
}
|
||||
}
|
||||
for _, resource := range testResources {
|
||||
responseProbeResist, err := getViaProxy(caddyForwardProxyProbeResist.addr, resource, caddyForwardProxyProbeResist.addr, httpProxyVer, wrongCreds, useTls)
|
||||
responseProbeResist, err := getViaProxy(caddyForwardProxyProbeResist.addr, resource, caddyForwardProxyProbeResist.addr, httpProxyVer, wrongCreds, useTLS, dialLocal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// get response from reference server without forwardproxy and compare them
|
||||
responseReference, err := getViaProxy(caddyDummyProbeResist.addr, resource, caddyDummyProbeResist.addr, httpProxyVer, wrongCreds, useTls)
|
||||
responseReference, err := getViaProxy(caddyDummyProbeResist.addr, resource, caddyDummyProbeResist.addr, httpProxyVer, wrongCreds, useTLS, dialLocal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// as a sanity check, get 407 from simple authenticated forwardproxy
|
||||
responseForwardProxy, err := getViaProxy(caddyForwardProxyAuth.addr, resource, caddyForwardProxyAuth.addr, httpProxyVer, wrongCreds, useTls)
|
||||
responseForwardProxy, err := getViaProxy(caddyForwardProxyAuth.addr, resource, caddyForwardProxyAuth.addr, httpProxyVer, wrongCreds, useTLS, dialLocal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -86,16 +87,14 @@ func TestGETAuthWrongProbeResist(t *testing.T) {
|
|||
|
||||
// test that responses on http redirect port are same
|
||||
func TestGETAuthWrongProbeResistRedir(t *testing.T) {
|
||||
useTls := false
|
||||
const useTLS, dialLocal = false, false
|
||||
httpProxyVer := "HTTP/1.1"
|
||||
for _, wrongCreds := range credentialsWrong {
|
||||
// request test target
|
||||
for _, resource := range testResources {
|
||||
responseProbeResist, rPRerr := getViaProxy(caddyTestTarget.addr, resource, stripPort(caddyForwardProxyProbeResist.addr)+":"+caddyForwardProxyProbeResist.HTTPRedirectPort,
|
||||
httpProxyVer, wrongCreds, useTls)
|
||||
responseProbeResist, rPRerr := getViaProxy(caddyTestTarget.addr, resource, changePort(caddyForwardProxyProbeResist.addr, caddyForwardProxyProbeResist.httpRedirPort), httpProxyVer, wrongCreds, useTLS, dialLocal)
|
||||
// get response from reference server without forwardproxy and compare them
|
||||
responseReference, rRerr := getViaProxy(caddyTestTarget.addr, resource, stripPort(caddyDummyProbeResist.addr)+":"+caddyDummyProbeResist.HTTPRedirectPort,
|
||||
httpProxyVer, wrongCreds, useTls)
|
||||
responseReference, rRerr := getViaProxy(caddyTestTarget.addr, resource, changePort(caddyDummyProbeResist.addr, caddyDummyProbeResist.httpRedirPort), httpProxyVer, wrongCreds, useTLS, dialLocal)
|
||||
if (rPRerr == nil && rRerr != nil) || (rPRerr != nil && rRerr == nil) {
|
||||
t.Fatalf("Reference error: %s. Probe resist error: %s", rRerr, rPRerr)
|
||||
}
|
||||
|
|
@ -109,14 +108,12 @@ func TestGETAuthWrongProbeResistRedir(t *testing.T) {
|
|||
}
|
||||
// request self
|
||||
for _, resource := range testResources {
|
||||
responseProbeResist, err := getViaProxy(caddyForwardProxyProbeResist.addr, resource, stripPort(caddyForwardProxyProbeResist.addr)+":"+caddyForwardProxyProbeResist.HTTPRedirectPort,
|
||||
httpProxyVer, wrongCreds, useTls)
|
||||
responseProbeResist, err := getViaProxy(caddyForwardProxyProbeResist.addr, resource, changePort(caddyForwardProxyProbeResist.addr, caddyForwardProxyProbeResist.httpRedirPort), httpProxyVer, wrongCreds, useTLS, dialLocal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// get response from reference server without forwardproxy and compare them
|
||||
responseReference, err := getViaProxy(caddyDummyProbeResist.addr, resource, stripPort(caddyDummyProbeResist.addr)+":"+caddyDummyProbeResist.HTTPRedirectPort,
|
||||
httpProxyVer, wrongCreds, useTls)
|
||||
responseReference, err := getViaProxy(caddyDummyProbeResist.addr, resource, changePort(caddyDummyProbeResist.addr, caddyDummyProbeResist.httpRedirPort), httpProxyVer, wrongCreds, useTLS, dialLocal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -132,11 +129,11 @@ func TestGETAuthWrongProbeResistRedir(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestConnectAuthCorrectProbeResist(t *testing.T) {
|
||||
useTls := true
|
||||
for _, httpProxyVer := range testHttpProxyVersions {
|
||||
for _, httpTargetVer := range testHttpTargetVersions {
|
||||
const useTLS, dialLocal = true, true
|
||||
for _, httpProxyVer := range testHTTPProxyVersions {
|
||||
for _, httpTargetVer := range testHTTPTargetVersions {
|
||||
for _, resource := range testResources {
|
||||
response, err := connectAndGetViaProxy(caddyTestTarget.addr, resource, caddyForwardProxyProbeResist.addr, httpTargetVer, credentialsCorrect, httpProxyVer, useTls)
|
||||
response, err := connectAndGetViaProxy(caddyTestTarget.addr, resource, caddyForwardProxyProbeResist.addr, httpTargetVer, credentialsCorrect, httpProxyVer, useTLS, dialLocal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err = responseExpected(response, caddyTestTarget.contents[resource]); err != nil {
|
||||
|
|
@ -148,22 +145,22 @@ func TestConnectAuthCorrectProbeResist(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestConnectAuthWrongProbeResist(t *testing.T) {
|
||||
useTls := true
|
||||
const useTLS, dialLocal = true, false
|
||||
for _, wrongCreds := range credentialsWrong {
|
||||
for _, httpProxyVer := range testHttpProxyVersions {
|
||||
for _, httpTargetVer := range testHttpTargetVersions {
|
||||
for _, httpProxyVer := range testHTTPProxyVersions {
|
||||
for _, httpTargetVer := range testHTTPTargetVersions {
|
||||
for _, resource := range testResources {
|
||||
responseProbeResist, err := connectAndGetViaProxy(caddyTestTarget.addr, resource, caddyForwardProxyProbeResist.addr, httpTargetVer, wrongCreds, httpProxyVer, useTls)
|
||||
responseProbeResist, err := connectAndGetViaProxy(caddyTestTarget.addr, resource, caddyForwardProxyProbeResist.addr, httpTargetVer, wrongCreds, httpProxyVer, useTLS, dialLocal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// get response from reference server without forwardproxy and compare them
|
||||
responseReference, err := connectAndGetViaProxy(caddyTestTarget.addr, resource, caddyDummyProbeResist.addr, httpTargetVer, wrongCreds, httpProxyVer, useTls)
|
||||
responseReference, err := connectAndGetViaProxy(caddyTestTarget.addr, resource, caddyDummyProbeResist.addr, httpTargetVer, wrongCreds, httpProxyVer, useTLS, dialLocal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// as a sanity check, get 407 from simple authenticated forwardproxy
|
||||
responseForwardProxy, err := connectAndGetViaProxy(caddyTestTarget.addr, resource, caddyForwardProxyAuth.addr, httpTargetVer, wrongCreds, httpProxyVer, useTls)
|
||||
responseForwardProxy, err := connectAndGetViaProxy(caddyTestTarget.addr, resource, caddyForwardProxyAuth.addr, httpTargetVer, wrongCreds, httpProxyVer, useTLS, dialLocal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -183,25 +180,26 @@ func TestConnectAuthWrongProbeResist(t *testing.T) {
|
|||
if httpTargetVer != httpProxyVer {
|
||||
continue
|
||||
}
|
||||
responseProbeResist, err := connectAndGetViaProxy(caddyForwardProxyProbeResist.addr, resource, caddyForwardProxyProbeResist.addr, httpTargetVer, wrongCreds, httpProxyVer, useTls)
|
||||
responseProbeResist, err := connectAndGetViaProxy(caddyForwardProxyProbeResist.addr, resource, caddyForwardProxyProbeResist.addr, httpTargetVer, wrongCreds, httpProxyVer, useTLS, dialLocal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// get response from reference server without forwardproxy and compare them
|
||||
responseReference, err := connectAndGetViaProxy(caddyDummyProbeResist.addr, resource, caddyDummyProbeResist.addr, httpTargetVer, wrongCreds, httpProxyVer, useTls)
|
||||
responseReference, err := connectAndGetViaProxy(caddyDummyProbeResist.addr, resource, caddyDummyProbeResist.addr, httpTargetVer, wrongCreds, httpProxyVer, useTLS, dialLocal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// as a sanity check, get 407 from simple authenticated forwardproxy
|
||||
responseForwardProxy, err := connectAndGetViaProxy(caddyForwardProxyAuth.addr, resource, caddyForwardProxyAuth.addr, httpTargetVer, wrongCreds, httpProxyVer, useTls)
|
||||
responseForwardProxy, err := connectAndGetViaProxy(caddyForwardProxyAuth.addr, resource, caddyForwardProxyAuth.addr, httpTargetVer, wrongCreds, httpProxyVer, useTLS, dialLocal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = responsesAreEqual(responseProbeResist, responseReference); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = responsesAreEqual(responseProbeResist, responseForwardProxy); err == nil {
|
||||
t.Fatal("Responses from servers with and without forwardproxy are expected to be different.")
|
||||
// TODO: this test was originally err == nil in Caddy v1, but not sure that makes sense in caddy v2? (similar test above on L174 is unchanged)
|
||||
if err = responsesAreEqual(responseProbeResist, responseForwardProxy); err != nil {
|
||||
t.Fatal("Responses from servers with and without probe resistance are expected to be the same:", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -211,20 +209,18 @@ func TestConnectAuthWrongProbeResist(t *testing.T) {
|
|||
|
||||
// test that responses on http redirect port are same
|
||||
func TestConnectAuthWrongProbeResistRedir(t *testing.T) {
|
||||
useTls := false
|
||||
const useTLS, dialLocal = false, false
|
||||
httpProxyVer := "HTTP/1.1"
|
||||
for _, wrongCreds := range credentialsWrong {
|
||||
for _, httpTargetVer := range testHttpTargetVersions {
|
||||
for _, httpTargetVer := range testHTTPTargetVersions {
|
||||
// request test target
|
||||
for _, resource := range testResources {
|
||||
responseProbeResist, err := connectAndGetViaProxy(caddyTestTarget.addr, resource, stripPort(caddyForwardProxyProbeResist.addr)+":"+caddyForwardProxyProbeResist.HTTPRedirectPort,
|
||||
httpTargetVer, wrongCreds, httpProxyVer, useTls)
|
||||
responseProbeResist, err := connectAndGetViaProxy(caddyTestTarget.addr, resource, changePort(caddyForwardProxyProbeResist.addr, caddyForwardProxyProbeResist.httpRedirPort), httpTargetVer, wrongCreds, httpProxyVer, useTLS, dialLocal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// get response from reference server without forwardproxy and compare them
|
||||
responseReference, err := connectAndGetViaProxy(caddyTestTarget.addr, resource, stripPort(caddyDummyProbeResist.addr)+":"+caddyDummyProbeResist.HTTPRedirectPort,
|
||||
httpTargetVer, wrongCreds, httpProxyVer, useTls)
|
||||
responseReference, err := connectAndGetViaProxy(caddyTestTarget.addr, resource, changePort(caddyDummyProbeResist.addr, caddyDummyProbeResist.httpRedirPort), httpTargetVer, wrongCreds, httpProxyVer, useTLS, dialLocal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -238,14 +234,12 @@ func TestConnectAuthWrongProbeResistRedir(t *testing.T) {
|
|||
}
|
||||
// request self
|
||||
for _, resource := range testResources {
|
||||
responseProbeResist, err := connectAndGetViaProxy(caddyForwardProxyProbeResist.addr, resource, stripPort(caddyForwardProxyProbeResist.addr)+":"+caddyForwardProxyProbeResist.HTTPRedirectPort,
|
||||
httpTargetVer, wrongCreds, httpProxyVer, useTls)
|
||||
responseProbeResist, err := connectAndGetViaProxy(caddyForwardProxyProbeResist.addr, resource, changePort(caddyForwardProxyProbeResist.addr, caddyForwardProxyProbeResist.httpRedirPort), httpTargetVer, wrongCreds, httpProxyVer, useTLS, dialLocal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// get response from reference server without forwardproxy and compare them
|
||||
responseReference, err := connectAndGetViaProxy(caddyDummyProbeResist.addr, resource, stripPort(caddyDummyProbeResist.addr)+":"+caddyDummyProbeResist.HTTPRedirectPort,
|
||||
httpTargetVer, wrongCreds, httpProxyVer, useTls)
|
||||
responseReference, err := connectAndGetViaProxy(caddyDummyProbeResist.addr, resource, changePort(caddyDummyProbeResist.addr, caddyDummyProbeResist.httpRedirPort), httpTargetVer, wrongCreds, httpProxyVer, useTLS, dialLocal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -270,36 +264,31 @@ func responsesAreEqual(res1, res2 *http.Response) error {
|
|||
return errors.New("res2 is nil")
|
||||
}
|
||||
if res1.Status != res2.Status {
|
||||
return errors.New("Status is different")
|
||||
return fmt.Errorf("status is different; %s != %s", res1.Status, res2.Status)
|
||||
}
|
||||
if res1.StatusCode != res2.StatusCode {
|
||||
return errors.New("StatusCode is different")
|
||||
return fmt.Errorf("status code is different; %d != %d", res1.StatusCode, res2.StatusCode)
|
||||
}
|
||||
|
||||
if res1.ProtoMajor != res2.ProtoMajor {
|
||||
return errors.New("ProtoMajor is different")
|
||||
return fmt.Errorf("proto major is different; %d != %d", res1.ProtoMajor, res2.ProtoMajor)
|
||||
}
|
||||
|
||||
if res1.Close != res2.Close {
|
||||
return errors.New("Close is different")
|
||||
}
|
||||
|
||||
if res1.ProtoMinor != res2.ProtoMinor {
|
||||
return errors.New("ProtoMinor is different")
|
||||
return fmt.Errorf("proto minor is different; %d != %d", res1.ProtoMinor, res2.ProtoMinor)
|
||||
}
|
||||
if res1.Close != res2.Close {
|
||||
return fmt.Errorf("close is different; %t != %t", res1.Close, res2.Close)
|
||||
}
|
||||
|
||||
if res1.ContentLength != res2.ContentLength {
|
||||
return errors.New("ContentLength is different")
|
||||
return fmt.Errorf("content length is different; %d != %d", res1.ContentLength, res2.ContentLength)
|
||||
}
|
||||
|
||||
if res1.Uncompressed != res2.Uncompressed {
|
||||
return errors.New("Uncompressed is different")
|
||||
return fmt.Errorf("uncompressed is different; %t != %t", res1.Uncompressed, res2.Uncompressed)
|
||||
}
|
||||
if res1.Proto != res2.Proto {
|
||||
return errors.New("Proto is different")
|
||||
return fmt.Errorf("proto is different; %s != %s", res1.Proto, res2.Proto)
|
||||
}
|
||||
if len(res1.TransferEncoding) != len(res2.TransferEncoding) {
|
||||
return errors.New("TransferEncodings have different length")
|
||||
return fmt.Errorf("transfer encodings have different lenght; %d != %d", len(res1.TransferEncoding), len(res2.TransferEncoding))
|
||||
}
|
||||
|
||||
// returns "" if equal
|
||||
|
|
@ -341,18 +330,18 @@ func responsesAreEqual(res1, res2 *http.Response) error {
|
|||
}
|
||||
v2, ok := res2.Header[k1]
|
||||
if !ok {
|
||||
return errors.New(fmt.Sprintf("Header \"%s: %s\" is absent in res2", k1, v1))
|
||||
}
|
||||
if k1Lower == "location" {
|
||||
for i, h := range v2 {
|
||||
v2[i] = removeAddressesStr(h)
|
||||
}
|
||||
for i, h := range v1 {
|
||||
v1[i] = removeAddressesStr(h)
|
||||
}
|
||||
return fmt.Errorf("header \"%s: %s\" is absent in res2", k1, v1)
|
||||
}
|
||||
// if k1Lower == "location" {
|
||||
// for i, h := range v2 {
|
||||
// v2[i] = removeAddressesStr(h)
|
||||
// }
|
||||
// for i, h := range v1 {
|
||||
// v1[i] = removeAddressesStr(h)
|
||||
// }
|
||||
// }
|
||||
if errStr = stringSlicesAreEqual(v1, v2); errStr != "" {
|
||||
return errors.New(fmt.Sprintf("Header \"%s\" is different: %s", k1, errStr))
|
||||
return fmt.Errorf("header \"%s\" is different: %s", k1, errStr)
|
||||
}
|
||||
}
|
||||
// Compare bodies
|
||||
|
|
@ -361,8 +350,8 @@ func responsesAreEqual(res1, res2 *http.Response) error {
|
|||
n1 := len(buf1)
|
||||
n2 := len(buf2)
|
||||
makeBodyError := func(s string) error {
|
||||
return errors.New(fmt.Sprintf("Bodies are different: %s. n1 = %d, n2 = %d. err1 = %v, err2 = %v. buf1 = %s, buf2 = %s",
|
||||
s, n1, n2, err1, err2, buf1[:n1], buf2[:n2]))
|
||||
return fmt.Errorf("bodies are different: %s. n1 = %d, n2 = %d. err1 = %v, err2 = %v. buf1 = %s, buf2 = %s",
|
||||
s, n1, n2, err1, err2, buf1[:n1], buf2[:n2])
|
||||
}
|
||||
if n2 != n1 {
|
||||
return makeBodyError("Body sizes are different")
|
||||
|
|
@ -393,3 +382,11 @@ func removeAddressesByte(b []byte) []byte {
|
|||
func removeAddressesStr(s string) string {
|
||||
return string(removeAddressesByte([]byte(s)))
|
||||
}
|
||||
|
||||
func changePort(inputAddr, toPort string) string {
|
||||
host, _, err := net.SplitHostPort(inputAddr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return net.JoinHostPort(host, toPort)
|
||||
}
|
||||
|
|
|
|||
409
setup.go
409
setup.go
|
|
@ -1,409 +0,0 @@
|
|||
// Copyright 2017 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package forwardproxy
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/caddy"
|
||||
"github.com/caddyserver/caddy/caddyhttp/httpserver"
|
||||
"github.com/caddyserver/forwardproxy/httpclient"
|
||||
"golang.org/x/net/proxy"
|
||||
)
|
||||
|
||||
func setup(c *caddy.Controller) error {
|
||||
httpserver.GetConfig(c).FallbackSite = true
|
||||
fp := &ForwardProxy{
|
||||
dialTimeout: time.Second * 20,
|
||||
hostname: httpserver.GetConfig(c).Host(), port: httpserver.GetConfig(c).Port(),
|
||||
httpTransport: http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
MaxIdleConns: 50,
|
||||
IdleConnTimeout: 60 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
},
|
||||
}
|
||||
|
||||
c.Next() // skip the directive name
|
||||
|
||||
args := c.RemainingArgs()
|
||||
if len(args) > 0 {
|
||||
return c.ArgErr()
|
||||
}
|
||||
|
||||
for c.NextBlock() {
|
||||
subdirective := c.Val()
|
||||
args := c.RemainingArgs()
|
||||
switch subdirective {
|
||||
case "basicauth":
|
||||
if len(args) != 2 {
|
||||
return c.ArgErr()
|
||||
}
|
||||
if len(args[0]) == 0 {
|
||||
return c.Err("empty usernames are not allowed")
|
||||
}
|
||||
// TODO: Evaluate policy of allowing empty passwords.
|
||||
if strings.Contains(args[0], ":") {
|
||||
return c.Err("character ':' in usernames is not allowed")
|
||||
}
|
||||
if fp.authCredentials == nil {
|
||||
fp.authCredentials = [][]byte{}
|
||||
}
|
||||
// base64-encode credentials
|
||||
buf := make([]byte, base64.StdEncoding.EncodedLen(len(args[0])+1+len(args[1])))
|
||||
base64.StdEncoding.Encode(buf, []byte(args[0]+":"+args[1]))
|
||||
fp.authCredentials = append(fp.authCredentials, buf)
|
||||
fp.authRequired = true
|
||||
case "ports":
|
||||
if len(args) == 0 {
|
||||
return c.ArgErr()
|
||||
}
|
||||
if len(fp.whitelistedPorts) != 0 {
|
||||
return c.Err("ports subdirective specified twice")
|
||||
}
|
||||
fp.whitelistedPorts = make([]int, len(args))
|
||||
for i, p := range args {
|
||||
intPort, err := strconv.Atoi(p)
|
||||
if intPort <= 0 || intPort > 65535 || err != nil {
|
||||
return c.Err("ports are expected to be space-separated" +
|
||||
" and in 0-65535 range. Got: " + p)
|
||||
}
|
||||
fp.whitelistedPorts[i] = intPort
|
||||
}
|
||||
case "hide_ip":
|
||||
if len(args) != 0 {
|
||||
return c.ArgErr()
|
||||
}
|
||||
fp.hideIP = true
|
||||
case "hide_via":
|
||||
if len(args) != 0 {
|
||||
return c.ArgErr()
|
||||
}
|
||||
fp.hideVia = true
|
||||
case "probe_resistance":
|
||||
if len(args) > 1 {
|
||||
return c.ArgErr()
|
||||
}
|
||||
fp.probeResistEnabled = true
|
||||
if len(args) == 1 {
|
||||
lowercaseArg := strings.ToLower(args[0])
|
||||
if lowercaseArg != args[0] {
|
||||
log.Println("WARNING: secret domain appears to have uppercase letters in it, which are not visitable")
|
||||
}
|
||||
fp.probeResistDomain = args[0]
|
||||
}
|
||||
case "serve_pac":
|
||||
if len(args) > 1 {
|
||||
return c.ArgErr()
|
||||
}
|
||||
if len(fp.pacFilePath) != 0 {
|
||||
return c.Err("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()
|
||||
}
|
||||
timeout, err := strconv.Atoi(args[0])
|
||||
if err != nil {
|
||||
return c.ArgErr()
|
||||
}
|
||||
if timeout < 0 {
|
||||
return c.Err("response_timeout cannot be negative.")
|
||||
}
|
||||
fp.httpTransport.ResponseHeaderTimeout = time.Duration(timeout) * time.Second
|
||||
case "dial_timeout":
|
||||
if len(args) != 1 {
|
||||
return c.ArgErr()
|
||||
}
|
||||
timeout, err := strconv.Atoi(args[0])
|
||||
if err != nil {
|
||||
return c.ArgErr()
|
||||
}
|
||||
if timeout < 0 {
|
||||
return c.Err("dial_timeout cannot be negative.")
|
||||
}
|
||||
fp.dialTimeout = time.Second * time.Duration(timeout)
|
||||
case "upstream":
|
||||
if len(args) != 1 {
|
||||
return c.ArgErr()
|
||||
}
|
||||
if fp.upstream != nil {
|
||||
return c.Err("upstream directive specified more than once")
|
||||
}
|
||||
var err error
|
||||
fp.upstream, err = url.Parse(args[0])
|
||||
if err != nil {
|
||||
return c.Err("failed to parse upstream address: " + err.Error())
|
||||
}
|
||||
case "acl":
|
||||
if len(args) != 0 {
|
||||
return c.Err("acl should be only subdirective on the line")
|
||||
}
|
||||
args := c.RemainingArgs()
|
||||
if len(args) > 0 {
|
||||
return c.ArgErr()
|
||||
}
|
||||
c.Next()
|
||||
if c.Val() != "{" {
|
||||
return c.Err("acl directive must be followed by opening curly braces \"{\"")
|
||||
}
|
||||
for {
|
||||
if !c.Next() {
|
||||
return c.Err("acl blockmust be ended by closing curly braces \"}\"")
|
||||
}
|
||||
aclDirective := c.Val()
|
||||
args := c.RemainingArgs()
|
||||
if aclDirective == "}" {
|
||||
break
|
||||
}
|
||||
if len(args) == 0 {
|
||||
return c.ArgErr()
|
||||
}
|
||||
var ruleSubjects []string
|
||||
var err error
|
||||
aclAllow := false
|
||||
switch aclDirective {
|
||||
case "allow":
|
||||
ruleSubjects = args[:]
|
||||
aclAllow = true
|
||||
case "allowfile":
|
||||
if len(args) != 1 {
|
||||
return c.Err("allowfile accepts a single filename argument")
|
||||
}
|
||||
ruleSubjects, err = readLinesFromFile(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
aclAllow = true
|
||||
case "deny":
|
||||
ruleSubjects = args[:]
|
||||
case "denyfile":
|
||||
if len(args) != 1 {
|
||||
return c.Err("denyfile accepts a single filename argument")
|
||||
}
|
||||
ruleSubjects, err = readLinesFromFile(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return c.Err("expected acl directive: allow/allowfile/deny/denyfile." +
|
||||
"got: " + aclDirective)
|
||||
}
|
||||
for _, rs := range ruleSubjects {
|
||||
ar, err := newAclRule(rs, aclAllow)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fp.aclRules = append(fp.aclRules, ar)
|
||||
}
|
||||
}
|
||||
default:
|
||||
return c.ArgErr()
|
||||
}
|
||||
}
|
||||
|
||||
if fp.upstream != nil && (fp.aclRules != nil || len(fp.whitelistedPorts) != 0) {
|
||||
return c.Err("upstream subdirective is incompatible with acl/ports subdirectives")
|
||||
}
|
||||
|
||||
for _, ipDeny := range []string{
|
||||
"10.0.0.0/8",
|
||||
"127.0.0.0/8",
|
||||
"172.16.0.0/12",
|
||||
"192.168.0.0/16",
|
||||
"::1/128",
|
||||
"fe80::/10",
|
||||
} {
|
||||
ar, err := newAclRule(ipDeny, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fp.aclRules = append(fp.aclRules, ar)
|
||||
}
|
||||
fp.aclRules = append(fp.aclRules, &aclAllRule{allow: true})
|
||||
|
||||
if fp.probeResistEnabled {
|
||||
if !fp.authRequired {
|
||||
return c.Err("probing resistance requires authentication: " +
|
||||
"add `basicauth username password` to forwardproxy")
|
||||
}
|
||||
if len(fp.probeResistDomain) > 0 {
|
||||
log.Printf("Secret domain used to connect to proxy: %s\n", fp.probeResistDomain)
|
||||
}
|
||||
}
|
||||
|
||||
dialer := &net.Dialer{
|
||||
Timeout: fp.dialTimeout,
|
||||
KeepAlive: 30 * time.Second,
|
||||
DualStack: true,
|
||||
}
|
||||
fp.dialContext = dialer.DialContext
|
||||
fp.httpTransport.DialContext = func(ctx context.Context, network string, address string) (net.Conn, error) {
|
||||
conn, err := fp.dialContextCheckACL(ctx, network, address)
|
||||
if err != nil {
|
||||
return conn, err
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
if fp.upstream != nil {
|
||||
if !isLocalhost(fp.upstream.Hostname()) && fp.upstream.Scheme != "https" {
|
||||
return errors.New("insecure schemes are only allowed to localhost upstreams")
|
||||
}
|
||||
|
||||
registerHTTPDialer := func(u *url.URL, _ proxy.Dialer) (proxy.Dialer, error) {
|
||||
// CONNECT request is proxied as-is, so we don't care about target url, but it could be
|
||||
// useful in future to implement policies of choosing between multiple upstream servers.
|
||||
// Given dialer is not used, since it's the same dialer provided by us.
|
||||
d, err := httpclient.NewHTTPConnectDialer(fp.upstream.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d.Dialer = *dialer
|
||||
if isLocalhost(fp.upstream.Hostname()) && fp.upstream.Scheme == "https" {
|
||||
// disabling verification helps with testing the package and setups
|
||||
// either way, it's impossible to have a legit TLS certificate for "127.0.0.1"
|
||||
log.Println("Localhost upstream detected, disabling verification of TLS certificate")
|
||||
d.DialTLS = func(network string, address string) (net.Conn, string, error) {
|
||||
conn, err := tls.Dial(network, address, &tls.Config{InsecureSkipVerify: true})
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return conn, conn.ConnectionState().NegotiatedProtocol, nil
|
||||
}
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
proxy.RegisterDialerType("https", registerHTTPDialer)
|
||||
proxy.RegisterDialerType("http", registerHTTPDialer)
|
||||
|
||||
upstreamDialer, err := proxy.FromURL(fp.upstream, dialer)
|
||||
if err != nil {
|
||||
return errors.New("failed to create proxy to upstream: " + err.Error())
|
||||
}
|
||||
|
||||
if ctxDialer, ok := upstreamDialer.(interface {
|
||||
DialContext(ctx context.Context, network, address string) (net.Conn, error)
|
||||
}); ok {
|
||||
// upstreamDialer has DialContext - use it
|
||||
fp.dialContext = ctxDialer.DialContext
|
||||
} else {
|
||||
// upstreamDialer does not have DialContext - ignore the context :(
|
||||
fp.dialContext = func(ctx context.Context, network string, address string) (net.Conn, error) {
|
||||
return upstreamDialer.Dial(network, address)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
|
||||
fp.Next = next
|
||||
return fp
|
||||
})
|
||||
|
||||
makeBuffer := func() interface{} { return make([]byte, 0, 32*1024) }
|
||||
bufferPool = sync.Pool{New: makeBuffer}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
caddy.RegisterPlugin("forwardproxy", caddy.Plugin{
|
||||
ServerType: "http",
|
||||
Action: setup,
|
||||
})
|
||||
}
|
||||
|
||||
func isLocalhost(hostname string) bool {
|
||||
if hostname == "localhost" || hostname == "127.0.0.1" || hostname == "::1" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func readLinesFromFile(filename string) ([]string, error) {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
var hostnames []string
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
hostnames = append(hostnames, scanner.Text())
|
||||
}
|
||||
|
||||
return hostnames, scanner.Err()
|
||||
}
|
||||
|
||||
// isValidDomainLite shamelessly rejects non-LDH names. returns nil if domains seems valid
|
||||
func isValidDomainLite(domain string) error {
|
||||
for i := 0; i < len(domain); i++ {
|
||||
c := domain[i]
|
||||
if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '_' || '0' <= c && c <= '9' ||
|
||||
c == '-' || c == '.' {
|
||||
continue
|
||||
}
|
||||
return errors.New("character " + string(c) + " is not allowed")
|
||||
}
|
||||
sections := strings.Split(domain, ".")
|
||||
for _, s := range sections {
|
||||
if len(s) == 0 {
|
||||
return errors.New("empty section between dots in domain name or trailing dot")
|
||||
}
|
||||
if len(s) > 63 {
|
||||
return errors.New("domain name section is too long")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ProxyError struct {
|
||||
S string
|
||||
Code int
|
||||
}
|
||||
|
||||
func (e *ProxyError) Error() string {
|
||||
return fmt.Sprintf("[%v] %s", e.Code, e.S)
|
||||
}
|
||||
|
||||
func (e *ProxyError) SplitCodeError() (int, error) {
|
||||
if e == nil {
|
||||
return 200, nil
|
||||
}
|
||||
return e.Code, errors.New(e.S)
|
||||
}
|
||||
139
setup_test.go
139
setup_test.go
|
|
@ -1,139 +0,0 @@
|
|||
// Copyright 2017 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package forwardproxy
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/caddyserver/caddy"
|
||||
)
|
||||
|
||||
func TestSetup(t *testing.T) {
|
||||
c := caddy.NewTestController("http", "forwardproxy string")
|
||||
err := setup(c)
|
||||
if err == nil {
|
||||
t.Fatal("Expected: failure. Got: success. Input: forwardproxy string")
|
||||
}
|
||||
|
||||
testParsing := func(subdirectives []string, shouldSucceed bool) {
|
||||
input := "forwardproxy"
|
||||
if len(subdirectives) > 0 {
|
||||
input += " {\n"
|
||||
for _, s := range subdirectives {
|
||||
input += s + "\n"
|
||||
}
|
||||
input += "}"
|
||||
}
|
||||
c := caddy.NewTestController("http", input)
|
||||
err := setup(c)
|
||||
if shouldSucceed && err != nil {
|
||||
t.Fatalf("Expected: success. Got: %v. Input:\n%s\n", err, input)
|
||||
}
|
||||
if !shouldSucceed && err == nil {
|
||||
t.Fatalf("Expected: failure. Got: success. Input:\n%s\n", input)
|
||||
}
|
||||
}
|
||||
testParsing(nil, true)
|
||||
testParsing([]string{}, true)
|
||||
testParsing([]string{"qweqwe"}, false)
|
||||
testParsing([]string{"0"}, false)
|
||||
|
||||
testParsing([]string{"basicauth john"}, false)
|
||||
testParsing([]string{"basicauth john \"\""}, true)
|
||||
testParsing([]string{"basicauth john", "basicauth john \"\""}, false)
|
||||
testParsing([]string{"basicauth john doe"}, true)
|
||||
testParsing([]string{"basicauth john doe foo"}, false)
|
||||
testParsing([]string{"basicauth john doe foo bar"}, false)
|
||||
testParsing([]string{"basicauth \"\" doe"}, false)
|
||||
testParsing([]string{"basicauth \"\" \"\""}, false)
|
||||
testParsing([]string{"basicauth 0"}, false)
|
||||
testParsing([]string{"basicauth 0 0"}, true)
|
||||
testParsing([]string{"basicauth 0 0 0"}, false)
|
||||
testParsing([]string{"basicauth 秘密"}, false)
|
||||
testParsing([]string{"basicauth 秘密 秘密"}, true)
|
||||
testParsing([]string{"basicauth 秘密 秘密 秘密"}, false)
|
||||
testParsing([]string{"basicauth cyrillic пароль"}, true)
|
||||
testParsing([]string{"basicauth john \"\"", "basicauth john doe", "basicauth 0 0", "basicauth 秘密 秘密", "basicauth cyrillic пароль"}, true)
|
||||
|
||||
testParsing([]string{"ports"}, false)
|
||||
testParsing([]string{"ports 0"}, false)
|
||||
testParsing([]string{"ports 0 1"}, false)
|
||||
testParsing([]string{"ports -1"}, false)
|
||||
testParsing([]string{"ports hi!"}, false)
|
||||
testParsing([]string{"ports 11, 122, 33"}, false)
|
||||
testParsing([]string{"ports 11, 122, 33"}, false)
|
||||
testParsing([]string{"ports 11111 99999"}, false)
|
||||
testParsing([]string{"ports 11 12"}, true)
|
||||
testParsing([]string{"ports 1"}, true)
|
||||
testParsing([]string{"ports 1 11 111 332 324 6546 33333"}, true)
|
||||
testParsing([]string{"ports 1 11 111 332 324 6546 33333", "ports 1 11 111 332 324 6546 33333"}, false)
|
||||
testParsing([]string{"ports 1", "ports 2"}, false)
|
||||
|
||||
testParsing([]string{"hide_ip"}, true)
|
||||
testParsing([]string{"hide_ip 0"}, false)
|
||||
testParsing([]string{"hide_ip 0 1"}, false)
|
||||
|
||||
testParsing([]string{"hide_via"}, true)
|
||||
testParsing([]string{"hide_via 0"}, false)
|
||||
testParsing([]string{"hide_via 0 1"}, false)
|
||||
|
||||
testParsing([]string{"probe_resistance"}, false)
|
||||
testParsing([]string{"probe_resistance local.host"}, false)
|
||||
testParsing([]string{"probe_resistance local.host very.local.host"}, false)
|
||||
testParsing([]string{"probe_resistance", "basicauth john doe"}, true)
|
||||
testParsing([]string{"probe_resistance local.host", "basicauth john doe"}, true)
|
||||
testParsing([]string{"probe_resistance 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)
|
||||
testParsing([]string{"response_timeout seven"}, false)
|
||||
testParsing([]string{"response_timeout 2"}, true)
|
||||
|
||||
testParsing([]string{"dial_timeout"}, false)
|
||||
testParsing([]string{"dial_timeout -1"}, false)
|
||||
testParsing([]string{"dial_timeout 1 2"}, false)
|
||||
testParsing([]string{"dial_timeout seven"}, false)
|
||||
testParsing([]string{"dial_timeout 2"}, true)
|
||||
|
||||
testParsing([]string{"upstream proxy.site"}, false)
|
||||
testParsing([]string{"upstream https://proxy.site https://proxy.site"}, false)
|
||||
testParsing([]string{"upstream http://localhost:1230"}, true)
|
||||
testParsing([]string{"upstream socks5://127.0.0.1:999"}, true)
|
||||
testParsing([]string{"upstream http://proxy.site"}, false)
|
||||
testParsing([]string{"upstream https://proxy.site https://proxy.site"}, false)
|
||||
testParsing([]string{"upstream https://proxy.site"}, true)
|
||||
testParsing([]string{"upstream https://caddyserver.com", "acl {\nallow all\n}"}, false)
|
||||
testParsing([]string{"upstream https://caddyserver.com", "ports 123"}, false)
|
||||
testParsing([]string{"upstream https://username:password@caddyserver.com", "ports 123"}, false)
|
||||
testParsing([]string{"upstream https://username:password@caddyserver.com:90", "ports 123"}, false)
|
||||
|
||||
testParsing([]string{"acl {\nallow all\n}"}, true)
|
||||
testParsing([]string{"acl {\nallow localhost 128.32.22.1/32 1.1.1.1 caddyserver.com\n deny all\n}"}, true)
|
||||
testParsing([]string{"acl {\nallowfile test/parseable_acl.txt\n}"}, true)
|
||||
testParsing([]string{"acl {\ndenyfile test/parseable_acl.txt\n}"}, true)
|
||||
testParsing([]string{"acl {\nallowfile test/unparseable_acl.txt\n}"}, false)
|
||||
testParsing([]string{"acl {\ndenyfile test/unparseable_acl.txt\n}"}, false)
|
||||
//testParsing([]string{"acl {\nallow all\n"}, false) // doesn't fail, but should: caddy itself doesn't demand curly brace to be closed
|
||||
testParsing([]string{"acl {\nallow all\n", "serve_pac"}, false) // this does fail
|
||||
testParsing([]string{"acl \nallow all\n}"}, false)
|
||||
testParsing([]string{"acl {allow all\n}"}, false)
|
||||
//testParsing([]string{"acl {\nallow all}"}, false) // '}' is not on the next line, "all}" parses as regexp
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue