TUN-5362: Adjust route ip commands to be aware of virtual networks

This commit is contained in:
Nuno Diegues 2021-11-29 12:00:31 +00:00
parent eec6b87eea
commit 571380b3f5
7 changed files with 305 additions and 104 deletions

View File

@ -1,8 +1,6 @@
package tunnel package tunnel
import ( import (
"net"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/cloudflare/cloudflared/teamnet" "github.com/cloudflare/cloudflared/teamnet"
@ -26,18 +24,18 @@ func (sc *subcommandContext) addRoute(newRoute teamnet.NewRoute) (teamnet.Route,
return client.AddRoute(newRoute) return client.AddRoute(newRoute)
} }
func (sc *subcommandContext) deleteRoute(network net.IPNet) error { func (sc *subcommandContext) deleteRoute(params teamnet.DeleteRouteParams) error {
client, err := sc.client() client, err := sc.client()
if err != nil { if err != nil {
return errors.Wrap(err, noClientMsg) return errors.Wrap(err, noClientMsg)
} }
return client.DeleteRoute(network) return client.DeleteRoute(params)
} }
func (sc *subcommandContext) getRouteByIP(ip net.IP) (teamnet.DetailedRoute, error) { func (sc *subcommandContext) getRouteByIP(params teamnet.GetRouteByIpParams) (teamnet.DetailedRoute, error) {
client, err := sc.client() client, err := sc.client()
if err != nil { if err != nil {
return teamnet.DetailedRoute{}, errors.Wrap(err, noClientMsg) return teamnet.DetailedRoute{}, errors.Wrap(err, noClientMsg)
} }
return client.GetByIP(ip) return client.GetByIP(params)
} }

View File

@ -6,6 +6,7 @@ import (
"os" "os"
"text/tabwriter" "text/tabwriter"
"github.com/google/uuid"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil" "github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
@ -15,26 +16,45 @@ import (
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
var (
vnetFlag = &cli.StringFlag{
Name: "virtual-network",
Aliases: []string{"vn"},
Usage: "The ID or name of the virtual network to which the route is associated to.",
}
)
func buildRouteIPSubcommand() *cli.Command { func buildRouteIPSubcommand() *cli.Command {
return &cli.Command{ return &cli.Command{
Name: "ip", Name: "ip",
Usage: "Configure and query Cloudflare WARP routing to services or private networks available through this tunnel.", Usage: "Configure and query Cloudflare WARP routing to private IP networks made available through Cloudflare Tunnels.",
UsageText: "cloudflared tunnel [--config FILEPATH] route COMMAND [arguments...]", UsageText: "cloudflared tunnel [--config FILEPATH] route COMMAND [arguments...]",
Description: `cloudflared can provision private routes from any IP space to origins in your corporate network. Description: `cloudflared can provision routes for any IP space in your corporate network. Users enrolled in
Users enrolled in your Cloudflare for Teams organization can reach those routes through the your Cloudflare for Teams organization can reach those IPs through the Cloudflare WARP
Cloudflare WARP client. You can also build rules to determine who can reach certain routes.`, client. You can then configure L7/L4 filtering on https://dash.teams.cloudflare.com to
determine who can reach certain routes.
By default IP routes all exist within a single virtual network. If you use the same IP
space(s) in different physical private networks, all meant to be reachable via IP routes,
then you have to manage the ambiguous IP routes by associating them to virtual networks.
See "cloudflared tunnel network --help" for more information.`,
Subcommands: []*cli.Command{ Subcommands: []*cli.Command{
{ {
Name: "add", Name: "add",
Action: cliutil.ConfiguredAction(addRouteCommand), Action: cliutil.ConfiguredAction(addRouteCommand),
Usage: "Add any new network to the routing table reachable via the tunnel", Usage: "Add a new network to the routing table reachable via a Tunnel",
UsageText: "cloudflared tunnel [--config FILEPATH] route ip add [CIDR] [TUNNEL] [COMMENT?]", UsageText: "cloudflared tunnel [--config FILEPATH] route ip add [flags] [CIDR] [TUNNEL] [COMMENT?]",
Description: `Adds any network route space (represented as a CIDR) to your routing table. Description: `Adds a network IP route space (represented as a CIDR) to your routing table.
That network space becomes reachable for requests egressing from a user's machine That network IP space becomes reachable for requests egressing from a user's machine
as long as it is using Cloudflare WARP client and is enrolled in the same account as long as it is using Cloudflare WARP client and is enrolled in the same account
that is running the tunnel chosen here. Further, those requests will be proxied to that is running the Tunnel chosen here. Further, those requests will be proxied to
the specified tunnel, and reach an IP in the given CIDR, as long as that IP is the specified Tunnel, and reach an IP in the given CIDR, as long as that IP is
reachable from the tunnel.`, reachable from cloudflared.
If the CIDR exists in more than one private network, to be connected with Cloudflare
Tunnels, then you have to manage those IP routes with virtual networks (see
"cloudflared tunnel network --help)". In those cases, you then have to tell
which virtual network's routing table you want to add the route to with:
"cloudflared tunnel route ip add --virtual-network [ID/name] [CIDR] [TUNNEL]".`,
Flags: []cli.Flag{vnetFlag},
}, },
{ {
Name: "show", Name: "show",
@ -49,17 +69,22 @@ reachable from the tunnel.`,
Name: "delete", Name: "delete",
Action: cliutil.ConfiguredAction(deleteRouteCommand), Action: cliutil.ConfiguredAction(deleteRouteCommand),
Usage: "Delete a row from your organization's private routing table", Usage: "Delete a row from your organization's private routing table",
UsageText: "cloudflared tunnel [--config FILEPATH] route ip delete [CIDR]", UsageText: "cloudflared tunnel [--config FILEPATH] route ip delete [flags] [CIDR]",
Description: `Deletes the row for a given CIDR from your routing table. That portion Description: `Deletes the row for a given CIDR from your routing table. That portion of your network
of your network will no longer be reachable by the WARP clients.`, will no longer be reachable by the WARP clients. Note that if you use virtual
networks, then you have to tell which virtual network whose routing table you
have a row deleted from.`,
Flags: []cli.Flag{vnetFlag},
}, },
{ {
Name: "get", Name: "get",
Action: cliutil.ConfiguredAction(getRouteByIPCommand), Action: cliutil.ConfiguredAction(getRouteByIPCommand),
Usage: "Check which row of the routing table matches a given IP.", Usage: "Check which row of the routing table matches a given IP.",
UsageText: "cloudflared tunnel [--config FILEPATH] route ip get [IP]", UsageText: "cloudflared tunnel [--config FILEPATH] route ip get [flags] [IP]",
Description: `Checks which row of the routing table will be used to proxy a given IP. Description: `Checks which row of the routing table will be used to proxy a given IP. This helps check
This helps check and validate your config.`, and validate your config. Note that if you use virtual networks, then you have
to tell which virtual network whose routing table you want to use.`,
Flags: []cli.Flag{vnetFlag},
}, },
}, },
} }
@ -112,7 +137,9 @@ func addRouteCommand(c *cli.Context) error {
if c.NArg() < 2 { if c.NArg() < 2 {
return errors.New("You must supply at least 2 arguments, first the network you wish to route (in CIDR form e.g. 1.2.3.4/32) and then the tunnel ID to proxy with") return errors.New("You must supply at least 2 arguments, first the network you wish to route (in CIDR form e.g. 1.2.3.4/32) and then the tunnel ID to proxy with")
} }
args := c.Args() args := c.Args()
_, network, err := net.ParseCIDR(args.Get(0)) _, network, err := net.ParseCIDR(args.Get(0))
if err != nil { if err != nil {
return errors.Wrap(err, "Invalid network CIDR") return errors.Wrap(err, "Invalid network CIDR")
@ -120,19 +147,32 @@ func addRouteCommand(c *cli.Context) error {
if network == nil { if network == nil {
return errors.New("Invalid network CIDR") return errors.New("Invalid network CIDR")
} }
tunnelRef := args.Get(1) tunnelRef := args.Get(1)
tunnelID, err := sc.findID(tunnelRef) tunnelID, err := sc.findID(tunnelRef)
if err != nil { if err != nil {
return errors.Wrap(err, "Invalid tunnel") return errors.Wrap(err, "Invalid tunnel")
} }
comment := "" comment := ""
if c.NArg() >= 3 { if c.NArg() >= 3 {
comment = args.Get(2) comment = args.Get(2)
} }
var vnetId *uuid.UUID
if c.IsSet(vnetFlag.Name) {
id, err := getVnetId(sc, c.String(vnetFlag.Name))
if err != nil {
return err
}
vnetId = &id
}
_, err = sc.addRoute(teamnet.NewRoute{ _, err = sc.addRoute(teamnet.NewRoute{
Comment: comment, Comment: comment,
Network: *network, Network: *network,
TunnelID: tunnelID, TunnelID: tunnelID,
VNetID: vnetId,
}) })
if err != nil { if err != nil {
return errors.Wrap(err, "API error") return errors.Wrap(err, "API error")
@ -146,9 +186,11 @@ func deleteRouteCommand(c *cli.Context) error {
if err != nil { if err != nil {
return err return err
} }
if c.NArg() != 1 { if c.NArg() != 1 {
return errors.New("You must supply exactly one argument, the network whose route you want to delete (in CIDR form e.g. 1.2.3.4/32)") return errors.New("You must supply exactly one argument, the network whose route you want to delete (in CIDR form e.g. 1.2.3.4/32)")
} }
_, network, err := net.ParseCIDR(c.Args().First()) _, network, err := net.ParseCIDR(c.Args().First())
if err != nil { if err != nil {
return errors.Wrap(err, "Invalid network CIDR") return errors.Wrap(err, "Invalid network CIDR")
@ -156,7 +198,20 @@ func deleteRouteCommand(c *cli.Context) error {
if network == nil { if network == nil {
return errors.New("Invalid network CIDR") return errors.New("Invalid network CIDR")
} }
if err := sc.deleteRoute(*network); err != nil {
params := teamnet.DeleteRouteParams{
Network: *network,
}
if c.IsSet(vnetFlag.Name) {
vnetId, err := getVnetId(sc, c.String(vnetFlag.Name))
if err != nil {
return err
}
params.VNetID = &vnetId
}
if err := sc.deleteRoute(params); err != nil {
return errors.Wrap(err, "API error") return errors.Wrap(err, "API error")
} }
fmt.Printf("Successfully deleted route for %s\n", network) fmt.Printf("Successfully deleted route for %s\n", network)
@ -177,7 +232,20 @@ func getRouteByIPCommand(c *cli.Context) error {
if ip == nil { if ip == nil {
return fmt.Errorf("Invalid IP %s", ipInput) return fmt.Errorf("Invalid IP %s", ipInput)
} }
route, err := sc.getRouteByIP(ip)
params := teamnet.GetRouteByIpParams{
Ip: ip,
}
if c.IsSet(vnetFlag.Name) {
vnetId, err := getVnetId(sc, c.String(vnetFlag.Name))
if err != nil {
return err
}
params.VNetID = &vnetId
}
route, err := sc.getRouteByIP(params)
if err != nil { if err != nil {
return errors.Wrap(err, "API error") return errors.Wrap(err, "API error")
} }
@ -202,7 +270,7 @@ func formatAndPrintRouteList(routes []*teamnet.DetailedRoute) {
defer writer.Flush() defer writer.Flush()
// Print column headers with tabbed columns // Print column headers with tabbed columns
_, _ = fmt.Fprintln(writer, "NETWORK\tCOMMENT\tTUNNEL ID\tTUNNEL NAME\tCREATED\tDELETED\t") _, _ = fmt.Fprintln(writer, "NETWORK\tVIRTUAL NET ID\tCOMMENT\tTUNNEL ID\tTUNNEL NAME\tCREATED\tDELETED\t")
// Loop through routes, create formatted string for each, and print using tabwriter // Loop through routes, create formatted string for each, and print using tabwriter
for _, route := range routes { for _, route := range routes {

View File

@ -18,6 +18,8 @@ import (
type Route struct { type Route struct {
Network CIDR `json:"network"` Network CIDR `json:"network"`
TunnelID uuid.UUID `json:"tunnel_id"` TunnelID uuid.UUID `json:"tunnel_id"`
// Optional field. When unset, it means the Route belongs to the default virtual network.
VNetID *uuid.UUID `json:"virtual_network_id,omitempty"`
Comment string `json:"comment"` Comment string `json:"comment"`
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
DeletedAt time.Time `json:"deleted_at"` DeletedAt time.Time `json:"deleted_at"`
@ -62,6 +64,8 @@ type NewRoute struct {
Network net.IPNet Network net.IPNet
TunnelID uuid.UUID TunnelID uuid.UUID
Comment string Comment string
// Optional field. If unset, backend will assume the default vnet for the account.
VNetID *uuid.UUID
} }
// MarshalJSON handles fields with non-JSON types (e.g. net.IPNet). // MarshalJSON handles fields with non-JSON types (e.g. net.IPNet).
@ -69,9 +73,11 @@ func (r NewRoute) MarshalJSON() ([]byte, error) {
return json.Marshal(&struct { return json.Marshal(&struct {
TunnelID uuid.UUID `json:"tunnel_id"` TunnelID uuid.UUID `json:"tunnel_id"`
Comment string `json:"comment"` Comment string `json:"comment"`
VNetID *uuid.UUID `json:"virtual_network_id,omitempty"`
}{ }{
TunnelID: r.TunnelID, TunnelID: r.TunnelID,
Comment: r.Comment, Comment: r.Comment,
VNetID: r.VNetID,
}) })
} }
@ -79,6 +85,8 @@ func (r NewRoute) MarshalJSON() ([]byte, error) {
type DetailedRoute struct { type DetailedRoute struct {
Network CIDR `json:"network"` Network CIDR `json:"network"`
TunnelID uuid.UUID `json:"tunnel_id"` TunnelID uuid.UUID `json:"tunnel_id"`
// Optional field. When unset, it means the DetailedRoute belongs to the default virtual network.
VNetID *uuid.UUID `json:"virtual_network_id,omitempty"`
Comment string `json:"comment"` Comment string `json:"comment"`
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
DeletedAt time.Time `json:"deleted_at"` DeletedAt time.Time `json:"deleted_at"`
@ -97,9 +105,15 @@ func (r DetailedRoute) TableString() string {
if !r.DeletedAt.IsZero() { if !r.DeletedAt.IsZero() {
deletedColumn = r.DeletedAt.Format(time.RFC3339) deletedColumn = r.DeletedAt.Format(time.RFC3339)
} }
vnetColumn := "default"
if r.VNetID != nil {
vnetColumn = r.VNetID.String()
}
return fmt.Sprintf( return fmt.Sprintf(
"%s\t%s\t%s\t%s\t%s\t%s\t", "%s\t%s\t%s\t%s\t%s\t%s\t%s\t",
r.Network.String(), r.Network.String(),
vnetColumn,
r.Comment, r.Comment,
r.TunnelID, r.TunnelID,
r.TunnelName, r.TunnelName,
@ -107,3 +121,15 @@ func (r DetailedRoute) TableString() string {
deletedColumn, deletedColumn,
) )
} }
type DeleteRouteParams struct {
Network net.IPNet
// Optional field. If unset, backend will assume the default vnet for the account.
VNetID *uuid.UUID
}
type GetRouteByIpParams struct {
Ip net.IP
// Optional field. If unset, backend will assume the default vnet for the account.
VNetID *uuid.UUID
}

View File

@ -12,14 +12,36 @@ import (
) )
func TestUnmarshalRoute(t *testing.T) { func TestUnmarshalRoute(t *testing.T) {
// Response from the teamnet route backend testCases := []struct {
data := `{ Json string
HasVnet bool
}{
{
`{
"network":"10.1.2.40/29", "network":"10.1.2.40/29",
"tunnel_id":"fba6ffea-807f-4e7a-a740-4184ee1b82c8", "tunnel_id":"fba6ffea-807f-4e7a-a740-4184ee1b82c8",
"comment":"test", "comment":"test",
"created_at":"2020-12-22T02:00:15.587008Z", "created_at":"2020-12-22T02:00:15.587008Z",
"deleted_at":null "deleted_at":null
}` }`,
false,
},
{
`{
"network":"10.1.2.40/29",
"tunnel_id":"fba6ffea-807f-4e7a-a740-4184ee1b82c8",
"comment":"test",
"created_at":"2020-12-22T02:00:15.587008Z",
"deleted_at":null,
"virtual_network_id":"38c95083-8191-4110-8339-3f438d44fdb9"
}`,
true,
},
}
for _, testCase := range testCases {
data := testCase.Json
var r Route var r Route
err := json.Unmarshal([]byte(data), &r) err := json.Unmarshal([]byte(data), &r)
@ -31,18 +53,48 @@ func TestUnmarshalRoute(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, CIDR(*cidr), r.Network) require.Equal(t, CIDR(*cidr), r.Network)
require.Equal(t, "test", r.Comment) require.Equal(t, "test", r.Comment)
if testCase.HasVnet {
require.Equal(t, uuid.MustParse("38c95083-8191-4110-8339-3f438d44fdb9"), *r.VNetID)
} else {
require.Nil(t, r.VNetID)
}
}
} }
func TestDetailedRouteJsonRoundtrip(t *testing.T) { func TestDetailedRouteJsonRoundtrip(t *testing.T) {
// Response from the teamnet route backend testCases := []struct {
data := `{ Json string
HasVnet bool
}{
{
`{
"network":"10.1.2.40/29", "network":"10.1.2.40/29",
"tunnel_id":"fba6ffea-807f-4e7a-a740-4184ee1b82c8", "tunnel_id":"fba6ffea-807f-4e7a-a740-4184ee1b82c8",
"comment":"test", "comment":"test",
"created_at":"2020-12-22T02:00:15.587008Z", "created_at":"2020-12-22T02:00:15.587008Z",
"deleted_at":"2021-01-14T05:01:42.183002Z", "deleted_at":"2021-01-14T05:01:42.183002Z",
"tunnel_name":"Mr. Tun" "tunnel_name":"Mr. Tun"
}` }`,
false,
},
{
`{
"network":"10.1.2.40/29",
"tunnel_id":"fba6ffea-807f-4e7a-a740-4184ee1b82c8",
"virtual_network_id":"38c95083-8191-4110-8339-3f438d44fdb9",
"comment":"test",
"created_at":"2020-12-22T02:00:15.587008Z",
"deleted_at":"2021-01-14T05:01:42.183002Z",
"tunnel_name":"Mr. Tun"
}`,
true,
},
}
for _, testCase := range testCases {
data := testCase.Json
var r DetailedRoute var r DetailedRoute
err := json.Unmarshal([]byte(data), &r) err := json.Unmarshal([]byte(data), &r)
@ -56,24 +108,42 @@ func TestDetailedRouteJsonRoundtrip(t *testing.T) {
require.Equal(t, "test", r.Comment) require.Equal(t, "test", r.Comment)
require.Equal(t, "Mr. Tun", r.TunnelName) require.Equal(t, "Mr. Tun", r.TunnelName)
if testCase.HasVnet {
require.Equal(t, uuid.MustParse("38c95083-8191-4110-8339-3f438d44fdb9"), *r.VNetID)
} else {
require.Nil(t, r.VNetID)
}
bytes, err := json.Marshal(r) bytes, err := json.Marshal(r)
require.NoError(t, err) require.NoError(t, err)
obtainedJson := string(bytes) obtainedJson := string(bytes)
data = strings.Replace(data, "\t", "", -1) data = strings.Replace(data, "\t", "", -1)
data = strings.Replace(data, "\n", "", -1) data = strings.Replace(data, "\n", "", -1)
require.Equal(t, data, obtainedJson) require.Equal(t, data, obtainedJson)
}
} }
func TestMarshalNewRoute(t *testing.T) { func TestMarshalNewRoute(t *testing.T) {
_, network, err := net.ParseCIDR("1.2.3.4/32") _, network, err := net.ParseCIDR("1.2.3.4/32")
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, network) require.NotNil(t, network)
newRoute := NewRoute{ vnetId := uuid.New()
newRoutes := []NewRoute{
{
Network: *network, Network: *network,
TunnelID: uuid.New(), TunnelID: uuid.New(),
Comment: "hi", Comment: "hi",
},
{
Network: *network,
TunnelID: uuid.New(),
Comment: "hi",
VNetID: &vnetId,
},
} }
for _, newRoute := range newRoutes {
// Test where receiver is struct // Test where receiver is struct
serialized, err := json.Marshal(newRoute) serialized, err := json.Marshal(newRoute)
require.NoError(t, err) require.NoError(t, err)
@ -83,6 +153,13 @@ func TestMarshalNewRoute(t *testing.T) {
serialized, err = json.Marshal(&newRoute) serialized, err = json.Marshal(&newRoute)
require.NoError(t, err) require.NoError(t, err)
require.True(t, strings.Contains(string(serialized), "tunnel_id")) require.True(t, strings.Contains(string(serialized), "tunnel_id"))
if newRoute.VNetID == nil {
require.False(t, strings.Contains(string(serialized), "virtual_network_id"))
} else {
require.True(t, strings.Contains(string(serialized), "virtual_network_id"))
}
}
} }
func TestRouteTableString(t *testing.T) { func TestRouteTableString(t *testing.T) {

View File

@ -35,6 +35,11 @@ var (
Name: "filter-comment-is", Name: "filter-comment-is",
Usage: "Show only routes with this comment.", Usage: "Show only routes with this comment.",
} }
filterVnet = cli.StringFlag{
Name: "filter-virtual-network-id",
Usage: "Show only routes that are attached to the given virtual network ID.",
}
// Flags contains all filter flags. // Flags contains all filter flags.
FilterFlags = []cli.Flag{ FilterFlags = []cli.Flag{
&filterDeleted, &filterDeleted,
@ -42,6 +47,7 @@ var (
&filterSubset, &filterSubset,
&filterSuperset, &filterSuperset,
&filterComment, &filterComment,
&filterVnet,
} }
) )
@ -82,11 +88,19 @@ func NewFromCLI(c *cli.Context) (*Filter, error) {
if tunnelID := c.String(filterTunnelID.Name); tunnelID != "" { if tunnelID := c.String(filterTunnelID.Name); tunnelID != "" {
u, err := uuid.Parse(tunnelID) u, err := uuid.Parse(tunnelID)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "Couldn't parse UUID from --filter-tunnel-id") return nil, errors.Wrapf(err, "Couldn't parse UUID from %s", filterTunnelID.Name)
} }
f.tunnelID(u) f.tunnelID(u)
} }
if vnetId := c.String(filterVnet.Name); vnetId != "" {
u, err := uuid.Parse(vnetId)
if err != nil {
return nil, errors.Wrapf(err, "Couldn't parse UUID from %s", filterVnet.Name)
}
f.vnetID(u)
}
if maxFetch := c.Int("max-fetch-size"); maxFetch > 0 { if maxFetch := c.Int("max-fetch-size"); maxFetch > 0 {
f.MaxFetchSize(uint(maxFetch)) f.MaxFetchSize(uint(maxFetch))
} }
@ -138,6 +152,10 @@ func (f *Filter) tunnelID(id uuid.UUID) {
f.queryParams.Set("tunnel_id", id.String()) f.queryParams.Set("tunnel_id", id.String())
} }
func (f *Filter) vnetID(id uuid.UUID) {
f.queryParams.Set("virtual_network_id", id.String())
}
func (f *Filter) MaxFetchSize(max uint) { func (f *Filter) MaxFetchSize(max uint) {
f.queryParams.Set("per_page", strconv.Itoa(int(max))) f.queryParams.Set("per_page", strconv.Itoa(int(max)))
} }

View File

@ -236,8 +236,8 @@ type Client interface {
// Teamnet endpoints // Teamnet endpoints
ListRoutes(filter *teamnet.Filter) ([]*teamnet.DetailedRoute, error) ListRoutes(filter *teamnet.Filter) ([]*teamnet.DetailedRoute, error)
AddRoute(newRoute teamnet.NewRoute) (teamnet.Route, error) AddRoute(newRoute teamnet.NewRoute) (teamnet.Route, error)
DeleteRoute(network net.IPNet) error DeleteRoute(params teamnet.DeleteRouteParams) error
GetByIP(ip net.IP) (teamnet.DetailedRoute, error) GetByIP(params teamnet.GetRouteByIpParams) (teamnet.DetailedRoute, error)
// Virtual Networks endpoints // Virtual Networks endpoints
CreateVirtualNetwork(newVnet vnet.NewVirtualNetwork) (vnet.VirtualNetwork, error) CreateVirtualNetwork(newVnet vnet.NewVirtualNetwork) (vnet.VirtualNetwork, error)

View File

@ -2,11 +2,11 @@ package tunnelstore
import ( import (
"io" "io"
"net"
"net/http" "net/http"
"net/url" "net/url"
"path" "path"
"github.com/google/uuid"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/cloudflare/cloudflared/teamnet" "github.com/cloudflare/cloudflared/teamnet"
@ -47,9 +47,11 @@ func (r *RESTClient) AddRoute(newRoute teamnet.NewRoute) (teamnet.Route, error)
} }
// DeleteRoute calls the Tunnelstore DELETE endpoint for a given route. // DeleteRoute calls the Tunnelstore DELETE endpoint for a given route.
func (r *RESTClient) DeleteRoute(network net.IPNet) error { func (r *RESTClient) DeleteRoute(params teamnet.DeleteRouteParams) error {
endpoint := r.baseEndpoints.accountRoutes endpoint := r.baseEndpoints.accountRoutes
endpoint.Path = path.Join(endpoint.Path, "network", url.PathEscape(network.String())) endpoint.Path = path.Join(endpoint.Path, "network", url.PathEscape(params.Network.String()))
setVnetParam(&endpoint, params.VNetID)
resp, err := r.sendRequest("DELETE", endpoint, nil) resp, err := r.sendRequest("DELETE", endpoint, nil)
if err != nil { if err != nil {
return errors.Wrap(err, "REST request failed") return errors.Wrap(err, "REST request failed")
@ -65,9 +67,11 @@ func (r *RESTClient) DeleteRoute(network net.IPNet) error {
} }
// GetByIP checks which route will proxy a given IP. // GetByIP checks which route will proxy a given IP.
func (r *RESTClient) GetByIP(ip net.IP) (teamnet.DetailedRoute, error) { func (r *RESTClient) GetByIP(params teamnet.GetRouteByIpParams) (teamnet.DetailedRoute, error) {
endpoint := r.baseEndpoints.accountRoutes endpoint := r.baseEndpoints.accountRoutes
endpoint.Path = path.Join(endpoint.Path, "ip", url.PathEscape(ip.String())) endpoint.Path = path.Join(endpoint.Path, "ip", url.PathEscape(params.Ip.String()))
setVnetParam(&endpoint, params.VNetID)
resp, err := r.sendRequest("GET", endpoint, nil) resp, err := r.sendRequest("GET", endpoint, nil)
if err != nil { if err != nil {
return teamnet.DetailedRoute{}, errors.Wrap(err, "REST request failed") return teamnet.DetailedRoute{}, errors.Wrap(err, "REST request failed")
@ -98,3 +102,13 @@ func parseDetailedRoute(body io.ReadCloser) (teamnet.DetailedRoute, error) {
err := parseResponse(body, &route) err := parseResponse(body, &route)
return route, err return route, err
} }
// setVnetParam overwrites the URL's query parameters with a query param to scope the Route action to a certain
// virtual network (if one is provided).
func setVnetParam(endpoint *url.URL, vnetID *uuid.UUID) {
queryParams := url.Values{}
if vnetID != nil {
queryParams.Set("virtual_network_id", vnetID.String())
}
endpoint.RawQuery = queryParams.Encode()
}