2020-12-22 02:06:46 +00:00
package tunnel
import (
"fmt"
"net"
"os"
"text/tabwriter"
2021-11-29 12:00:31 +00:00
"github.com/google/uuid"
2021-03-16 22:36:46 +00:00
"github.com/pkg/errors"
2021-12-27 14:56:50 +00:00
"github.com/urfave/cli/v2"
2021-03-16 22:36:46 +00:00
2021-12-27 14:56:50 +00:00
"github.com/cloudflare/cloudflared/cfapi"
2020-12-22 02:06:46 +00:00
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
2021-02-28 23:24:38 +00:00
"github.com/cloudflare/cloudflared/cmd/cloudflared/updater"
2020-12-22 02:06:46 +00:00
)
2021-11-29 12:00:31 +00:00
var (
vnetFlag = & cli . StringFlag {
2022-01-24 10:53:32 +00:00
Name : "vnet" ,
2021-11-29 12:00:31 +00:00
Aliases : [ ] string { "vn" } ,
Usage : "The ID or name of the virtual network to which the route is associated to." ,
}
2023-09-15 14:17:23 +00:00
routeAddError = errors . New ( "You must supply exactly one argument, the ID or CIDR of the route you want to delete" )
2021-11-29 12:00:31 +00:00
)
2020-12-22 02:06:46 +00:00
func buildRouteIPSubcommand ( ) * cli . Command {
return & cli . Command {
Name : "ip" ,
2021-11-29 12:00:31 +00:00
Usage : "Configure and query Cloudflare WARP routing to private IP networks made available through Cloudflare Tunnels." ,
2020-12-22 02:06:46 +00:00
UsageText : "cloudflared tunnel [--config FILEPATH] route COMMAND [arguments...]" ,
2021-11-29 12:00:31 +00:00
Description : ` cloudflared can provision routes for any IP space in your corporate network . Users enrolled in
your Cloudflare for Teams organization can reach those IPs through the Cloudflare WARP
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 .
2022-01-24 10:53:32 +00:00
See "cloudflared tunnel vnet --help" for more information . ` ,
2020-12-22 02:06:46 +00:00
Subcommands : [ ] * cli . Command {
{
Name : "add" ,
2021-03-16 22:36:46 +00:00
Action : cliutil . ConfiguredAction ( addRouteCommand ) ,
2021-11-29 12:00:31 +00:00
Usage : "Add a new network to the routing table reachable via a Tunnel" ,
UsageText : "cloudflared tunnel [--config FILEPATH] route ip add [flags] [CIDR] [TUNNEL] [COMMENT?]" ,
Description : ` Adds a network IP route space ( represented as a CIDR ) to your routing table .
That network IP space becomes reachable for requests egressing from a user ' s machine
2021-02-26 09:50:19 +00:00
as long as it is using Cloudflare WARP client and is enrolled in the same account
2021-11-29 12:00:31 +00:00
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
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
2022-01-24 10:53:32 +00:00
"cloudflared tunnel vnet --help)" . In those cases , you then have to tell
2021-11-29 12:00:31 +00:00
which virtual network ' s routing table you want to add the route to with :
2022-01-24 10:53:32 +00:00
"cloudflared tunnel route ip add --vnet [ID/name] [CIDR] [TUNNEL]" . ` ,
2021-11-29 12:00:31 +00:00
Flags : [ ] cli . Flag { vnetFlag } ,
2020-12-22 02:06:46 +00:00
} ,
{
2021-01-08 21:10:36 +00:00
Name : "show" ,
2021-02-26 01:21:42 +00:00
Aliases : [ ] string { "list" } ,
2021-03-16 22:36:46 +00:00
Action : cliutil . ConfiguredAction ( showRoutesCommand ) ,
2021-01-08 21:10:36 +00:00
Usage : "Show the routing table" ,
UsageText : "cloudflared tunnel [--config FILEPATH] route ip show [flags]" ,
2021-02-26 09:50:19 +00:00
Description : ` Shows your organization private routing table. You can use flags to filter the results. ` ,
2021-01-26 11:58:56 +00:00
Flags : showRoutesFlags ( ) ,
2020-12-29 17:51:42 +00:00
} ,
{
2021-03-16 22:36:46 +00:00
Name : "delete" ,
Action : cliutil . ConfiguredAction ( deleteRouteCommand ) ,
Usage : "Delete a row from your organization's private routing table" ,
2023-09-15 14:17:23 +00:00
UsageText : "cloudflared tunnel [--config FILEPATH] route ip delete [flags] [Route ID or CIDR]" ,
Description : ` Deletes the row for the given route ID from your routing table . That portion of your network
will no longer be reachable . ` ,
2021-11-29 12:00:31 +00:00
Flags : [ ] cli . Flag { vnetFlag } ,
2020-12-22 02:06:46 +00:00
} ,
2021-01-05 23:55:18 +00:00
{
2021-01-08 21:10:36 +00:00
Name : "get" ,
2021-03-16 22:36:46 +00:00
Action : cliutil . ConfiguredAction ( getRouteByIPCommand ) ,
2021-02-26 09:50:19 +00:00
Usage : "Check which row of the routing table matches a given IP." ,
2021-11-29 12:00:31 +00:00
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 . This helps check
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 } ,
2021-01-05 23:55:18 +00:00
} ,
2020-12-22 02:06:46 +00:00
} ,
}
}
2021-01-26 11:58:56 +00:00
func showRoutesFlags ( ) [ ] cli . Flag {
flags := make ( [ ] cli . Flag , 0 )
2021-12-27 14:56:50 +00:00
flags = append ( flags , cfapi . IpRouteFilterFlags ... )
2021-01-26 11:58:56 +00:00
flags = append ( flags , outputFormatFlag )
return flags
}
2020-12-22 02:06:46 +00:00
func showRoutesCommand ( c * cli . Context ) error {
sc , err := newSubcommandContext ( c )
if err != nil {
return err
}
2021-12-27 14:56:50 +00:00
filter , err := cfapi . NewIpRouteFilterFromCLI ( c )
2020-12-22 02:06:46 +00:00
if err != nil {
return errors . Wrap ( err , "invalid config for routing filters" )
}
2021-02-28 23:24:38 +00:00
warningChecker := updater . StartWarningCheck ( c )
defer warningChecker . LogWarningIfAny ( sc . log )
2020-12-22 02:06:46 +00:00
routes , err := sc . listRoutes ( filter )
if err != nil {
return err
}
if outputFormat := c . String ( outputFormatFlag . Name ) ; outputFormat != "" {
return renderOutput ( outputFormat , routes )
}
if len ( routes ) > 0 {
formatAndPrintRouteList ( routes )
} else {
2021-11-26 12:37:54 +00:00
fmt . Println ( "No routes were found for the given filter flags. You can use 'cloudflared tunnel route ip add' to add a route." )
2020-12-22 02:06:46 +00:00
}
2021-02-28 23:24:38 +00:00
2020-12-22 02:06:46 +00:00
return nil
}
func addRouteCommand ( c * cli . Context ) error {
sc , err := newSubcommandContext ( c )
if err != nil {
return err
}
if c . NArg ( ) < 2 {
2020-12-29 17:51:42 +00:00
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" )
2020-12-22 02:06:46 +00:00
}
2021-11-29 12:00:31 +00:00
2020-12-22 02:06:46 +00:00
args := c . Args ( )
2021-11-29 12:00:31 +00:00
2020-12-22 02:06:46 +00:00
_ , network , err := net . ParseCIDR ( args . Get ( 0 ) )
if err != nil {
return errors . Wrap ( err , "Invalid network CIDR" )
}
if network == nil {
return errors . New ( "Invalid network CIDR" )
}
2021-11-29 12:00:31 +00:00
2020-12-22 02:06:46 +00:00
tunnelRef := args . Get ( 1 )
tunnelID , err := sc . findID ( tunnelRef )
if err != nil {
return errors . Wrap ( err , "Invalid tunnel" )
}
2021-11-29 12:00:31 +00:00
2020-12-22 02:06:46 +00:00
comment := ""
if c . NArg ( ) >= 3 {
comment = args . Get ( 2 )
}
2021-11-29 12:00:31 +00:00
var vnetId * uuid . UUID
if c . IsSet ( vnetFlag . Name ) {
id , err := getVnetId ( sc , c . String ( vnetFlag . Name ) )
if err != nil {
return err
}
vnetId = & id
}
2021-12-27 14:56:50 +00:00
_ , err = sc . addRoute ( cfapi . NewRoute {
2020-12-22 02:06:46 +00:00
Comment : comment ,
Network : * network ,
TunnelID : tunnelID ,
2021-11-29 12:00:31 +00:00
VNetID : vnetId ,
2020-12-22 02:06:46 +00:00
} )
if err != nil {
return errors . Wrap ( err , "API error" )
}
fmt . Printf ( "Successfully added route for %s over tunnel %s\n" , network , tunnelID )
return nil
}
2020-12-29 17:51:42 +00:00
func deleteRouteCommand ( c * cli . Context ) error {
sc , err := newSubcommandContext ( c )
if err != nil {
return err
}
2021-11-29 12:00:31 +00:00
2020-12-29 17:51:42 +00:00
if c . NArg ( ) != 1 {
2023-09-15 14:17:23 +00:00
return routeAddError
2020-12-29 17:51:42 +00:00
}
2021-11-29 12:00:31 +00:00
2023-09-15 14:17:23 +00:00
var routeId uuid . UUID
routeId , err = uuid . Parse ( c . Args ( ) . First ( ) )
2020-12-29 17:51:42 +00:00
if err != nil {
2023-09-15 14:17:23 +00:00
_ , network , err := net . ParseCIDR ( c . Args ( ) . First ( ) )
if err != nil || network == nil {
return routeAddError
}
2021-11-29 12:00:31 +00:00
2023-09-15 14:17:23 +00:00
var vnetId * uuid . UUID
if c . IsSet ( vnetFlag . Name ) {
id , err := getVnetId ( sc , c . String ( vnetFlag . Name ) )
if err != nil {
return err
}
vnetId = & id
}
2021-11-29 12:00:31 +00:00
2023-09-15 14:17:23 +00:00
routeId , err = sc . getRouteId ( * network , vnetId )
2021-11-29 12:00:31 +00:00
if err != nil {
return err
}
}
2023-09-15 14:17:23 +00:00
if err := sc . deleteRoute ( routeId ) ; err != nil {
2020-12-29 17:51:42 +00:00
return errors . Wrap ( err , "API error" )
}
2023-09-15 14:17:23 +00:00
fmt . Printf ( "Successfully deleted route with ID %s\n" , routeId )
2020-12-29 17:51:42 +00:00
return nil
}
2021-01-05 23:55:18 +00:00
func getRouteByIPCommand ( c * cli . Context ) error {
sc , err := newSubcommandContext ( c )
if err != nil {
return err
}
if c . NArg ( ) != 1 {
return errors . New ( "You must supply exactly one argument, an IP whose route will be queried (e.g. 1.2.3.4 or 2001:0db8:::7334)" )
}
ipInput := c . Args ( ) . First ( )
ip := net . ParseIP ( ipInput )
if ip == nil {
return fmt . Errorf ( "Invalid IP %s" , ipInput )
}
2021-11-29 12:00:31 +00:00
2021-12-27 14:56:50 +00:00
params := cfapi . GetRouteByIpParams {
2021-11-29 12:00:31 +00:00
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 )
2021-01-05 23:55:18 +00:00
if err != nil {
return errors . Wrap ( err , "API error" )
}
if route . IsZero ( ) {
fmt . Printf ( "No route matches the IP %s\n" , ip )
} else {
2021-12-27 14:56:50 +00:00
formatAndPrintRouteList ( [ ] * cfapi . DetailedRoute { & route } )
2021-01-05 23:55:18 +00:00
}
return nil
}
2021-12-27 14:56:50 +00:00
func formatAndPrintRouteList ( routes [ ] * cfapi . DetailedRoute ) {
2020-12-22 02:06:46 +00:00
const (
minWidth = 0
tabWidth = 8
padding = 1
padChar = ' '
flags = 0
)
writer := tabwriter . NewWriter ( os . Stdout , minWidth , tabWidth , padding , padChar , flags )
defer writer . Flush ( )
// Print column headers with tabbed columns
2023-09-15 14:17:23 +00:00
_ , _ = fmt . Fprintln ( writer , "ID\tNETWORK\tVIRTUAL NET ID\tCOMMENT\tTUNNEL ID\tTUNNEL NAME\tCREATED\tDELETED\t" )
2020-12-22 02:06:46 +00:00
// Loop through routes, create formatted string for each, and print using tabwriter
for _ , route := range routes {
formattedStr := route . TableString ( )
_ , _ = fmt . Fprintln ( writer , formattedStr )
}
}