package pkg import ( "encoding/json" "errors" "fmt" "regexp" "strconv" "strings" "github.rackspace.com/gorax/clients/sshAPI" "github.rackspace.com/gorax/tools/util" ) const ( promptRegex = `\(tmos\)#\s+$` bashPromptRegex = `#\s+$` defaultCommandTimer = 120 ltmLogsCommand = `run /util bash -c "cat /var/log/ltm && zcat /var/log/ltm*.gz"` ) var ( packetLossFullRegex = regexp.MustCompile(`(?i)100%\s+packet\s+loss`) packetLossRegex = regexp.MustCompile(`(?i)packet\s+loss`) masterKeyRe = regexp.MustCompile(`(?i)\n(.\S+)\n`) provisionAFMRegex = regexp.MustCompile(`(?i)level\s+(\S+)`) ) // ltmComponent : Properly formats a name/partition combo to support mult-partition F5's func ltmComponent(name, partition string) string { // If neither name nor partition were given, return all the things! if partition == "" && name == "" { return "/*/*" } // If no partition was provided, just use the name by itself if partition == "" { return name } // if a partition was provided, but not a name, then use /partition/* if name == "" { name = "*" } // if both a partition and a name was provided (or we set the name to *), format it as /partition/name return fmt.Sprintf("/%s/%s", partition, name) } func (f5 *F5) CmDevices(n string, timeout int) error { // If the version isn't already populated, do so, we'll need it for parsing the return if f5.Status.Version == nil { if err := f5.Version(30); err != nil { return fmt.Errorf("error populating software version which is required to know how to parse returned data: %s", err.Error()) } } cmd, err := f5.sshClient.PostCommand(fmt.Sprintf("show /cm device %s", n), defaultPrompt, timeout) if err != nil { return err } f5.Status.CmDevices = ParseCmDevice(*cmd.Response, f5.Status.Version.Version) return nil } func (f5 *F5) CmDeviceGroups(n string, timeout int) error { cmd, err := f5.sshClient.PostCommand(fmt.Sprintf("list /cm device-group %s all-properties", n), defaultPrompt, timeout) if err != nil { return err } c := new(CmDeviceGroups) c.Parse(*cmd.Response) f5.Config.CM.DeviceGroups = c return nil } func (f5 *F5) Version(timeout int) error { cmd, err := f5.sshClient.PostCommand("show sys version", defaultPrompt, timeout) if err != nil { return err } f5.Status.Version = ParseSysVersion(*cmd.Response) return nil } func (f5 *F5) InterfaceConfig(n string, timeout int) error { cmd, err := f5.sshClient.PostCommand(fmt.Sprintf("list /net interface %s", n), defaultPrompt, timeout) if err != nil { return err } i, err := ParseInterfaces(*cmd.Response) if err != nil { return err } f5.Config.Interfaces = i return nil } func (f5 *F5) VlanConfig(n string, timeout int) error { cmd, err := f5.sshClient.PostCommand(fmt.Sprintf("list /net vlan %s", n), defaultPrompt, timeout) if err != nil { return err } i, err := ParseVLANs(*cmd.Response) if err != nil { return err } f5.Config.VLANs = i return nil } func (f5 *F5) RouteConfig(n string, timeout int) error { cmd, err := f5.sshClient.PostCommand(fmt.Sprintf("list /net route %s", n), defaultPrompt, timeout) if err != nil { return err } i, err := ParseRoutes(*cmd.Response) if err != nil { return err } f5.Config.Routes = i return nil } func (f5 *F5) NatConfig(n, p string, timeout int) error { c := fmt.Sprintf("list /ltm nat %s", ltmComponent(n, p)) cmd, err := f5.sshClient.PostCommand(c, defaultPrompt, timeout) if err != nil { return err } r, err := responseCleanup(*cmd.Response) if err != nil { return err } i, err := ParseNATs(r) if err != nil { return err } f5.Config.Nat = i return nil } func (f5 *F5) NodeConfig(name, partition string, timeout int) error { c := "list ltm node " + ltmComponent(name, partition) + " all-properties one-line" cmd, err := f5.sshClient.PostCommand(c, defaultPrompt, timeout) if err != nil { return err } r, err := responseCleanup(*cmd.Response) if err != nil { return err } i, err := ParseNodes(r) if err != nil { return err } f5.Config.Nodes = i return nil } func (f5 *F5) PasswordPolicy(timeout int) error { cmd, err := f5.sshClient.PostCommand("list /auth password-policy", defaultPrompt, timeout) if err != nil { return err } i := PasswordPolicyParse(*cmd.Response) f5.Config.PasswordPolicy = i return nil } func (f5 *F5) PoolConfig(name, partition string, timeout int) error { c := fmt.Sprintf("list ltm pool %s all-properties one-line", ltmComponent(name, partition)) cmd, err := f5.sshClient.PostCommand(c, defaultPrompt, timeout) if err != nil { return fmt.Errorf("error executing command `%s`: %s", c, err.Error()) } clean, err := responseCleanup(*cmd.Response) if err != nil { return fmt.Errorf("error from response cleanup: %s", err.Error()) } f5.Config.Pools, err = ParsePools(clean) if err != nil { return fmt.Errorf("error parsing pools: %s", err.Error()) } return nil } func (f5 *F5) VirtualConfig(name, partition string, timeout int) error { c := fmt.Sprintf("list /ltm virtual %s", ltmComponent(name, partition)) cmd, err := f5.sshClient.PostCommand(c, defaultPrompt, timeout) if err != nil { return err } r, err := responseCleanup(*cmd.Response) if err != nil { return err } i, err := ParseVirtuals(r) if err != nil { return err } f5.Config.Virtuals = i return nil } func (f5 *F5) RouteDomainsConfig(n string, timeout int) error { cmd, err := f5.sshClient.PostCommand(fmt.Sprintf("list /net route-domain %s", n), defaultPrompt, timeout) if err != nil { return err } i, err := ParseRouteDomains(*cmd.Response) if err != nil { return err } f5.Config.RouteDomains = i return nil } func (f5 *F5) ARPStatus(timeout int) error { cmd, err := f5.sshClient.PostCommand("show net arp", defaultPrompt, timeout) if err != nil { return err } i := ParseARPStatus(*cmd.Response) f5.Status.ARP = i.ArpTable return nil } func (f5 *F5) LicenseStatus(timeout int) error { cmd, err := f5.sshClient.PostCommand("show /sys license", defaultPrompt, timeout) if err != nil { return err } f5.Status.License = ParseSysLicense(*cmd.Response) return nil } func (f5 *F5) PartitionStatus(timeout int) error { cmd, err := f5.sshClient.PostCommand("list /auth partition all-properties", defaultPrompt, timeout) if err != nil { return err } f5.Status.SysPartitions = ParseSysPartitions(*cmd.Response) return nil } func (f5 *F5) SoftwareStatus(timeout int) error { cmd, err := f5.sshClient.PostCommand("show /sys software", defaultPrompt, timeout) if err != nil { return err } i := ParseSysSoftware(*cmd.Response) f5.Status.SysSoftware = i.Software return nil } func (f5 *F5) InterfaceStatus(timeout int) error { cmd, err := f5.sshClient.PostCommand("show net interface raw", defaultPrompt, timeout) if err != nil { return err } i := ParseInterfaceStatus(*cmd.Response) f5.Status.Interfaces = i.Interfaces return nil } func (f5 *F5) GetSyncState(timeout int) error { ver, err := f5.MajorVersion() if err != nil { return err } cmdSend := "show cm sync-status" if strings.Contains(ver, "10") { cmdSend = "show sys config-sync" } cmd, err := f5.sshClient.PostCommand(cmdSend, defaultPrompt, timeout) if err != nil { return err } var c *SyncState switch ver { case "10": c = ParseV10SyncState(*cmd.Response) break case "12": c = ParseV12SyncState(*cmd.Response) default: c = ParseV11SyncState(*cmd.Response) } f5.Status.SyncState = c return nil } func (f5 *F5) SysDB(key string) (*SysDB, error) { c := fmt.Sprintf("list sys db %s", key) cmd, err := f5.sshClient.PostCommand(c, defaultPrompt, defaultTimeout) if err != nil { return nil, err } i := ParseSysDB(*cmd.Response) return i, nil } func (f5 *F5) FullSysDB() error { i, err := f5.SysDB("") if err != nil { return err } f5.Config.SysDB = i.Fields return nil } func (f5 *F5) GetDBKey(key string) (*DBItem, error) { if k := FindDBKey(key, f5.Config.SysDB); k != nil { return k, nil } db, err := f5.SysDB(key) if err != nil { return nil, err } if k := db.FindDBKey(key); k != nil { return k, nil } return nil, fmt.Errorf("unable to find %s in the sys db", key) } func (f5 *F5) HasAfm() (hasAfm bool, level string, err error) { c := "list /sys provision afm one-line" var cmd *sshAPI.SSHCommand cmd, err = f5.sshClient.PostCommand(c, defaultPrompt, defaultTimeout) if err != nil { err = fmt.Errorf("error running '%s': %s", c, err.Error()) return } lines := strings.Split(*cmd.Response, "\n") for _, l := range lines { rgxAfm := provisionAFMRegex.FindStringSubmatch(l) if len(rgxAfm) == 2 { hasAfm = true level = rgxAfm[1] return } } return } func (f5 *F5) GetSNMP() error { c := "list /sys snmp" cmd, err := f5.sshClient.PostCommand(c, defaultPrompt, defaultTimeout) if err != nil { return fmt.Errorf("error running '%s': %s", c, err.Error()) } if err = f5.Config.SNMPConfig.Parse(*cmd.Response); err != nil { return fmt.Errorf("error parsing 'list /sys snmp' raw response: %s", err.Error()) } return nil } func (f5 *F5) GetMonitors(ignoreUnknownMonitors bool) error { c := "list /ltm monitor" cmd, err := f5.sshClient.PostCommand(c, defaultPrompt, defaultTimeout) if err != nil { return fmt.Errorf("error running '%s': %s", c, err.Error()) } f5.Config.LTMMonitors = new(LTMMonitors) //init to avoid nil references if err = f5.Config.LTMMonitors.Parse(*cmd.Response, ignoreUnknownMonitors); err != nil { return fmt.Errorf("error parsing 'list /ltm monitor' raw response: %s", err.Error()) } return nil } func (f5 *F5) GetDf() error { cmd, err := f5.sshClient.PostCommand("run util bash -c 'df -h'", defaultPrompt, 10) if err != nil { return err } f5.Management.DfSpace = ParseDfSpace(*cmd.Response) return nil } func (f5 *F5) GetFilesystems() error { if err := f5.GetDf(); err != nil { return err } cmd, err := f5.sshClient.PostCommand("run util bash -c 'blkid'", defaultPrompt, 10) if err != nil { return err } f, err := ParseFilesystemInfo(*cmd.Response) if err != nil { return err } err = SetFilesystemMounted(&f, f5.Management.DfSpace) if err != nil { return err } f5.Management.Filesystems = f return nil } func (f5 *F5) GetDumpe2fs() error { if err := f5.GetFilesystems(); err != nil { return err } dumpe2fsCommand := "set -x; " for _, filesystem := range f5.Management.Filesystems.Info { if string(filesystem.Type) == "ext3" { dumpe2fsCommand += "dumpe2fs -h " + filesystem.Device + "; " } } dumpe2fsCommand += "set +x;" cmd, err := f5.sshClient.PostCommand(fmt.Sprintf("run util bash -c '%s'", dumpe2fsCommand), defaultPrompt, 10) if err != nil { return err } d, err := ParseDumpe2fsInfo(*cmd.Response) if err != nil { return err } f5.Management.Dumpe2fsInfo = d return nil } func (f5 *F5) GetFSCK() error { if err := f5.GetDumpe2fs(); err != nil { return err } f, err := BuildFSCKInfo(f5.Management.Dumpe2fsInfo) if err != nil { return err } f5.Management.FSCK = f return nil } func (f5 *F5) ManagementIP(timeout int) error { cmd, err := f5.sshClient.PostCommand("list sys management-ip", defaultPrompt, timeout) if err != nil { return err } i := ParseManagementIP(*cmd.Response) f5.Management.IP = i return nil } func (f5 *F5) ManagementRoutes(timeout int) error { cmd, err := f5.sshClient.PostCommand("list sys management-route", defaultPrompt, timeout) if err != nil { return err } i := ParseManagementRoutes(*cmd.Response) f5.Management.Routes = i.MgmtRoutes return nil } func (f5 *F5) PacketFilterConfig(n string, timeout int) error { c := fmt.Sprintf("list /net packet-filter %s", n) cmd, err := f5.sshClient.PostCommand(c, defaultPrompt, defaultTimeout) if err != nil { return err } i, err := ParsePacketFilters(*cmd.Response) if err != nil { return err } f5.Config.PacketFilters = i return nil } func (f5 *F5) SysProvision(timeout int) error { cmd, err := f5.sshClient.PostCommand("list /sys provision", defaultPrompt, timeout) if err != nil { return err } i := ParseProvision(*cmd.Response) f5.Config.Provision = i return nil } func (f5 *F5) RouteStatus(timeout int) error { cmd, err := f5.sshClient.PostCommand("show net route", defaultPrompt, timeout) if err != nil { return err } i := ParseShowRoutes(*cmd.Response) f5.Status.Routes = i.Routes return nil } func (f5 *F5) VlanStatus(n string, timeout int) error { cmd, err := f5.sshClient.PostCommand(fmt.Sprintf("show /net vlan %s", n), defaultPrompt, timeout) if err != nil { return err } i := ParseVlanStatus(*cmd.Response) f5.Status.Vlans = i.Vlans return nil } func (f5 *F5) SelfAllowed(n string, timeout int) error { cmd, err := f5.sshClient.PostCommand(fmt.Sprintf("list /net self-allow %s", n), defaultPrompt, timeout) if err != nil { return err } i := ParseSelfAllow(*cmd.Response) f5.Config.SelfAllow = i return nil } func (f5 *F5) SelfConfig(n string, timeout int) error { cmd, err := f5.sshClient.PostCommand(fmt.Sprintf("list /net self %s", n), defaultPrompt, timeout) if err != nil { return err } i, err := ParseSelfIPs(*cmd.Response) if err != nil { return err } f5.Config.SelfIPs = i return nil } func (f5 *F5) VirtualAddressConfig(name, partition string) error { c := fmt.Sprintf("list /ltm virtual-address %s", ltmComponent(name, partition)) cmd, err := f5.sshClient.PostCommand(c, defaultPrompt, defaultTimeout) if err != nil { return err } r, err := responseCleanup(*cmd.Response) if err != nil { return err } i, err := ParseVirtualAddresses(r) if err != nil { return err } f5.Config.VirtualAddresses = i return nil } func (f5 *F5) TrafficGroupConfig(name string, timeout int) error { // If they passed us a custom timeout via a query param, use it instead if timeout == 0 { timeout = defaultTimeout } c := fmt.Sprintf("list /cm traffic-group %s all-properties", name) cmd, err := f5.sshClient.PostCommand(c, defaultPrompt, timeout) if err != nil { return err } i := ParseTrafficGroupConfig(*cmd.Response) f5.Config.CM.TrafficGroups = i.CmTrafficGroups return nil } func (f5 *F5) Ping(ip string) (bool, string, error) { cmd, err := f5.sshClient.PostCommand(fmt.Sprintf("run /util ping %s -c 3", ip), defaultPrompt, defaultTimeout) if err != nil { return false, "", err } if packetLossRegex.MatchString(*cmd.Response) { if !packetLossFullRegex.MatchString(*cmd.Response) { return true, *cmd.Response, nil } } return false, *cmd.Response, nil } func (f5 *F5) GetSoftwareImage(n string, timeout int) error { cmd, err := f5.sshClient.PostCommand(fmt.Sprintf("list /sys software image %s", n), defaultPrompt, timeout) if err != nil { return err } i := ParseSoftwareImage(*cmd.Response) f5.Management.SoftwareImages = i.SwImages return nil } func (f5 *F5) GetSoftwareHotfix(n string, timeout int) error { cmd, err := f5.sshClient.PostCommand(fmt.Sprintf("list /sys software hotfix %s", n), defaultPrompt, timeout) if err != nil { return err } i := ParseSofwareHotfix(*cmd.Response) f5.Management.SoftwareHotfix = i.SwHotfixes return nil } func (f5 *F5) VerifyImage(filename string, timeout int) (*string, error) { cmd, err := f5.sshClient.PostCommand("run /util bash -c 'md5sum /shared/images/"+filename+"'", defaultPrompt, timeout) if err != nil { return nil, err } i := ParseVerify(*cmd.Response) return i.Md5, nil } func (f5 *F5) Md5Sum(filename string, timeout int) (*Verify, error) { cmd, err := f5.sshClient.PostCommand("run /util bash -c 'md5sum /shared/images/"+filename+"'", defaultPrompt, timeout) if err != nil { return nil, err } i := ParseVerify(*cmd.Response) if i.Md5 == nil { return nil, fmt.Errorf("unable to validate md5. Verify that /shared/images/%v exists on disk", filename) } return i, nil } func (f5 *F5) CurlToDisk(Path string, Dest string, MaxTimeout int, ConnTimeout int) (string, error) { var ( CMDRESP = regexp.MustCompile(`.*\n(.*\S+)`) ) cmd, err := f5.sshClient.PostReCommand(fmt.Sprintf("run /util bash -c 'curl --silent -o %v %v --connect-timeout %v > /dev/null'", Dest, Path, strconv.Itoa(ConnTimeout)), promptRegex, MaxTimeout) if err != nil { return "", err } resp := util.RegexpString(CMDRESP, *cmd.Response) if strings.Contains(resp, "curl: (") { return "", errors.New(resp) } return resp, nil } func (f5 *F5) ListImages(path string, timeout int) error { if timeout == 0 { timeout = defaultTimeout } if path == "" { path = "/shared/images/" } cmd, err := f5.sshClient.PostReCommand(fmt.Sprintf("run /util bash -c 'ls -lk %v'", path), promptRegex, timeout) if err != nil { return err } resp := util.RegexpString(regexp.MustCompile(`(?i)(no\s+such\s+file\s+or\s+directory)`), *cmd.Response) if resp != "" { return errors.New(resp) } f5.Management.DirectoryList = ParseDirectoryList(*cmd.Response) return nil } func (f5 *F5) GetChangeAudit() (*LogEntries, error) { s := `run /util bash -c "zcat /var/log/audit.1.gz | egrep 'cmd_data=create|cmd_data=delete|cmd_data=modify|object 0 - modify|object 0 - create|object 0 - delete'"` cmd, err := f5.sshClient.PostReCommand(s, promptRegex, 120) if err != nil { return nil, err } i := ParseAuditLogs(*cmd.Response) return i, nil } // GetLtmLogs method will populate the F5.Logs.LtmLogs field with parsed ltm logs from both the current and zipped log files in the format of []LtmLogLine func (f5 *F5) GetLtmLogs() (err error) { if cmd, err := f5.sshClient.PostReCommand(ltmLogsCommand, promptRegex, defaultCommandTimer); err != nil { return err } else { f5.Logs.LtmLogs = ParseLtmLog(*cmd.Response) return err } } func (f5 *F5) GetSysMasterKey() error { cmd, err := f5.sshClient.PostReCommand(`show /sys master-key`, promptRegex, 20) if err != nil { return err } i, err := ParseMasterKey(*cmd.Response) if err != nil { return err } f5.Status.MasterKey = i return nil } func (f5 *F5) GetMasterKey(timeout int) error { cmd, err := f5.sshClient.PostReCommand(`run util bash -c 'f5mku -K'`, promptRegex, timeout) if err != nil { return err } s := masterKeyRe.FindStringSubmatch(*cmd.Response) if len(s) != 2 { return errors.New("unable to parse master-key values") } f5.Config.MasterKey.Key = s[1] return nil } // getSSHError : Will attempt to json decode the respond from sshapi and look for an "error" entry func getSSHError(s string) interface{} { var data map[string]interface{} if err := json.Unmarshal([]byte(s), &data); err != nil { return s } if v, ok := data["error"]; ok { return v.(string) } return s } func (f5 *F5) GetAfmRuleLists(n string, timeout int) error { f5.log.Debug("GetAfmRuleLists() - Begin") defer f5.log.Debug("GetAfmRuleLists() - Complete") c := fmt.Sprintf("list /security firewall rule-list %s", n) cmd, err := f5.sshClient.PostCommand(c, defaultPrompt, timeout) if err != nil { return fmt.Errorf("error running `%s`: %v", c, getSSHError(err.Error())) } i, err := ParseRuleLists(*cmd.Response) if err != nil { return fmt.Errorf("ParseRuleLists: %s", err.Error()) } f5.Config.AFMRuleLists = i return nil } func (f5 *F5) GetAfmPolicyLists(n string, timeout int) error { f5.log.Debug("GetAfmPolicyLists() - Begin") defer f5.log.Debug("GetAfmPolicyLists() - Complete") c := fmt.Sprintf("list /security firewall policy %s", n) cmd, err := f5.sshClient.PostCommand(c, defaultPrompt, timeout) if err != nil { return fmt.Errorf("error running `%s`: %v", c, getSSHError(err.Error())) } i, err := ParsePolicyLists(*cmd.Response) if err != nil { return err } f5.Config.AFMPolicyLists = i return nil } func (f5 *F5) GetAfmAddressLists(n string, timeout int) error { f5.log.Debug("GetAfmAddressLists() - Begin") defer f5.log.Debug("GetAfmAddressLists() - Complete") c := fmt.Sprintf("list /security firewall address-list %s", n) cmd, err := f5.sshClient.PostCommand(c, defaultPrompt, timeout) if err != nil { return fmt.Errorf("error running `%s`: %v", c, getSSHError(err.Error())) } i, err := ParseAddressLists(*cmd.Response) if err != nil { return fmt.Errorf("error parsing address lists: %s", err.Error()) } f5.Config.AFMAddressLists = i return nil } func (f5 *F5) GetAfmGlobalRules(timeout int) error { if timeout == 0 { timeout = defaultTimeout } c := fmt.Sprintf("list /security firewall global-rules") cmd, err := f5.sshClient.PostCommand(c, defaultPrompt, timeout) if err != nil { return err } var i GlobalRules i.Parse(*cmd.Response) f5.Config.AFMGlobalRules = i.Rule return nil } func (f5 *F5) GetCliGlobalSettings(timeout int) error { if timeout == 0 { timeout = defaultTimeout } cmd, err := f5.sshClient.PostCommand("list cli global-settings all-properties", defaultPrompt, timeout) if err != nil { return err } f5.Config.CliSettings = ParseCliGlobalSettings(*cmd.Response) return nil } // Active : Returns the core device ID for the active device in an HA pair func (f5 *F5) Active() (string, error) { res, err := f5.sshClient.PostCommand("\n", "# ", 30) if err != nil { return "", err } if strings.Contains(*res.Response, "(Standby)") { if f5.deviceInfo == nil { return "", errors.New("core api device info is not populated") } if f5.deviceInfo.FailoverDevice == nil { return "", errors.New("core api failover device info is not populated") } return f5.deviceInfo.FailoverDevice.FailoverID, nil } return strconv.Itoa(f5.Device), nil }