diff --git a/.gitignore b/.gitignore index 7f7476169986078971aef995fd16883f6c7afa55..ba7f950e18c75f5fbed00dde0a0564d2509a67f1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ -bitmaskcfg *.swp *.swo +./bitmaskcfg diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/cmd/bitmaskcfg/cmd/genConfig.go b/cmd/bitmaskcfg/cmd/genConfig.go new file mode 100644 index 0000000000000000000000000000000000000000..beb47d0587176a85776a635b10ec2ded600940ef --- /dev/null +++ b/cmd/bitmaskcfg/cmd/genConfig.go @@ -0,0 +1,116 @@ +/* +Copyright © 2023 NAME HERE <EMAIL ADDRESS> +*/ +package cmd + +import ( + "flag" + "fmt" + "log" + "os" + "path/filepath" + "time" + + "0xacab.org/leap/bitmask-core/pkg/bootstrap" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "github.com/spf13/viper" +) + +var ( + appName string = filepath.Base(os.Args[0]) +) + +// genConfigCmd represents the genConfig command +var genConfigCmd = &cobra.Command{ + Use: "gen-config", + Short: "Generate a working configuration", + Long: `A longer description that spans multiple lines and likely contains examples +and usage of using your command. For example: + +Blah blah.`, + Run: func(cmd *cobra.Command, args []string) { + generateOpenVPNConfig() + }, +} + +func init() { + flag.String(host, "", "Host serving menshen API") + flag.String(proxy, "", "Proxy (SOCKS5)") + + flag.String(transport, "", "Filter gateway/bridges by transport (TCP/UDP)") + flag.String(port, "", "Filter gateway/bridges listening on this port") + flag.String(location, "", "Filter by location") + flag.String(introducerStr, "", "Use an obfuscated introducer") + + pflag.CommandLine.AddGoFlagSet(flag.CommandLine) + pflag.Parse() + viper.BindPFlags(pflag.CommandLine) + + rootCmd.AddCommand(genConfigCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // genConfigCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // genConfigCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} + +func generateOpenVPNConfig() { + location := viper.GetString(location) + port := viper.GetString(port) + transport = viper.GetString(transport) + + host := viper.GetString(host) + proxy := viper.GetString(proxy) + + cfg := bootstrap.NewConfig() + cfg.Host = host + cfg.Proxy = proxy + + if proxy != "" { + fmt.Println("using socks5:", proxy) + } + + start := time.Now() + + api := bootstrap.NewAPI(cfg) + + // begin by attempting to geolocate our base network (CC) + api.DoGeolocationLookup() + + // attempt to fetch service + if err := api.FetchService(); err != nil { + log.Fatal(err) + } + + // request gateways + if location != "" || port != "" || transport != "" { + params := &bootstrap.GatewayParams{} + params.Location = location + params.Port = port + params.Transport = transport + if err := api.FetchAllGateways(params); err != nil { + log.Fatal(err) + } + } else { + + if err := api.FetchAllGateways(nil); err != nil { + log.Fatal(err) + } + } + if err := api.FetchOpenVPNCert(); err != nil { + log.Fatal(err) + } + elapsed := time.Since(start) + log.Printf("bootstrap ok (took %s)\n", elapsed.Round(time.Second).String()) + vpnCfg, err := api.SerializeConfig() + if err != nil { + log.Fatal(err) + } + fmt.Printf("%s\n", vpnCfg) +} diff --git a/cmd/bitmaskcfg/cmd/introducer.go b/cmd/bitmaskcfg/cmd/introducer.go new file mode 100644 index 0000000000000000000000000000000000000000..070244333e7cc100ffbc8db5c3cafad72e2d9832 --- /dev/null +++ b/cmd/bitmaskcfg/cmd/introducer.go @@ -0,0 +1,65 @@ +/* +Copyright © 2023 NAME HERE <EMAIL ADDRESS> +*/ +package cmd + +import ( + "fmt" + "log" + "os" + + "0xacab.org/leap/bitmask-core/pkg/introducer" + qrcode "github.com/skip2/go-qrcode" + "github.com/spf13/cobra" +) + +// introducerCmd represents the introducer command +var introducerCmd = &cobra.Command{ + Use: "introducer", + Short: "A brief description of your command", + Long: `A longer description that spans multiple lines and likely contains examples +and usage of using your command. For example: + +Cobra is a CLI library for Go that empowers applications. +This application is a tool to generate the needed files +to quickly create a Cobra application.`, + Run: func(cmd *cobra.Command, args []string) { + genQRForIntroducer(args) + }, +} + +func init() { + rootCmd.AddCommand(introducerCmd) + // introducerCmd.PersistentFlags().String("qrgen", "", "Generate a QR for this introducer") + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // introducerCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // introducerCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} + +func genQRForIntroducer(args []string) { + if len(args) != 1 { + fmt.Println("bitmaskcfg introducer <URL>") + os.Exit(1) + } + url := args[0] + _, err := introducer.NewIntroducerFromURL(url) + if err != nil { + log.Fatalf("not a valid introducer: %v", err) + } + qrCode, err := qrcode.New(url, qrcode.Medium) + if err != nil { + log.Fatalf("Could not generate QR code: %v", err) + } + fmt.Println() + fmt.Println("You can share the introducer with this QR code") + fmt.Println() + fmt.Println(qrCode.ToSmallString(false)) + // TODO export to PNG too +} diff --git a/cmd/bitmaskcfg/cmd/locations.go b/cmd/bitmaskcfg/cmd/locations.go new file mode 100644 index 0000000000000000000000000000000000000000..8cf533a1c44f15f747eeaf9feb540e709745093c --- /dev/null +++ b/cmd/bitmaskcfg/cmd/locations.go @@ -0,0 +1,51 @@ +/* +Copyright © 2023 atanarjuat <atanarjuat@riseup.net> +*/ +package cmd + +import ( + "fmt" + "log" + + "0xacab.org/leap/bitmask-core/pkg/bootstrap" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// locationsCmd represents the locations command +var locationsCmd = &cobra.Command{ + Use: "locations", + Short: "List all known locations", + Long: `Fetches a list of all the locations from the given host.`, + Run: func(cmd *cobra.Command, args []string) { + locations := getLocationList() + for idx, loc := range locations { + fmt.Printf("%d. %s\n", idx+1, loc) + } + }, +} + +func init() { + rootCmd.AddCommand(locationsCmd) +} + +func getLocationList() []string { + host := viper.GetString(host) + introducer := viper.GetString(introducerStr) + + cfg := bootstrap.NewConfig() + cfg.Host = host + cfg.Introducer = introducer + + api := bootstrap.NewAPI(cfg) + if err := api.FetchService(); err != nil { + log.Fatal(err) + } + locationMap := api.Locations().(map[string]any) + + locations := []string{} + for locname := range locationMap { + locations = append(locations, locname) + } + return locations +} diff --git a/cmd/bitmaskcfg/cmd/pick.go b/cmd/bitmaskcfg/cmd/pick.go new file mode 100644 index 0000000000000000000000000000000000000000..322b3361dca040be70e920b5717b071b10925556 --- /dev/null +++ b/cmd/bitmaskcfg/cmd/pick.go @@ -0,0 +1,93 @@ +/* +Copyright © 2023 NAME HERE <EMAIL ADDRESS> +*/ +package cmd + +import ( + "fmt" + "log" + + "0xacab.org/leap/bitmask-core/pkg/bootstrap" + "0xacab.org/leap/bitmask-core/pkg/latency" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// pickCmd represents the pick command +var pickCmd = &cobra.Command{ + Use: "pick", + Short: "Pick the best location", + Long: `Perform a short comprobation (ICMP ping) and return the location that seems closer +to us in terms of latency.`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("Picking best location...") + candidates := getGatewayForEachLocation() + best := pickBest(candidates) + for l, ipaddr := range candidates { + if best == ipaddr { + fmt.Printf("best: %s (%s)\n", l, best) + break + } + } + }, +} + +type loc string +type ip string + +// getGatewayForEachLocation will call FetchAllGateways, and return one gateway +// for each location. No error handling is done, no randomization, and the order +// is always the first gateway found. +func getGatewayForEachLocation() map[loc]ip { + ips := make(map[loc]ip) + + host := viper.GetString(host) + cfg := bootstrap.NewConfig() + cfg.Host = host + + api := bootstrap.NewAPI(cfg) + if err := api.FetchService(); err != nil { + log.Fatal(err) + } + param := &bootstrap.GatewayParams{ + Transport: "udp", + Port: "1194", + } + if err := api.FetchAllGateways(param); err != nil { + log.Fatal(err) + } + locationMap := api.Locations().(map[string]any) + + for locname := range locationMap { + gateways, _ := api.GatewaysForLocation(locname) + // picking the first one - we probably want to randomize, + // or ensure we're not testing an unhealthy or overloaded endpoint. + ips[loc(locname)] = ip(gateways[0].IPAddr) + } + return ips +} + +// pickBest will select the best endpoint by the lower latency. +func pickBest(candidates map[loc]ip) ip { + endpoints := []string{} + for _, ipaddr := range candidates { + endpoints = append(endpoints, string(ipaddr)) + } + + best := latency.PickBestEndpointByLatency(endpoints) + return ip(best) +} + +func init() { + rootCmd.AddCommand(pickCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // pickCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // pickCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} diff --git a/cmd/bitmaskcfg/cmd/root.go b/cmd/bitmaskcfg/cmd/root.go new file mode 100644 index 0000000000000000000000000000000000000000..7ce2240f3b2645ef9b8c4d0e0f9840ef67349abb --- /dev/null +++ b/cmd/bitmaskcfg/cmd/root.go @@ -0,0 +1,73 @@ +/* +Copyright © Atarnajuat <atanarjuat@riseup.net> +*/ +package cmd + +import ( + "os" + + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var ( + // host is the variable for passing the base URL for the menshen API. + host = "host" + + // use this socks proxy to fetch data + proxy = "socks5-proxy" + + // introducer is an obfuscated introducer to contact the API + introducerStr = "introducer" + + location = "location" + port = "port" + transport = "transport" + + //cmdGen = "gen" + //cmdListGateways = "list-gateways" +) + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "bitmaskcfg", + Short: "Generates working configuration for Bitmask, the LEAP VPN Client", + Long: `bitmaskcfg is a CLI tool to fetch a working configuration +from a LEAP VPN service. + +This application can be used to generate an openvpn config file, +or to inspect the health of different endpoints.`, + // Uncomment the following line if your bare application + // has an action associated with it: + // Run: func(cmd *cobra.Command, args []string) { }, +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } +} + +func init() { + viper.SetEnvPrefix("BITMASKCFG") + viper.AutomaticEnv() + + // TODO host and proxy should be set as global (persistent?) cobra flags + /* + flag.String(host, "", "Host serving menshen API") + flag.String(proxy, "", "Proxy (SOCKS5)") + pflag.CommandLine.AddGoFlagSet(flag.CommandLine) + pflag.Parse() + viper.BindPFlags(pflag.CommandLine) + */ + + rootCmd.Flags().SortFlags = false + rootCmd.PersistentFlags().SortFlags = false + + // Cobra also supports local flags, which will only run + // when this action is called directly. + // rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} diff --git a/cmd/bitmaskcfg/main.go b/cmd/bitmaskcfg/main.go new file mode 100644 index 0000000000000000000000000000000000000000..9c35ca54357ecbfc3b593d893dfa8f16a68bc62d --- /dev/null +++ b/cmd/bitmaskcfg/main.go @@ -0,0 +1,10 @@ +/* +Copyright © 2023 Atanarjuat The Fast Runner <atanarjuat@riseup.net> +*/ +package main + +import "0xacab.org/leap/bitmask-core/cmd/bitmaskcfg/cmd" + +func main() { + cmd.Execute() +} diff --git a/go.mod b/go.mod index 4def6f9d913f26c9e35216c9a8c9aa39887e634a..faacc91356af0e2e317d15aabfb585f25fc19926 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/prometheus-community/pro-bing v0.3.0 github.com/refraction-networking/utls v1.3.3 github.com/sirupsen/logrus v1.4.2 + github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.16.0 diff --git a/go.sum b/go.sum index 657818b9a978095264a7000a4ede20e0c023fe66..97b009d1b8d1320b1a4a2e6964edab029073e5c1 100644 --- a/go.sum +++ b/go.sum @@ -284,6 +284,8 @@ github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=