AUTH-2369: RDP Bastion prototype

This commit is contained in:
Michael Borkenstein
2020-05-04 15:15:17 -05:00
parent 6a7418e1af
commit b89cc22896
8 changed files with 78 additions and 33 deletions

View File

@@ -7,6 +7,7 @@ import (
"github.com/cloudflare/cloudflared/carrier"
"github.com/cloudflare/cloudflared/cmd/cloudflared/config"
"github.com/cloudflare/cloudflared/h2mux"
"github.com/cloudflare/cloudflared/validation"
"github.com/pkg/errors"
cli "gopkg.in/urfave/cli.v2"
@@ -54,15 +55,15 @@ func ssh(c *cli.Context) error {
// get the headers from the cmdline and add them
headers := buildRequestHeaders(c.StringSlice(sshHeaderFlag))
if c.IsSet(sshTokenIDFlag) {
headers.Add("CF-Access-Client-Id", c.String(sshTokenIDFlag))
headers.Add(h2mux.CFAccessClientIDHeader, c.String(sshTokenIDFlag))
}
if c.IsSet(sshTokenSecretFlag) {
headers.Add("CF-Access-Client-Secret", c.String(sshTokenSecretFlag))
headers.Add(h2mux.CFAccessClientSecretHeader, c.String(sshTokenSecretFlag))
}
destination := c.String(sshDestinationFlag)
if destination != "" {
headers.Add("CF-Access-SSH-Destination", destination)
headers.Add(h2mux.CFJumpDestinationHeader, destination)
}
options := &carrier.StartOptions{

View File

@@ -13,6 +13,7 @@ import (
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
"github.com/cloudflare/cloudflared/cmd/cloudflared/shell"
"github.com/cloudflare/cloudflared/cmd/cloudflared/token"
"github.com/cloudflare/cloudflared/h2mux"
"github.com/cloudflare/cloudflared/sshgen"
"github.com/cloudflare/cloudflared/validation"
"github.com/pkg/errors"
@@ -262,7 +263,7 @@ func curl(c *cli.Context) error {
}
cmdArgs = append(cmdArgs, "-H")
cmdArgs = append(cmdArgs, fmt.Sprintf("cf-access-token: %s", tok))
cmdArgs = append(cmdArgs, fmt.Sprintf("%s: %s", h2mux.CFAccessTokenHeader, tok))
return shell.Run("curl", cmdArgs...)
}
@@ -415,10 +416,10 @@ func isFileThere(candidate string) bool {
func verifyTokenAtEdge(appUrl *url.URL, c *cli.Context) error {
headers := buildRequestHeaders(c.StringSlice(sshHeaderFlag))
if c.IsSet(sshTokenIDFlag) {
headers.Add("CF-Access-Client-Id", c.String(sshTokenIDFlag))
headers.Add(h2mux.CFAccessClientIDHeader, c.String(sshTokenIDFlag))
}
if c.IsSet(sshTokenSecretFlag) {
headers.Add("CF-Access-Client-Secret", c.String(sshTokenSecretFlag))
headers.Add(h2mux.CFAccessClientSecretHeader, c.String(sshTokenSecretFlag))
}
options := &carrier.StartOptions{OriginURL: appUrl.String(), Headers: headers}

View File

@@ -6,6 +6,7 @@ import (
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"os"
"reflect"
@@ -22,6 +23,7 @@ import (
"github.com/cloudflare/cloudflared/cmd/cloudflared/updater"
"github.com/cloudflare/cloudflared/connection"
"github.com/cloudflare/cloudflared/dbconnect"
"github.com/cloudflare/cloudflared/h2mux"
"github.com/cloudflare/cloudflared/hello"
"github.com/cloudflare/cloudflared/metrics"
"github.com/cloudflare/cloudflared/origin"
@@ -81,9 +83,15 @@ const (
// hostKeyPath is the path of the dir to save SSH host keys too
hostKeyPath = "host-key-path"
//sshServerFlag enables cloudflared ssh proxy server
sshServerFlag = "ssh-server"
// socks5Flag is to enable the socks server to deframe
socks5Flag = "socks5"
// bastionFlag is to enable bastion, or jump host, operation
bastionFlag = "bastion"
noIntentMsg = "The --intent argument is required. Cloudflared looks up an Intent to determine what configuration to use (i.e. which tunnels to start). If you don't have any Intents yet, you can use a placeholder Intent Label for now. Then, when you make an Intent with that label, cloudflared will get notified and open the tunnels you specified in that Intent."
)
@@ -343,12 +351,11 @@ func StartServer(c *cli.Context, version string, shutdownC, graceShutdownC chan
c.Set("url", "https://"+helloListener.Addr().String())
}
if c.IsSet("ssh-server") {
if c.IsSet(sshServerFlag) {
if runtime.GOOS != "darwin" && runtime.GOOS != "linux" {
msg := fmt.Sprintf("--ssh-server is not supported on %s", runtime.GOOS)
logger.Error(msg)
return errors.New(msg)
}
logger.Infof("ssh-server set")
@@ -394,7 +401,7 @@ func StartServer(c *cli.Context, version string, shutdownC, graceShutdownC chan
c.Set("url", "ssh://"+localServerAddress)
}
if host := hostnameFromURI(c.String("url")); host != "" {
if staticHost := hostnameFromURI(c.String("url")); isProxyDestinationConfigured(staticHost, c) {
listener, err := net.Listen("tcp", "127.0.0.1:")
if err != nil {
logger.WithError(err).Error("Cannot start Websocket Proxy Server")
@@ -406,15 +413,26 @@ func StartServer(c *cli.Context, version string, shutdownC, graceShutdownC chan
streamHandler := websocket.DefaultStreamHandler
if c.IsSet(socks5Flag) {
logger.Info("SOCKS5 server started")
streamHandler = func(wsConn *websocket.Conn, remoteConn net.Conn) {
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)
}
} else if c.IsSet(sshServerFlag) {
streamHandler = func(wsConn *websocket.Conn, remoteConn net.Conn, requestHeaders http.Header) {
if finalDestination := requestHeaders.Get(h2mux.CFJumpDestinationHeader); finalDestination != "" {
token := requestHeaders.Get(h2mux.CFAccessTokenHeader)
if err := websocket.SendSSHPreamble(remoteConn, finalDestination, token); err != nil {
logger.WithError(err).Error("Failed to send SSH preamble")
return
}
}
websocket.DefaultStreamHandler(wsConn, remoteConn, requestHeaders)
}
}
errC <- websocket.StartProxyServer(logger, listener, host, shutdownC, streamHandler)
errC <- websocket.StartProxyServer(logger, listener, staticHost, shutdownC, streamHandler)
}()
c.Set("url", "http://"+listener.Addr().String())
}
@@ -457,6 +475,11 @@ func Before(c *cli.Context) error {
return nil
}
// isProxyDestinationConfigured returns true if there is a static host set or if bastion mode is set.
func isProxyDestinationConfigured(staticHost string, c *cli.Context) bool {
return staticHost != "" || c.IsSet(bastionFlag)
}
func startDeclarativeTunnel(ctx context.Context,
c *cli.Context,
cloudflaredID uuid.UUID,
@@ -882,7 +905,7 @@ func tunnelFlags(shouldHide bool) []cli.Flag {
Hidden: shouldHide,
}),
altsrc.NewBoolFlag(&cli.BoolFlag{
Name: "ssh-server",
Name: sshServerFlag,
Value: false,
Usage: "Run an SSH Server",
EnvVars: []string{"TUNNEL_SSH_SERVER"},
@@ -1118,7 +1141,14 @@ func tunnelFlags(shouldHide bool) []cli.Flag {
Usage: "specify if this tunnel is running as a SOCK5 Server",
EnvVars: []string{"TUNNEL_SOCKS"},
Value: false,
Hidden: false,
Hidden: shouldHide,
}),
altsrc.NewBoolFlag(&cli.BoolFlag{
Name: bastionFlag,
Value: false,
Usage: "Runs as jump host",
EnvVars: []string{"TUNNEL_BASTION"},
Hidden: shouldHide,
}),
}
}

View File

@@ -233,10 +233,11 @@ func prepareTunnelConfig(
if !c.IsSet("hello-world") && c.IsSet("origin-server-name") {
httpTransport.TLSClientConfig.ServerName = c.String("origin-server-name")
}
err = validation.ValidateHTTPService(originURL, hostname, httpTransport)
if err != nil {
logger.WithError(err).Error("unable to connect to the origin")
// If tunnel running in bastion mode, a connection to origin will not exist until initiated by the client.
if !c.IsSet(bastionFlag) {
if err = validation.ValidateHTTPService(originURL, hostname, httpTransport); err != nil {
logger.WithError(err).Error("unable to connect to the origin")
}
}
toEdgeTLSConfig, err := tlsconfig.CreateTunnelConfig(c)