package pkg import ( "errors" "fmt" "regexp" "strconv" "strings" ) type ( SSLCertInfo struct { CertificateKeySize int `json:"certificate_key_size"` City string `json:"city"` CommonName string `json:"common_name"` Country string `json:"country"` EmailAddress string `json:"email_address"` Expiration string `json:"expiration"` Organization string `json:"origanization"` OU string `json:"ou"` PublicKeyType string `json:"public_key_type"` State string `json:"state"` SubjectAlternativeName string `json:"subject_alternative_name"` } SysCryptoKey struct { KeySize int `json:"key_size"` KeyType string `json:"key_type"` SecurityType string `json:"security_type"` CurveName string `json:"curve_name,omitempty"` } SysCrypto struct { Certs map[string]SSLCertInfo `json:"cert"` Keys map[string]SysCryptoKey `json:"key"` } SysApplication struct { APLScript map[string]APLScript `json:"apl_script,omitempty"` CustomStat map[string]CustomStat `json:"custom_stat,omitempty"` Service map[string]Service `json:"service,omitempty"` Template map[string]Template `json:"template,omitempty"` } Service struct { // Sample F5 does not have any of these defined, so not sure what types they are Description interface{} `json:"description"` // User defined description. DeviceGroup interface{} `json:"device_group"` // The name of the device group that the application service is assigned to. InheritedDeviceGroup interface{} `json:"inherited_device_group"` // Read-only. Shows whether the application folder will automatically remain with the same device-group as its parent folder. Use 'device-group default' or 'device-group non-default' to set this. InheritedTrafficGroup interface{} `json:"inherited_traffic_group"` // Read-only. Shows whether the application folder will automatically remain with the same traffic-group as its parent folder. Use 'traffic-group default' or 'traffic-group non-default' to set this. Lists interface{} `json:"lists"` // The set of list variables and values that are passed to template scripts. Metadata interface{} `json:"metadata"` // User defined generic data for the application service. It is a name and value pair. Specifies the encapsulation metadata to use for the application service. Partition interface{} `json:"partition"` // Displays the administrative partition within which the application resides. StrictUpdates interface{} `json:"strict_updates"` // Specifies whether configuration objects contained in the application may be directly modified, outside the context of the system's application management interfaces. Tables interface{} `json:"tables"` // The set of table variables and values that are passed to template scripts. Template interface{} `json:"template"` // The template defines the configuration for the application. This may be changed after the application has been created to move the application to a new template. TemplateModified interface{} `json:"template_modified"` // Indicates that the application template used to deploy the application has been modified. The application should be updated to make use of the latest changes. TemplatePreRequisiteErrors interface{} `json:"template_prerequisite_errors"` // Indicates any missing prerequisites associated with the template that defines this application. TrafficGroup interface{} `json:"traffic_group"` // The name of the traffic group that the application service is assigned to. Variables interface{} `json:"variables"` // The set of atomic variables and values that are passed to template scripts. } Template struct{} CustomStat struct{} APLScript struct{} Sys struct { //Application interface{} `json:"application"` // Configure an application - The sample F5 doesn't have anything in this Crypto SysCrypto `json:"crypto"` // Manage cryptographic objects such as keys and certificates. DaemonLogSettings interface{} `json:"daemon_log_settings"` // Daemon log configuration Disk interface{} `json:"disk"` // Manage disk configuration and utilization ECM interface{} `json:"ecm" ` // Configures the Elastic Compute Manager(ECM) File SysFile `json:"file"` // System files management FPGA interface{} `json:"fpga"` // FPGA(Field-Programmable Gate Array) firmware configuration ICall interface{} `json:"icall"` // Manage iCall configuration items IPFix interface{} `json:"ipfix"` // Manages the common ipfix interface LogConfig interface{} `json:"log_config"` // Manages the common logging interface PFMan interface{} `json:"pfman"` // Pfman Health Status Management SFlow interface{} `json:"sflow"` // sFlow configuration Software interface{} `json:"software"` // Software Installation and Volume Management URLDB interface{} `json:"url_db"` // URL Database Configuration and Category Management } SysFile struct { ApacheSSLCert map[string]FileInfo `json:"apache_ssl_cert"` // SSL Certificates Management BrowserCapabilitiesDB map[string]FileInfo `json:"browser_capabilities_db"` // Browser cpabilities DB file management DashboardViewset map[string]FileInfo `json:"dashboard_viewset"` DataGroups map[string]FileInfo `json:"data_groups"` // External Data Group files management DeviceCapabilitiesDB map[string]FileInfo `json:"device_capabilities_db"` // Device capabilities DB file management ExternalMonitor map[string]FileInfo `json:"external_monitor"` // External Monitor files management IFile map[string]FileInfo `json:"ifile"` // iFile files management LWTunnelTbl map[string]FileInfo `json:"lwtunneltbl"` // LW4o6 table files management SSLCert map[string]FileInfo `json:"ssl_cert"` // SSL certificates management SSLCRL map[string]FileInfo `json:"ssl_crl"` // SSL CRL files management SSLCSR map[string]FileInfo `json:"ssl_csr"` SSLKey map[string]FileInfo `json:"ssl_key"` // SSL certificate keys management SystemSSLCert map[string]FileInfo `json:"system_sls_cert"` SystemSSLKey map[string]FileInfo `json:"system_ssl_key"` } ) // Parse : Parse the Sys structure func (s *Sys) Parse(f5 *F5) error { f5.log.Debug("Sys.Parse() - Begin") defer f5.log.Debug("Sys.Parse() - End") // list sys application - The sample F5 doesn't use this, so not sure what's in it // TODO : list sys crypto // TODO : list sys daemon-log-settings // TODO : list sys disk // TODO : list sys ecm // TODO : list sys fpga // TODO : list sys icall // TODO : list sys ipfix // TODO : list sys log-config // TODO : list sys pfman // TODO : list sys sflow // TODO : list sys software // TODO : list sys url-db f5.log.WithField("command", "list sys file").Debug("Executing command") res, err := f5.sshClient.PostCommand("list sys file", defaultPrompt, defaultTimeout) if err != nil { return err } if err := s.File.Parse(f5, *res.Response); err != nil { return err } return nil } // Parse : Parse the SysFile Structure func (sf *SysFile) Parse(f5 *F5, raw string) error { f5.log.Debug("SysFile.Parse() - Begin") defer f5.log.Debug("SysFile.Parse() - End") // Initialize maps sf.ApacheSSLCert = make(map[string]FileInfo) sf.BrowserCapabilitiesDB = make(map[string]FileInfo) sf.DashboardViewset = make(map[string]FileInfo) sf.DataGroups = make(map[string]FileInfo) sf.DeviceCapabilitiesDB = make(map[string]FileInfo) sf.ExternalMonitor = make(map[string]FileInfo) sf.IFile = make(map[string]FileInfo) sf.LWTunnelTbl = make(map[string]FileInfo) sf.SSLCert = make(map[string]FileInfo) sf.SSLCRL = make(map[string]FileInfo) sf.SSLCSR = make(map[string]FileInfo) sf.SSLKey = make(map[string]FileInfo) sf.SystemSSLCert = make(map[string]FileInfo) sf.SystemSSLKey = make(map[string]FileInfo) // Find all the file names and their types res := regexp.MustCompile(`sys file (\S+) (\S+) {`).FindAllStringSubmatch(raw, -1) // Process one file at a time for _, dg := range res { // Get the details for this file c := fmt.Sprintf("list sys file %s %s", dg[1], dg[2]) resp, err := f5.sshClient.PostCommand(c, defaultPrompt, defaultTimeout) if err != nil { return err } // Parse the raw output var foundFile FileInfo if err := foundFile.Parse(*resp.Response); err != nil { return err } foundFile.FileName = dg[2] if err := foundFile.getCachePath(f5); err != nil { return err } // Put the fileInfo in the correct group switch dg[1] { case "apache-ssl-cert": sf.ApacheSSLCert[dg[2]] = foundFile case "browser-capabilities-db": sf.BrowserCapabilitiesDB[dg[2]] = foundFile case "dashboard-viewset": sf.DashboardViewset[dg[2]] = foundFile case "device-capabilities-db": sf.DeviceCapabilitiesDB[dg[2]] = foundFile case "external-monitor": sf.ExternalMonitor[dg[2]] = foundFile case "lwtunneltbl": sf.LWTunnelTbl[dg[2]] = foundFile case "ssl-crl": sf.SSLCRL[dg[2]] = foundFile case "ssl-csr": sf.SSLCSR[dg[2]] = foundFile case "system-ssl-cert": sf.SystemSSLCert[dg[2]] = foundFile case "system-ssl-key": sf.SystemSSLKey[dg[2]] = foundFile case "ifile": sf.IFile[dg[2]] = foundFile case "data-group": if err := foundFile.getFileContents(f5); err != nil { return err } sf.DataGroups[dg[2]] = foundFile case "ssl-cert": sf.SSLCert[dg[2]] = foundFile case "ssl-key": sf.SSLKey[dg[2]] = foundFile default: return fmt.Errorf("unknown file-type parsing list sys file: %s %s", dg[1], dg[2]) } } return nil } // PopulateSys : Populate the f5.device.sys structure func (f5 *F5) PopulateSys() error { f5.log.Debug("PopulateSys() - Begin") defer f5.log.Debug("PopulateSys() - Complete") if err := f5.Sys.Parse(f5); err != nil { return err } return nil } // PopulateSysFile : Populate the f5.device.sys.file structure func (f5 *F5) PopulateSysFile() error { f5.log.Debug("PopulateSysFile() - Begin") defer f5.log.Debug("PopulateSysFile() - Complete") res, err := f5.sshClient.PostCommand("list sys file", defaultPrompt, defaultTimeout) if err != nil { return err } if err := f5.Sys.File.Parse(f5, *res.Response); err != nil { return err } return nil } // PopulateSysFileDataGroup : Populate the f5.device.sys.file structure func (f5 *F5) PopulateSysFileDataGroup() error { f5.log.Debug("PopulateSysFileDataGroup() - Begin") defer f5.log.Debug("PopulateSysFileDataGroup() - Complete") res, err := f5.sshClient.PostCommand("list sys file data-group", defaultPrompt, defaultTimeout) if err != nil { return err } if err := f5.Sys.File.Parse(f5, *res.Response); err != nil { return err } return nil } // FileInfo : Contains information on a system file type FileInfo struct { CachePath string `json:"cache_path,omitempty"` // Location of the actual file CertificateKeySize int `json:"certificate_key_size,omitempty"` Checksum string `json:"checksum,omitempty"` Contents []string `json:"contents,omitempty"` // Used to store contents of the file CreatedBy string `json:"created_by,omitempty"` CreateTime string `json:"create_time,omitempty"` ExpirationDate int `json:"expiration_date,omitempty"` ExpirationString string `json:"expiration_string,omitempty"` FileName string `json:"filename,omitempty"` // Stores 'name' of the file (same as the index in the map Issuer string `json:"issuer,omitempty"` KeySize int `json:"key_size,omitempty"` KeyType string `json:"key_type,omitempty"` LastUpdateTime string `json:"last_update_time,omitempty"` Mode int `json:"mode,omitempty"` Revision int `json:"revision,omitempty"` SerialNumber string `json:"serial_number,omitempty"` Size int `json:"size,omitempty"` SourcePath string `json:"source_path,omitempty"` // Not used if it isn't external, i.e. key that's just pasted in Subject string `json:"subject,omitempty"` SubjectAlternateName string `json:"subject_alternate_name,omitempty"` Type string `json:"type,omitempty"` UpdatedBy string `json:"updated_by,omitempty"` } // Parse : Parses a given raw string in to an ExternalDataGroup func (fi *FileInfo) Parse(raw string) error { log.Debug("FileInfo.Parse() - Begin") defer log.Debug("FileInfo.Parse() - End") res := regexp.MustCompile(`checksum (\S+)`).FindStringSubmatch(raw) if len(res) > 0 { fi.Checksum = res[1] } res = regexp.MustCompile(`create-time (\S+)`).FindStringSubmatch(raw) if len(res) > 0 { fi.CreateTime = res[1] } res = regexp.MustCompile(`created-by (\S+)`).FindStringSubmatch(raw) if len(res) > 0 { fi.CreatedBy = res[1] } res = regexp.MustCompile(`last-update-time (\S+)`).FindStringSubmatch(raw) if len(res) > 0 { fi.LastUpdateTime = res[1] } res = regexp.MustCompile(`mode (\S+)`).FindStringSubmatch(raw) if len(res) > 0 { modeI, err := strconv.Atoi(res[1]) if err != nil { return err } fi.Mode = modeI } res = regexp.MustCompile(`revision (\S+)`).FindStringSubmatch(raw) if len(res) > 0 { revI, err := strconv.Atoi(res[1]) if err != nil { return err } fi.Revision = revI } res = regexp.MustCompile(`size (\S+)`).FindStringSubmatch(raw) if len(res) > 0 { sizeI, err := strconv.Atoi(res[1]) if err != nil { return err } fi.Size = sizeI } res = regexp.MustCompile(`source-path (\S+)`).FindStringSubmatch(raw) if len(res) > 0 { fi.SourcePath = res[1] } res = regexp.MustCompile(`type (\S+)`).FindStringSubmatch(raw) if len(res) > 0 { fi.Type = res[1] } res = regexp.MustCompile(`updated-by (\S+)`).FindStringSubmatch(raw) if len(res) > 0 { fi.UpdatedBy = res[1] } res = regexp.MustCompile(`key-size (\S+)`).FindStringSubmatch(raw) if len(res) > 0 { keySizeI, err := strconv.Atoi(res[1]) if err != nil { return err } fi.KeySize = keySizeI } res = regexp.MustCompile(`certificate-key-size (\S+)`).FindStringSubmatch(raw) if len(res) > 0 { size, err := strconv.Atoi(res[1]) if err != nil { return err } fi.CertificateKeySize = size } res = regexp.MustCompile(`expiration-date (.*)`).FindStringSubmatch(raw) if len(res) > 0 { date, err := strconv.Atoi(res[1]) if err != nil { return err } fi.ExpirationDate = date } res = regexp.MustCompile(`expiration-string "(.*)"`).FindStringSubmatch(raw) if len(res) > 0 { fi.ExpirationString = res[1] } res = regexp.MustCompile(`issuer "(.*)"`).FindStringSubmatch(raw) if len(res) > 0 { fi.Issuer = res[1] } res = regexp.MustCompile(`key-type (\S+)`).FindStringSubmatch(raw) if len(res) > 0 { fi.KeyType = res[1] } res = regexp.MustCompile(`serial-number (\S+)`).FindStringSubmatch(raw) if len(res) > 0 { fi.SerialNumber = res[1] } res = regexp.MustCompile(`subject "(.*)"`).FindStringSubmatch(raw) if len(res) > 0 { fi.Subject = res[1] } res = regexp.MustCompile(`subject-alternative-name "(.*)"`).FindStringSubmatch(raw) if len(res) > 0 { fi.SubjectAlternateName = res[1] } return nil } func (fi *FileInfo) getFileContents(f5 *F5) error { if fi.CachePath == "" { return errors.New("cache-path not populated") } cmd := fmt.Sprintf("run util bash -c 'cat %s'", fi.CachePath) res, err := f5.sshClient.PostCommand(cmd, defaultPrompt, defaultTimeout) if err != nil { return nil } for i, line := range strings.Split(*res.Response, "\n") { if i == 0 { // Skip the first line as it's our command continue } fi.Contents = append(fi.Contents, line) } return nil } func (fi *FileInfo) getCachePath(f5 *F5) error { cmd := fmt.Sprintf("run util bash -c 'cat /config/bigip.conf | grep %s'", fi.FileName) res, err := f5.sshClient.PostCommand(cmd, defaultPrompt, defaultTimeout) if err != nil { return err } resp := regexp.MustCompile(`cache-path (.*)`).FindStringSubmatch(*res.Response) if len(resp) > 0 { fi.CachePath = resp[1] return nil } return fmt.Errorf("cache-path not found for file %s", fi.FileName) }