Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ ifneq ($(TARGET_ARM), )
ARM_COMMAND := GOARM=$(TARGET_ARM)
endif

ifeq ($(TARGET_ARM), 7)
ifeq ($(TARGET_ARM), 7)
PACKAGE_ARCH := armhf
else
PACKAGE_ARCH := $(TARGET_ARCH)
Expand Down
13 changes: 9 additions & 4 deletions carrier/carrier.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ import (
"github.com/pkg/errors"
"github.com/rs/zerolog"

"github.com/cloudflare/cloudflared/config"
"github.com/cloudflare/cloudflared/token"
)

const (
LogFieldOriginURL = "originURL"
CFAccessTokenHeader = "Cf-Access-Token"
cfJumpDestinationHeader = "Cf-Access-Jump-Destination"
CFJumpDestinationHeader = "Cf-Access-Jump-Destination"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need this rename?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mostly for a distinction from this, but can look into DRYing it a bit more

)

type StartOptions struct {
Expand Down Expand Up @@ -163,12 +164,16 @@ func BuildAccessRequest(options *StartOptions, log *zerolog.Logger) (*http.Reque

func SetBastionDest(header http.Header, destination string) {
if destination != "" {
header.Set(cfJumpDestinationHeader, destination)
header.Set(CFJumpDestinationHeader, destination)
}
}

func ResolveBastionDest(r *http.Request) (string, error) {
jumpDestination := r.Header.Get(cfJumpDestinationHeader)
func ResolveBastionDest(req *http.Request, bastionMode bool, service string) (string, error) {
jumpDestination := req.Header.Get(CFJumpDestinationHeader)
if bastionMode && service != config.BastionFlag {
jumpDestination = service
}

if jumpDestination == "" {
return "", fmt.Errorf("Did not receive final destination from client. The --destination flag is likely not set on the client side")
}
Expand Down
52 changes: 41 additions & 11 deletions carrier/carrier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,80 +158,110 @@ func testRequest(t *testing.T, url string, stream io.ReadWriter) *http.Request {
}

func TestBastionDestination(t *testing.T) {

tests := []struct {
name string
header http.Header
expectedDest string
wantErr bool
bastionMode bool
service string
}{
{
name: "hostname destination",
header: http.Header{
cfJumpDestinationHeader: []string{"localhost"},
CFJumpDestinationHeader: []string{"localhost"},
},
expectedDest: "localhost",
},
{
name: "hostname destination with port",
header: http.Header{
cfJumpDestinationHeader: []string{"localhost:9000"},
CFJumpDestinationHeader: []string{"localhost:9000"},
},
expectedDest: "localhost:9000",
},
{
name: "hostname destination with scheme and port",
header: http.Header{
cfJumpDestinationHeader: []string{"ssh://localhost:9000"},
CFJumpDestinationHeader: []string{"ssh://localhost:9000"},
},
expectedDest: "localhost:9000",
},
{
name: "full hostname url",
header: http.Header{
cfJumpDestinationHeader: []string{"ssh://localhost:9000/metrics"},
CFJumpDestinationHeader: []string{"ssh://localhost:9000/metrics"},
},
expectedDest: "localhost:9000",
},
{
name: "hostname destination with port and path",
header: http.Header{
cfJumpDestinationHeader: []string{"localhost:9000/metrics"},
CFJumpDestinationHeader: []string{"localhost:9000/metrics"},
},
expectedDest: "localhost:9000",
},
{
name: "ip destination",
header: http.Header{
cfJumpDestinationHeader: []string{"127.0.0.1"},
CFJumpDestinationHeader: []string{"127.0.0.1"},
},
expectedDest: "127.0.0.1",
},
{
name: "ip destination with port",
header: http.Header{
cfJumpDestinationHeader: []string{"127.0.0.1:9000"},
CFJumpDestinationHeader: []string{"127.0.0.1:9000"},
},
expectedDest: "127.0.0.1:9000",
},
{
name: "ip destination with port and path",
header: http.Header{
cfJumpDestinationHeader: []string{"127.0.0.1:9000/metrics"},
CFJumpDestinationHeader: []string{"127.0.0.1:9000/metrics"},
},
expectedDest: "127.0.0.1:9000",
},
{
name: "ip destination with schem and port",
header: http.Header{
cfJumpDestinationHeader: []string{"tcp://127.0.0.1:9000"},
CFJumpDestinationHeader: []string{"tcp://127.0.0.1:9000"},
},
expectedDest: "127.0.0.1:9000",
},
{
name: "full ip url",
header: http.Header{
cfJumpDestinationHeader: []string{"ssh://127.0.0.1:9000/metrics"},
CFJumpDestinationHeader: []string{"ssh://127.0.0.1:9000/metrics"},
},
expectedDest: "127.0.0.1:9000",
},
{
name: "full ip url with bastion mode",
header: http.Header{
CFJumpDestinationHeader: []string{"ssh://127.0.0.1:9000/metrics"},
},
bastionMode: true,
service: "ssh://127.0.0.1:9002/metrics",
expectedDest: "127.0.0.1:9002",
},
{
name: "ip destination with port and path with bastion mode",
header: http.Header{
CFJumpDestinationHeader: []string{"127.0.0.1:9000/metrics"},
},
bastionMode: true,
service: "127.0.0.1:9002/metrics",
expectedDest: "127.0.0.1:9002",
},
{
name: "ip destination with port and path without bastion mode",
header: http.Header{
CFJumpDestinationHeader: []string{"127.0.0.1:9000/metrics"},
},
bastionMode: false,
service: "127.0.0.1:9002/metrics",
expectedDest: "127.0.0.1:9000",
},
{
Expand All @@ -243,7 +273,7 @@ func TestBastionDestination(t *testing.T) {
r := &http.Request{
Header: test.header,
}
dest, err := ResolveBastionDest(r)
dest, err := ResolveBastionDest(r, test.bastionMode, test.service)
if test.wantErr {
assert.Error(t, err, "Test %s expects error", test.name)
} else {
Expand Down
2 changes: 1 addition & 1 deletion cmd/cloudflared/tunnel/ingress_subcommands.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ func testURLCommand(c *cli.Context) error {
return errors.Wrap(err, "Validation failed")
}

_, i := ing.FindMatchingRule(requestURL.Hostname(), requestURL.Path)
_, i := ing.FindMatchingRule(requestURL.Hostname(), requestURL.Path, "")
fmt.Printf("Matched rule #%d\n", i)
fmt.Println(ing.Rules[i].MultiLineString())
return nil
Expand Down
28 changes: 23 additions & 5 deletions ingress/ingress.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ var (
)

const (
ServiceBastion = "bastion"
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consolidate the use via config.BastionFlag and also to avoid import cycle not allowed in test which got raised when I was referencing ingress from carrier

ServiceSocksProxy = "socks-proxy"
ServiceWarpRouting = "warp-routing"
)
Expand All @@ -38,12 +37,13 @@ const (
// which is the case if the rules were instantiated via the ingress#Validate method.
//
// Negative index rule signifies local cloudflared rules (not-user defined).
func (ing Ingress) FindMatchingRule(hostname, path string) (*Rule, int) {
func (ing Ingress) FindMatchingRule(hostname, path string, cfJumpDestinationHeader string) (*Rule, int) {
// The hostname might contain port. We only want to compare the host part with the rule
host, _, err := net.SplitHostPort(hostname)
if err == nil {
hostname = host
}
derivedHostName := hostname
for i, rule := range ing.InternalRules {
if rule.Matches(hostname, path) {
// Local rule matches return a negative rule index to distiguish local rules from user-defined rules in logs
Expand All @@ -52,7 +52,15 @@ func (ing Ingress) FindMatchingRule(hostname, path string) (*Rule, int) {
}
}
for i, rule := range ing.Rules {
if rule.Matches(hostname, path) {
// If bastion mode is turned on and request is made as bastion, attempt
// to match a rule where jump destination header matches the hostname
if rule.Config.BastionMode && len(cfJumpDestinationHeader) > 0 {
jumpDestinationUri, err := url.Parse(cfJumpDestinationHeader)
if err == nil {
derivedHostName = jumpDestinationUri.Hostname()
}
}
if rule.Matches(derivedHostName, path) {
return &rule, i
}
}
Expand Down Expand Up @@ -265,6 +273,7 @@ func validateIngress(ingress []config.UnvalidatedIngressRule, defaults OriginReq
}
srv := newStatusCode(statusCode)
service = &srv

} else if r.Service == HelloWorldFlag || r.Service == HelloWorldService {
service = new(helloWorld)
} else if r.Service == ServiceSocksProxy {
Expand All @@ -284,12 +293,21 @@ func validateIngress(ingress []config.UnvalidatedIngressRule, defaults OriginReq
}

service = newSocksProxyOverWSService(accessPolicy)
} else if r.Service == ServiceBastion || cfg.BastionMode {
} else if r.Service == config.BastionFlag || cfg.BastionMode {
// Bastion mode will always start a Websocket proxy server, which will
// overwrite the localService.URL field when `start` is called. So,
// leave the URL field empty for now.
cfg.BastionMode = true
service = newBastionService()

if cfg.BastionMode && r.Service != config.BastionFlag {
u, err := url.Parse(r.Service)
if err != nil {
return Ingress{}, err
}
service = newBastionServiceWithDest(u)
} else {
service = newBastionService()
}
} else {
// Validate URL services
u, err := url.Parse(r.Service)
Expand Down
Loading