182 lines
5.2 KiB
Go
182 lines
5.2 KiB
Go
package ingress
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"sync"
|
|
|
|
"github.com/cloudflare/cloudflared/hello"
|
|
"github.com/cloudflare/cloudflared/logger"
|
|
"github.com/cloudflare/cloudflared/socks"
|
|
"github.com/cloudflare/cloudflared/websocket"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// OriginService is something a tunnel can proxy traffic to.
|
|
type OriginService interface {
|
|
Address() string
|
|
// Start the origin service if it's managed by cloudflared, e.g. proxy servers or Hello World.
|
|
// If it's not managed by cloudflared, this is a no-op because the user is responsible for
|
|
// starting the origin service.
|
|
Start(wg *sync.WaitGroup, log logger.Service, shutdownC <-chan struct{}, errC chan error, cfg OriginRequestConfig) error
|
|
String() string
|
|
// RewriteOriginURL modifies the HTTP request from cloudflared to the origin, so that it apply
|
|
// this particular type of origin service's specific routing logic.
|
|
RewriteOriginURL(*url.URL)
|
|
}
|
|
|
|
// UnixSocketPath is an OriginService representing a unix socket (which accepts HTTP)
|
|
type UnixSocketPath string
|
|
|
|
func (o UnixSocketPath) Address() string {
|
|
return string(o)
|
|
}
|
|
|
|
func (o UnixSocketPath) String() string {
|
|
return "unix socket: " + string(o)
|
|
}
|
|
|
|
func (o UnixSocketPath) Start(wg *sync.WaitGroup, log logger.Service, shutdownC <-chan struct{}, errC chan error, cfg OriginRequestConfig) error {
|
|
return nil
|
|
}
|
|
|
|
func (o UnixSocketPath) RewriteOriginURL(u *url.URL) {
|
|
// No changes necessary because the origin request URL isn't used.
|
|
// Instead, HTTPTransport's dial is already configured to address the unix socket.
|
|
}
|
|
|
|
// URL is an OriginService listening on a TCP address
|
|
type URL struct {
|
|
// The URL for the user's origin service
|
|
RootURL *url.URL
|
|
// The URL that cloudflared should send requests to.
|
|
// If this origin requires starting a proxy, this is the proxy's address,
|
|
// and that proxy points to RootURL. Otherwise, this is equal to RootURL.
|
|
URL *url.URL
|
|
}
|
|
|
|
func (o *URL) Address() string {
|
|
return o.URL.String()
|
|
}
|
|
|
|
func (o *URL) Start(wg *sync.WaitGroup, log logger.Service, shutdownC <-chan struct{}, errC chan error, cfg OriginRequestConfig) error {
|
|
staticHost := o.staticHost()
|
|
if !originRequiresProxy(staticHost, cfg) {
|
|
return nil
|
|
}
|
|
|
|
// Start a listener for the proxy
|
|
proxyAddress := net.JoinHostPort(cfg.ProxyAddress, strconv.Itoa(int(cfg.ProxyPort)))
|
|
listener, err := net.Listen("tcp", proxyAddress)
|
|
if err != nil {
|
|
log.Errorf("Cannot start Websocket Proxy Server: %s", err)
|
|
return errors.Wrap(err, "Cannot start Websocket Proxy Server")
|
|
}
|
|
|
|
// Start the proxy itself
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
streamHandler := websocket.DefaultStreamHandler
|
|
// This origin's config specifies what type of proxy to start.
|
|
switch cfg.ProxyType {
|
|
case socksProxy:
|
|
log.Info("SOCKS5 server started")
|
|
streamHandler = func(wsConn *websocket.Conn, remoteConn net.Conn, _ http.Header) {
|
|
dialer := socks.NewConnDialer(remoteConn)
|
|
requestHandler := socks.NewRequestHandler(dialer)
|
|
socksServer := socks.NewConnectionHandler(requestHandler)
|
|
|
|
socksServer.Serve(wsConn)
|
|
}
|
|
case "":
|
|
log.Debug("Not starting any websocket proxy")
|
|
default:
|
|
log.Errorf("%s isn't a valid proxy (valid options are {%s})", cfg.ProxyType, socksProxy)
|
|
}
|
|
|
|
errC <- websocket.StartProxyServer(log, listener, staticHost, shutdownC, streamHandler)
|
|
}()
|
|
|
|
// Modify this origin, so that it no longer points at the origin service directly.
|
|
// Instead, it points at the proxy to the origin service.
|
|
newURL, err := url.Parse("http://" + listener.Addr().String())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
o.URL = newURL
|
|
return nil
|
|
}
|
|
|
|
func (o *URL) String() string {
|
|
return o.Address()
|
|
}
|
|
|
|
func (o *URL) RewriteOriginURL(u *url.URL) {
|
|
u.Host = o.URL.Host
|
|
u.Scheme = o.URL.Scheme
|
|
}
|
|
|
|
func (o *URL) staticHost() string {
|
|
|
|
addPortIfMissing := func(uri *url.URL, port int) string {
|
|
if uri.Port() != "" {
|
|
return uri.Host
|
|
}
|
|
return fmt.Sprintf("%s:%d", uri.Hostname(), port)
|
|
}
|
|
|
|
switch o.URL.Scheme {
|
|
case "ssh":
|
|
return addPortIfMissing(o.URL, 22)
|
|
case "rdp":
|
|
return addPortIfMissing(o.URL, 3389)
|
|
case "smb":
|
|
return addPortIfMissing(o.URL, 445)
|
|
case "tcp":
|
|
return addPortIfMissing(o.URL, 7864) // just a random port since there isn't a default in this case
|
|
}
|
|
return ""
|
|
|
|
}
|
|
|
|
// HelloWorld is the built-in Hello World service. Used for testing and experimenting with cloudflared.
|
|
type HelloWorld struct {
|
|
server net.Listener
|
|
}
|
|
|
|
func (o *HelloWorld) Address() string {
|
|
return o.server.Addr().String()
|
|
}
|
|
|
|
func (o *HelloWorld) String() string {
|
|
return "Hello World static HTML service"
|
|
}
|
|
|
|
// Start starts a HelloWorld server and stores its address in the Service receiver.
|
|
func (o *HelloWorld) Start(wg *sync.WaitGroup, log logger.Service, shutdownC <-chan struct{}, errC chan error, cfg OriginRequestConfig) error {
|
|
helloListener, err := hello.CreateTLSListener("127.0.0.1:")
|
|
if err != nil {
|
|
return errors.Wrap(err, "Cannot start Hello World Server")
|
|
}
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
_ = hello.StartHelloWorldServer(log, helloListener, shutdownC)
|
|
}()
|
|
o.server = helloListener
|
|
return nil
|
|
}
|
|
|
|
func (o *HelloWorld) RewriteOriginURL(u *url.URL) {
|
|
u.Host = o.Address()
|
|
u.Scheme = "https"
|
|
}
|
|
|
|
func originRequiresProxy(staticHost string, cfg OriginRequestConfig) bool {
|
|
return staticHost != "" || cfg.BastionMode
|
|
}
|