cloudflared-mirror/ingress/origin_service.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
}