AUTH-1188: UX Review and Changes for CLI SSH Access
This commit is contained in:
parent
6acc95f756
commit
80a75e91d2
|
@ -11,7 +11,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/access"
|
"github.com/cloudflare/cloudflared/cmd/cloudflared/token"
|
||||||
"github.com/cloudflare/cloudflared/websocket"
|
"github.com/cloudflare/cloudflared/websocket"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
@ -121,7 +121,7 @@ func buildAccessRequest(originURL string) (*http.Request, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
token, err := access.FetchToken(req.URL)
|
token, err := token.FetchToken(req.URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
package tunnel
|
package access
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"github.com/cloudflare/cloudflared/carrier"
|
"github.com/cloudflare/cloudflared/carrier"
|
||||||
|
"github.com/cloudflare/cloudflared/cmd/cloudflared/config"
|
||||||
"github.com/cloudflare/cloudflared/validation"
|
"github.com/cloudflare/cloudflared/validation"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
cli "gopkg.in/urfave/cli.v2"
|
cli "gopkg.in/urfave/cli.v2"
|
||||||
|
@ -15,13 +16,12 @@ import (
|
||||||
// (which you can put Access in front of)
|
// (which you can put Access in front of)
|
||||||
func ssh(c *cli.Context) error {
|
func ssh(c *cli.Context) error {
|
||||||
hostname, err := validation.ValidateHostname(c.String("hostname"))
|
hostname, err := validation.ValidateHostname(c.String("hostname"))
|
||||||
if err != nil {
|
if err != nil || c.String("hostname") == "" {
|
||||||
logger.WithError(err).Error("Invalid hostname")
|
return cli.ShowCommandHelp(c, "ssh")
|
||||||
return errors.Wrap(err, "invalid hostname")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.NArg() > 0 || c.IsSet("url") {
|
if c.NArg() > 0 || c.IsSet("url") {
|
||||||
localForwarder, err := validateUrl(c)
|
localForwarder, err := config.ValidateUrl(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.WithError(err).Error("Error validating origin URL")
|
logger.WithError(err).Error("Error validating origin URL")
|
||||||
return errors.Wrap(err, "error validating origin URL")
|
return errors.Wrap(err, "error validating origin URL")
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/shell"
|
"github.com/cloudflare/cloudflared/cmd/cloudflared/shell"
|
||||||
|
"github.com/cloudflare/cloudflared/cmd/cloudflared/token"
|
||||||
"golang.org/x/net/idna"
|
"golang.org/x/net/idna"
|
||||||
|
|
||||||
"github.com/cloudflare/cloudflared/log"
|
"github.com/cloudflare/cloudflared/log"
|
||||||
|
@ -16,6 +17,17 @@ import (
|
||||||
|
|
||||||
const sentryDSN = "https://56a9c9fa5c364ab28f34b14f35ea0f1b@sentry.io/189878"
|
const sentryDSN = "https://56a9c9fa5c364ab28f34b14f35ea0f1b@sentry.io/189878"
|
||||||
|
|
||||||
|
var (
|
||||||
|
logger = log.CreateLogger()
|
||||||
|
shutdownC chan struct{}
|
||||||
|
graceShutdownC chan struct{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Init will initialize and store vars from the main program
|
||||||
|
func Init(s, g chan struct{}) {
|
||||||
|
shutdownC, graceShutdownC = s, g
|
||||||
|
}
|
||||||
|
|
||||||
// Flags return the global flags for Access related commands (hopefully none)
|
// Flags return the global flags for Access related commands (hopefully none)
|
||||||
func Flags() []cli.Flag {
|
func Flags() []cli.Flag {
|
||||||
return []cli.Flag{} // no flags yet.
|
return []cli.Flag{} // no flags yet.
|
||||||
|
@ -61,7 +73,7 @@ func Commands() []*cli.Command {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "token",
|
Name: "token",
|
||||||
Action: token,
|
Action: generateToken,
|
||||||
Usage: "token -app=<url of access application>",
|
Usage: "token -app=<url of access application>",
|
||||||
ArgsUsage: "url of Access application",
|
ArgsUsage: "url of Access application",
|
||||||
Description: `The token subcommand produces a JWT which can be used to authenticate requests.`,
|
Description: `The token subcommand produces a JWT which can be used to authenticate requests.`,
|
||||||
|
@ -71,6 +83,30 @@ func Commands() []*cli.Command {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "ssh",
|
||||||
|
Action: ssh,
|
||||||
|
Usage: "",
|
||||||
|
ArgsUsage: "",
|
||||||
|
Description: `The ssh subcommand sends data over a proxy to the Cloudflare edge.`,
|
||||||
|
Hidden: true,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "hostname",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "url",
|
||||||
|
Hidden: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "ssh-config",
|
||||||
|
Action: sshConfig,
|
||||||
|
Usage: "ssh-config",
|
||||||
|
Description: `Prints an example configuration ~/.ssh/config`,
|
||||||
|
Hidden: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -86,7 +122,7 @@ func login(c *cli.Context) error {
|
||||||
logger.Errorf("Please provide the url of the Access application\n")
|
logger.Errorf("Please provide the url of the Access application\n")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
token, err := FetchToken(appURL)
|
token, err := token.FetchToken(appURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("Failed to fetch token: %s\n", err)
|
logger.Errorf("Failed to fetch token: %s\n", err)
|
||||||
return err
|
return err
|
||||||
|
@ -111,13 +147,13 @@ func curl(c *cli.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
token, err := getTokenIfExists(appURL)
|
tok, err := token.GetTokenIfExists(appURL)
|
||||||
if err != nil || token == "" {
|
if err != nil || tok == "" {
|
||||||
if allowRequest {
|
if allowRequest {
|
||||||
logger.Warn("You don't have an Access token set. Please run access token <access application> to fetch one.")
|
logger.Warn("You don't have an Access token set. Please run access token <access application> to fetch one.")
|
||||||
return shell.Run("curl", cmdArgs...)
|
return shell.Run("curl", cmdArgs...)
|
||||||
}
|
}
|
||||||
token, err = FetchToken(appURL)
|
tok, err = token.FetchToken(appURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Failed to refresh token: ", err)
|
logger.Error("Failed to refresh token: ", err)
|
||||||
return err
|
return err
|
||||||
|
@ -125,31 +161,39 @@ func curl(c *cli.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
cmdArgs = append(cmdArgs, "-H")
|
cmdArgs = append(cmdArgs, "-H")
|
||||||
cmdArgs = append(cmdArgs, fmt.Sprintf("cf-access-token: %s", token))
|
cmdArgs = append(cmdArgs, fmt.Sprintf("cf-access-token: %s", tok))
|
||||||
return shell.Run("curl", cmdArgs...)
|
return shell.Run("curl", cmdArgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// token dumps provided token to stdout
|
// token dumps provided token to stdout
|
||||||
func token(c *cli.Context) error {
|
func generateToken(c *cli.Context) error {
|
||||||
raven.SetDSN(sentryDSN)
|
raven.SetDSN(sentryDSN)
|
||||||
appURL, err := url.Parse(c.String("app"))
|
appURL, err := url.Parse(c.String("app"))
|
||||||
if err != nil || c.NumFlags() < 1 {
|
if err != nil || c.NumFlags() < 1 {
|
||||||
fmt.Fprintln(os.Stderr, "Please provide a url.")
|
fmt.Fprintln(os.Stderr, "Please provide a url.")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
token, err := getTokenIfExists(appURL)
|
tok, err := token.GetTokenIfExists(appURL)
|
||||||
if err != nil || token == "" {
|
if err != nil || tok == "" {
|
||||||
fmt.Fprintln(os.Stderr, "Unable to find token for provided application. Please run token command to generate token.")
|
fmt.Fprintln(os.Stderr, "Unable to find token for provided application. Please run token command to generate token.")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := fmt.Fprint(os.Stdout, token); err != nil {
|
if _, err := fmt.Fprint(os.Stdout, tok); err != nil {
|
||||||
fmt.Fprintln(os.Stderr, "Failed to write token to stdout.")
|
fmt.Fprintln(os.Stderr, "Failed to write token to stdout.")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sshConfig prints an example SSH config to stdout
|
||||||
|
func sshConfig(c *cli.Context) error {
|
||||||
|
_, err := os.Stdout.Write([]byte(`Add this configuration block to your $HOME/.ssh/config
|
||||||
|
Host <your hostname>
|
||||||
|
ProxyCommand cloudflared access ssh --hostname %h` + "\n"))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// processURL will preprocess the string (parse to a url, convert to punycode, etc).
|
// processURL will preprocess the string (parse to a url, convert to punycode, etc).
|
||||||
func processURL(s string) (*url.URL, error) {
|
func processURL(s string) (*url.URL, error) {
|
||||||
u, err := url.ParseRequestURI(s)
|
u, err := url.ParseRequestURI(s)
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/cloudflare/cloudflared/validation"
|
||||||
homedir "github.com/mitchellh/go-homedir"
|
homedir "github.com/mitchellh/go-homedir"
|
||||||
"gopkg.in/urfave/cli.v2"
|
"gopkg.in/urfave/cli.v2"
|
||||||
"gopkg.in/urfave/cli.v2/altsrc"
|
"gopkg.in/urfave/cli.v2/altsrc"
|
||||||
|
@ -60,3 +62,16 @@ func FindDefaultConfigPath() string {
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ValidateUrl will validate url flag correctness. It can be either from --url or argument
|
||||||
|
func ValidateUrl(c *cli.Context) (string, error) {
|
||||||
|
var url = c.String("url")
|
||||||
|
if c.NArg() > 0 {
|
||||||
|
if c.IsSet("url") {
|
||||||
|
return "", errors.New("Specified origin urls using both --url and argument. Decide which one you want, I can only support one.")
|
||||||
|
}
|
||||||
|
url = c.Args().Get(0)
|
||||||
|
}
|
||||||
|
validUrl, err := validation.ValidateUrl(url)
|
||||||
|
return validUrl, err
|
||||||
|
}
|
||||||
|
|
|
@ -55,6 +55,7 @@ func main() {
|
||||||
app.Commands = commands()
|
app.Commands = commands()
|
||||||
|
|
||||||
tunnel.Init(Version, shutdownC, graceShutdownC) // we need this to support the tunnel sub command...
|
tunnel.Init(Version, shutdownC, graceShutdownC) // we need this to support the tunnel sub command...
|
||||||
|
access.Init(shutdownC, graceShutdownC)
|
||||||
runApp(app, shutdownC, graceShutdownC)
|
runApp(app, shutdownC, graceShutdownC)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package access
|
package token
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -21,7 +21,7 @@ var logger = log.CreateLogger()
|
||||||
|
|
||||||
// FetchToken will either load a stored token or generate a new one
|
// FetchToken will either load a stored token or generate a new one
|
||||||
func FetchToken(appURL *url.URL) (string, error) {
|
func FetchToken(appURL *url.URL) (string, error) {
|
||||||
if token, err := getTokenIfExists(appURL); token != "" && err == nil {
|
if token, err := GetTokenIfExists(appURL); token != "" && err == nil {
|
||||||
return token, nil
|
return token, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,8 +42,8 @@ func FetchToken(appURL *url.URL) (string, error) {
|
||||||
return string(token), nil
|
return string(token), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getTokenIfExists will return the token from local storage if it exists
|
// GetTokenIfExists will return the token from local storage if it exists
|
||||||
func getTokenIfExists(url *url.URL) (string, error) {
|
func GetTokenIfExists(url *url.URL) (string, error) {
|
||||||
path, err := generateFilePathForTokenURL(url)
|
path, err := generateFilePathForTokenURL(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"runtime/trace"
|
"runtime/trace"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -139,21 +140,6 @@ func Commands() []*cli.Command {
|
||||||
},
|
},
|
||||||
Hidden: true,
|
Hidden: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
Name: "ssh",
|
|
||||||
Action: ssh,
|
|
||||||
Usage: `ssh -o ProxyCommand="cloudflared tunnel ssh --hostname %h" ssh.warptunnels.org`,
|
|
||||||
ArgsUsage: "[origin-url]",
|
|
||||||
Description: `The ssh subcommand wraps sends data over a WebSocket proxy to the Cloudflare edge.`,
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "hostname",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "url",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var subcommands []*cli.Command
|
var subcommands []*cli.Command
|
||||||
|
@ -325,7 +311,11 @@ func StartServer(c *cli.Context, version string, shutdownC, graceShutdownC chan
|
||||||
c.Set("url", "https://"+helloListener.Addr().String())
|
c.Set("url", "https://"+helloListener.Addr().String())
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.IsSet("ws-proxy-server") {
|
if uri, _ := url.Parse(c.String("url")); uri.Scheme == "ssh" {
|
||||||
|
host := uri.Host
|
||||||
|
if uri.Port() == "" { // default to 22
|
||||||
|
host = uri.Hostname() + ":22"
|
||||||
|
}
|
||||||
listener, err := net.Listen("tcp", "127.0.0.1:")
|
listener, err := net.Listen("tcp", "127.0.0.1:")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.WithError(err).Error("Cannot start Websocket Proxy Server")
|
logger.WithError(err).Error("Cannot start Websocket Proxy Server")
|
||||||
|
@ -334,7 +324,7 @@ func StartServer(c *cli.Context, version string, shutdownC, graceShutdownC chan
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
errC <- websocket.StartProxyServer(logger, listener, c.String("remote"), shutdownC)
|
errC <- websocket.StartProxyServer(logger, listener, host, shutdownC)
|
||||||
}()
|
}()
|
||||||
c.Set("url", "http://"+listener.Addr().String())
|
c.Set("url", "http://"+listener.Addr().String())
|
||||||
}
|
}
|
||||||
|
@ -355,19 +345,19 @@ func StartServer(c *cli.Context, version string, shutdownC, graceShutdownC chan
|
||||||
|
|
||||||
func Before(c *cli.Context) error {
|
func Before(c *cli.Context) error {
|
||||||
if c.String("config") == "" {
|
if c.String("config") == "" {
|
||||||
logger.Warnf("Cannot determine default configuration path. No file %v in %v", config.DefaultConfigFiles, config.DefaultConfigDirs)
|
logger.Debugf("Cannot determine default configuration path. No file %v in %v", config.DefaultConfigFiles, config.DefaultConfigDirs)
|
||||||
}
|
}
|
||||||
inputSource, err := config.FindInputSourceContext(c)
|
inputSource, err := config.FindInputSourceContext(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.WithError(err).Infof("Cannot load configuration from %s", c.String("config"))
|
logger.WithError(err).Debugf("Cannot load configuration from %s", c.String("config"))
|
||||||
return err
|
return err
|
||||||
} else if inputSource != nil {
|
} else if inputSource != nil {
|
||||||
err := altsrc.ApplyInputSourceValues(c, inputSource, c.App.Flags)
|
err := altsrc.ApplyInputSourceValues(c, inputSource, c.App.Flags)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.WithError(err).Infof("Cannot apply configuration from %s", c.String("config"))
|
logger.WithError(err).Debugf("Cannot apply configuration from %s", c.String("config"))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
logger.Infof("Applied configuration from %s", c.String("config"))
|
logger.Debugf("Applied configuration from %s", c.String("config"))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -478,13 +468,6 @@ func tunnelFlags(shouldHide bool) []cli.Flag {
|
||||||
EnvVars: []string{"TUNNEL_URL"},
|
EnvVars: []string{"TUNNEL_URL"},
|
||||||
Hidden: shouldHide,
|
Hidden: shouldHide,
|
||||||
}),
|
}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{
|
|
||||||
Name: "remote",
|
|
||||||
Value: "localhost:22",
|
|
||||||
Usage: "Connect to the local server over tcp at `remote`.",
|
|
||||||
EnvVars: []string{"TUNNEL_REMOTE"},
|
|
||||||
Hidden: shouldHide,
|
|
||||||
}),
|
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{
|
altsrc.NewStringFlag(&cli.StringFlag{
|
||||||
Name: "hostname",
|
Name: "hostname",
|
||||||
Usage: "Set a hostname on a Cloudflare zone to route traffic through this tunnel.",
|
Usage: "Set a hostname on a Cloudflare zone to route traffic through this tunnel.",
|
||||||
|
@ -587,13 +570,6 @@ func tunnelFlags(shouldHide bool) []cli.Flag {
|
||||||
EnvVars: []string{"TUNNEL_HELLO_WORLD"},
|
EnvVars: []string{"TUNNEL_HELLO_WORLD"},
|
||||||
Hidden: shouldHide,
|
Hidden: shouldHide,
|
||||||
}),
|
}),
|
||||||
altsrc.NewBoolFlag(&cli.BoolFlag{
|
|
||||||
Name: "ws-proxy-server",
|
|
||||||
Value: false,
|
|
||||||
Usage: "Run WS proxy Server",
|
|
||||||
EnvVars: []string{"TUNNEL_WS_PROXY"},
|
|
||||||
Hidden: shouldHide,
|
|
||||||
}),
|
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{
|
altsrc.NewStringFlag(&cli.StringFlag{
|
||||||
Name: "pidfile",
|
Name: "pidfile",
|
||||||
Usage: "Write the application's PID to this file after first successful connection.",
|
Usage: "Write the application's PID to this file after first successful connection.",
|
||||||
|
|
|
@ -64,19 +64,6 @@ func handleDeprecatedOptions(c *cli.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate url. It can be either from --url or argument
|
|
||||||
func validateUrl(c *cli.Context) (string, error) {
|
|
||||||
var url = c.String("url")
|
|
||||||
if c.NArg() > 0 {
|
|
||||||
if c.IsSet("url") {
|
|
||||||
return "", errors.New("Specified origin urls using both --url and argument. Decide which one you want, I can only support one.")
|
|
||||||
}
|
|
||||||
url = c.Args().Get(0)
|
|
||||||
}
|
|
||||||
validUrl, err := validation.ValidateUrl(url)
|
|
||||||
return validUrl, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func logClientOptions(c *cli.Context) {
|
func logClientOptions(c *cli.Context) {
|
||||||
flags := make(map[string]interface{})
|
flags := make(map[string]interface{})
|
||||||
for _, flag := range c.LocalFlagNames() {
|
for _, flag := range c.LocalFlagNames() {
|
||||||
|
@ -168,7 +155,7 @@ func prepareTunnelConfig(c *cli.Context, buildInfo *origin.BuildInfo, version st
|
||||||
|
|
||||||
tags = append(tags, tunnelpogs.Tag{Name: "ID", Value: clientID})
|
tags = append(tags, tunnelpogs.Tag{Name: "ID", Value: clientID})
|
||||||
|
|
||||||
originURL, err := validateUrl(c)
|
originURL, err := config.ValidateUrl(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.WithError(err).Error("Error validating origin URL")
|
logger.WithError(err).Error("Error validating origin URL")
|
||||||
return nil, errors.Wrap(err, "Error validating origin URL")
|
return nil, errors.Wrap(err, "Error validating origin URL")
|
||||||
|
|
|
@ -0,0 +1,312 @@
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
func nonWebSocketRequestPage() []byte {
|
||||||
|
return []byte(`<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="robots" content="noindex">
|
||||||
|
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
|
||||||
|
<title>Cloudflare Access</title>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.7em;
|
||||||
|
font-family: "Open Sans", sans-serif;
|
||||||
|
color: #424242;
|
||||||
|
background: #f3f1fe;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-title {
|
||||||
|
font-size: 30px;
|
||||||
|
color: #fff;
|
||||||
|
padding: 40px;
|
||||||
|
background: rgb(129, 118, 181);
|
||||||
|
background: linear-gradient(72deg, rgba(129, 118, 181, 1) 0%, rgba(127, 120, 183, 1) 35%, rgba(119, 195, 224, 1) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-content {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-1 {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
padding: 40px;
|
||||||
|
box-shadow: 0 4px 9px 1px rgba(129, 118, 181, 0.25);
|
||||||
|
background: #fff;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-section {
|
||||||
|
padding: 20px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding-right: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-section > div {
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
min-width: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-1-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin: 40px;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-basis: calc(70% - 100px);
|
||||||
|
max-width: calc(70% - 100px);
|
||||||
|
border-right: 1px solid #d8d0ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-1-content .main-message {
|
||||||
|
flex-basis: calc(49% - 40px);
|
||||||
|
max-width: calc(49% - 40px);
|
||||||
|
padding: 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-1-content .debug-details {
|
||||||
|
padding: 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
color: #7e7e7e;
|
||||||
|
flex-basis: calc(49% - 40px);
|
||||||
|
max-width: calc(49% - 40px);
|
||||||
|
flex-basis: calc(49% - 40px);
|
||||||
|
max-width: calc(49% - 40px);
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-1-content .main-message .title {
|
||||||
|
font-size: 50px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-1-content .main-message .sub-title {
|
||||||
|
color: #8f8f8f;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-2 {
|
||||||
|
padding: 50px 100px;
|
||||||
|
color: rgb(71, 64, 106);
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-2 .zd-link-message {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-2 .cf-link {
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-2 .cf-link-message {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-3 {
|
||||||
|
color: rgb(71, 64, 106);
|
||||||
|
|
||||||
|
padding: 40px 80px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
border-top: 1px solid #d8d0ff;
|
||||||
|
padding-top: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #2400cf;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:visited {
|
||||||
|
color: #3f2ba2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Message-is-warning {
|
||||||
|
color: #cc8400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Message-is-success {
|
||||||
|
color: #028402;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appName {
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.org-logo {
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
max-width: 155px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.watermark-logo::after {
|
||||||
|
opacity: 0.05;
|
||||||
|
height: 100%;
|
||||||
|
width: 70%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
content: "#";
|
||||||
|
z-index: 1;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.watermark-logo .debug-text {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-items: center;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.watermark-logo p, .watermark-logo div {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-title {
|
||||||
|
color: #5f5f5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 1046px) {
|
||||||
|
.section-1-content .main-message {
|
||||||
|
flex-basis: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-1-content .debug-details {
|
||||||
|
flex-basis: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
padding: 20px;
|
||||||
|
margin-top: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 890px) {
|
||||||
|
.section-1 {
|
||||||
|
flex-wrap: wrap-reverse;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-1-content {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-2 {
|
||||||
|
padding: 50px 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
min-width: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-section {
|
||||||
|
flex-basis: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-1-content {
|
||||||
|
border-right: 0;
|
||||||
|
flex-basis: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<header class="main-title">
|
||||||
|
Cloudflare Access
|
||||||
|
</header>
|
||||||
|
<div class="main-content">
|
||||||
|
<div class="section-1">
|
||||||
|
<div class="section-1-content">
|
||||||
|
<div class="main-message">
|
||||||
|
<div class="title"> Success </div>
|
||||||
|
<div class="sub-title">
|
||||||
|
You are now logged in and can reach this application over SSH from your command line.
|
||||||
|
You can close this browser window.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="logo-section">
|
||||||
|
<div>
|
||||||
|
|
||||||
|
<svg class="logo" width="250" viewBox="0 0 122 53" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect class="" width="100%" height="100%" fill="none" style=""/>
|
||||||
|
<defs>
|
||||||
|
<style>.cls-5 {
|
||||||
|
fill: #fff
|
||||||
|
}
|
||||||
|
|
||||||
|
.st0 {
|
||||||
|
fill: #8176b5
|
||||||
|
}</style>
|
||||||
|
</defs>
|
||||||
|
<g class="currentLayer" style="">
|
||||||
|
<path class="" d="m113.65 12.721l-6.72-1.56-1.2-0.48-30.843 0.24v14.882l38.763 0.12z"
|
||||||
|
fill="#fff"/>
|
||||||
|
<path class=""
|
||||||
|
d="m101.05 24.482c0.36-1.2 0.24-2.4-0.36-3.12s-1.44-1.2-2.52-1.32l-20.882-0.24c-0.12 0-0.24-0.12-0.36-0.12-0.12-0.12-0.12-0.24 0-0.36 0.12-0.24 0.24-0.36 0.48-0.36l21.002-0.24c2.52-0.12 5.16-2.16 6.12-4.56l1.2-3.12c0-0.12 0.12-0.24 0-0.36-1.32-6.121-6.84-10.682-13.32-10.682-6.001 0-11.162 3.84-12.962 9.241-1.2-0.84-2.64-1.32-4.32-1.2-2.88 0.24-5.16 2.64-5.52 5.52-0.12 0.72 0 1.44 0.12 2.16-4.681 0.12-8.521 3.961-8.521 8.761 0 0.48 0 0.84 0.12 1.32 0 0.24 0.24 0.36 0.36 0.36h38.523c0.24 0 0.48-0.12 0.48-0.36l0.36-1.32z"
|
||||||
|
fill="#f48120"/>
|
||||||
|
<path class=""
|
||||||
|
d="m107.65 11.041h-0.6c-0.12 0-0.24 0.12-0.36 0.24l-0.84 2.88c-0.36 1.2-0.24 2.4 0.36 3.12s1.44 1.2 2.52 1.32l4.44 0.24c0.12 0 0.24 0.12 0.36 0.12 0.12 0.12 0.12 0.241 0 0.361-0.12 0.24-0.24 0.36-0.48 0.36l-4.56 0.24c-2.52 0.12-5.16 2.16-6.12 4.56l-0.24 1.08c-0.12 0.12 0 0.36 0.24 0.36h15.84c0.24 0 0.36-0.12 0.36-0.36 0.24-0.96 0.48-2.04 0.48-3.12 0-6.24-5.16-11.4-11.4-11.4"
|
||||||
|
fill="#faad3f"/>
|
||||||
|
<path class="st0"
|
||||||
|
d="m120.61 32.643c-0.6 0-1.08-0.48-1.08-1.08s0.48-1.08 1.08-1.08 1.08 0.48 1.08 1.08-0.48 1.08-1.08 1.08m0-1.92c-0.48 0-0.84 0.36-0.84 0.84s0.36 0.84 0.84 0.84 0.84-0.36 0.84-0.84-0.36-0.84-0.84-0.84m0.48 1.44h-0.24l-0.24-0.36h-0.24v0.36h-0.24v-1.08h0.6c0.24 0 0.36 0.12 0.36 0.36 0 0.12-0.12 0.24-0.24 0.36l0.24 0.36zm-0.36-0.6c0.12 0 0.12 0 0.12-0.12s-0.12-0.12-0.12-0.12h-0.36v0.36h0.36zm-107.65-1.08h2.64v7.2h4.562v2.28h-7.2zm9.962 4.68c0-2.76 2.16-4.92 5.16-4.92s5.04 2.16 5.04 4.92-2.16 4.92-5.16 4.92c-2.88 0-5.04-2.16-5.04-4.92m7.56 0c0-1.44-0.96-2.64-2.4-2.64s-2.4 1.2-2.4 2.52 0.96 2.52 2.4 2.52c1.44 0.24 2.4-0.96 2.4-2.4m5.88 0.6v-5.28h2.64v5.28c0 1.32 0.72 2.04 1.801 2.04s1.8-0.6 1.8-1.92v-5.4h2.64v5.28c0 3.12-1.8 4.44-4.44 4.44-2.76-0.12-4.44-1.44-4.44-4.44m12.84-5.28h3.721c3.36 0 5.4 1.92 5.4 4.68s-2.04 4.8-5.4 4.8h-3.6v-9.48zm3.721 7.08c1.56 0 2.64-0.84 2.64-2.4s-1.08-2.4-2.64-2.4h-1.08v4.8h1.08zm9.12-7.08h7.561v2.28h-4.92v1.56h4.44v2.16h-4.44v3.48h-2.64zm11.282 0h2.64v7.2h4.56v2.28h-7.2zm14.04-0.12h2.641l4.08 9.6h-2.88l-0.72-1.68h-3.72l-0.72 1.68h-2.76l4.08-9.6zm2.401 5.88l-1.08-2.64-1.08 2.64h2.16zm7.68-5.76h4.441c1.44 0 2.4 0.36 3.12 1.08 0.6 0.6 0.84 1.32 0.84 2.16 0 1.44-0.72 2.4-1.92 2.88l2.28 3.36h-3l-1.92-2.88h-1.2v2.88h-2.64v-9.48zm4.321 4.56c0.84 0 1.44-0.48 1.44-1.08 0-0.72-0.6-1.08-1.44-1.08h-1.68v2.28h1.68zm7.8-4.56h7.681v2.16h-5.04v1.44h4.56v2.16h-4.56v1.44h5.16v2.28h-7.8zm-102.37 5.88a2.37 2.37 0 0 1 -2.16 1.44c-1.44 0-2.4-1.2-2.4-2.52s0.96-2.52 2.4-2.52c1.08 0 1.92 0.72 2.28 1.56h2.76c-0.48-2.28-2.4-3.96-5.04-3.96-2.88 0-5.16 2.16-5.16 4.92s2.16 4.92 5.04 4.92c2.52 0 4.44-1.68 5.04-3.84h-2.76z"
|
||||||
|
fill="#8176b5"/>
|
||||||
|
<path class="st0"
|
||||||
|
d="m53.092 43.286h2.614l4.04 9.68h-2.852l-0.713-1.695h-3.683l-0.713 1.694h-2.733l4.04-9.68zm2.376 5.928h-2.138l1.07-2.662 1.069 2.662zm58.031-5.928c1.71 0 3.8 0.783 3.8 2.966h-2.93c0-1.281-2.371-0.985-2.123 0 0.223 0.886 1.533 0.783 2.22 0.914 2.422 0.46 3.12 1.804 3.12 2.765 0 1.353-0.64 3.216-4.087 3.216-1.854 0-4.393-0.546-4.387-3.086h2.99c0 1.223 2.494 1.332 2.494 0.13 0-0.664-1.166-1.085-2.35-1.245-0.875-0.119-2.912-0.73-2.912-2.634 0-1.048 0.457-3.026 4.165-3.026zm-11.504 0c1.71 0 3.8 0.783 3.8 2.966h-2.931c0-1.281-2.37-0.985-2.123 0 0.223 0.886 1.534 0.783 2.22 0.914 2.422 0.46 3.12 1.804 3.12 2.765 0 1.353-0.639 3.216-4.086 3.216-1.854 0-4.394-0.546-4.387-3.086h2.99c0 1.223 2.494 1.332 2.494 0.13 0-0.664-1.167-1.085-2.35-1.245-0.876-0.119-2.913-0.73-2.913-2.634 0-1.048 0.458-3.026 4.166-3.026zm-15.05 0.202h7.406v2.225h-4.877v1.433h4.417v2.073h-4.417v1.5h4.942v2.226h-7.47v-9.457zm-18.326 5.702l2.692 0.016c-0.492 2.158-2.397 3.776-4.827 3.776-2.84 0-4.942-2.174-4.942-4.888v-0.034c0-2.714 2.135-4.922 4.975-4.922 2.496 0 4.433 1.669 4.86 3.928h-2.693c-0.328-0.91-1.133-1.568-2.183-1.568-1.396 0-2.332 1.163-2.332 2.529v0.033c0 1.349 0.952 2.546 2.348 2.546 0.985 0 1.74-0.59 2.102-1.416zm12.326 0l2.693 0.016c-0.493 2.158-2.397 3.776-4.828 3.776-2.84 0-4.942-2.174-4.942-4.888v-0.034c0-2.714 2.135-4.922 4.975-4.922 2.496 0 4.433 1.669 4.86 3.928h-2.691c-0.329-0.91-1.133-1.568-2.184-1.568-1.396 0-2.332 1.163-2.332 2.529v0.033c0 1.349 0.953 2.546 2.348 2.546 0.985 0 1.74-0.59 2.102-1.416z"
|
||||||
|
fill="#8176b5" fill-rule="evenodd"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="section-2">
|
||||||
|
</div>
|
||||||
|
<div class="section-3 ">
|
||||||
|
<footer>
|
||||||
|
<a href="https://support.cloudflare.com/hc/en-us" target="_blank"> Help </a>
|
||||||
|
•
|
||||||
|
<span>Performance & Security by
|
||||||
|
<a href="https://www.cloudflare.com/products/cloudflare-access/" target="_blank">Cloudflare Access</a>
|
||||||
|
</span>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>`)
|
||||||
|
}
|
|
@ -136,6 +136,10 @@ func StartProxyServer(logger *logrus.Logger, listener net.Listener, remote strin
|
||||||
}
|
}
|
||||||
defer stream.Close()
|
defer stream.Close()
|
||||||
|
|
||||||
|
if !websocket.IsWebSocketUpgrade(r) {
|
||||||
|
w.Write(nonWebSocketRequestPage())
|
||||||
|
return
|
||||||
|
}
|
||||||
conn, err := upgrader.Upgrade(w, r, nil)
|
conn, err := upgrader.Upgrade(w, r, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.WithError(err).Error("failed to upgrade")
|
logger.WithError(err).Error("failed to upgrade")
|
||||||
|
|
Loading…
Reference in New Issue