Proxy auth and isolate test

* Don't use Tor info for proxy auth and update docs which fixes #18
* Add new test to confirm functionality of IsolateSOCKSAuth
This commit is contained in:
Chad Retz 2018-11-05 11:53:41 -06:00
parent 25e2ee8b21
commit 5c62b5e9f6
4 changed files with 85 additions and 22 deletions

View File

@ -8,12 +8,13 @@ import (
"testing"
"time"
"github.com/cretz/bine/tor"
"golang.org/x/net/context/ctxhttp"
)
func TestDialerSimpleHTTP(t *testing.T) {
ctx := GlobalEnabledNetworkContext(t)
httpClient := httpClient(ctx)
httpClient := httpClient(ctx, nil)
// IsTor check
byts := httpGet(ctx, httpClient, "https://check.torproject.org/api/ip")
jsn := map[string]interface{}{}
@ -21,12 +22,12 @@ func TestDialerSimpleHTTP(t *testing.T) {
ctx.Require.True(jsn["IsTor"].(bool))
}
func httpClient(ctx *TestContext) *http.Client {
func httpClient(ctx *TestContext, conf *tor.DialConf) *http.Client {
// 15 seconds max to dial
dialCtx, dialCancel := context.WithTimeout(ctx, 15*time.Second)
defer dialCancel()
// Make connection
dialer, err := ctx.Dialer(dialCtx, nil)
dialer, err := ctx.Dialer(dialCtx, conf)
ctx.Require.NoError(err)
return &http.Client{Transport: &http.Transport{DialContext: dialer.DialContext}}
}

View File

@ -0,0 +1,72 @@
package tests
import (
"context"
"strconv"
"strings"
"testing"
"time"
"github.com/cretz/bine/tor"
"golang.org/x/net/proxy"
)
// TestIsolateSocksAuth simply confirms the functionality of IsolateSOCKSAuth,
// namely that it uses a different circuit for different SOCKS credentials.
func TestIsolateSocksAuth(t *testing.T) {
// Create context w/ no isolate
ctx := NewTestContext(t, &tor.StartConf{
NoAutoSocksPort: true,
ExtraArgs: []string{"--SocksPort", "auto NoIsolateSOCKSAuth"},
})
// Make sure it reused the circuit (i.e. only has one) for both separate-auth calls
uniqueCircuitIDs := doSeparateAuthHttpCalls(ctx)
ctx.Debugf("Unique IDs without isolate: %v", uniqueCircuitIDs)
ctx.Require.Len(uniqueCircuitIDs, 1)
// Create context w/ isolate
ctx = NewTestContext(t, nil)
// Make sure it made a new circuit (i.e. has two) for each separate-auth call
uniqueCircuitIDs = doSeparateAuthHttpCalls(ctx)
ctx.Debugf("Unique IDs with isolate: %v", uniqueCircuitIDs)
ctx.Require.Len(uniqueCircuitIDs, 2)
}
// Returns the map keyed with unique circuit IDs after second separate-auth HTTP call
func doSeparateAuthHttpCalls(ctx *TestContext) map[int]struct{} {
defer ctx.Close()
enableCtx, enableCancel := context.WithTimeout(ctx, 100*time.Second)
defer enableCancel()
ctx.Require.NoError(ctx.EnableNetwork(enableCtx, true))
// Make HTTP call w/ an auth
client := httpClient(ctx, &tor.DialConf{ProxyAuth: &proxy.Auth{"foo", "bar"}})
byts := httpGet(ctx, client, "https://check.torproject.org/api/ip")
ctx.Debugf("Read bytes: %v", string(byts))
// Confirm just size 1
ids := uniqueStreamCircuitIDs(ctx)
ctx.Require.Len(ids, 1)
// Now make call with another auth and just return circuit IDs
client = httpClient(ctx, &tor.DialConf{ProxyAuth: &proxy.Auth{"baz", "qux"}})
byts = httpGet(ctx, client, "https://check.torproject.org/api/ip")
ctx.Debugf("Read bytes: %v", string(byts))
return uniqueStreamCircuitIDs(ctx)
}
// Return each stream circuit as a key of an empty-val map
func uniqueStreamCircuitIDs(ctx *TestContext) map[int]struct{} {
ret := map[int]struct{}{}
vals, err := ctx.Control.GetInfo("stream-status")
ctx.Require.NoError(err)
for _, val := range vals {
ctx.Require.Equal("stream-status", val.Key)
for _, line := range strings.Split(val.Val, "\n") {
pieces := strings.Split(strings.TrimSpace(line), " ")
if len(pieces) < 3 {
continue
}
i, err := strconv.Atoi(pieces[2])
ctx.Require.NoError(err)
ret[i] = struct{}{}
}
}
return ret
}

View File

@ -50,7 +50,7 @@ func startHTTPServer(
handlePattern string,
handler func(http.ResponseWriter, *http.Request),
) (*http.Client, *http.Server, *tor.OnionService) {
httpClient := httpClient(ctx)
httpClient := httpClient(ctx, nil)
// Wait at most a few minutes for the entire test
listenCtx, listenCancel := context.WithTimeout(context.Background(), 4*time.Minute)
defer listenCancel()

View File

@ -25,8 +25,13 @@ type DialConf struct {
// is empty and ProxyAddress is not empty, it defaults to "tcp".
ProxyNetwork string
// ProxyAuth is the auth for the proxy. An empty auth means no auth, a nil
// auth means it is looked up.
// ProxyAuth is the auth for the proxy. Since Tor's SOCKS5 proxy is
// unauthenticated, this is rarely needed. It can be used when
// IsolateSOCKSAuth is set to ensure separate circuits.
//
// This should not be confused with downstream SOCKS proxy authentication
// which is set via Tor values for Socks5ProxyUsername and
// Socks5ProxyPassword when Socks5Proxy is set.
ProxyAuth *proxy.Auth
// SkipEnableNetwork, if true, will skip the enable network step in Dialer.
@ -72,23 +77,8 @@ func (t *Tor) Dialer(ctx context.Context, conf *DialConf) (*Dialer, error) {
} else if proxyNetwork == "" {
proxyNetwork = "tcp"
}
// Lookup proxy auth as needed
proxyAuth := conf.ProxyAuth
if proxyAuth == nil {
info, err := t.Control.GetConf("Socks5ProxyUsername", "Socks5ProxyPassword")
if err != nil {
return nil, err
}
if len(info) != 2 || info[0].Key != "Socks5ProxyUsername" || info[1].Key != "Socks5ProxyPassword" {
return nil, fmt.Errorf("Unable to get proxy auth")
}
proxyAuth = &proxy.Auth{User: info[0].Val, Password: info[1].Val}
}
if proxyAuth.User == "" && proxyAuth.Password == "" {
proxyAuth = nil
}
dialer, err := proxy.SOCKS5(proxyNetwork, proxyAddress, proxyAuth, conf.Forward)
dialer, err := proxy.SOCKS5(proxyNetwork, proxyAddress, conf.ProxyAuth, conf.Forward)
if err != nil {
return nil, err
}