cloudflared-mirror/cmd/cloudflared/access/carrier.go

147 lines
4.5 KiB
Go

package access
import (
"crypto/tls"
"fmt"
"io"
"net/http"
"strings"
"github.com/pkg/errors"
"github.com/rs/zerolog"
"github.com/urfave/cli/v2"
"github.com/cloudflare/cloudflared/carrier"
"github.com/cloudflare/cloudflared/config"
"github.com/cloudflare/cloudflared/logger"
"github.com/cloudflare/cloudflared/stream"
"github.com/cloudflare/cloudflared/validation"
)
const (
LogFieldHost = "host"
cfAccessClientIDHeader = "Cf-Access-Client-Id"
cfAccessClientSecretHeader = "Cf-Access-Client-Secret"
)
// StartForwarder starts a client side websocket forward
func StartForwarder(forwarder config.Forwarder, shutdown <-chan struct{}, log *zerolog.Logger) error {
validURL, err := validation.ValidateUrl(forwarder.Listener)
if err != nil {
return errors.Wrap(err, "error validating origin URL")
}
// get the headers from the config file and add to the request
headers := make(http.Header)
if forwarder.TokenClientID != "" {
headers.Set(cfAccessClientIDHeader, forwarder.TokenClientID)
}
if forwarder.TokenSecret != "" {
headers.Set(cfAccessClientSecretHeader, forwarder.TokenSecret)
}
headers.Set("User-Agent", userAgent)
carrier.SetBastionDest(headers, forwarder.Destination)
options := &carrier.StartOptions{
OriginURL: forwarder.URL,
Headers: headers, //TODO: TUN-2688 support custom headers from config file
}
// we could add a cmd line variable for this bool if we want the SOCK5 server to be on the client side
wsConn := carrier.NewWSConnection(log)
log.Info().Str(LogFieldHost, validURL.Host).Msg("Start Websocket listener")
return carrier.StartForwarder(wsConn, validURL.Host, shutdown, options)
}
// 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 {
// 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)
// get the hostname from the cmdline and error out if its not provided
rawHostName := c.String(sshHostnameFlag)
url, err := parseURL(rawHostName)
if err != nil {
log.Err(err).Send()
return cli.ShowCommandHelp(c, "ssh")
}
// get the headers from the cmdline and add them
headers := parseRequestHeaders(c.StringSlice(sshHeaderFlag))
if c.IsSet(sshTokenIDFlag) {
headers.Set(cfAccessClientIDHeader, c.String(sshTokenIDFlag))
}
if c.IsSet(sshTokenSecretFlag) {
headers.Set(cfAccessClientSecretHeader, c.String(sshTokenSecretFlag))
}
headers.Set("User-Agent", userAgent)
carrier.SetBastionDest(headers, c.String(sshDestinationFlag))
options := &carrier.StartOptions{
OriginURL: url.String(),
Headers: headers,
Host: url.Host,
}
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,
ServerName: parts[0],
}
log.Warn().Msgf("Using insecure SSL connection because SNI overridden to %s", parts[0])
default:
return fmt.Errorf("invalid connection override: %s", connectTo)
}
}
// we could add a cmd line variable for this bool if we want the SOCK5 server to be on the client side
wsConn := carrier.NewWSConnection(log)
if c.NArg() > 0 || c.IsSet(sshURLFlag) {
forwarder, err := config.ValidateUrl(c, true)
if err != nil {
log.Err(err).Msg("Error validating origin URL")
return errors.Wrap(err, "error validating origin URL")
}
log.Info().Str(LogFieldHost, forwarder.Host).Msg("Start Websocket listener")
err = carrier.StartForwarder(wsConn, forwarder.Host, shutdownC, options)
if err != nil {
log.Err(err).Msg("Error on Websocket listener")
}
return err
}
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", url.Host).Logger()
s = stream.NewDebugStream(s, &logger, maxMessages)
}
carrier.StartClient(wsConn, s, options)
return nil
}