Browse Source

refactor so that the server can't maliciously connect to any arbitrary

port on the client
master
forest 1 year ago
parent
commit
9090f4771a
11 changed files with 80 additions and 122 deletions
  1. +12
    -8
      README.md
  2. +15
    -6
      main.go
  3. +14
    -23
      tunnel-lib/client.go
  4. +2
    -13
      tunnel-lib/proto/control_msg.go
  5. +1
    -1
      tunnel-lib/proto/proto.go
  6. +4
    -19
      tunnel-lib/proxy.go
  7. +9
    -10
      tunnel-lib/server.go
  8. +12
    -34
      tunnel-lib/tcpproxy.go
  9. +5
    -5
      tunnel-lib/virtualaddr.go
  10. +5
    -2
      usage-example/client-config.json
  11. +1
    -1
      usage-example/tunnels.json

+ 12
- 8
README.md View File

@ -31,8 +31,7 @@ Starting the tunnel server with tunnel mux port: 9056, management port: 9057
Starting the "listener" test app. It listens on port 9001. This would be your web application server.
Listener: I am listening on port 9001
2020/06/20 18:24:27 using config:
2020/08/06 14:00:03 threshold server is starting up using config:
{
"DebugLog": false,
"TunnelControlPort": 9056,
@ -42,10 +41,11 @@ Listener: I am listening on port 9001
"ServerTlsKeyFile": "localhost.key",
"ServerTlsCertificateFile": "localhost+chain.crt"
}
2020/06/20 18:24:27 runServer(): the server should be running now
Listener: I am listening on port 9001
2020/08/06 14:00:03 runServer(): the server should be running now
Starting the tunnel client. Client Identifier: TestClient1
2020/06/20 18:24:28 using config:
2020/08/06 14:00:04 theshold client is starting up using config:
{
"DebugLog": false,
"ClientIdentifier": "TestClient1",
@ -53,6 +53,9 @@ Starting the tunnel client. Client Identifier: TestClient1
"ServerTunnelControlPort": 9056,
"ServerManagementPort": 9057,
"UseTls": true,
"ServiceToLocalAddrMap": {
"fooService": "127.0.0.1:9001"
},
"CaCertificateFile": "InternalCA+chain.crt",
"ClientTlsKeyFile": "TestClient1@example.com.key",
"ClientTlsCertificateFile": "TestClient1@example.com+chain.crt"
@ -66,14 +69,14 @@ Sending the tunnel configuration to the server.
HTTP PUT localhost:9057/tunnels:
now listening on 127.0.0.1:9000
[{"HaProxyProxyProtocol":true,"ListenAddress":"127.0.0.1","ListenHostnameGlob":"*","ListenPort":9000,"BackEndPort":9001,"ClientIdentifier":"TestClient1"}]
[{"HaProxyProxyProtocol":true,"ListenAddress":"127.0.0.1","ListenHostnameGlob":"*","ListenPort":9000,"BackEndService":"fooService","ClientIdentifier":"TestClient1"}]
Starting the "sender" test app.
It connects to the front end port of the tunnel (port 9000). This would be your end user who wants to use the web application.
Sender: I am dialing localhost:9000 from 127.0.0.1:35488
Sender: I am dialing localhost:9000 from 127.0.0.1:59382
Sender: sent 16 bytes
Listener: Someone connected from: 127.0.0.1:35488
Listener: Someone connected from: 127.0.0.1:59382
Listener: read 16 bytes
Listener: the sender sent: Hello ! Hello!
@ -104,7 +107,8 @@ I have a few requirements for this system.
* PUT /tunnnels
* Simplicity and Laser-like focus on "opaque" usage of TCP/TLS. Removed HTTP/WebSocket/old Virtual Hosts code.
* Added support for HAProxy "PROXY" protocol.
* Added support for Port mappings between front end and back end.
* Added support for Port mappings between front end and back end.
* Introduced concept of a "service" string instead of port number, so the client decides what ports to connect to & how, not the server.
* Added support TLS SNI based virtual hosts. (Hostname based routing)
* Fixed various bugs related to connection lifecycle.


+ 15
- 6
main.go View File

@ -4,6 +4,7 @@ import (
"crypto/tls"
"crypto/x509"
"encoding/json"
"errors"
"flag"
"fmt"
"io"
@ -36,6 +37,7 @@ type ClientConfig struct {
ServerTunnelControlPort int
ServerManagementPort int
UseTls bool
ServiceToLocalAddrMap map[string]string
CaCertificateFile string
ClientTlsKeyFile string
ClientTlsCertificateFile string
@ -46,7 +48,7 @@ type ListenerConfig struct {
ListenAddress string
ListenHostnameGlob string
ListenPort int
BackEndPort int
BackEndService string
ClientIdentifier string
}
@ -94,7 +96,7 @@ func runClient(configFileName *string) {
}
configToLog, _ := json.MarshalIndent(config, "", " ")
log.Printf("using config:\n%s\n", string(configToLog))
log.Printf("theshold client is starting up using config:\n%s\n", string(configToLog))
dialFunction := net.Dial
@ -126,7 +128,14 @@ func runClient(configFileName *string) {
DebugLog: config.DebugLog,
Identifier: config.ClientIdentifier,
ServerAddr: fmt.Sprintf("%s:%d", config.ServerHost, config.ServerTunnelControlPort),
Dial: dialFunction,
FetchLocalAddr: func(service string) (string, error) {
localAddr, hasLocalAddr := config.ServiceToLocalAddrMap[service]
if !hasLocalAddr {
return "", errors.New("service not configured. See ServiceToLocalAddrMap in client config file.")
}
return localAddr, nil
},
Dial: dialFunction,
}
client, err = tunnel.NewClient(tunnelClientConfig)
@ -151,7 +160,7 @@ func runServer(configFileName *string) {
}
configToLog, _ := json.MarshalIndent(config, "", " ")
log.Printf("using config:\n%s\n", string(configToLog))
log.Printf("threshold server is starting up using config:\n%s\n", string(configToLog))
clientStateChangeChannel := make(chan *tunnel.ClientStateChange)
@ -289,7 +298,7 @@ func setListeners(listenerConfigs []ListenerConfig) (int, string) {
newListenerConfig.ListenHostnameGlob,
newListenerConfig.ClientIdentifier,
newListenerConfig.HaProxyProxyProtocol,
newListenerConfig.BackEndPort,
newListenerConfig.BackEndService,
)
if err != nil {
@ -315,7 +324,7 @@ func compareListenerConfigs(a, b ListenerConfig) bool {
return (a.ListenPort == b.ListenPort &&
a.ListenAddress == b.ListenAddress &&
a.ListenHostnameGlob == b.ListenHostnameGlob &&
a.BackEndPort == b.BackEndPort &&
a.BackEndService == b.BackEndService &&
a.ClientIdentifier == b.ClientIdentifier &&
a.HaProxyProxyProtocol == b.HaProxyProxyProtocol)
}


+ 14
- 23
tunnel-lib/client.go View File

@ -117,6 +117,15 @@ type ClientConfig struct {
// Required if ServerAddress is not set.
FetchServerAddr func() (string, error)
// REQUIRED either FetchLocalAddr or Proxy MUST be provided.
// a function that returns local address (ip and port) depending on service name
FetchLocalAddr func(service string) (string, error)
// Proxy defines custom proxing logic. This is optional extension point
// where you can provide your local server selection or communication rules.
Proxy ProxyFunc
// Dial provides custom transport layer for client server communication.
//
// If nil, default implementation is to return net.Dial("tcp", address).
@ -125,10 +134,6 @@ type ClientConfig struct {
// securing the connection.
Dial func(network, address string) (net.Conn, error)
// Proxy defines custom proxing logic. This is optional extension point
// where you can provide your local server selection or communication rules.
Proxy ProxyFunc
// StateChanges receives state transition details each time client
// connection state changes. The channel is expected to be sufficiently
// buffered to keep up with event pace.
@ -153,14 +158,6 @@ type ClientConfig struct {
// Debug enables debug mode, enable only if you want to debug the server.
DebugLog bool
// DEPRECATED:
// LocalAddr is DEPRECATED please use ProxyHTTP.LocalAddr, see ProxyOverwrite for more details.
LocalAddr string
// FetchLocalAddr is DEPRECATED please use ProxyTCP.FetchLocalAddr, see ProxyOverwrite for more details.
FetchLocalAddr func(port int) (string, error)
}
// verify is used to verify the ClientConfig
@ -179,8 +176,8 @@ func (c *ClientConfig) verify() error {
}
}
if c.Proxy != nil && (c.LocalAddr != "" || c.FetchLocalAddr != nil) {
return errors.New("both Proxy and LocalAddr or FetchLocalAddr are set")
if c.FetchLocalAddr == nil && c.Proxy == nil {
return errors.New("one of either Proxy or FetchLocalAddr is required")
}
return nil
@ -200,16 +197,10 @@ func NewClient(cfg *ClientConfig) (*Client, error) {
yamuxConfig = cfg.YamuxConfig
}
var proxy = DefaultProxy
if cfg.Proxy != nil {
proxy = cfg.Proxy
}
// DEPRECATED API SUPPORT
if cfg.LocalAddr != "" || cfg.FetchLocalAddr != nil {
proxy := cfg.Proxy
if proxy == nil {
var f ProxyFuncs
if cfg.FetchLocalAddr != nil {
f.TCP = (&TCPProxy{FetchLocalAddr: cfg.FetchLocalAddr, DebugLog: cfg.DebugLog}).Proxy
}
f.TCP = (&TCPProxy{FetchLocalAddr: cfg.FetchLocalAddr, DebugLog: cfg.DebugLog}).Proxy
proxy = Proxy(f)
}


+ 2
- 13
tunnel-lib/proto/control_msg.go View File

@ -2,9 +2,8 @@ package proto
// ControlMessage is sent from server to client to establish tunneled connection.
type ControlMessage struct {
Action Action `json:"action"`
Protocol Type `json:"transportProtocol"`
LocalPort int `json:"localPort"`
Action Action `json:"action"`
Service string `json:"service"`
}
// Action represents type of ControlMsg request.
@ -14,13 +13,3 @@ type Action int
const (
RequestClientSession Action = iota + 1
)
// Type represents tunneled connection type.
type Type int
// ControlMessage protocols.
const (
HTTP Type = iota + 1
TCP
WS
)

+ 1
- 1
tunnel-lib/proto/proto.go View File

@ -6,7 +6,7 @@ const (
ControlPath = "/_controlPath/"
// ClientIdentifierHeader is header carrying information about tunnel identifier.
ClientIdentifierHeader = "X-KTunnel-Identifier"
ClientIdentifierHeader = "X-Threshold-Identifier"
// control messages


+ 4
- 19
tunnel-lib/proxy.go View File

@ -13,15 +13,6 @@ import (
// ProxyFunc is responsible for forwarding a remote connection to local server and writing the response back.
type ProxyFunc func(remote net.Conn, msg *proto.ControlMessage)
var (
// DefaultProxyFuncs holds global default proxy functions for all transport protocols.
DefaultProxyFuncs = ProxyFuncs{
TCP: new(TCPProxy).Proxy,
}
// DefaultProxy is a ProxyFunc that uses DefaultProxyFuncs.
DefaultProxy = Proxy(ProxyFuncs{})
)
// ProxyFuncs is a collection of ProxyFunc.
type ProxyFuncs struct {
// TCP is custom implementation of TCP proxing.
@ -31,18 +22,12 @@ type ProxyFuncs struct {
// Proxy returns a ProxyFunc that uses custom function if provided, otherwise falls back to DefaultProxyFuncs.
func Proxy(p ProxyFuncs) ProxyFunc {
return func(remote net.Conn, msg *proto.ControlMessage) {
var f ProxyFunc
f = DefaultProxyFuncs.TCP
if p.TCP != nil {
f = p.TCP
}
if f == nil {
log.Printf("Proxy(): Could not determine proxy function for %v\n", msg)
remote.Close()
if p.TCP == nil {
panic("TCP handler is required for Proxy")
}
f(remote, msg)
// I removed all the other handlers that are not TCP 😇
p.TCP(remote, msg)
}
}


+ 9
- 10
tunnel-lib/server.go View File

@ -172,11 +172,11 @@ func (s *Server) handleTCPConn(conn net.Conn) error {
return err
}
backendPortToDial := port
if listenerInfo.BackendPort != -1 && listenerInfo.BackendPort != 0 {
backendPortToDial = listenerInfo.BackendPort
service := strconv.Itoa(port)
if listenerInfo.BackendService != "" {
service = listenerInfo.BackendService
}
stream, err := s.dial(listenerInfo.AssociatedClientIdentity, proto.TCP, backendPortToDial)
stream, err := s.dial(listenerInfo.AssociatedClientIdentity, service)
if err != nil {
return err
}
@ -225,7 +225,7 @@ func (s *Server) proxy(disconnectedChan chan bool, dst, src net.Conn, side strin
}
}
func (s *Server) dial(identifier string, p proto.Type, port int) (net.Conn, error) {
func (s *Server) dial(identifier string, service string) (net.Conn, error) {
control, ok := s.getControl(identifier)
if !ok {
return nil, errNoClientSession
@ -237,9 +237,8 @@ func (s *Server) dial(identifier string, p proto.Type, port int) (net.Conn, erro
}
msg := proto.ControlMessage{
Action: proto.RequestClientSession,
Protocol: p,
LocalPort: port,
Action: proto.RequestClientSession,
Service: service,
}
if s.debugLog {
@ -508,9 +507,9 @@ func (s *Server) AddAddr(
hostnameGlob string,
identifier string,
sendProxyProtocolv1 bool,
backendPort int,
service string,
) error {
return s.virtualAddrs.Add(ip, port, hostnameGlob, identifier, sendProxyProtocolv1, backendPort)
return s.virtualAddrs.Add(ip, port, hostnameGlob, identifier, sendProxyProtocolv1, service)
}
// DeleteAddr stops listening for connections on the given listener.


+ 12
- 34
tunnel-lib/tcpproxy.go View File

@ -1,7 +1,6 @@
package tunnel
import (
"fmt"
"log"
"net"
@ -10,22 +9,15 @@ import (
// TCPProxy forwards TCP streams.
//
// If port-based routing is used, LocalAddr or FetchLocalAddr field is required
// for tunneling to function properly.
// Otherwise you'll be forwarding traffic to random ports and this is usually not desired.
//
// If IP-based routing is used then tunnel server connection request is
// proxied to 127.0.0.1:incomingPort where incomingPort is control message LocalPort.
// Usually this is tunnel server's public exposed Port.
// This behaviour can be changed by setting LocalAddr or FetchLocalAddr.
// FetchLocalAddr takes precedence over LocalAddr.
// the incoming ControlMessage will specify a service (string) and the TCPProxy will call FetchLocalAddr
// to determine which address to proxy to for that service name (for example, 127.0.0.1:8080 for fooService)
// or, it will fail/cancel if FetchLocalAddr returns an error.
type TCPProxy struct {
// LocalAddr defines the TCP address of the local server.
// This is optional if you want to specify a single TCP address.
LocalAddr string
// FetchLocalAddr is used for looking up TCP address of the server.
// This is optional if you want to specify a dynamic TCP address based on incommig port.
FetchLocalAddr func(port int) (string, error)
// FetchLocalAddr is used for looking up TCP address of the services.
FetchLocalAddr func(service string) (string, error)
// Log is a custom logger that can be used for the proxy.
// If not set a "tcp" logger is used.
DebugLog bool
@ -33,25 +25,11 @@ type TCPProxy struct {
// Proxy is a ProxyFunc.
func (p *TCPProxy) Proxy(remote net.Conn, msg *proto.ControlMessage) {
if msg.Protocol != proto.TCP {
panic("Proxy mismatch")
}
var port = msg.LocalPort
if port == 0 {
log.Println("TCPProxy.Proxy(): TCP proxy to port 0")
}
var localAddr = fmt.Sprintf("127.0.0.1:%d", port)
if p.LocalAddr != "" {
localAddr = p.LocalAddr
} else if p.FetchLocalAddr != nil {
l, err := p.FetchLocalAddr(msg.LocalPort)
if err != nil {
log.Println("TCPProxy.Proxy(): Failed to get custom local address: %s", err)
return
}
localAddr = l
localAddr, err := p.FetchLocalAddr(msg.Service)
if err != nil {
log.Printf("TCPProxy.Proxy(): FetchLocalAddr('%s') returned %s.\n", msg.Service, err)
return
}
//log.Debug("Dialing local server: %q", localAddr)


+ 5
- 5
tunnel-lib/virtualaddr.go View File

@ -16,7 +16,7 @@ type ListenerInfo struct {
//Send the HAProxy PROXY protocol v1 header to the proxy client before streaming TCP from the remote client.
SendProxyProtocolv1 bool
BackendPort int
BackendService string
AssociatedClientIdentity string
HostnameGlob string
}
@ -110,7 +110,7 @@ func (l *listener) stop() {
// }
}
func (vaddr *vaddrStorage) Add(ip net.IP, port int, hostnameGlob string, ident string, sendProxyProtocolv1 bool, backendPort int) error {
func (vaddr *vaddrStorage) Add(ip net.IP, port int, hostnameGlob string, ident string, sendProxyProtocolv1 bool, backendService string) error {
vaddr.mu.Lock()
defer vaddr.mu.Unlock()
@ -127,7 +127,7 @@ func (vaddr *vaddrStorage) Add(ip net.IP, port int, hostnameGlob string, ident s
go listener.serve()
}
listener.addHost(hostnameGlob, ident, sendProxyProtocolv1, backendPort)
listener.addHost(hostnameGlob, ident, sendProxyProtocolv1, backendService)
// vaddr.ports[mustPort(l)] = lis
// if ip != nil {
@ -140,12 +140,12 @@ func (vaddr *vaddrStorage) Add(ip net.IP, port int, hostnameGlob string, ident s
return nil
}
func (l *listener) addHost(hostnameGlob string, ident string, sendProxyProtocolv1 bool, backendPort int) {
func (l *listener) addHost(hostnameGlob string, ident string, sendProxyProtocolv1 bool, service string) {
l.backends = append(l.backends, ListenerInfo{
HostnameGlob: hostnameGlob,
AssociatedClientIdentity: ident,
SendProxyProtocolv1: sendProxyProtocolv1,
BackendPort: backendPort,
BackendService: service,
})
}


+ 5
- 2
usage-example/client-config.json View File

@ -1,10 +1,13 @@
{
"DebugLog": false,
"DebugLog": false,
"ClientIdentifier": "TestClient1",
"ServerHost": "localhost",
"ServerTunnelControlPort": 9056,
"ServerManagementPort": 9057,
"UseTls": true,
"UseTls": true,
"ServiceToLocalAddrMap": {
"fooService": "127.0.0.1:9001"
},
"CaCertificateFile": "InternalCA+chain.crt",
"ClientTlsKeyFile": "TestClient1@example.com.key",
"ClientTlsCertificateFile": "TestClient1@example.com+chain.crt"


+ 1
- 1
usage-example/tunnels.json View File

@ -4,7 +4,7 @@
"ListenPort": 9000,
"ListenAddress": "127.0.0.1",
"ListenHostnameGlob": "*",
"BackEndPort": 9001,
"BackEndService": "fooService",
"HaProxyProxyProtocol": true
}
]

Loading…
Cancel
Save