2020-08-18 21:54:05 +00:00
|
|
|
package tunnel
|
|
|
|
|
|
|
|
import (
|
2020-11-23 21:36:16 +00:00
|
|
|
"encoding/base64"
|
|
|
|
"flag"
|
|
|
|
"fmt"
|
2020-08-18 21:54:05 +00:00
|
|
|
"reflect"
|
|
|
|
"testing"
|
2020-12-04 11:06:13 +00:00
|
|
|
"time"
|
2020-08-18 21:54:05 +00:00
|
|
|
|
2021-03-23 14:30:43 +00:00
|
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/pkg/errors"
|
2021-03-19 23:26:51 +00:00
|
|
|
"github.com/rs/zerolog"
|
2022-02-22 15:51:43 +00:00
|
|
|
"github.com/stretchr/testify/assert"
|
2021-03-23 14:30:43 +00:00
|
|
|
"github.com/urfave/cli/v2"
|
2021-03-19 23:26:51 +00:00
|
|
|
|
2021-12-27 14:56:50 +00:00
|
|
|
"github.com/cloudflare/cloudflared/cfapi"
|
2020-11-23 21:36:16 +00:00
|
|
|
"github.com/cloudflare/cloudflared/connection"
|
2023-04-12 16:43:38 +00:00
|
|
|
"github.com/cloudflare/cloudflared/credentials"
|
2020-08-18 21:54:05 +00:00
|
|
|
)
|
|
|
|
|
2020-11-23 21:36:16 +00:00
|
|
|
type mockFileSystem struct {
|
|
|
|
rf func(string) ([]byte, error)
|
|
|
|
vfp func(string) bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fs mockFileSystem) validFilePath(path string) bool {
|
|
|
|
return fs.vfp(path)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fs mockFileSystem) readFile(filePath string) ([]byte, error) {
|
|
|
|
return fs.rf(filePath)
|
|
|
|
}
|
|
|
|
|
|
|
|
func Test_subcommandContext_findCredentials(t *testing.T) {
|
|
|
|
type fields struct {
|
|
|
|
c *cli.Context
|
2020-11-25 06:55:13 +00:00
|
|
|
log *zerolog.Logger
|
2020-11-23 21:36:16 +00:00
|
|
|
fs fileSystem
|
2021-12-27 14:56:50 +00:00
|
|
|
tunnelstoreClient cfapi.Client
|
2023-04-12 16:43:38 +00:00
|
|
|
userCredential *credentials.User
|
2020-11-23 21:36:16 +00:00
|
|
|
}
|
|
|
|
type args struct {
|
|
|
|
tunnelID uuid.UUID
|
|
|
|
}
|
|
|
|
oldCertPath := "old_cert.json"
|
|
|
|
newCertPath := "new_cert.json"
|
|
|
|
accountTag := "0000d4d14e84bd4ae5a6a02e0000ac63"
|
|
|
|
secret := []byte{211, 79, 177, 245, 179, 194, 152, 127, 140, 71, 18, 46, 183, 209, 10, 24, 192, 150, 55, 249, 211, 16, 167, 30, 113, 51, 152, 168, 72, 100, 205, 144}
|
|
|
|
secretB64 := base64.StdEncoding.EncodeToString(secret)
|
|
|
|
tunnelID := uuid.MustParse("df5ed608-b8b4-4109-89f3-9f2cf199df64")
|
|
|
|
name := "mytunnel"
|
|
|
|
|
|
|
|
fs := mockFileSystem{
|
|
|
|
rf: func(filePath string) ([]byte, error) {
|
|
|
|
if filePath == oldCertPath {
|
|
|
|
// An old credentials file created before TUN-3581 added the new fields
|
|
|
|
return []byte(fmt.Sprintf(`{"AccountTag":"%s","TunnelSecret":"%s"}`, accountTag, secretB64)), nil
|
|
|
|
}
|
|
|
|
if filePath == newCertPath {
|
|
|
|
// A new credentials file created after TUN-3581 with its new fields.
|
|
|
|
return []byte(fmt.Sprintf(`{"AccountTag":"%s","TunnelSecret":"%s","TunnelID":"%s","TunnelName":"%s"}`, accountTag, secretB64, tunnelID, name)), nil
|
|
|
|
}
|
|
|
|
return nil, errors.New("file not found")
|
|
|
|
},
|
|
|
|
vfp: func(string) bool { return true },
|
|
|
|
}
|
2020-11-25 06:55:13 +00:00
|
|
|
log := zerolog.Nop()
|
2020-11-23 21:36:16 +00:00
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
fields fields
|
|
|
|
args args
|
|
|
|
want connection.Credentials
|
|
|
|
wantErr bool
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "Filepath given leads to old credentials file",
|
|
|
|
fields: fields{
|
2020-11-25 06:55:13 +00:00
|
|
|
log: &log,
|
|
|
|
fs: fs,
|
2020-11-23 21:36:16 +00:00
|
|
|
c: func() *cli.Context {
|
|
|
|
flagSet := flag.NewFlagSet("test0", flag.PanicOnError)
|
|
|
|
flagSet.String(CredFileFlag, oldCertPath, "")
|
|
|
|
c := cli.NewContext(cli.NewApp(), flagSet, nil)
|
2020-11-25 06:55:13 +00:00
|
|
|
_ = c.Set(CredFileFlag, oldCertPath)
|
2020-11-23 21:36:16 +00:00
|
|
|
return c
|
|
|
|
}(),
|
|
|
|
},
|
|
|
|
args: args{
|
|
|
|
tunnelID: tunnelID,
|
|
|
|
},
|
|
|
|
want: connection.Credentials{
|
|
|
|
AccountTag: accountTag,
|
|
|
|
TunnelID: tunnelID,
|
|
|
|
TunnelSecret: secret,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Filepath given leads to new credentials file",
|
|
|
|
fields: fields{
|
2020-11-25 06:55:13 +00:00
|
|
|
log: &log,
|
|
|
|
fs: fs,
|
2020-11-23 21:36:16 +00:00
|
|
|
c: func() *cli.Context {
|
|
|
|
flagSet := flag.NewFlagSet("test0", flag.PanicOnError)
|
|
|
|
flagSet.String(CredFileFlag, newCertPath, "")
|
|
|
|
c := cli.NewContext(cli.NewApp(), flagSet, nil)
|
2020-11-25 06:55:13 +00:00
|
|
|
_ = c.Set(CredFileFlag, newCertPath)
|
2020-11-23 21:36:16 +00:00
|
|
|
return c
|
|
|
|
}(),
|
|
|
|
},
|
|
|
|
args: args{
|
|
|
|
tunnelID: tunnelID,
|
|
|
|
},
|
|
|
|
want: connection.Credentials{
|
|
|
|
AccountTag: accountTag,
|
|
|
|
TunnelID: tunnelID,
|
|
|
|
TunnelSecret: secret,
|
|
|
|
},
|
|
|
|
},
|
2021-09-03 15:01:45 +00:00
|
|
|
{
|
|
|
|
name: "TUNNEL_CRED_CONTENTS given contains old credentials contents",
|
|
|
|
fields: fields{
|
|
|
|
log: &log,
|
|
|
|
fs: fs,
|
|
|
|
c: func() *cli.Context {
|
|
|
|
flagSet := flag.NewFlagSet("test0", flag.PanicOnError)
|
|
|
|
flagSet.String(CredContentsFlag, "", "")
|
|
|
|
c := cli.NewContext(cli.NewApp(), flagSet, nil)
|
|
|
|
_ = c.Set(CredContentsFlag, fmt.Sprintf(`{"AccountTag":"%s","TunnelSecret":"%s"}`, accountTag, secretB64))
|
|
|
|
return c
|
|
|
|
}(),
|
|
|
|
},
|
|
|
|
args: args{
|
|
|
|
tunnelID: tunnelID,
|
|
|
|
},
|
|
|
|
want: connection.Credentials{
|
|
|
|
AccountTag: accountTag,
|
|
|
|
TunnelID: tunnelID,
|
|
|
|
TunnelSecret: secret,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "TUNNEL_CRED_CONTENTS given contains new credentials contents",
|
|
|
|
fields: fields{
|
|
|
|
log: &log,
|
|
|
|
fs: fs,
|
|
|
|
c: func() *cli.Context {
|
|
|
|
flagSet := flag.NewFlagSet("test0", flag.PanicOnError)
|
|
|
|
flagSet.String(CredContentsFlag, "", "")
|
|
|
|
c := cli.NewContext(cli.NewApp(), flagSet, nil)
|
|
|
|
_ = c.Set(CredContentsFlag, fmt.Sprintf(`{"AccountTag":"%s","TunnelSecret":"%s","TunnelID":"%s","TunnelName":"%s"}`, accountTag, secretB64, tunnelID, name))
|
|
|
|
return c
|
|
|
|
}(),
|
|
|
|
},
|
|
|
|
args: args{
|
|
|
|
tunnelID: tunnelID,
|
|
|
|
},
|
|
|
|
want: connection.Credentials{
|
|
|
|
AccountTag: accountTag,
|
|
|
|
TunnelID: tunnelID,
|
|
|
|
TunnelSecret: secret,
|
|
|
|
},
|
|
|
|
},
|
2020-11-23 21:36:16 +00:00
|
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
sc := &subcommandContext{
|
|
|
|
c: tt.fields.c,
|
2020-11-25 06:55:13 +00:00
|
|
|
log: tt.fields.log,
|
2020-11-23 21:36:16 +00:00
|
|
|
fs: tt.fields.fs,
|
|
|
|
tunnelstoreClient: tt.fields.tunnelstoreClient,
|
|
|
|
userCredential: tt.fields.userCredential,
|
|
|
|
}
|
|
|
|
got, err := sc.findCredentials(tt.args.tunnelID)
|
|
|
|
if (err != nil) != tt.wantErr {
|
|
|
|
t.Errorf("subcommandContext.findCredentials() error = %v, wantErr %v", err, tt.wantErr)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if !reflect.DeepEqual(got, tt.want) {
|
|
|
|
t.Errorf("subcommandContext.findCredentials() = %v, want %v", got, tt.want)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2020-12-04 11:06:13 +00:00
|
|
|
|
|
|
|
type deleteMockTunnelStore struct {
|
2021-12-27 14:56:50 +00:00
|
|
|
cfapi.Client
|
2020-12-04 11:06:13 +00:00
|
|
|
mockTunnels map[uuid.UUID]mockTunnelBehaviour
|
|
|
|
deletedTunnelIDs []uuid.UUID
|
|
|
|
}
|
|
|
|
|
|
|
|
type mockTunnelBehaviour struct {
|
2021-12-27 14:56:50 +00:00
|
|
|
tunnel cfapi.Tunnel
|
2020-12-04 11:06:13 +00:00
|
|
|
deleteErr error
|
|
|
|
cleanupErr error
|
|
|
|
}
|
|
|
|
|
|
|
|
func newDeleteMockTunnelStore(tunnels ...mockTunnelBehaviour) *deleteMockTunnelStore {
|
|
|
|
mockTunnels := make(map[uuid.UUID]mockTunnelBehaviour)
|
|
|
|
for _, tunnel := range tunnels {
|
|
|
|
mockTunnels[tunnel.tunnel.ID] = tunnel
|
|
|
|
}
|
|
|
|
return &deleteMockTunnelStore{
|
|
|
|
mockTunnels: mockTunnels,
|
|
|
|
deletedTunnelIDs: make([]uuid.UUID, 0),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-27 14:56:50 +00:00
|
|
|
func (d *deleteMockTunnelStore) GetTunnel(tunnelID uuid.UUID) (*cfapi.Tunnel, error) {
|
2020-12-04 11:06:13 +00:00
|
|
|
tunnel, ok := d.mockTunnels[tunnelID]
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("Couldn't find tunnel: %v", tunnelID)
|
|
|
|
}
|
|
|
|
return &tunnel.tunnel, nil
|
|
|
|
}
|
|
|
|
|
2022-03-22 12:46:07 +00:00
|
|
|
func (d *deleteMockTunnelStore) GetTunnelToken(tunnelID uuid.UUID) (string, error) {
|
|
|
|
return "token", nil
|
|
|
|
}
|
|
|
|
|
2020-12-04 11:06:13 +00:00
|
|
|
func (d *deleteMockTunnelStore) DeleteTunnel(tunnelID uuid.UUID) error {
|
|
|
|
tunnel, ok := d.mockTunnels[tunnelID]
|
|
|
|
if !ok {
|
|
|
|
return fmt.Errorf("Couldn't find tunnel: %v", tunnelID)
|
|
|
|
}
|
|
|
|
|
|
|
|
if tunnel.deleteErr != nil {
|
|
|
|
return tunnel.deleteErr
|
|
|
|
}
|
|
|
|
|
|
|
|
d.deletedTunnelIDs = append(d.deletedTunnelIDs, tunnelID)
|
|
|
|
tunnel.tunnel.DeletedAt = time.Now()
|
|
|
|
delete(d.mockTunnels, tunnelID)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-12-27 14:56:50 +00:00
|
|
|
func (d *deleteMockTunnelStore) CleanupConnections(tunnelID uuid.UUID, _ *cfapi.CleanupParams) error {
|
2020-12-04 11:06:13 +00:00
|
|
|
tunnel, ok := d.mockTunnels[tunnelID]
|
|
|
|
if !ok {
|
|
|
|
return fmt.Errorf("Couldn't find tunnel: %v", tunnelID)
|
|
|
|
}
|
|
|
|
return tunnel.cleanupErr
|
|
|
|
}
|
|
|
|
|
|
|
|
func Test_subcommandContext_Delete(t *testing.T) {
|
|
|
|
type fields struct {
|
|
|
|
c *cli.Context
|
2020-12-08 18:16:25 +00:00
|
|
|
log *zerolog.Logger
|
2020-12-04 11:06:13 +00:00
|
|
|
isUIEnabled bool
|
|
|
|
fs fileSystem
|
|
|
|
tunnelstoreClient *deleteMockTunnelStore
|
2023-04-12 16:43:38 +00:00
|
|
|
userCredential *credentials.User
|
2020-12-04 11:06:13 +00:00
|
|
|
}
|
|
|
|
type args struct {
|
|
|
|
tunnelIDs []uuid.UUID
|
|
|
|
}
|
|
|
|
newCertPath := "new_cert.json"
|
|
|
|
tunnelID1 := uuid.MustParse("df5ed608-b8b4-4109-89f3-9f2cf199df64")
|
|
|
|
tunnelID2 := uuid.MustParse("af5ed608-b8b4-4109-89f3-9f2cf199df64")
|
2020-12-08 18:16:25 +00:00
|
|
|
log := zerolog.Nop()
|
2020-12-04 11:06:13 +00:00
|
|
|
|
|
|
|
var tests = []struct {
|
|
|
|
name string
|
|
|
|
fields fields
|
|
|
|
args args
|
|
|
|
want []uuid.UUID
|
|
|
|
wantErr bool
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "clean up continues if credentials are not found",
|
|
|
|
fields: fields{
|
2020-12-08 18:16:25 +00:00
|
|
|
log: &log,
|
2020-12-04 11:06:13 +00:00
|
|
|
fs: mockFileSystem{
|
|
|
|
rf: func(filePath string) ([]byte, error) {
|
|
|
|
return nil, errors.New("file not found")
|
|
|
|
},
|
|
|
|
vfp: func(string) bool { return true },
|
|
|
|
},
|
|
|
|
c: func() *cli.Context {
|
|
|
|
flagSet := flag.NewFlagSet("test0", flag.PanicOnError)
|
|
|
|
flagSet.String(CredFileFlag, newCertPath, "")
|
|
|
|
c := cli.NewContext(cli.NewApp(), flagSet, nil)
|
2020-12-08 18:16:25 +00:00
|
|
|
_ = c.Set(CredFileFlag, newCertPath)
|
2020-12-04 11:06:13 +00:00
|
|
|
return c
|
|
|
|
}(),
|
|
|
|
tunnelstoreClient: newDeleteMockTunnelStore(
|
|
|
|
mockTunnelBehaviour{
|
2021-12-27 14:56:50 +00:00
|
|
|
tunnel: cfapi.Tunnel{ID: tunnelID1},
|
2020-12-04 11:06:13 +00:00
|
|
|
},
|
|
|
|
mockTunnelBehaviour{
|
2021-12-27 14:56:50 +00:00
|
|
|
tunnel: cfapi.Tunnel{ID: tunnelID2},
|
2020-12-04 11:06:13 +00:00
|
|
|
},
|
|
|
|
),
|
|
|
|
},
|
|
|
|
|
|
|
|
args: args{
|
|
|
|
tunnelIDs: []uuid.UUID{tunnelID1, tunnelID2},
|
|
|
|
},
|
|
|
|
want: []uuid.UUID{tunnelID1, tunnelID2},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
sc := &subcommandContext{
|
|
|
|
c: tt.fields.c,
|
2020-12-08 18:16:25 +00:00
|
|
|
log: tt.fields.log,
|
2020-12-04 11:06:13 +00:00
|
|
|
fs: tt.fields.fs,
|
|
|
|
tunnelstoreClient: tt.fields.tunnelstoreClient,
|
|
|
|
userCredential: tt.fields.userCredential,
|
|
|
|
}
|
|
|
|
err := sc.delete(tt.args.tunnelIDs)
|
|
|
|
if (err != nil) != tt.wantErr {
|
|
|
|
t.Errorf("subcommandContext.findCredentials() error = %v, wantErr %v", err, tt.wantErr)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
got := tt.fields.tunnelstoreClient.deletedTunnelIDs
|
|
|
|
if !reflect.DeepEqual(got, tt.want) {
|
|
|
|
t.Errorf("subcommandContext.findCredentials() = %v, want %v", got, tt.want)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2022-02-22 15:51:43 +00:00
|
|
|
|
|
|
|
func Test_subcommandContext_ValidateIngressCommand(t *testing.T) {
|
|
|
|
var tests = []struct {
|
|
|
|
name string
|
|
|
|
c *cli.Context
|
|
|
|
wantErr bool
|
|
|
|
expectedErr error
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "read a valid configuration from data",
|
|
|
|
c: func() *cli.Context {
|
|
|
|
data := `{ "warp-routing": {"enabled": true}, "originRequest" : {"connectTimeout": 10}, "ingress" : [ {"hostname": "test", "service": "https://localhost:8000" } , {"service": "http_status:404"} ]}`
|
|
|
|
flagSet := flag.NewFlagSet("json", flag.PanicOnError)
|
|
|
|
flagSet.String(ingressDataJSONFlagName, data, "")
|
|
|
|
c := cli.NewContext(cli.NewApp(), flagSet, nil)
|
|
|
|
_ = c.Set(ingressDataJSONFlagName, data)
|
|
|
|
return c
|
|
|
|
}(),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "read an invalid configuration with multiple mistakes",
|
|
|
|
c: func() *cli.Context {
|
|
|
|
data := `{ "ingress" : [ {"hostname": "test", "service": "localhost:8000" } , {"service": "http_status:invalid_status"} ]}`
|
|
|
|
flagSet := flag.NewFlagSet("json", flag.PanicOnError)
|
|
|
|
flagSet.String(ingressDataJSONFlagName, data, "")
|
|
|
|
c := cli.NewContext(cli.NewApp(), flagSet, nil)
|
|
|
|
_ = c.Set(ingressDataJSONFlagName, data)
|
|
|
|
return c
|
|
|
|
}(),
|
|
|
|
wantErr: true,
|
|
|
|
expectedErr: errors.New("Validation failed: localhost:8000 is an invalid address, please make sure it has a scheme and a hostname"),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
err := validateIngressCommand(tt.c, "")
|
|
|
|
if tt.wantErr {
|
|
|
|
assert.Equal(t, tt.expectedErr.Error(), err.Error())
|
|
|
|
} else {
|
|
|
|
assert.Nil(t, err)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|