diff --git a/cmd/create.go b/cmd/create.go index 43629cbc..142ba054 100644 --- a/cmd/create.go +++ b/cmd/create.go @@ -7,19 +7,25 @@ import ( "encoding/base64" "fmt" "io/ioutil" + "os" + "os/signal" "strconv" "strings" + "syscall" "time" names "github.com/inlets/inletsctl/pkg/names" provision "github.com/inlets/inletsctl/pkg/provision" + execute "github.com/alexellis/go-execute/pkg/v1" "github.com/pkg/errors" password "github.com/sethvargo/go-password/password" "github.com/spf13/cobra" "github.com/spf13/pflag" ) +var delTunnel bool + func init() { inletsCmd.AddCommand(createCmd) createCmd.Flags().StringP("provider", "p", "digitalocean", "The cloud provider - digitalocean, gce, ec2, packet, scaleway, or civo") @@ -36,8 +42,13 @@ func init() { createCmd.Flags().String("project-id", "", "Project ID (Packet.com, Google Compute Engine)") createCmd.Flags().StringP("remote-tcp", "c", "", `Remote host for inlets-pro to use for forwarding TCP connections`) + createCmd.Flags().String("tcp-ports", "80,443", "Comma-separated list of TCP ports to proxy (default '80,443')") createCmd.Flags().DurationP("poll", "n", time.Second*2, "poll every N seconds, use a higher value if you encounter rate-limiting") + + createCmd.Flags().BoolVar(&delTunnel, "rm", false, "Delete the exit node on pressing control + c") + createCmd.Flags().StringP("upstream", "u", "http://127.0.0.1:3000", "The upstream server running locally") + createCmd.Flags().StringP("license", "l", "", "The license key for inlets-pro") } // clientCmd represents the client sub command. @@ -60,7 +71,6 @@ along with what OS version and spec will be used is explained in the README. } func runCreate(cmd *cobra.Command, _ []string) error { - provider, err := cmd.Flags().GetString("provider") if err != nil { return errors.Wrap(err, "failed to get 'provider' value.") @@ -139,15 +149,25 @@ func runCreate(cmd *cobra.Command, _ []string) error { } remoteTCP, _ := cmd.Flags().GetString("remote-tcp") + upstream, _ := cmd.Flags().GetString("upstream") + var pro bool + var tcpPorts string + var inletsProLicenseKey string if len(remoteTCP) > 0 { pro = true - } + tcpPorts, _ = cmd.Flags().GetString("tcp-ports") + inletsProLicenseKey, _ = cmd.Flags().GetString("license") + } name := strings.Replace(names.GetRandomName(10), "_", "-", -1) inletsControlPort := 8080 + proPort := 8123 + if pro { + inletsControlPort = proPort + } userData := makeUserdata(inletsToken, inletsControlPort, remoteTCP) @@ -180,46 +200,87 @@ func runCreate(cmd *cobra.Command, _ []string) error { return err } - fmt.Printf("[%d/%d] Host: %s, status: %s\n", - i+1, max, hostStatus.ID, hostStatus.Status) - if hostStatus.Status == "active" { - if !pro { - fmt.Printf(`Inlets OSS exit-node summary: - IP: %s - Auth-token: %s - -Command: - export UPSTREAM=http://127.0.0.1:8000 - inlets client --remote "ws://%s:%d" \ - --token "%s" \ - --upstream $UPSTREAM - -To Delete: - inletsctl delete --provider %s --id "%s" -`, - hostStatus.IP, inletsToken, hostStatus.IP, inletsControlPort, inletsToken, provider, hostStatus.ID) - return nil + if delTunnel == true { + sig := make(chan os.Signal, 1) + done := make(chan bool, 1) + + signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM) + + go func() { + sigval := <-sig + fmt.Printf("\n%v\n", sigval) + done <- true + }() + + fmt.Printf("Your IP is: %s\n", hostStatus.IP) + + port := inletsControlPort + if pro { + port = proPort + } + + var err error = nil + if pro { + err = runInletsClient(pro, hostStatus.IP, remoteTCP, port, inletsToken, tcpPorts, inletsProLicenseKey) + } else { + err = runInletsClient(pro, hostStatus.IP, upstream, port, inletsToken, tcpPorts, "") + } + if err != nil { + return fmt.Errorf("Error running inlets: %v", err) + } + + <-done + hostDelReq := provision.HostDeleteRequest{ + ID: hostStatus.ID, + IP: hostStatus.IP, + ProjectID: projectID, + Zone: zone, + } + fmt.Println("Deleting tunnel") + err = provisioner.Delete(hostDelReq) + if err != nil { + return fmt.Errorf("error deleting the exitnode: %v", err) + } + fmt.Println("exiting") + + } else { + if !pro { + fmt.Printf(`Inlets OSS exit-node summary: + IP: %s + Auth-token: %s + + Command: + export UPSTREAM=http://127.0.0.1:8000 + inlets client --remote "ws://%s:%d" \ + --token "%s" \ + --upstream $UPSTREAM + + To Delete: + inletsctl delete --provider %s --id "%s" + `, + hostStatus.IP, inletsToken, hostStatus.IP, inletsControlPort, inletsToken, provider, hostStatus.ID) + return nil + } + + fmt.Printf(`inlets-pro exit-node summary: + IP: %s + Auth-token: %s + + Command: + export TCP_PORTS="8000" + export LICENSE="" + inlets-pro client --connect "wss://%s:%d/connect" \ + --token "%s" \ + --license "$LICENSE" \ + --tcp-ports $TCP_PORTS + + To Delete: + inletsctl delete --provider %s --id "%s" + `, + hostStatus.IP, inletsToken, hostStatus.IP, proPort, inletsToken, provider, hostStatus.ID) } - proPort := 8123 - fmt.Printf(`inlets-pro exit-node summary: - IP: %s - Auth-token: %s - -Command: - export TCP_PORTS="8000" - export LICENSE="" - inlets-pro client --connect "wss://%s:%d/connect" \ - --token "%s" \ - --license "$LICENSE" \ - --tcp-ports $TCP_PORTS - -To Delete: - inletsctl delete --provider %s --id "%s" -`, - hostStatus.IP, inletsToken, hostStatus.IP, proPort, inletsToken, provider, hostStatus.ID) - return nil } } @@ -300,6 +361,7 @@ func createHost(provider, name, region, zone, projectID, userData, inletsPort st "zone": zone, "firewall-name": "inlets", "firewall-port": inletsPort, + "pro": fmt.Sprint(pro), }, }, nil } else if provider == "ec2" { @@ -313,7 +375,7 @@ func createHost(provider, name, region, zone, projectID, userData, inletsPort st UserData: base64.StdEncoding.EncodeToString([]byte(userData)), Additional: map[string]string{ "inlets-port": inletsPort, - "pro": fmt.Sprint(pro), + "pro": fmt.Sprint(pro), }, }, nil } @@ -381,3 +443,68 @@ func getFileOrString(flags *pflag.FlagSet, file, value string, required bool) (s return val, nil } + +func checkIfInletsIsInstalled(usingPro bool) (bool, error) { + basePath := "/usr/local/bin/%s" + if usingPro { + basePath = fmt.Sprintf(basePath, "inlets-pro") + } else { + basePath = fmt.Sprintf(basePath, "inlets") + } + + fileInfo, err := os.Stat(basePath) + if err != nil { + return false, fmt.Errorf("Error finding file: %v", err) + } + + if strings.SplitAfter(basePath, "/usr/local/bin/")[1] == fileInfo.Name() { + return true, nil + } else { + return false, nil + } + +} + +func runInletsClient(pro bool, exitNodeIP string, upstream string, inletsControlPort int, authToken string, tcpPorts string, license string) error { + installed, err := checkIfInletsIsInstalled(pro) + if err != nil { + return fmt.Errorf("could not check if inlets is installed: %v", err) + } + + if !installed { + return fmt.Errorf("inlets/inlets-pro not installed") + } + + if !pro { + fmt.Printf("Starting 'inlets client' now, hit control+c to delete the tunnel\n") + cmd := execute.ExecTask{ + Command: fmt.Sprintf("inlets client --remote %s --token %s --upstream %s", fmt.Sprintf("ws://%s:%d", exitNodeIP, inletsControlPort), authToken, upstream), + } + res, err := cmd.Execute() + if err != nil { + return fmt.Errorf("Could not run inlets: %v", err) + } + if res.ExitCode != 0 { + fmt.Printf("stdout: %q, stderr: %q, exit-code: %d\n", res.Stdout, res.Stderr, res.ExitCode) + } + + } else { + fmt.Printf("Starting 'inlets-pro client' now, hit control+c to delete the tunnel\n") + cmd := execute.ExecTask{ + Command: fmt.Sprintf("inlets-pro client --connect %s --token %s --tcp-ports %s --license %s", fmt.Sprintf("wss://%s:%d/connect", exitNodeIP, inletsControlPort), authToken, tcpPorts, license), + } + res, err := cmd.Execute() + if err != nil { + return fmt.Errorf("Could not run inlets: %v", err) + } + if res.ExitCode != 0 { + fmt.Printf("stdout: %q, stderr: %q, exit-code: %d\n", res.Stdout, res.Stderr, res.ExitCode) + } + } + + if err != nil && fmt.Sprintf("%s", err) != "signal: interrupt" { + return fmt.Errorf("%v", err) + } + + return nil +} diff --git a/pkg/provision/ec2.go b/pkg/provision/ec2.go index 17add121..7c62885f 100644 --- a/pkg/provision/ec2.go +++ b/pkg/provision/ec2.go @@ -2,10 +2,11 @@ package provision import ( "fmt" - "github.com/aws/aws-sdk-go/aws/credentials" "strconv" "strings" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/ec2" @@ -40,7 +41,7 @@ func (p *EC2Provisioner) Provision(host BasicHost) (*ProvisionedHost, error) { } pro := host.Additional["pro"] - groupID, name, err := p.creteEC2SecurityGroup(port, pro) + groupID, name, err := p.createEC2SecurityGroup(port, pro) if err != nil { return nil, err } @@ -230,9 +231,9 @@ func (p *EC2Provisioner) lookupID(request HostDeleteRequest) (string, error) { } // creteEC2SecurityGroup creates a security group for the exit-node -func (p *EC2Provisioner) creteEC2SecurityGroup(controlPort int, pro string) (*string, *string, error) { +func (p *EC2Provisioner) createEC2SecurityGroup(controlPort int, pro string) (*string, *string, error) { ports := []int{80, 443, controlPort} - proPorts := []int{1024,65535} + proPorts := []int{1024, 65535} groupName := "inlets-" + uuid.New().String() group, err := p.ec2Provisioner.CreateSecurityGroup(&ec2.CreateSecurityGroupInput{ Description: aws.String("inlets security group"), diff --git a/pkg/provision/gce.go b/pkg/provision/gce.go index b3ddc05c..16a95585 100644 --- a/pkg/provision/gce.go +++ b/pkg/provision/gce.go @@ -90,17 +90,10 @@ func (p *GCEProvisioner) Provision(host BasicHost) (*ProvisionedHost, error) { }, } - exists, _ := p.checkInletsFirewallRuleExists(host.Additional["projectid"], host.Additional["firewall-name"], host.Additional["firewall-port"]) - - if !exists { - err := p.createInletsFirewallRule(host.Additional["projectid"], host.Additional["firewall-name"], host.Additional["firewall-port"]) - log.Println("inlets firewallRule does not exist") - if err != nil { - return nil, fmt.Errorf("could not create inlets firewall rule: %v", err) - } - log.Printf("Creating inlets firewallRule opening port: %s\n", host.Additional["firewall-port"]) - } else { - log.Println("inlets firewallRule exists") + log.Printf("Creating inlets firewallRule opening port: %s\n", host.Additional["firewall-port"]) + err := p.createInletsFirewallRule(host.Additional["projectid"], host.Additional["firewall-name"], host.Additional["firewall-port"], host.Additional["pro"]) + if err != nil { + return nil, err } op, err := p.gceProvisioner.Instances.Insert(host.Additional["projectid"], host.Additional["zone"], instance).Do() @@ -121,25 +114,23 @@ func (p *GCEProvisioner) Provision(host BasicHost) (*ProvisionedHost, error) { } // checkInletsFirewallRuleExists checks if the inlets firewall rule exists or not -func (p *GCEProvisioner) checkInletsFirewallRuleExists(projectID string, firewallRuleName string, inletsPort string) (bool, error) { +func (p *GCEProvisioner) checkInletsFirewallRuleExists(projectID string, firewallRuleName string) (bool, error) { op, err := p.gceProvisioner.Firewalls.Get(projectID, firewallRuleName).Do() if err != nil { return false, fmt.Errorf("could not get inlets firewall rule: %v", err) } if op.Name == firewallRuleName { - for _, firewallRule := range op.Allowed { - for _, port := range firewallRule.Ports { - if port == inletsPort { - return true, nil - } - } - } + return true, nil } return false, nil } // createInletsFirewallRule creates a firewall rule opening up the control port for inlets -func (p *GCEProvisioner) createInletsFirewallRule(projectID string, firewallRuleName string, inletsPort string) error { +func (p *GCEProvisioner) createInletsFirewallRule(projectID string, firewallRuleName string, inletsPort string, pro string) error { + if pro == "true" { + inletsPort = "1024-65535" + } + firewallRule := &compute.Firewall{ Name: firewallRuleName, Description: "Firewall rule created by inlets-operator", @@ -155,9 +146,19 @@ func (p *GCEProvisioner) createInletsFirewallRule(projectID string, firewallRule TargetTags: []string{"inlets"}, } + exists, _ := p.checkInletsFirewallRuleExists(projectID, firewallRuleName) + if exists { + log.Println("inlets firewallRule exists, updating firewall-rules") + _, err := p.gceProvisioner.Firewalls.Update(projectID, firewallRuleName, firewallRule).Do() + if err != nil { + return fmt.Errorf("could not update inlets firewall rule: %v", err) + } + return nil + } + _, err := p.gceProvisioner.Firewalls.Insert(projectID, firewallRule).Do() if err != nil { - return fmt.Errorf("could not create firewall rule: %v", err) + return fmt.Errorf("could not create inlets firewall rule: %v", err) } return nil }