TUN-4017: Add support for using cloudflared as a full socks proxy.

To use cloudflared as a socks proxy, add an ingress on the server
side with your desired rules. Rules are matched in the order they
are added.  If there are no rules, it is an implicit allow.  If
there are rules, but no rule matches match, the connection is denied.

ingress:
  - hostname: socks.example.com
    service: socks-proxy
    originRequest:
      ipRules:
        - prefix: 1.1.1.1/24
          ports: [80, 443]
          allow: true
        - prefix: 0.0.0.0/0
          allow: false

On the client, run using tcp mode:
cloudflared access tcp --hostname socks.example.com --url 127.0.0.1:8080

Set your socks proxy as 127.0.0.1:8080 and you will now be proxying
all connections to the remote machine.
This commit is contained in:
Lee Valentine
2021-03-01 16:26:37 -06:00
parent b0e69c4b8a
commit 206523344f
12 changed files with 419 additions and 7 deletions

View File

@@ -40,7 +40,7 @@ func sendSocksRequest(t *testing.T) []byte {
func startTestServer(t *testing.T, httpHandler func(w http.ResponseWriter, r *http.Request)) {
// create a socks server
requestHandler := NewRequestHandler(NewNetDialer())
requestHandler := NewRequestHandler(NewNetDialer(), nil)
socksServer := NewConnectionHandler(requestHandler)
listener, err := net.Listen("tcp", "localhost:8086")
assert.NoError(t, err)

View File

@@ -6,6 +6,7 @@ import (
"net"
"strings"
"github.com/cloudflare/cloudflared/ipaccess"
"github.com/rs/zerolog"
)
@@ -16,14 +17,16 @@ type RequestHandler interface {
// StandardRequestHandler implements the base socks5 command processing
type StandardRequestHandler struct {
dialer Dialer
dialer Dialer
accessPolicy *ipaccess.Policy
}
// NewRequestHandler creates a standard SOCKS5 request handler
// This handles the SOCKS5 commands and proxies them to their destination
func NewRequestHandler(dialer Dialer) RequestHandler {
func NewRequestHandler(dialer Dialer, accessPolicy *ipaccess.Policy) RequestHandler {
return &StandardRequestHandler{
dialer: dialer,
dialer: dialer,
accessPolicy: accessPolicy,
}
}
@@ -46,6 +49,25 @@ func (h *StandardRequestHandler) Handle(req *Request, conn io.ReadWriter) error
// handleConnect is used to handle a connect command
func (h *StandardRequestHandler) handleConnect(conn io.ReadWriter, req *Request) error {
if h.accessPolicy != nil {
if req.DestAddr.IP == nil {
addr, err := net.ResolveIPAddr("ip", req.DestAddr.FQDN)
if err != nil {
_ = sendReply(conn, ruleFailure, req.DestAddr)
return fmt.Errorf("unable to resolve host to confirm acceess")
}
req.DestAddr.IP = addr.IP
}
if allowed, rule := h.accessPolicy.Allowed(req.DestAddr.IP, req.DestAddr.Port); !allowed {
_ = sendReply(conn, ruleFailure, req.DestAddr)
if rule != nil {
return fmt.Errorf("Connect to %v denied due to iprule: %s", req.DestAddr, rule.String())
}
return fmt.Errorf("Connect to %v denied", req.DestAddr)
}
}
target, localAddr, err := h.dialer.Dial(req.DestAddr.Address())
if err != nil {
msg := err.Error()
@@ -110,7 +132,17 @@ func (h *StandardRequestHandler) handleAssociate(conn io.ReadWriter, req *Reques
func StreamHandler(tunnelConn io.ReadWriter, originConn net.Conn, log *zerolog.Logger) {
dialer := NewConnDialer(originConn)
requestHandler := NewRequestHandler(dialer)
requestHandler := NewRequestHandler(dialer, nil)
socksServer := NewConnectionHandler(requestHandler)
if err := socksServer.Serve(tunnelConn); err != nil {
log.Debug().Err(err).Msg("Socks stream handler error")
}
}
func StreamNetHandler(tunnelConn io.ReadWriter, accessPolicy *ipaccess.Policy, log *zerolog.Logger) {
dialer := NewNetDialer()
requestHandler := NewRequestHandler(dialer, accessPolicy)
socksServer := NewConnectionHandler(requestHandler)
if err := socksServer.Serve(tunnelConn); err != nil {

View File

@@ -4,6 +4,7 @@ import (
"bytes"
"testing"
"github.com/cloudflare/cloudflared/ipaccess"
"github.com/stretchr/testify/assert"
)
@@ -11,7 +12,7 @@ func TestUnsupportedBind(t *testing.T) {
req := createRequest(t, socks5Version, bindCommand, "2001:db8::68", 1337, false)
var b bytes.Buffer
requestHandler := NewRequestHandler(NewNetDialer())
requestHandler := NewRequestHandler(NewNetDialer(), nil)
err := requestHandler.Handle(req, &b)
assert.NoError(t, err)
assert.True(t, b.Bytes()[1] == commandNotSupported, "expected a response")
@@ -21,8 +22,61 @@ func TestUnsupportedAssociate(t *testing.T) {
req := createRequest(t, socks5Version, associateCommand, "127.0.0.1", 1337, false)
var b bytes.Buffer
requestHandler := NewRequestHandler(NewNetDialer())
requestHandler := NewRequestHandler(NewNetDialer(), nil)
err := requestHandler.Handle(req, &b)
assert.NoError(t, err)
assert.True(t, b.Bytes()[1] == commandNotSupported, "expected a response")
}
func TestHandleConnect(t *testing.T) {
req := createRequest(t, socks5Version, connectCommand, "127.0.0.1", 1337, false)
var b bytes.Buffer
requestHandler := NewRequestHandler(NewNetDialer(), nil)
err := requestHandler.Handle(req, &b)
assert.Error(t, err)
assert.True(t, b.Bytes()[1] == connectionRefused, "expected a response")
}
func TestHandleConnectIPAccess(t *testing.T) {
prefix := "127.0.0.0/24"
rule1, _ := ipaccess.NewRuleByCIDR(&prefix, []int{1337}, true)
rule2, _ := ipaccess.NewRuleByCIDR(&prefix, []int{1338}, false)
rules := []ipaccess.Rule{rule1, rule2}
var b bytes.Buffer
accessPolicy, _ := ipaccess.NewPolicy(false, nil)
requestHandler := NewRequestHandler(NewNetDialer(), accessPolicy)
req := createRequest(t, socks5Version, connectCommand, "127.0.0.1", 1337, false)
err := requestHandler.Handle(req, &b)
assert.Error(t, err)
assert.True(t, b.Bytes()[1] == ruleFailure, "expected to be denied as no rules and defaultAllow=false")
b.Reset()
accessPolicy, _ = ipaccess.NewPolicy(true, nil)
requestHandler = NewRequestHandler(NewNetDialer(), accessPolicy)
req = createRequest(t, socks5Version, connectCommand, "127.0.0.1", 1337, false)
err = requestHandler.Handle(req, &b)
assert.Error(t, err)
assert.True(t, b.Bytes()[1] == connectionRefused, "expected to be allowed as no rules and defaultAllow=true")
b.Reset()
accessPolicy, _ = ipaccess.NewPolicy(false, rules)
requestHandler = NewRequestHandler(NewNetDialer(), accessPolicy)
req = createRequest(t, socks5Version, connectCommand, "127.0.0.1", 1337, false)
err = requestHandler.Handle(req, &b)
assert.Error(t, err)
assert.True(t, b.Bytes()[1] == connectionRefused, "expected to be allowed as matching rule")
b.Reset()
req = createRequest(t, socks5Version, connectCommand, "127.0.0.1", 1338, false)
err = requestHandler.Handle(req, &b)
assert.Error(t, err)
assert.True(t, b.Bytes()[1] == ruleFailure, "expected to be denied as matching rule")
b.Reset()
req = createRequest(t, socks5Version, connectCommand, "127.0.0.1", 1339, false)
err = requestHandler.Handle(req, &b)
assert.Error(t, err)
assert.True(t, b.Bytes()[1] == ruleFailure, "expect to be denied as no matching rule and defaultAllow=false")
}