2018-10-19 20:44:35 +00:00
|
|
|
package access
|
2018-09-21 15:18:23 +00:00
|
|
|
|
|
|
|
import (
|
2021-02-03 19:00:55 +00:00
|
|
|
"crypto/tls"
|
|
|
|
"fmt"
|
2023-06-29 17:29:15 +00:00
|
|
|
"io"
|
2019-02-07 16:56:33 +00:00
|
|
|
"net/http"
|
|
|
|
"strings"
|
2018-09-21 15:18:23 +00:00
|
|
|
|
2021-03-26 04:04:56 +00:00
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/rs/zerolog"
|
|
|
|
"github.com/urfave/cli/v2"
|
|
|
|
|
2018-09-21 15:18:23 +00:00
|
|
|
"github.com/cloudflare/cloudflared/carrier"
|
2021-03-08 16:46:23 +00:00
|
|
|
"github.com/cloudflare/cloudflared/config"
|
2020-04-29 20:51:32 +00:00
|
|
|
"github.com/cloudflare/cloudflared/logger"
|
2023-06-29 17:29:15 +00:00
|
|
|
"github.com/cloudflare/cloudflared/stream"
|
2018-09-21 15:18:23 +00:00
|
|
|
"github.com/cloudflare/cloudflared/validation"
|
|
|
|
)
|
|
|
|
|
2020-12-28 18:10:01 +00:00
|
|
|
const (
|
2021-03-26 04:04:56 +00:00
|
|
|
LogFieldHost = "host"
|
|
|
|
cfAccessClientIDHeader = "Cf-Access-Client-Id"
|
|
|
|
cfAccessClientSecretHeader = "Cf-Access-Client-Secret"
|
2020-12-28 18:10:01 +00:00
|
|
|
)
|
|
|
|
|
2020-04-13 17:22:00 +00:00
|
|
|
// StartForwarder starts a client side websocket forward
|
2020-11-25 06:55:13 +00:00
|
|
|
func StartForwarder(forwarder config.Forwarder, shutdown <-chan struct{}, log *zerolog.Logger) error {
|
2020-10-08 10:12:26 +00:00
|
|
|
validURL, err := validation.ValidateUrl(forwarder.Listener)
|
2020-04-13 17:22:00 +00:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "error validating origin URL")
|
|
|
|
}
|
|
|
|
|
2020-06-08 22:01:48 +00:00
|
|
|
// get the headers from the config file and add to the request
|
|
|
|
headers := make(http.Header)
|
|
|
|
if forwarder.TokenClientID != "" {
|
2021-03-26 04:04:56 +00:00
|
|
|
headers.Set(cfAccessClientIDHeader, forwarder.TokenClientID)
|
2020-06-08 22:01:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if forwarder.TokenSecret != "" {
|
2021-03-26 04:04:56 +00:00
|
|
|
headers.Set(cfAccessClientSecretHeader, forwarder.TokenSecret)
|
2020-06-08 22:01:48 +00:00
|
|
|
}
|
2023-06-29 17:29:15 +00:00
|
|
|
headers.Set("User-Agent", userAgent)
|
2020-06-08 22:01:48 +00:00
|
|
|
|
2021-03-26 04:04:56 +00:00
|
|
|
carrier.SetBastionDest(headers, forwarder.Destination)
|
2020-06-12 16:57:21 +00:00
|
|
|
|
2020-04-13 17:22:00 +00:00
|
|
|
options := &carrier.StartOptions{
|
|
|
|
OriginURL: forwarder.URL,
|
2020-06-08 22:01:48 +00:00
|
|
|
Headers: headers, //TODO: TUN-2688 support custom headers from config file
|
2020-04-13 17:22:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// we could add a cmd line variable for this bool if we want the SOCK5 server to be on the client side
|
2021-02-10 16:19:55 +00:00
|
|
|
wsConn := carrier.NewWSConnection(log)
|
2020-04-13 17:22:00 +00:00
|
|
|
|
2020-12-28 18:10:01 +00:00
|
|
|
log.Info().Str(LogFieldHost, validURL.Host).Msg("Start Websocket listener")
|
2020-04-13 17:22:00 +00:00
|
|
|
return carrier.StartForwarder(wsConn, validURL.Host, shutdown, options)
|
|
|
|
}
|
|
|
|
|
2018-09-21 15:18:23 +00:00
|
|
|
// ssh will start a WS proxy server for server mode
|
|
|
|
// or copy from stdin/stdout for client mode
|
|
|
|
// useful for proxying other protocols (like ssh) over websockets
|
|
|
|
// (which you can put Access in front of)
|
|
|
|
func ssh(c *cli.Context) error {
|
2023-06-29 17:29:15 +00:00
|
|
|
// If not running as a forwarder, disable terminal logs as it collides with the stdin/stdout of the parent process
|
|
|
|
outputTerminal := logger.DisableTerminalLog
|
|
|
|
if c.IsSet(sshURLFlag) {
|
|
|
|
outputTerminal = logger.EnableTerminalLog
|
|
|
|
}
|
|
|
|
log := logger.CreateSSHLoggerFromContext(c, outputTerminal)
|
2020-04-29 20:51:32 +00:00
|
|
|
|
2019-01-23 21:42:10 +00:00
|
|
|
// get the hostname from the cmdline and error out if its not provided
|
|
|
|
rawHostName := c.String(sshHostnameFlag)
|
|
|
|
hostname, err := validation.ValidateHostname(rawHostName)
|
|
|
|
if err != nil || rawHostName == "" {
|
2018-10-19 20:44:35 +00:00
|
|
|
return cli.ShowCommandHelp(c, "ssh")
|
2018-09-21 15:18:23 +00:00
|
|
|
}
|
2019-10-22 15:41:44 +00:00
|
|
|
originURL := ensureURLScheme(hostname)
|
2019-01-23 21:42:10 +00:00
|
|
|
|
|
|
|
// get the headers from the cmdline and add them
|
|
|
|
headers := buildRequestHeaders(c.StringSlice(sshHeaderFlag))
|
|
|
|
if c.IsSet(sshTokenIDFlag) {
|
2021-03-26 04:04:56 +00:00
|
|
|
headers.Set(cfAccessClientIDHeader, c.String(sshTokenIDFlag))
|
2019-01-23 21:42:10 +00:00
|
|
|
}
|
|
|
|
if c.IsSet(sshTokenSecretFlag) {
|
2021-03-26 04:04:56 +00:00
|
|
|
headers.Set(cfAccessClientSecretHeader, c.String(sshTokenSecretFlag))
|
2019-03-06 19:09:13 +00:00
|
|
|
}
|
2023-06-29 17:29:15 +00:00
|
|
|
headers.Set("User-Agent", userAgent)
|
2019-01-23 21:42:10 +00:00
|
|
|
|
2021-03-26 04:04:56 +00:00
|
|
|
carrier.SetBastionDest(headers, c.String(sshDestinationFlag))
|
2019-10-02 20:56:28 +00:00
|
|
|
|
2019-01-23 21:42:10 +00:00
|
|
|
options := &carrier.StartOptions{
|
2019-05-22 20:41:21 +00:00
|
|
|
OriginURL: originURL,
|
|
|
|
Headers: headers,
|
2021-02-03 19:00:55 +00:00
|
|
|
Host: hostname,
|
|
|
|
}
|
|
|
|
|
|
|
|
if connectTo := c.String(sshConnectTo); connectTo != "" {
|
|
|
|
parts := strings.Split(connectTo, ":")
|
|
|
|
switch len(parts) {
|
|
|
|
case 1:
|
|
|
|
options.OriginURL = fmt.Sprintf("https://%s", parts[0])
|
|
|
|
case 2:
|
|
|
|
options.OriginURL = fmt.Sprintf("https://%s:%s", parts[0], parts[1])
|
|
|
|
case 3:
|
|
|
|
options.OriginURL = fmt.Sprintf("https://%s:%s", parts[2], parts[1])
|
|
|
|
options.TLSClientConfig = &tls.Config{
|
|
|
|
InsecureSkipVerify: true,
|
2021-02-10 16:19:55 +00:00
|
|
|
ServerName: parts[0],
|
2021-02-03 19:00:55 +00:00
|
|
|
}
|
|
|
|
log.Warn().Msgf("Using insecure SSL connection because SNI overridden to %s", parts[0])
|
|
|
|
default:
|
|
|
|
return fmt.Errorf("invalid connection override: %s", connectTo)
|
|
|
|
}
|
2019-03-06 19:09:13 +00:00
|
|
|
}
|
2018-09-21 15:18:23 +00:00
|
|
|
|
2020-03-31 14:56:22 +00:00
|
|
|
// we could add a cmd line variable for this bool if we want the SOCK5 server to be on the client side
|
2021-02-10 16:19:55 +00:00
|
|
|
wsConn := carrier.NewWSConnection(log)
|
2020-03-31 14:56:22 +00:00
|
|
|
|
2019-01-23 21:42:10 +00:00
|
|
|
if c.NArg() > 0 || c.IsSet(sshURLFlag) {
|
2020-10-08 10:12:26 +00:00
|
|
|
forwarder, err := config.ValidateUrl(c, true)
|
2018-09-21 15:18:23 +00:00
|
|
|
if err != nil {
|
2020-12-28 18:10:01 +00:00
|
|
|
log.Err(err).Msg("Error validating origin URL")
|
2018-09-21 15:18:23 +00:00
|
|
|
return errors.Wrap(err, "error validating origin URL")
|
|
|
|
}
|
2020-12-28 18:10:01 +00:00
|
|
|
log.Info().Str(LogFieldHost, forwarder.Host).Msg("Start Websocket listener")
|
2020-04-29 20:51:32 +00:00
|
|
|
err = carrier.StartForwarder(wsConn, forwarder.Host, shutdownC, options)
|
|
|
|
if err != nil {
|
2020-12-28 18:10:01 +00:00
|
|
|
log.Err(err).Msg("Error on Websocket listener")
|
2020-04-29 20:51:32 +00:00
|
|
|
}
|
|
|
|
return err
|
2018-09-21 15:18:23 +00:00
|
|
|
}
|
|
|
|
|
2023-06-29 17:29:15 +00:00
|
|
|
var s io.ReadWriter
|
|
|
|
s = &carrier.StdinoutStream{}
|
|
|
|
if c.IsSet(sshDebugStream) {
|
|
|
|
maxMessages := c.Uint64(sshDebugStream)
|
|
|
|
if maxMessages == 0 {
|
|
|
|
// default to 10 if provided but unset
|
|
|
|
maxMessages = 10
|
|
|
|
}
|
|
|
|
logger := log.With().Str("host", hostname).Logger()
|
|
|
|
s = stream.NewDebugStream(s, &logger, maxMessages)
|
|
|
|
}
|
|
|
|
carrier.StartClient(wsConn, s, options)
|
|
|
|
return nil
|
2019-02-07 16:56:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func buildRequestHeaders(values []string) http.Header {
|
|
|
|
headers := make(http.Header)
|
|
|
|
for _, valuePair := range values {
|
|
|
|
split := strings.Split(valuePair, ":")
|
|
|
|
if len(split) > 1 {
|
|
|
|
headers.Add(strings.TrimSpace(split[0]), strings.TrimSpace(split[1]))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return headers
|
2018-09-21 15:18:23 +00:00
|
|
|
}
|