diff --git a/cmd/config/root.go b/cmd/config/root.go index ff3479a..2f808bf 100644 --- a/cmd/config/root.go +++ b/cmd/config/root.go @@ -12,7 +12,7 @@ import ( "github.com/spf13/viper" ) -// globalProtectCmd represents the globalProtect command +// configCmd represents the config command var configCmd = &cobra.Command{ Use: "config", Short: "A set of commands for working with the config", @@ -22,7 +22,7 @@ var configCmd = &cobra.Command{ }, } -// listCmd represents the get command +// listCmd represents the list command var listCmd = &cobra.Command{ Use: "list", Short: "Print config file path", @@ -32,15 +32,17 @@ var listCmd = &cobra.Command{ }, } -// showCmd represents the get command +// showCmd represents the show command var showCmd = &cobra.Command{ Use: "show", Short: "Print config file", Long: ``, Run: func(cmd *cobra.Command, args []string) { + log.Println() + file, err := os.Open(viper.ConfigFileUsed()) if err != nil { - panic(err) + log.Fatalf("%v\n\n", err) } defer file.Close() @@ -52,7 +54,7 @@ var showCmd = &cobra.Command{ }, } -// editCmd represents the globalProtect command +// editCmd represents the edit command var editCmd = &cobra.Command{ Use: "edit", Short: "Open config file in default editor", diff --git a/cmd/firewall/get_config_set.go b/cmd/firewall/get_config_set.go index 9fb169e..44e459c 100644 --- a/cmd/firewall/get_config_set.go +++ b/cmd/firewall/get_config_set.go @@ -5,6 +5,7 @@ package firewall import ( "bufio" + "bytes" "fmt" "log" "os" @@ -39,7 +40,9 @@ Examples: > panos-cli firewall get config set --filter 'mgt-config' fw01.example.com`, Run: func(cmd *cobra.Command, args []string) { - fmt.Fprintln(os.Stderr) + log.Println() + + // Ensure at least one host is specified hosts = cmd.Flags().Args() if len(hosts) == 0 { if isInputFromPipe() { @@ -50,8 +53,7 @@ Examples: } } else { cmd.Help() - fmt.Printf("\nno hosts specified\n") - os.Exit(1) + log.Fatalf("\nno hosts specified\n\n") } } @@ -65,8 +67,7 @@ Examples: } } else { cmd.Help() - fmt.Printf("\nunable to retrieve password from standard input\n") - os.Exit(1) + log.Fatalf("\nunable to retrieve password from standard input\n\n") } } @@ -81,17 +82,17 @@ Examples: if !keyBasedAuth && viper.GetString("password") == "" && password == "" { tty, err := os.Open("/dev/tty") if err != nil { - log.Fatal(err, "error allocating terminal") + log.Fatalf("error allocating terminal\n\n") } fd := int(tty.Fd()) fmt.Fprintf(os.Stderr, "Password (%s): ", user) bytepw, err := term.ReadPassword(int(fd)) if err != nil { - panic(err) + log.Fatalf("%v\n\n", err) } tty.Close() password = string(bytepw) - fmt.Fprintf(os.Stderr, "\n\n") + log.Printf("\n\n") } else if password == "" { password = viper.GetString("password") } @@ -99,7 +100,7 @@ Examples: if keyBasedAuth { file, err := os.ReadFile(filepath.Join(os.Getenv("HOME"), ".ssh", "id_rsa")) if err != nil { - log.Fatal(err) + log.Fatalf("%v\n\n", err) } signer, err = ssh.ParsePrivateKey(file) @@ -107,22 +108,22 @@ Examples: if err.Error() == "ssh: this private key is passphrase protected" { tty, err := os.Open("/dev/tty") if err != nil { - log.Fatal(err, "error allocating terminal") + log.Fatalf("error allocating terminal: %v", err) } fd := int(tty.Fd()) fmt.Fprintf(os.Stderr, "SSH Private Key Passphrase: ") passphrase, err := term.ReadPassword(fd) if err != nil { - log.Fatal(err) + log.Fatalf("%v\n\n", err) } tty.Close() - fmt.Fprintf(os.Stderr, "\n\n") + log.Printf("\n\n") signer, err = ssh.ParsePrivateKeyWithPassphrase(file, passphrase) if err != nil { - log.Fatal(err) + log.Fatalf("%v\n\n", err) } } else { - log.Fatal(err) + log.Fatalf("%v\n\n", err) } } } @@ -136,10 +137,11 @@ Examples: start := time.Now() + var errorBuffer bytes.Buffer ch := make(chan sessionDetails, 100) doneCh := make(chan struct{}) - go printConfigSet(ch, doneCh) + go printConfigSet(ch, &errorBuffer, doneCh) for _, host := range hosts { wg.Add(1) @@ -149,9 +151,12 @@ Examples: close(ch) <-doneCh + // Print errors + log.Println(errorBuffer.String()) + // Print summary elapsed := time.Since(start) - fmt.Fprintf(os.Stderr, " Completed in %.3f seconds\n", elapsed.Seconds()) + log.Printf(" Completed in %.3f seconds\n", elapsed.Seconds()) }, } @@ -169,18 +174,17 @@ func init() { getConfigSetCmd.Flags().StringVarP(&configFilter, "filter", "f", configFilter, "filter configuration output") } -// printConfigSet prints the results -func printConfigSet(ch <-chan sessionDetails, doneCh chan<- struct{}) { - for { - if session, chanIsOpen := <-ch; chanIsOpen { +func printConfigSet(ch <-chan sessionDetails, error *bytes.Buffer, done chan<- struct{}) { + for session := range ch { + if session.error != "" { + error.WriteString(session.error) + } else { green.Printf("\n*** %s ***\n\n", session.host) fmt.Printf("%s\n\n", trimConfigSetOutput(session.results[cmds[2]])) blue.Printf("################################################################################\n\n") - } else { - doneCh <- struct{}{} - return } } + done <- struct{}{} // Notify when done } // trimOutput removes the echoed command and the prompt from the output diff --git a/cmd/firewall/get_config_xml.go b/cmd/firewall/get_config_xml.go index a1901a3..75573d8 100644 --- a/cmd/firewall/get_config_xml.go +++ b/cmd/firewall/get_config_xml.go @@ -2,6 +2,7 @@ package firewall import ( "bufio" + "bytes" "crypto/tls" "encoding/base64" "encoding/xml" @@ -30,6 +31,7 @@ type innerXML struct { type configuration struct { Host string Config innerXML `xml:"result"` + Error string } // getConfigXmlCmd represents the 'config xml' command @@ -55,12 +57,14 @@ Examples: > panos-cli firewall get config xml --type 'effective-running' --xpath 'mgt-config' fw01.example.com`, Run: func(cmd *cobra.Command, args []string) { + log.Println() + // Ensure at least one host is specified hosts = cmd.Flags().Args() if len(hosts) == 0 { if isInputFromPipe() { if viper.GetString("apikey") == "" { - log.Fatal("api key based auth is required when reading hosts from stdin, execute `panos-cli config edit` to add an api key") + log.Fatalf("api key based auth is required when reading hosts from stdin, execute `panos-cli config edit` to add an api key\n\n") } // Read hosts from stdin @@ -70,27 +74,23 @@ Examples: } } else { cmd.Help() - fmt.Printf("\nno hosts specified\n") - os.Exit(1) + log.Fatalf("\nno hosts specified\n\n") } } if !slices.Contains([]string{"candidate", "effective-running", "merged", "pushed-shared-policy", "pushed-template", "running", "synced", "synced-diff"}, configType) { cmd.Help() - fmt.Printf("\ninvalid configuration type\n") - os.Exit(1) + log.Fatalf("\ninvalid configuration type\n\n") } if !slices.Contains([]string{"effective-running", "running"}, configType) && xpath != "." { cmd.Help() - fmt.Printf("\nxpath should only be used with configuration types 'effective-running' and 'running'\n") - os.Exit(1) + log.Fatalf("\nxpath should only be used with configuration types 'effective-running' and 'running'\n\n") } // If the user flag is not set or the user is not set, prompt for user - fmt.Fprintln(os.Stderr) if viper.GetString("user") == "" && user == "" { - fmt.Fprint(os.Stderr, "PAN User: ") + fmt.Fprintf(os.Stderr, "PAN User: ") fmt.Scanln(&user) } else if user == "" { user = viper.GetString("user") @@ -101,25 +101,26 @@ Examples: if userFlagSet || (viper.GetString("apikey") == "" && password == "") { tty, err := os.Open("/dev/tty") if err != nil { - log.Fatal(err, "error allocating terminal") + log.Fatalf("error allocating terminal: %v\n\n", err) } fd := int(tty.Fd()) fmt.Fprintf(os.Stderr, "Password (%s): ", user) bytepw, err := term.ReadPassword(int(fd)) if err != nil { - panic(err) + log.Fatalf("%v\n\n", err) } tty.Close() password = string(bytepw) - fmt.Fprintf(os.Stderr, "\n\n") + log.Printf("\n\n") } start := time.Now() + var errorBuffer bytes.Buffer ch := make(chan configuration, 10) doneCh := make(chan struct{}) - go printConfigXml(ch, doneCh) + go printConfigXml(ch, &errorBuffer, doneCh) wg.Add(len(hosts)) for _, fw := range hosts { @@ -128,11 +129,13 @@ Examples: wg.Wait() close(ch) <-doneCh - fmt.Fprintln(os.Stderr) + + // Print errors + log.Println(errorBuffer.String()) // Print summary elapsed := time.Since(start) - fmt.Fprintf(os.Stderr, " Completed in %.3f seconds\n", elapsed.Seconds()) + log.Printf(" Completed in %.3f seconds\n", elapsed.Seconds()) }, } @@ -148,6 +151,11 @@ func init() { func getConfigXml(ch chan<- configuration, fw string, userFlagSet bool, xpath string) { defer wg.Done() + config := configuration{Host: fw} + defer func() { + ch <- config + }() + var cmd string if slices.Contains([]string{"effective-running", "running"}, configType) { cmd = fmt.Sprintf("<%s>%s", configType, xpath, configType) @@ -163,8 +171,8 @@ func getConfigXml(ch chan<- configuration, fw string, userFlagSet bool, xpath st url := fmt.Sprintf("https://%s/api/", fw) req, err := http.NewRequest("GET", url, nil) if err != nil { - red.Fprintf(os.Stderr, "fail\n\n") - panic(err) + config.Error = fmt.Sprintf("%s: %v\n\n", fw, err) + return } // Get configuration @@ -183,40 +191,38 @@ func getConfigXml(ch chan<- configuration, fw string, userFlagSet bool, xpath st resp, err := client.Do(req) if err != nil { - red.Fprintf(os.Stderr, "fail\n\n") - panic(err) + config.Error = fmt.Sprintf("%s: %v\n\n", fw, err) + return } if resp.StatusCode != 200 { - red.Fprintf(os.Stderr, "fail\n\n") - log.Fatalf("%s (%s)", resp.Status, fw) + config.Error = fmt.Sprintf("%s: response status code: %s\n\n", fw, resp.Status) + return } respBody, err := io.ReadAll(resp.Body) if err != nil { - red.Fprintf(os.Stderr, "fail\n\n") - panic(err) + config.Error = fmt.Sprintf("%s: %v\n\n", fw, err) + return } resp.Body.Close() - config := configuration{Host: fw} err = xml.Unmarshal(respBody, &config) if err != nil { - panic(err) + config.Error = fmt.Sprintf("%s: %v\n\n", fw, err) + return } - - ch <- config } -func printConfigXml(ch <-chan configuration, doneCh chan<- struct{}) { - for { - if session, chanIsOpen := <-ch; chanIsOpen { +func printConfigXml(ch <-chan configuration, error *bytes.Buffer, done chan<- struct{}) { + for session := range ch { + if session.Error != "" { + error.WriteString(session.Error) + } else { green.Printf("\n*** %s ***\n\n", session.Host) fmt.Printf("%s\n\n", session.Config.InnerXML) blue.Printf("################################################################################\n\n") - } else { - doneCh <- struct{}{} - return } } + done <- struct{}{} // Notify when done } diff --git a/cmd/firewall/get_interfaces.go b/cmd/firewall/get_interfaces.go index caf0881..89bdde0 100644 --- a/cmd/firewall/get_interfaces.go +++ b/cmd/firewall/get_interfaces.go @@ -2,6 +2,7 @@ package firewall import ( "bufio" + "bytes" "crypto/tls" "encoding/base64" "encoding/xml" @@ -38,6 +39,7 @@ type interfaceSlice struct { AggregateConfig []*interfaceConfig `xml:"result>interface>aggregate-ethernet>entry"` LoopbackConfig []*interfaceConfig `xml:"result>interface>loopback>units>entry"` TunnelConfig []*interfaceConfig `xml:"result>interface>tunnel>units>entry"` + Error string } type interfaceNetwork struct { @@ -112,12 +114,14 @@ Examples: > panos-cli firewall get interfaces --has-ip --name "eth*","ae*" fw01.example.com`, Run: func(cmd *cobra.Command, args []string) { + log.Println() + // Ensure at least one host is specified hosts = cmd.Flags().Args() if len(hosts) == 0 { if isInputFromPipe() { if viper.GetString("apikey") == "" { - log.Fatal("api key based auth is required when reading hosts from stdin, execute `panos-cli config edit` to add an api key") + log.Fatalf("api key based auth is required when reading hosts from stdin, execute `panos-cli config edit` to add an api key\n\n") } // Read hosts from stdin @@ -127,13 +131,11 @@ Examples: } } else { cmd.Help() - fmt.Printf("\nno hosts specified\n") - os.Exit(1) + log.Fatalf("\nno hosts specified\n\n") } } // If the user flag is not set or the user is not set, prompt for user - fmt.Fprintln(os.Stderr) if viper.GetString("user") == "" && user == "" { fmt.Fprint(os.Stderr, "PAN User: ") fmt.Scanln(&user) @@ -146,25 +148,26 @@ Examples: if userFlagSet || (viper.GetString("apikey") == "" && password == "") { tty, err := os.Open("/dev/tty") if err != nil { - log.Fatal(err, "error allocating terminal") + log.Fatalf("error allocating terminal: %v\n\n", err) } fd := int(tty.Fd()) fmt.Fprintf(os.Stderr, "Password (%s): ", user) bytepw, err := term.ReadPassword(int(fd)) if err != nil { - panic(err) + log.Fatalf("%v\n\n", err) } tty.Close() password = string(bytepw) - fmt.Fprintf(os.Stderr, "\n\n") + log.Printf("\n\n") } start := time.Now() + var errorBuffer bytes.Buffer ch := make(chan interfaceSlice, 10) doneCh := make(chan struct{}) - go printInterfaces(ch, doneCh, cmd) + go printInterfaces(ch, &errorBuffer, doneCh, cmd) wg.Add(len(hosts)) for _, fw := range hosts { @@ -173,11 +176,17 @@ Examples: wg.Wait() close(ch) <-doneCh - fmt.Fprintln(os.Stderr) + + log.Println() + + // Print errors + if errorBuffer.String() != "" { + log.Printf("%s", errorBuffer.String()) + } // Print summary elapsed := time.Since(start) - fmt.Fprintf(os.Stderr, " Completed in %.3f seconds\n", elapsed.Seconds()) + log.Printf("\n Completed in %.3f seconds\n", elapsed.Seconds()) }, } @@ -195,6 +204,11 @@ func init() { func getInterfaces(ch chan<- interfaceSlice, fw string, userFlagSet bool) { defer wg.Done() + interfaces := interfaceSlice{Firewall: fw} + defer func() { + ch <- interfaces + }() + tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } @@ -203,8 +217,8 @@ func getInterfaces(ch chan<- interfaceSlice, fw string, userFlagSet bool) { url := fmt.Sprintf("https://%s/api/", fw) req, err := http.NewRequest("GET", url, nil) if err != nil { - red.Fprintf(os.Stderr, "fail\n\n") - panic(err) + interfaces.Error = fmt.Sprintf("%s: %v\n\n", fw, err) + return } // Get interface operational data @@ -223,8 +237,8 @@ func getInterfaces(ch chan<- interfaceSlice, fw string, userFlagSet bool) { resp, err := client.Do(req) if err != nil { - red.Fprintf(os.Stderr, "fail\n\n") - panic(err) + interfaces.Error = fmt.Sprintf("%s: %v\n\n", fw, err) + return } if resp.StatusCode != 200 { red.Fprintf(os.Stderr, "fail\n\n") @@ -233,16 +247,16 @@ func getInterfaces(ch chan<- interfaceSlice, fw string, userFlagSet bool) { respBody, err := io.ReadAll(resp.Body) if err != nil { - red.Fprintf(os.Stderr, "fail\n\n") - panic(err) + interfaces.Error = fmt.Sprintf("%s: %v\n\n", fw, err) + return } resp.Body.Close() - interfaces := interfaceSlice{Firewall: fw} err = xml.Unmarshal(respBody, &interfaces) if err != nil { - panic(err) + interfaces.Error = fmt.Sprintf("%s: %v\n\n", fw, err) + return } // Get interface configuration data @@ -262,31 +276,30 @@ func getInterfaces(ch chan<- interfaceSlice, fw string, userFlagSet bool) { resp, err = client.Do(req) if err != nil { - red.Fprintf(os.Stderr, "fail\n\n") - panic(err) + interfaces.Error = fmt.Sprintf("%s: %v\n\n", fw, err) + return } if resp.StatusCode != 200 { - red.Fprintf(os.Stderr, "fail\n\n") - log.Fatal(resp.Status) + interfaces.Error = fmt.Sprintf("%s: response status code: %s\n\n", fw, resp.Status) + return } respBody, err = io.ReadAll(resp.Body) if err != nil { - red.Fprintf(os.Stderr, "fail\n\n") - panic(err) + interfaces.Error = fmt.Sprintf("%s: %v\n\n", fw, err) + return } resp.Body.Close() err = xml.Unmarshal(respBody, &interfaces) if err != nil { - panic(err) + interfaces.Error = fmt.Sprintf("%s: %v\n\n", fw, err) + return } - - ch <- interfaces } -func printInterfaces(ch <-chan interfaceSlice, doneCh chan<- struct{}, cmd *cobra.Command) { +func printInterfaces(ch <-chan interfaceSlice, error *bytes.Buffer, done chan<- struct{}, cmd *cobra.Command) { // Print interfaces headerFmt := color.New(color.FgBlue, color.Underline).SprintfFunc() columnFmt := color.New(color.FgHiYellow).SprintfFunc() @@ -295,120 +308,125 @@ func printInterfaces(ch <-chan interfaceSlice, doneCh chan<- struct{}, cmd *cobr tbl.WithHeaderFormatter(headerFmt).WithFirstColumnFormatter(columnFmt) for fw := range ch { - // Parse interface hardware data - ints := map[string]*firewallInterface{} - for _, i := range fw.Hardware { - ints[i.Name] = &firewallInterface{ - Firewall: fw.Firewall, - Name: i.Name, - MAC: i.MAC, - Speed: i.Speed, - Duplex: i.Duplex, - Status: i.Status, - Mode: i.Mode, - State: i.State, - } - } - - // Interface network data - for _, i := range fw.Network { - i.VirtualSystem = fmt.Sprintf("vsys%s", i.VirtualSystem) - if i.VLAN == "0" { - i.VLAN = "" - } - if _, ok := ints[i.Name]; !ok { - ints[i.Name] = &firewallInterface{} + if fw.Error != "" { + error.WriteString(fw.Error) + } else { + + // Parse interface hardware data + ints := map[string]*firewallInterface{} + for _, i := range fw.Hardware { + ints[i.Name] = &firewallInterface{ + Firewall: fw.Firewall, + Name: i.Name, + MAC: i.MAC, + Speed: i.Speed, + Duplex: i.Duplex, + Status: i.Status, + Mode: i.Mode, + State: i.State, + } } - ints[i.Name].Firewall = fw.Firewall - ints[i.Name].Name = i.Name - ints[i.Name].Type = i.Type - ints[i.Name].VirtualSystem = i.VirtualSystem - ints[i.Name].VLAN = i.VLAN - ints[i.Name].Zone = i.Zone - } - // Parse interface configuration data - var configs []*interfaceConfig - configs = append(configs, fw.EthernetConfig...) - configs = append(configs, fw.AggregateConfig...) - configs = append(configs, fw.LoopbackConfig...) - configs = append(configs, fw.TunnelConfig...) - for _, i := range configs { - if _, ok := ints[i.Name]; !ok { - ints[i.Name] = &firewallInterface{} - } - ints[i.Name].Comment = i.Comment - ints[i.Name].AggregateGroup = i.AggregateGroup - ints[i.Name].MTU = i.MTU - - // Parse IP addresses from merged local/Panorama configurations - addresses := []string{} - var addrs []*ipAddresses - addrs = append(addrs, i.IP...) - addrs = append(addrs, i.Layer3IP...) - for _, addr := range addrs { - addresses = append(addresses, addr.IP) + // Interface network data + for _, i := range fw.Network { + i.VirtualSystem = fmt.Sprintf("vsys%s", i.VirtualSystem) + if i.VLAN == "0" { + i.VLAN = "" + } + if _, ok := ints[i.Name]; !ok { + ints[i.Name] = &firewallInterface{} + } + ints[i.Name].Firewall = fw.Firewall + ints[i.Name].Name = i.Name + ints[i.Name].Type = i.Type + ints[i.Name].VirtualSystem = i.VirtualSystem + ints[i.Name].VLAN = i.VLAN + ints[i.Name].Zone = i.Zone } - ints[i.Name].IP = strings.Join(addresses, "\n") - // Parse sub interfaces - for _, si := range i.SubInterfaces { - if _, ok := ints[si.Name]; !ok { - ints[si.Name] = &firewallInterface{} + // Parse interface configuration data + var configs []*interfaceConfig + configs = append(configs, fw.EthernetConfig...) + configs = append(configs, fw.AggregateConfig...) + configs = append(configs, fw.LoopbackConfig...) + configs = append(configs, fw.TunnelConfig...) + for _, i := range configs { + if _, ok := ints[i.Name]; !ok { + ints[i.Name] = &firewallInterface{} } - ints[si.Name].Comment = si.Comment + ints[i.Name].Comment = i.Comment + ints[i.Name].AggregateGroup = i.AggregateGroup + ints[i.Name].MTU = i.MTU + // Parse IP addresses from merged local/Panorama configurations addresses := []string{} var addrs []*ipAddresses - addrs = append(addrs, si.IP...) - addrs = append(addrs, si.Layer3IP...) + addrs = append(addrs, i.IP...) + addrs = append(addrs, i.Layer3IP...) for _, addr := range addrs { addresses = append(addresses, addr.IP) } - ints[si.Name].IP = strings.Join(addresses, "\n") + ints[i.Name].IP = strings.Join(addresses, "\n") + + // Parse sub interfaces + for _, si := range i.SubInterfaces { + if _, ok := ints[si.Name]; !ok { + ints[si.Name] = &firewallInterface{} + } + ints[si.Name].Comment = si.Comment + + addresses := []string{} + var addrs []*ipAddresses + addrs = append(addrs, si.IP...) + addrs = append(addrs, si.Layer3IP...) + for _, addr := range addrs { + addresses = append(addresses, addr.IP) + } + ints[si.Name].IP = strings.Join(addresses, "\n") + } } - } - // Sort interfaces by name - var keys []string - for k := range ints { - keys = append(keys, k) - } - sort.Strings(keys) - - // TODO: Sort interface numbers correctly - //sort.SliceStable(keys, func(i, j int) bool { - //iSplit := strings.Split(keys[i], ".") - //jSplit := strings.Split(keys[j], ".") - //if len(iSplit) < 2 { - //return true - //} - //if len(jSplit) < 2 { - //return true - //} - //return iSplit[1] < jSplit[1] - //}) - - // Match one or more IP addresses, with or without slash notation - r := regexp.MustCompile(`^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(/\d{2})?(, \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(/\d{2})?)?$`) - for _, k := range keys { - switch { - case cmd.Flags().Changed("name") && !match(namePattern, "", ints[k].Name, "/", ""): - continue - case cmd.Flags().Changed("vsys") && !match(vsysPattern, "", ints[k].VirtualSystem): - continue - case cmd.Flags().Changed("aggregate-group") && !match(aePattern, "", ints[k].AggregateGroup) && !match(aePattern, "", ints[k].Name): - continue - case hasIpAddress && !r.MatchString(ints[k].IP): - continue + // Sort interfaces by name + var keys []string + for k := range ints { + keys = append(keys, k) + } + sort.Strings(keys) + + // TODO: Sort interface numbers correctly + //sort.SliceStable(keys, func(i, j int) bool { + //iSplit := strings.Split(keys[i], ".") + //jSplit := strings.Split(keys[j], ".") + //if len(iSplit) < 2 { + //return true + //} + //if len(jSplit) < 2 { + //return true + //} + //return iSplit[1] < jSplit[1] + //}) + + // Match one or more IP addresses, with or without slash notation + r := regexp.MustCompile(`^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(/\d{2})?(, \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(/\d{2})?)?$`) + for _, k := range keys { + switch { + case cmd.Flags().Changed("name") && !match(namePattern, "", ints[k].Name, "/", ""): + continue + case cmd.Flags().Changed("vsys") && !match(vsysPattern, "", ints[k].VirtualSystem): + continue + case cmd.Flags().Changed("aggregate-group") && !match(aePattern, "", ints[k].AggregateGroup) && !match(aePattern, "", ints[k].Name): + continue + case hasIpAddress && !r.MatchString(ints[k].IP): + continue + } + tbl.AddRow(ints[k].Firewall, ints[k].Name, ints[k].IP, ints[k].MTU, ints[k].Type, ints[k].MAC, ints[k].Status, ints[k].State, ints[k].VirtualSystem, ints[k].VLAN, ints[k].AggregateGroup, ints[k].Zone, ints[k].Comment) } - tbl.AddRow(ints[k].Firewall, ints[k].Name, ints[k].IP, ints[k].MTU, ints[k].Type, ints[k].MAC, ints[k].Status, ints[k].State, ints[k].VirtualSystem, ints[k].VLAN, ints[k].AggregateGroup, ints[k].Zone, ints[k].Comment) } } tbl.Print() - doneCh <- struct{}{} + done <- struct{}{} } func match(patns []string, trim string, item ...string) bool { diff --git a/cmd/firewall/get_object-limits.go b/cmd/firewall/get_object-limits.go index e544caf..3bb2fd2 100644 --- a/cmd/firewall/get_object-limits.go +++ b/cmd/firewall/get_object-limits.go @@ -2,6 +2,7 @@ package firewall import ( "bufio" + "bytes" "crypto/tls" "encoding/base64" "fmt" @@ -32,6 +33,7 @@ type objectLimits struct { PbfPolicyRule objectLimit NatPolicyRule objectLimit QosPolicyRule objectLimit + Error string } type objectLimit struct { @@ -56,12 +58,14 @@ Examples: > panos-cli panorama get firewalls --terse | panos-cli firewall get object-limits`, Run: func(cmd *cobra.Command, args []string) { + log.Println() + // Ensure at least one host is specified hosts = cmd.Flags().Args() if len(hosts) == 0 { if isInputFromPipe() { if viper.GetString("apikey") == "" { - log.Fatal("api key based auth is required when reading hosts from stdin, execute `panos-cli config edit` to add an api key") + log.Fatalf("api key based auth is required when reading hosts from stdin, execute `panos-cli config edit` to add an api key\n\n") } // Read hosts from stdin @@ -71,13 +75,11 @@ Examples: } } else { cmd.Help() - fmt.Printf("\nno hosts specified\n") - os.Exit(1) + log.Fatalf("\nno hosts specified\n\n") } } // If the user flag is not set or the user is not set, prompt for user - fmt.Fprintln(os.Stderr) if viper.GetString("user") == "" && user == "" { fmt.Fprint(os.Stderr, "PAN User: ") fmt.Scanln(&user) @@ -90,25 +92,26 @@ Examples: if userFlagSet || (viper.GetString("apikey") == "" && password == "") { tty, err := os.Open("/dev/tty") if err != nil { - log.Fatal(err, "error allocating terminal") + log.Fatalf("error allocating terminal\n\n") } fd := int(tty.Fd()) fmt.Fprintf(os.Stderr, "Password (%s): ", user) bytepw, err := term.ReadPassword(int(fd)) if err != nil { - panic(err) + log.Fatalf("%v\n\n", err) } tty.Close() password = string(bytepw) - fmt.Fprintf(os.Stderr, "\n\n") + log.Printf("\n\n") } start := time.Now() + var errorBuffer bytes.Buffer ch := make(chan objectLimits, 10) doneCh := make(chan struct{}) - go printObjectLimits(ch, doneCh, cmd) + go printObjectLimits(ch, &errorBuffer, doneCh) wg.Add(len(hosts)) for _, fw := range hosts { @@ -117,11 +120,17 @@ Examples: wg.Wait() close(ch) <-doneCh - fmt.Fprintln(os.Stderr) + + // Print errors + if errorBuffer.String() != "" { + log.Printf("\n%s\n", errorBuffer.String()) + } else { + log.Printf("\n\n") + } // Print summary elapsed := time.Since(start) - fmt.Fprintf(os.Stderr, " Completed in %.3f seconds\n", elapsed.Seconds()) + log.Printf(" Completed in %.3f seconds\n", elapsed.Seconds()) }, } @@ -135,6 +144,11 @@ func init() { func getObjectLimits(ch chan<- objectLimits, fw string, userFlagSet bool) { defer wg.Done() + objLimits := objectLimits{Firewall: fw} + defer func() { + ch <- objLimits + }() + tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } @@ -143,8 +157,8 @@ func getObjectLimits(ch chan<- objectLimits, fw string, userFlagSet bool) { url := fmt.Sprintf("https://%s/api/", fw) req, err := http.NewRequest("GET", url, nil) if err != nil { - red.Fprintf(os.Stderr, "fail\n\n") - panic(err) + objLimits.Error = fmt.Sprintf("%s: %v\n\n", fw, err) + return } // Get interface operational data @@ -163,8 +177,8 @@ func getObjectLimits(ch chan<- objectLimits, fw string, userFlagSet bool) { resp, err := client.Do(req) if err != nil { - red.Fprintf(os.Stderr, "fail\n\n") - panic(err) + objLimits.Error = fmt.Sprintf("%s: %v\n\n", fw, err) + return } if resp.StatusCode != 200 { red.Fprintf(os.Stderr, "fail\n\n") @@ -173,8 +187,8 @@ func getObjectLimits(ch chan<- objectLimits, fw string, userFlagSet bool) { respBody, err := io.ReadAll(resp.Body) if err != nil { - red.Fprintf(os.Stderr, "fail\n\n") - panic(err) + objLimits.Error = fmt.Sprintf("%s: %v\n\n", fw, err) + return } resp.Body.Close() @@ -199,9 +213,6 @@ func getObjectLimits(ch chan<- objectLimits, fw string, userFlagSet bool) { limitMap[object] = limit } - // TEST - // fmt.Println(limitMap) - // Get firewall configuration q = req.URL.Query() q.Add("type", "op") @@ -218,8 +229,8 @@ func getObjectLimits(ch chan<- objectLimits, fw string, userFlagSet bool) { resp, err = client.Do(req) if err != nil { - red.Fprintf(os.Stderr, "fail\n\n") - panic(err) + objLimits.Error = fmt.Sprintf("%s: %v\n\n", fw, err) + return } if resp.StatusCode != 200 { red.Fprintf(os.Stderr, "fail\n\n") @@ -228,15 +239,12 @@ func getObjectLimits(ch chan<- objectLimits, fw string, userFlagSet bool) { respBody, err = io.ReadAll(resp.Body) if err != nil { - red.Fprintf(os.Stderr, "fail\n\n") - panic(err) + objLimits.Error = fmt.Sprintf("%s: %v\n\n", fw, err) + return } resp.Body.Close() - // TEST - // fmt.Println(string(respBody)) - // Get current object counts doc, _ := xmlquery.Parse(strings.NewReader(string(respBody))) usedMap := make(map[string]int64) @@ -249,71 +257,58 @@ func getObjectLimits(ch chan<- objectLimits, fw string, userFlagSet bool) { usedMap["pbf-policy-rule"] = int64(len(xmlquery.Find(doc, "//pbf/rules/entry"))) usedMap["qos-policy-rule"] = int64(len(xmlquery.Find(doc, "//qos/rules/entry"))) - // TEST - // fmt.Println(addressCount, addressGroupCount, serviceCount, serviceGroupCount, securityPolicyCount, natPolicyCount, qosPolicyCount, pbfPolicyCount) - // os.Exit(0) - // Populate a struct with the data - objLimits := objectLimits{ - Firewall: fw, - Address: objectLimit{ - Limit: limitMap["address"], - Used: usedMap["address"], - Remaining: limitMap["address"] - usedMap["address"], - PercentUsed: fmt.Sprintf("%d%%", int64(float64(usedMap["address"])/float64(limitMap["address"])*100)), - }, - AddressGroup: objectLimit{ - Limit: limitMap["address-group"], - Used: usedMap["address-group"], - Remaining: limitMap["address-group"] - usedMap["address-group"], - PercentUsed: fmt.Sprintf("%d%%", int64(float64(usedMap["address-group"])/float64(limitMap["address-group"])*100)), - }, - Service: objectLimit{ - Limit: limitMap["service"], - Used: usedMap["service"], - Remaining: limitMap["service"] - usedMap["service"], - PercentUsed: fmt.Sprintf("%d%%", int64(float64(usedMap["service"])/float64(limitMap["service"])*100)), - }, - ServiceGroup: objectLimit{ - Limit: limitMap["service-group"], - Used: usedMap["service-group"], - Remaining: limitMap["service-group"] - usedMap["service-group"], - PercentUsed: fmt.Sprintf("%d%%", int64(float64(usedMap["service-group"])/float64(limitMap["service-group"])*100)), - }, - PolicyRule: objectLimit{ - Limit: limitMap["policy-rule"], - Used: usedMap["policy-rule"], - Remaining: limitMap["policy-rule"] - usedMap["policy-rule"], - PercentUsed: fmt.Sprintf("%d%%", int64(float64(usedMap["policy-rule"])/float64(limitMap["policy-rule"])*100)), - }, - NatPolicyRule: objectLimit{ - Limit: limitMap["nat-policy-rule"], - Used: usedMap["nat-policy-rule"], - Remaining: limitMap["nat-policy-rule"] - usedMap["nat-policy-rule"], - PercentUsed: fmt.Sprintf("%d%%", int64(float64(usedMap["nat-policy-rule"])/float64(limitMap["nat-policy-rule"])*100)), - }, - PbfPolicyRule: objectLimit{ - Limit: limitMap["pbf-policy-rule"], - Used: usedMap["pbf-policy-rule"], - Remaining: limitMap["pbf-policy-rule"] - usedMap["pbf-policy-rule"], - PercentUsed: fmt.Sprintf("%d%%", int64(float64(usedMap["pbf-policy-rule"])/float64(limitMap["pbf-policy-rule"])*100)), - }, - QosPolicyRule: objectLimit{ - Limit: limitMap["qos-policy-rule"], - Used: usedMap["qos-policy-rule"], - Remaining: limitMap["qos-policy-rule"] - usedMap["qos-policy-rule"], - PercentUsed: fmt.Sprintf("%d%%", int64(float64(usedMap["qos-policy-rule"])/float64(limitMap["qos-policy-rule"])*100)), - }, + objLimits.Address = objectLimit{ + Limit: limitMap["address"], + Used: usedMap["address"], + Remaining: limitMap["address"] - usedMap["address"], + PercentUsed: fmt.Sprintf("%d%%", int64(float64(usedMap["address"])/float64(limitMap["address"])*100)), + } + objLimits.AddressGroup = objectLimit{ + Limit: limitMap["address-group"], + Used: usedMap["address-group"], + Remaining: limitMap["address-group"] - usedMap["address-group"], + PercentUsed: fmt.Sprintf("%d%%", int64(float64(usedMap["address-group"])/float64(limitMap["address-group"])*100)), + } + objLimits.Service = objectLimit{ + Limit: limitMap["service"], + Used: usedMap["service"], + Remaining: limitMap["service"] - usedMap["service"], + PercentUsed: fmt.Sprintf("%d%%", int64(float64(usedMap["service"])/float64(limitMap["service"])*100)), + } + objLimits.ServiceGroup = objectLimit{ + Limit: limitMap["service-group"], + Used: usedMap["service-group"], + Remaining: limitMap["service-group"] - usedMap["service-group"], + PercentUsed: fmt.Sprintf("%d%%", int64(float64(usedMap["service-group"])/float64(limitMap["service-group"])*100)), + } + objLimits.PolicyRule = objectLimit{ + Limit: limitMap["policy-rule"], + Used: usedMap["policy-rule"], + Remaining: limitMap["policy-rule"] - usedMap["policy-rule"], + PercentUsed: fmt.Sprintf("%d%%", int64(float64(usedMap["policy-rule"])/float64(limitMap["policy-rule"])*100)), + } + objLimits.NatPolicyRule = objectLimit{ + Limit: limitMap["nat-policy-rule"], + Used: usedMap["nat-policy-rule"], + Remaining: limitMap["nat-policy-rule"] - usedMap["nat-policy-rule"], + PercentUsed: fmt.Sprintf("%d%%", int64(float64(usedMap["nat-policy-rule"])/float64(limitMap["nat-policy-rule"])*100)), + } + objLimits.PbfPolicyRule = objectLimit{ + Limit: limitMap["pbf-policy-rule"], + Used: usedMap["pbf-policy-rule"], + Remaining: limitMap["pbf-policy-rule"] - usedMap["pbf-policy-rule"], + PercentUsed: fmt.Sprintf("%d%%", int64(float64(usedMap["pbf-policy-rule"])/float64(limitMap["pbf-policy-rule"])*100)), + } + objLimits.QosPolicyRule = objectLimit{ + Limit: limitMap["qos-policy-rule"], + Used: usedMap["qos-policy-rule"], + Remaining: limitMap["qos-policy-rule"] - usedMap["qos-policy-rule"], + PercentUsed: fmt.Sprintf("%d%%", int64(float64(usedMap["qos-policy-rule"])/float64(limitMap["qos-policy-rule"])*100)), } - - // TEST - // fmt.Printf("%+v\n", objLimits) - // os.Exit(0) - - ch <- objLimits } -func printObjectLimits(ch <-chan objectLimits, doneCh chan<- struct{}, cmd *cobra.Command) { +func printObjectLimits(ch <-chan objectLimits, error *bytes.Buffer, done chan<- struct{}) { // Print interfaces headerFmt := color.New(color.FgBlue, color.Underline).SprintfFunc() columnFmt := color.New(color.FgHiYellow).SprintfFunc() @@ -322,17 +317,21 @@ func printObjectLimits(ch <-chan objectLimits, doneCh chan<- struct{}, cmd *cobr tbl.WithHeaderFormatter(headerFmt).WithFirstColumnFormatter(columnFmt) for fw := range ch { - tbl.AddRow(fw.Firewall, "Address", fw.Address.Limit, fw.Address.Used, fw.Address.Remaining, fw.Address.PercentUsed) - tbl.AddRow(fw.Firewall, "Address Group", fw.AddressGroup.Limit, fw.AddressGroup.Used, fw.AddressGroup.Remaining, fw.AddressGroup.PercentUsed) - tbl.AddRow(fw.Firewall, "Service", fw.Service.Limit, fw.Service.Used, fw.Service.Remaining, fw.Service.PercentUsed) - tbl.AddRow(fw.Firewall, "Service Group", fw.ServiceGroup.Limit, fw.ServiceGroup.Used, fw.ServiceGroup.Remaining, fw.ServiceGroup.PercentUsed) - tbl.AddRow(fw.Firewall, "Security Policy", fw.PolicyRule.Limit, fw.PolicyRule.Used, fw.PolicyRule.Remaining, fw.PolicyRule.PercentUsed) - tbl.AddRow(fw.Firewall, "PBF Policy", fw.PbfPolicyRule.Limit, fw.PbfPolicyRule.Used, fw.PbfPolicyRule.Remaining, fw.PbfPolicyRule.PercentUsed) - tbl.AddRow(fw.Firewall, "NAT Policy", fw.NatPolicyRule.Limit, fw.NatPolicyRule.Used, fw.NatPolicyRule.Remaining, fw.NatPolicyRule.PercentUsed) - tbl.AddRow(fw.Firewall, "QoS Policy", fw.QosPolicyRule.Limit, fw.QosPolicyRule.Used, fw.QosPolicyRule.Remaining, fw.QosPolicyRule.PercentUsed) + if fw.Error != "" { + error.WriteString(fw.Error) + } else { + tbl.AddRow(fw.Firewall, "Address", fw.Address.Limit, fw.Address.Used, fw.Address.Remaining, fw.Address.PercentUsed) + tbl.AddRow(fw.Firewall, "Address Group", fw.AddressGroup.Limit, fw.AddressGroup.Used, fw.AddressGroup.Remaining, fw.AddressGroup.PercentUsed) + tbl.AddRow(fw.Firewall, "Service", fw.Service.Limit, fw.Service.Used, fw.Service.Remaining, fw.Service.PercentUsed) + tbl.AddRow(fw.Firewall, "Service Group", fw.ServiceGroup.Limit, fw.ServiceGroup.Used, fw.ServiceGroup.Remaining, fw.ServiceGroup.PercentUsed) + tbl.AddRow(fw.Firewall, "Security Policy", fw.PolicyRule.Limit, fw.PolicyRule.Used, fw.PolicyRule.Remaining, fw.PolicyRule.PercentUsed) + tbl.AddRow(fw.Firewall, "PBF Policy", fw.PbfPolicyRule.Limit, fw.PbfPolicyRule.Used, fw.PbfPolicyRule.Remaining, fw.PbfPolicyRule.PercentUsed) + tbl.AddRow(fw.Firewall, "NAT Policy", fw.NatPolicyRule.Limit, fw.NatPolicyRule.Used, fw.NatPolicyRule.Remaining, fw.NatPolicyRule.PercentUsed) + tbl.AddRow(fw.Firewall, "QoS Policy", fw.QosPolicyRule.Limit, fw.QosPolicyRule.Used, fw.QosPolicyRule.Remaining, fw.QosPolicyRule.PercentUsed) + } } tbl.Print() - doneCh <- struct{}{} + done <- struct{}{} } diff --git a/cmd/firewall/get_pingable_hosts.go b/cmd/firewall/get_pingable_hosts.go index 9215335..17baf3b 100644 --- a/cmd/firewall/get_pingable_hosts.go +++ b/cmd/firewall/get_pingable_hosts.go @@ -5,6 +5,7 @@ import ( "crypto/tls" "encoding/base64" "encoding/xml" + "errors" "fmt" "io" "log" @@ -47,13 +48,14 @@ Examples: > panos-cli firewall get pingable-hosts --timeout 1000 --num-addrs 4 fw01.example.com`, Run: func(cmd *cobra.Command, args []string) { + log.Println() + var firewall string // Ensure the target firewall is defined, otherwise exit and display usage if len(args) != 1 { cmd.Help() - fmt.Fprintf(os.Stderr, "\nError: No firewall specified\n") - os.Exit(1) + log.Fatalf("\nno host specified\n\n") } else { firewall = args[0] } @@ -72,13 +74,13 @@ Examples: if userFlagSet || (viper.GetString("apikey") == "" && password == "") { tty, err := os.Open("/dev/tty") if err != nil { - log.Fatal(err, "error allocating terminal") + log.Fatalf("error allocating terminal: %v\n\n", err) } fd := int(tty.Fd()) fmt.Fprintf(os.Stderr, "Password (%s): ", user) bytepw, err := term.ReadPassword(int(fd)) if err != nil { - panic(err) + log.Fatalf("%v\n\n", err) } tty.Close() password = string(bytepw) @@ -87,14 +89,18 @@ Examples: start := time.Now() - fmt.Fprintf(os.Stderr, "Downloading ARP cache from %v ... ", firewall) - data := getArpCache(firewall, userFlagSet) + fmt.Printf("Downloading ARP cache from %v ... ", firewall) + data, err := getArpCache(firewall, userFlagSet) + if err != nil { + red.Fprintf(os.Stderr, "fail\n\n") + log.Fatalf("%v\n\n", err) + } var arpCache addressSlice - err := xml.Unmarshal([]byte(data), &arpCache) + err = xml.Unmarshal([]byte(data), &arpCache) if err != nil { red.Fprintf(os.Stderr, "fail\n\n") - panic(err) + log.Fatalf("%v\n\n", err) } green.Fprintf(os.Stderr, "success\n") @@ -110,7 +116,12 @@ Examples: // Harvest pingable addresses from each interface var pingableHosts []string for _, addrs := range interfaces { - pingableHosts = append(pingableHosts, getPingableAddresses(addrs)...) + pingableAddrs, err := getPingableAddresses(addrs) + if err != nil { + red.Fprintf(os.Stderr, "fail\n") + log.Fatalf("%v\n\n", err) + } + pingableHosts = append(pingableHosts, pingableAddrs...) } green.Fprintf(os.Stderr, "success\n\n") @@ -127,11 +138,11 @@ Examples: for _, addr := range pingableHostsSorted { fmt.Println(addr) } - fmt.Fprintln(os.Stderr) + log.Println() // Print summary elapsed := time.Since(start) - fmt.Fprintf(os.Stderr, " Collection complete: Discovered %d pingable addresses in %.3f seconds\n", len(pingableHosts), elapsed.Seconds()) + log.Printf(" Collection complete: Discovered %d pingable addresses in %.3f seconds\n", len(pingableHosts), elapsed.Seconds()) }, } @@ -144,7 +155,7 @@ func init() { getPingableHostsCmd.Flags().IntVarP(&timeout, "timeout", "t", 250, "ICMP timeout in milliseconds") } -func getPingableAddresses(addrs []string) []string { +func getPingableAddresses(addrs []string) ([]string, error) { var pingableAddrs []string for _, addr := range addrs { @@ -154,7 +165,10 @@ func getPingableAddresses(addrs []string) []string { } // Ping ip addr and add to pingableAddrs if a response is received - stats := pingAddr(addr) + stats, err := pingAddr(addr) + if err != nil { + return nil, err + } if stats.PacketLoss == 0 { pingableAddrs = append(pingableAddrs, addr) @@ -166,15 +180,15 @@ func getPingableAddresses(addrs []string) []string { } } - return pingableAddrs + return pingableAddrs, nil } -func pingAddr(addr string) *ping.Statistics { +func pingAddr(addr string) (*ping.Statistics, error) { // ping ip addr pinger, err := ping.NewPinger(addr) if err != nil { - panic(err) + return nil, err } pinger.SetPrivileged(true) @@ -183,15 +197,15 @@ func pingAddr(addr string) *ping.Statistics { err = pinger.Run() if err != nil { - log.Fatalf("ICMP socket operations require 'sudo'\n") + return nil, errors.New("\nICMP socket operations require 'sudo'") } stats := pinger.Statistics() - return stats + return stats, nil } -func getArpCache(fw string, userFlagSet bool) string { +func getArpCache(fw string, userFlagSet bool) (string, error) { tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } @@ -200,8 +214,7 @@ func getArpCache(fw string, userFlagSet bool) string { url := fmt.Sprintf("https://%s/api/", fw) req, err := http.NewRequest("GET", url, nil) if err != nil { - red.Fprintf(os.Stderr, "fail\n\n") - panic(err) + return "", err } q := req.URL.Query() @@ -219,8 +232,7 @@ func getArpCache(fw string, userFlagSet bool) string { resp, err := client.Do(req) if err != nil { - red.Fprintf(os.Stderr, "fail\n\n") - panic(err) + return "", err } if resp.StatusCode != 200 { red.Fprintf(os.Stderr, "fail\n\n") @@ -231,9 +243,8 @@ func getArpCache(fw string, userFlagSet bool) string { respBody, err := io.ReadAll(resp.Body) if err != nil { - red.Fprintf(os.Stderr, "fail\n\n") - panic(err) + return "", err } - return string(respBody) + return string(respBody), nil } diff --git a/cmd/firewall/run_commands.go b/cmd/firewall/run_commands.go index 1213994..3e11339 100644 --- a/cmd/firewall/run_commands.go +++ b/cmd/firewall/run_commands.go @@ -5,6 +5,7 @@ package firewall import ( "bufio" + "bytes" "fmt" "log" "net" @@ -42,6 +43,7 @@ const SESSION_SETUP = "set cli scripting-mode on" type sessionDetails struct { host string results map[string]string + error string } // runCommandsCmd represents the runCommands command @@ -63,14 +65,9 @@ Examples: > panos-cli panorama get firewalls --terse | panos-cli firewall run commands --command "show system info" --key-based-auth`, Run: func(cmd *cobra.Command, args []string) { - // If the cmds flag is not set, exit and display usage - fmt.Fprintln(os.Stderr) - if len(cmds) == 0 { - cmd.Help() - fmt.Printf("\nno commands specified\n") - os.Exit(1) - } + log.Println() + // Ensure at least one host is specified hosts = cmd.Flags().Args() if len(hosts) == 0 { if isInputFromPipe() { @@ -81,11 +78,16 @@ Examples: } } else { cmd.Help() - fmt.Printf("\nno hosts specified\n") - os.Exit(1) + log.Fatalf("\nno hosts specified\n\n") } } + // If the cmds flag is not set, exit and display usage + if len(cmds) == 0 { + cmd.Help() + log.Fatalf("\nno commands specified\n\n") + } + // Retrieve password from standard input if passwordStdin { if isInputFromPipe() { @@ -96,8 +98,7 @@ Examples: } } else { cmd.Help() - fmt.Printf("\nunable to retrieve password from standard input\n") - os.Exit(1) + log.Fatalf("unable to retrieve password from standard input\n\n") } } @@ -121,8 +122,8 @@ Examples: log.Fatal(err) } tty.Close() - fmt.Fprintf(os.Stderr, "\n\n") password = string(bytepw) + log.Printf("\n\n") } else if password == "" { password = viper.GetString("password") } @@ -130,7 +131,7 @@ Examples: if keyBasedAuth { file, err := os.ReadFile(filepath.Join(os.Getenv("HOME"), ".ssh", "id_rsa")) if err != nil { - log.Fatal(err) + log.Fatalf("%v\n\n", err) } signer, err = ssh.ParsePrivateKey(file) @@ -147,23 +148,24 @@ Examples: log.Fatal(err) } tty.Close() - fmt.Fprintf(os.Stderr, "\n\n") + log.Printf("\n\n") signer, err = ssh.ParsePrivateKeyWithPassphrase(file, passphrase) if err != nil { - log.Fatal(err) + log.Fatalf("%v\n\n", err) } } else { - log.Fatal(err) + log.Fatalf("%v\n\n", err) } } } start := time.Now() + var errorBuffer bytes.Buffer ch := make(chan sessionDetails, 100) doneCh := make(chan struct{}) - go printCmdResults(ch, doneCh) + go printCmdResults(ch, &errorBuffer, doneCh) for _, host := range hosts { wg.Add(1) @@ -173,9 +175,12 @@ Examples: close(ch) <-doneCh - elapsed := time.Since(start) + // Print errors + log.Println(errorBuffer.String()) - fmt.Printf(" Complete: %d command(s) executed on %d host(s) in %.3f seconds\n", len(cmds), len(hosts), elapsed.Seconds()) + // Print summary + elapsed := time.Since(start) + log.Printf(" Complete: %d command(s) executed on %d host(s) in %.3f seconds\n", len(cmds), len(hosts), elapsed.Seconds()) }, } @@ -202,6 +207,10 @@ func runCommands(ch chan<- sessionDetails, host string) { host: host, results: make(map[string]string), } + defer func() { + ch <- session + }() + expectTimeout := time.Duration(expectTimeout) * time.Second // Set auth method @@ -220,7 +229,8 @@ func runCommands(ch chan<- sessionDetails, host string) { var err error hostkeyCallback, err = knownhosts.New(path.Join(os.Getenv("HOME"), ".ssh", "known_hosts")) if err != nil { - log.Fatalf("unable to load ssh known_hosts: %v", err) + session.error = fmt.Sprintf("unable to load ssh known_hosts: %v", err) + return } } @@ -233,53 +243,60 @@ func runCommands(ch chan<- sessionDetails, host string) { Timeout: time.Duration(sshTimeout) * time.Second, }) if err != nil { - log.Fatalf("ssh.Dial(%q) failed: %v", host, err) + session.error = fmt.Sprintf("%s: %v\n\n", host, err) + return } defer sshClt.Close() // Start SSH session e, _, err := expect.SpawnSSH(sshClt, expectTimeout) if err != nil { - log.Fatalf("%s: %v", host, err) + session.error = fmt.Sprintf("%s: %v", host, err) + return } defer e.Close() // Wait for prompt after login _, _, err = e.Expect(promptRE, expectTimeout) if err != nil { - log.Fatalf("%s: %v", host, err) + session.error = fmt.Sprintf("%s: %v", host, err) + return } // Set up session err = e.Send(SESSION_SETUP + "\n") if err != nil { - log.Fatal(err) + session.error = fmt.Sprint(err) + return } _, _, err = e.Expect(promptRE, expectTimeout) if err != nil { - log.Fatalf("%s: %v", host, err) + session.error = fmt.Sprintf("%s: %v", host, err) + return } // Execute commands for _, cmd := range cmds { err = e.Send(cmd + "\n") if err != nil { - log.Fatal(err) + session.error = fmt.Sprint(err) + return } result, _, err := e.Expect(promptRE, expectTimeout) if err != nil { - log.Fatalf("%s: %v", host, err) + session.error = fmt.Sprintf("%s: %v", host, err) + return } session.results[cmd] = result } - - ch <- session } // printCmdResults prints the results -func printCmdResults(ch <-chan sessionDetails, doneCh chan<- struct{}) { - for { - if session, chanIsOpen := <-ch; chanIsOpen { +func printCmdResults(ch <-chan sessionDetails, error *bytes.Buffer, done chan<- struct{}) { + for session := range ch { + if session.error != "" { + error.WriteString(session.error) + } else { green.Printf("\n*** %s ***\n\n\n", session.host) // Sort results by command @@ -292,11 +309,9 @@ func printCmdResults(ch <-chan sessionDetails, doneCh chan<- struct{}) { fmt.Printf("%s\n\n", trimOutput(session.results[cmd])) } blue.Printf("################################################################################\n\n") - } else { - doneCh <- struct{}{} - return } } + done <- struct{}{} // Notify when done } // trimOutput removes the echoed command and the prompt from the output diff --git a/cmd/globalProtect/get_users.go b/cmd/globalProtect/get_users.go index aee1c4d..63fab0d 100644 --- a/cmd/globalProtect/get_users.go +++ b/cmd/globalProtect/get_users.go @@ -1,6 +1,7 @@ package globalProtect import ( + "bytes" "crypto/tls" "encoding/base64" "encoding/xml" @@ -36,7 +37,9 @@ type haState struct { } type userSlice struct { - Users []*conUser `xml:"result>entry"` + Gateway string + Users []*conUser `xml:"result>entry"` + Error string } type conUser struct { @@ -69,18 +72,18 @@ Examples: > panos-cli global-protect get users --connected-user "*doe*"`, Run: func(cmd *cobra.Command, args []string) { + log.Println() + // If no gateways are set by flag or config file, exit cGateways := viper.GetStringMapStringSlice("global-protect")["gateways"] if len(gateways) == 0 && (len(cGateways) == 0 || (len(cGateways) == 1 && cGateways[0] == "")) { cmd.Help() - fmt.Fprintf(os.Stderr, "\n\nNo GlobalProtect Gateways found in config file %v. Update config file or use the --gateways flag.\n", viper.ConfigFileUsed()) - os.Exit(1) + log.Fatalf("\nNo GlobalProtect Gateways found in config file %v. Update config file or use the --gateways flag.\n\n", viper.ConfigFileUsed()) } else if len(gateways) == 0 { gateways = cGateways } // If the apikey and user is not set, prompt for user - fmt.Fprintln(os.Stderr) apikey := viper.GetString("apikey") cUser := viper.GetString("user") if apikey == "" && user == "" && cUser == "" { @@ -101,23 +104,24 @@ Examples: fmt.Fprintf(os.Stderr, "Password (%s): ", user) bytepw, err := term.ReadPassword(int(fd)) if err != nil { - panic(err) + log.Fatalf("%v\n\n", err) } tty.Close() password = string(bytepw) - fmt.Fprintf(os.Stderr, "\n\n") + log.Printf("\n\n") } start := time.Now() + var errorBuffer bytes.Buffer ch := make(chan userSlice, 100) doneCh := make(chan struct{}) userCount := map[string]int{} connectedUserFlagSet := cmd.Flags().Changed("connected-user") - go printResults(ch, doneCh, userCount, connectedUserFlagSet) + go printResults(ch, &errorBuffer, doneCh, userCount, connectedUserFlagSet) - fmt.Fprintf(os.Stderr, "Getting connected users ...\n\n") + log.Printf("Getting connected users ...\n\n") // Get connected users for _, gw := range gateways { @@ -145,15 +149,19 @@ Examples: if k == "total" { continue } - fmt.Fprintf(os.Stderr, "%v: %v\n", k, userCount[k]) + log.Printf("%v: %v\n", k, userCount[k]) } - fmt.Fprintln(os.Stderr) - fmt.Fprintln(os.Stderr, "Total Connected Users:", userCount["total"]) + log.Println("\nTotal Connected Users:", userCount["total"]) + } + + // Print errors + if errorBuffer.String() != "" { + log.Printf("\n%s\n", errorBuffer.String()) } // Print summary elapsed := time.Since(start) - fmt.Fprintf(os.Stderr, "\n\n Completed in %.3f seconds\n", elapsed.Seconds()) + log.Printf("\n\n Completed in %.3f seconds\n", elapsed.Seconds()) }, } @@ -167,7 +175,7 @@ func init() { getUsersCmd.Flags().BoolVarP(&stats, "stats", "s", false, "show connected user statistics") } -func printResults(ch <-chan userSlice, doneCh chan<- struct{}, userCount map[string]int, connectedUserFlagSet bool) { +func printResults(ch <-chan userSlice, error *bytes.Buffer, done chan<- struct{}, userCount map[string]int, connectedUserFlagSet bool) { // Print connected users headerFmt := color.New(color.FgBlue, color.Underline).SprintfFunc() columnFmt := color.New(color.FgHiYellow).SprintfFunc() @@ -177,34 +185,40 @@ func printResults(ch <-chan userSlice, doneCh chan<- struct{}, userCount map[str var connectedUsers []*conUser for users := range ch { - for _, user := range users.Users { - // Print user - if connectedUserFlagSet { - if m, _ := wildcard.Match(connectedUser, user.Username); m { + if users.Error != "" { + error.WriteString(users.Error) + } else { + for _, user := range users.Users { + // Print user + if connectedUserFlagSet { + if m, _ := wildcard.Match(connectedUser, user.Username); m { + connectedUsers = append(connectedUsers, user) + } + } else { connectedUsers = append(connectedUsers, user) } - } else { - connectedUsers = append(connectedUsers, user) + userCount[user.Gateway] += 1 + userCount["total"] += 1 } - userCount[user.Gateway] += 1 - userCount["total"] += 1 } - } - sort.Slice(connectedUsers, func(i, j int) bool { - return connectedUsers[i].Username < connectedUsers[j].Username - }) + sort.Slice(connectedUsers, func(i, j int) bool { + return connectedUsers[i].Username < connectedUsers[j].Username + }) - for _, user := range connectedUsers { - tbl.AddRow(user.Username, user.Domain, user.Computer, user.Client, user.VirtualIP, user.PublicIP, user.LoginTime, user.Gateway) + for _, user := range connectedUsers { + tbl.AddRow(user.Username, user.Domain, user.Computer, user.Client, user.VirtualIP, user.PublicIP, user.LoginTime, user.Gateway) + } } tbl.Print() - doneCh <- struct{}{} + done <- struct{}{} } func queryGateway(gw, apikey string, userFlagSet bool) userSlice { + var connectedUsers userSlice + tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } @@ -213,7 +227,8 @@ func queryGateway(gw, apikey string, userFlagSet bool) userSlice { url := fmt.Sprintf("https://%s/api/", gw) req, err := http.NewRequest("GET", url, nil) if err != nil { - panic(err) + connectedUsers.Error = fmt.Sprintf("%s: %v\n\n", gw, err) + return connectedUsers } q := req.URL.Query() @@ -231,7 +246,8 @@ func queryGateway(gw, apikey string, userFlagSet bool) userSlice { resp, err := client.Do(req) if err != nil { - panic(err) + connectedUsers.Error = fmt.Sprintf("%s: %v\n\n", gw, err) + return connectedUsers } if resp.StatusCode != 200 { log.Fatal(resp.Status) @@ -241,13 +257,14 @@ func queryGateway(gw, apikey string, userFlagSet bool) userSlice { respBody, err := io.ReadAll(resp.Body) if err != nil { - panic(err) + connectedUsers.Error = fmt.Sprintf("%s: %v\n\n", gw, err) + return connectedUsers } - var connectedUsers userSlice err = xml.Unmarshal([]byte(respBody), &connectedUsers) if err != nil { - panic(err) + connectedUsers.Error = fmt.Sprintf("%s: %v\n\n", gw, err) + return connectedUsers } for _, user := range connectedUsers.Users { @@ -257,7 +274,7 @@ func queryGateway(gw, apikey string, userFlagSet bool) userSlice { return connectedUsers } -func gatewayActive(gw, apikey string, userFlagSet bool) bool { +func gatewayActive(gw, apikey string, userFlagSet bool) (bool, error) { tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } @@ -266,7 +283,7 @@ func gatewayActive(gw, apikey string, userFlagSet bool) bool { url := fmt.Sprintf("https://%s/api/", gw) req, err := http.NewRequest("GET", url, nil) if err != nil { - panic(err) + return false, err } q := req.URL.Query() @@ -284,7 +301,7 @@ func gatewayActive(gw, apikey string, userFlagSet bool) bool { resp, err := client.Do(req) if err != nil { - panic(err) + return false, err } if resp.StatusCode != 200 { log.Fatal(resp.Status) @@ -294,25 +311,31 @@ func gatewayActive(gw, apikey string, userFlagSet bool) bool { respBody, err := io.ReadAll(resp.Body) if err != nil { - panic(err) + return false, err } var haState haState err = xml.Unmarshal([]byte(respBody), &haState) if err != nil { - panic(err) + return false, err } if haState.Enabled == "no" || haState.State == "active" { - return true + return true, nil } - return false + return false, nil } func getConnectedUsers(gw, apikey string, ch chan<- userSlice, userFlagSet bool) { defer wg.Done() - if gatewayActive(gw, apikey, userFlagSet) { + + if active, err := gatewayActive(gw, apikey, userFlagSet); err != nil { + ch <- userSlice{ + Gateway: gw, + Error: err.Error(), + } + } else if active { ch <- queryGateway(gw, apikey, userFlagSet) } } diff --git a/cmd/panorama/get_firewalls.go b/cmd/panorama/get_firewalls.go index a876c19..1bc2f46 100644 --- a/cmd/panorama/get_firewalls.go +++ b/cmd/panorama/get_firewalls.go @@ -241,7 +241,7 @@ Examples: // Print summary elapsed := time.Since(start) if !terse { - fmt.Fprintf(os.Stderr, " \n\nCompleted in %.3f seconds\n", elapsed.Seconds()) + fmt.Fprintf(os.Stderr, " Completed in %.3f seconds\n", elapsed.Seconds()) } }, } diff --git a/cmd/root.go b/cmd/root.go index 3e24d23..242e55c 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -4,6 +4,7 @@ import ( "bufio" "bytes" "fmt" + "log" "os" "strings" @@ -33,13 +34,17 @@ var RootCmd = &cobra.Command{ func Execute() { err := RootCmd.Execute() if err != nil { - os.Exit(1) + panic(err) } } func init() { RootCmd.PersistentFlags().BoolVar(&noConfig, "no-config", false, "bypass the configuration file") + RootCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) { + log.SetFlags(0) + } + // Bypass the config file if the --no-config flag is set if slices.Contains(os.Args, "--no-config") { return