package pkg import ( "errors" "fmt" "net" "regexp" "strconv" "strings" "time" "github.com/sirupsen/logrus" "github.rackspace.com/gorax/f5api/pkg/autoshundb" "github.rackspace.com/gorax/tools/passkey" ) type ( // BlacklistCategory : /security/ip-intelligence blacklist-category structure BlacklistCategory struct { AppService string `json:"app_service,omitempty"` BLMatchDirection string `json:"bl_match_direction,omitempty"` Description string `json:"description,omitempty"` Partition string `json:"partition,omitempty"` } // IPIntelligence : /security/ip-intelligence structure IPIntelligence struct { BlacklistCategories map[string]BlacklistCategory `json:"blacklist_categories"` GlobalPolicy IPIGlobalPolicy `json:"global_policy"` Policies map[string]IPIPolicy `json:"policies"` } IPIPolicy struct { AppService string `json:"app_service,omitempty"` BlacklistCategories map[string]IPIPolicyCategory `json:"blacklist_categories,omitempty"` DefaultAction string `json:"default_action,omitempty"` DefaultLogBlacklistHitOnly string `json:"default_blacklist_hit_only,omitempty"` DefaultLogBlacklistWhitelistHit string `json:"default_log_blacklist_whitelist_hit,omitempty"` Description string `json:"description,omitempty"` FeedLists []string `json:"feed_lists,omitempty"` Partition string `json:"partition,omitempty"` } IPIPolicyCategory struct { Action string `json:"action,omitempty"` AppService string `json:"app_service,omitempty"` Description string `json:"description,omitempty"` LogBlacklistHitOnly string `json:"log_blacklist_hit_only,omitempty"` LogBlacklistWhitelistHit string `json:"log_blacklist_whitelist_hit,omitempty"` MatchDirectionOverride string `json:"match_direction_override,omitempty"` } IPIGlobalPolicy struct { IPIntelligencePolicy string `json:"ip_intelligence_policy"` } // Security : /security structure Security struct { IPIntelligence IPIntelligence `json:"ip_intelligence"` } // AutoShunIP : Holds information on an IP in the AUTO_SHUN blacklist AutoShunIP struct { Address string `json:"address"` IPIntelligenceSources string `json:"ip_intelligence_sources"` WhitelistedSource string `json:"whitelisted_source"` WhitelistedDestination string `json:"whitelisted_destination"` PolicyActionSource string `json:"policy_action_source"` PolicyActionDestination string `json:"policy_action_destination"` MatchType string `json:"match_type"` CategoriesSource string `json:"categories_source"` CategoriesDestination string `json:"categories_destination"` DBInfo *autoshundb.Shun `json:"db_info,omitempty"` } ) const autoShunWrongVersion = "unsupported version. autoshun requires 12 code. this device's version: %s" // ipCIDRregex : regex for matching bare IP or IP/CIDR, here to ensure compilation only once to save speed var ipCIDRregex = regexp.MustCompile(`(\d+\.\d+\.\d+\.\d+)(/\d+)*`) // Parse : Parses a raw output of 'show security ip-intelligence info address
' in to an AutoShunIP func (a *AutoShunIP) Parse(raw string) error { /* Security::IP Intelligence Address : 203.0.113.1 Global context IP Intelligence Sources : User-defined Whitelisted (Source) : no Whitelisted (Destination) : no Policy Action (Source) : drop Policy Action (Destination) : allow Match Type : Source Categories (Source) (1) : AUTO_SHUN Categories (Destination) (0) Total records returned: 1 */ address := regexp.MustCompile(`Security::IP Intelligence Address\s+:\s+(\S+)`).FindStringSubmatch(raw) if len(address) > 0 { a.Address = address[1] } source := regexp.MustCompile(`IP Intelligence Sources\s+:\s+(.*)`).FindStringSubmatch(raw) if len(source) > 0 { a.IPIntelligenceSources = strings.TrimSpace(source[1]) } ws := regexp.MustCompile(`Whitelisted \(Source\)\s+:\s+(.*)`).FindStringSubmatch(raw) if len(ws) > 0 { a.WhitelistedSource = ws[1] } wd := regexp.MustCompile(`Whitelisted \(Destination\)\s+:\s+(.*)`).FindStringSubmatch(raw) if len(wd) > 0 { a.WhitelistedDestination = wd[1] } ps := regexp.MustCompile(`Policy Action \(Source\)\s+:\s+(.*)`).FindStringSubmatch(raw) if len(ps) > 0 { a.PolicyActionSource = ps[1] } pd := regexp.MustCompile(`Policy Action \(Destination\)\s+:\s+(.*)`).FindStringSubmatch(raw) if len(pd) > 0 { a.PolicyActionDestination = pd[1] } mt := regexp.MustCompile(`Match Type\s+:\s+(.*)`).FindStringSubmatch(raw) if len(mt) > 0 { a.MatchType = mt[1] } cs := regexp.MustCompile(`Categories \(Source\) (\S+)\s+:\s+(.*)`).FindStringSubmatch(raw) if len(cs) > 0 { a.CategoriesSource = cs[2] } cd := regexp.MustCompile(`Categories \(Destination\) (\S+)\s+:\s+(.*)`).FindStringSubmatch(raw) if len(cd) > 0 { a.CategoriesDestination = cs[2] } return nil } func autoShunVersionCheck(f5 *F5) error { // Populate version if err := f5.Version(15); err != nil { return fmt.Errorf("error getting device version %s", err.Error()) } f5.log.Debugf("raw version: %v", f5.Status.Version.Version) sep := strings.Split(f5.Status.Version.Version, ".") if len(sep) < 1 { return fmt.Errorf("unable to parse verison number: %v", f5.Status.Version.Version) } v, err := strconv.Atoi(sep[0]) if err != nil { return fmt.Errorf("error converting version to integer: %v", err) } f5.log.Debugf("int version: %v", v) if v < 12 { return fmt.Errorf(autoShunWrongVersion, f5.Status.Version.Version) } return nil } // AutoShun : Adds an IP to the IP Intelligence AUTO_SHUN blacklist category func (f5 *F5) AutoShun( stringIP, procesID, user, reqID string, shunLength time.Duration, pk *passkey.Client, ) error { f5.log.Debug("F5.AutoShun() - Begin") defer f5.log.Debug("F5.AutoShun() - Complete") // Check Version if err := autoShunVersionCheck(f5); err != nil { return err } // Parse the string to a net.IP object to make sure it is valid ip := net.ParseIP(stringIP) if ip == nil { return fmt.Errorf("%s is not a valid IP address", stringIP) } // Step 0.5 - check our DB db, err := autoshundb.New(false, pk, f5.log) if err != nil { return fmt.Errorf("error connecting to NSI object rocket instance: %s", err.Error()) } defer func() { if err := db.Close(); err != nil { log.WithError(err).Error("error closing object rocket connection") } }() sDev := strconv.Itoa(f5.Device) dbDevice := new(autoshundb.Device) // Check if the device is in object rocket dbDevice, err = db.FindDevice(sDev) if err != nil { if err == autoshundb.ErrNotFound { dbDevice, err = db.NewDevice(sDev, user, reqID) if err != nil { return fmt.Errorf("device not found in object rocket, and error encountered when attempting to add it: %s", err.Error()) } } else { return fmt.Errorf("unknown error encountered finding device in object rocket: %s", err.Error()) } } dbShun := dbDevice.FindShun(stringIP) if dbShun != nil { return fmt.Errorf("ip found as already shunned in object rocket. date added: %s, process_id: %s", dbShun.DateAdded.String(), dbShun.IncidentID) } // STEP 1 - Check if infrastructure is in place to support autoshun if err := f5.asStep1(); err != nil { return fmt.Errorf("error during step 1: %s", err.Error()) } // STEP 2 - Check if IP is already shunned if err := f5.asStep2(ip); err != nil { return fmt.Errorf("error during step 2: %s", err.Error()) } // STEP 3 - Check if IP is somewhere else in the config and shouldn't be autoshunned manually if err := f5.asStep3(ip); err != nil { return fmt.Errorf("error during step 3: %s", err.Error()) } try := 0 // retry counter // loop till we get a return or hit max retries for { try++ log.WithField("attempt", try).Debug("attempting to add ip to shun list") // STEP 4 - Shun IP if err := f5.asStep4(ip); err != nil { return fmt.Errorf("error during step 4: %s", err.Error()) } f5.log.Trace("delaying 2 seconds before confirming IP added successfully") time.Sleep(time.Second * 2) // insert a delay, otherwise the step5 could report 0 added // STEP 5 - Verify shun in place err := f5.asStep5(ip) if err != nil { if try >= 2 { return fmt.Errorf("error during step 5: %s", err.Error()) } log.WithError(err).Error("error during step 5, retrying") continue } else { break // successful step5 confirmation, break out of loop } } removeDate := time.Now().Add(shunLength) // record shun in db err = db.AddShun(dbDevice, stringIP, user, procesID, reqID, shunLength, removeDate) if err != nil { return fmt.Errorf("error adding shun to object rocket: %s", err.Error()) } return nil } // asStep1 : Verifies infrastructure is in place to support shunning an ip func (f5 *F5) asStep1() error { /* create security ip-intelligence blacklist-category AUTO_SHUN bl-match-direction source description "Used by Auto Shun Automation" create security ip-intelligence policy global_ip_policy blacklist-categories replace-all-with { AUTO_SHUN } description "Used by Auto Shun Automation" default-action drop modify security ip-intelligence global-policy ip-intelligence-policy global_ip_policy */ f5.log.Debug("F5.asStep1() - Begin") defer f5.log.Debug("F5.asStep1() - Complete") // Gather information on IP Intelligence policies if err := f5.IPIPolicies(defaultTimeout); err != nil { return fmt.Errorf("f5.IPIPolicies: %s", err.Error()) } // Gather information on IP Intelligence blacklist categories if err := f5.IPIBlacklistCategories(defaultTimeout); err != nil { return fmt.Errorf("f5.IPIBlacklistCategories: %s", err.Error()) } // Gather information on IP Intelligence Global Policy if err := f5.IPIGlobalPolicy(defaultTimeout); err != nil { return fmt.Errorf("f5.IPIGlobalPolicy: %s", err.Error()) } if _, ok := f5.Security.IPIntelligence.BlacklistCategories["AUTO_SHUN"]; !ok { cmd := `create security ip-intelligence blacklist-category AUTO_SHUN bl-match-direction source description "Used by Auto Shun Automation"` _, err := f5.sshClient.PostCommand(cmd, defaultPrompt, defaultTimeout) if err != nil { return fmt.Errorf("error creating security ip-intelligence blacklist-category: %s", err.Error()) } } globalPolicy, ok := f5.Security.IPIntelligence.Policies["global_ip_policy"] if !ok { cmd := `create security ip-intelligence policy global_ip_policy blacklist-categories replace-all-with { AUTO_SHUN } description "Used by Auto Shun Automation" default-action drop` if _, err := f5.sshClient.PostCommand(cmd, defaultPrompt, defaultTimeout); err != nil { return fmt.Errorf("error creating security ip-intelligence policy: %s", err.Error()) } } // Make sure AUTO_SHUN is in the blacklist-categories of the global-ip-policy if _, ok := globalPolicy.BlacklistCategories["AUTO_SHUN"]; !ok { cmd := fmt.Sprintf("modify security ip-intelligence policy global_ip_policy blacklist-categories add { AUTO_SHUN }") _, err := f5.sshClient.PostCommand(cmd, defaultPrompt, defaultTimeout) if err != nil { return fmt.Errorf("error modifying security ip-intelligence policy: %s", err.Error()) } // return errors.New("AUTO_SHUN is not a part of the global policy blacklist") } // Make sure default action is set to drop if strings.ToLower(globalPolicy.DefaultAction) != "drop" { _, err := f5.sshClient.PostCommand("modify security ip-intelligence policy global_ip_policy default-action drop", defaultPrompt, defaultTimeout) if err != nil { return fmt.Errorf("error modifying ip-intelligence policy: %s", err.Error()) } // return errors.New("AUTO_SHUN blacklist category is not set to DROP") } // Make sure global-ip-policy is being used as the global policy if f5.Security.IPIntelligence.GlobalPolicy.IPIntelligencePolicy == "" { _, err := f5.sshClient.PostCommand("modify security ip-intelligence global-policy ip-intelligence-policy global_ip_policy", defaultPrompt, defaultTimeout) if err != nil { return fmt.Errorf("error modifying security ip-intelligence global policy: %s", err.Error()) } } else if f5.Security.IPIntelligence.GlobalPolicy.IPIntelligencePolicy != "global_ip_policy" { return fmt.Errorf("global policy is already defined as: %s", f5.Security.IPIntelligence.GlobalPolicy.IPIntelligencePolicy) } return nil } // asStep2 : Check if IP is already shunned func (f5 *F5) asStep2(ip net.IP) error { /* show security ip-intelligence info address 172.99.99.10 */ f5.log.Debug("F5.asStep2() - Begin") defer f5.log.Debug("F5.asStep2() - Complete") c := fmt.Sprintf("show security ip-intelligence info address %s", ip.String()) res, err := f5.sshClient.PostCommand(c, defaultPrompt, defaultTimeout) if err != nil { return fmt.Errorf("error running show security ip-intelligence: %s", err.Error()) } f := regexp.MustCompile(`Total records returned: (\d+)`).FindStringSubmatch(*res.Response) if len(f) < 1 { return fmt.Errorf("unable to decode response: %s", *res.Response) } if f[1] != "0" { return errors.New("IP is already in ip-intelligence category") } return nil } func (f5 *F5) asStep3(ip net.IP) error { f5.log.Debug("F5.asStep3() - Begin") defer f5.log.Debug("F5.asStep3() - Complete") if err := asDataGroupsCheck(f5, ip); err != nil { return err } if err := asAddressListCheck(f5, ip); err != nil { return err } if err := asRuleListCheck(f5, ip); err != nil { return err } if err := asIRuleCheck(f5, ip); err != nil { return err } if err := asRouteCheck(f5, ip); err != nil { return err } if err := asBigIPConfCheck(f5, ip); err != nil { return err } return nil } // cidrRegex : regex for matching a /xx cidr, here so it's only compiled once for speed var cidrRegex = regexp.MustCompile(`(/\d+)`) // ipToCIDR : convert a bare IP or IP/CIDR to net.IPNet object func ipToCIDR(ip string) *net.IPNet { if !cidrRegex.MatchString(ip) { // if ip is bare, make it a /32 ip = strings.TrimSpace(ip) + "/32" } _, n, err := net.ParseCIDR(ip) if err != nil { panic(err) } return n } func asDataGroupsCheck(f5 *F5, ip net.IP) error { // Populate data group information f5.log.Debug("Populating LTM Data Groups") if err := f5.PopulateLTMDataGroups(); err != nil { return fmt.Errorf("f5.PopulateLTMDataGroups: %s", err.Error()) } // Check internal data groups f5.log.Debug("Checking Internal DataGroups") for dgName, dg := range f5.LTM.DataGroups.Internal { for record := range dg.Records { for _, foundIP := range ipCIDRregex.FindAllStringSubmatch(record, -1) { n := ipToCIDR(foundIP[0]) if n.String() == "0.0.0.0/0" { continue } if n.Contains(ip) { return fmt.Errorf("ip found in device: internal data-group '%s' contains ip in record %s", dgName, record) } } } } // Check external data groups f5.log.Debug("Checking External DataGroups") for dgName, dg := range f5.LTM.DataGroups.External { for i, line := range dg.Contents { for _, foundIP := range ipCIDRregex.FindAllStringSubmatch(line, -1) { n := ipToCIDR(foundIP[0]) if n.String() == "0.0.0.0/0" { continue } if n.Contains(ip) { return fmt.Errorf("ip found in device: external data-group '%s' contains ip in line %d: '%s'", dgName, i, line) } } } } return nil } func asAddressListCheck(f5 *F5, ip net.IP) error { // Populat the address list info f5.log.Debug("Getting AFM Address Lists") if err := f5.GetAfmAddressLists("", defaultTimeout); err != nil { return fmt.Errorf("f5.GetAfmAddressLists: %s", err.Error()) } // Check address lists f5.log.Debug("Checking address-lists") for _, list := range f5.Config.AFMAddressLists { for _, add := range list.Addresses { for _, foundIP := range ipCIDRregex.FindAllStringSubmatch(add, -1) { n := ipToCIDR(foundIP[0]) if n.String() == "0.0.0.0/0" { continue } if n.Contains(ip) { return fmt.Errorf("ip found in device: address-list '%s' contains ip in entry: '%s'", list.Name, add) } } } } return nil } func asRuleListCheck(f5 *F5, ip net.IP) error { // Populate afm rule lists f5.log.Debug("Getting AFM Rule Lists") if err := f5.GetAfmRuleLists("", defaultTimeout); err != nil { return fmt.Errorf("f5.GetAfmRuleLists: %s", err.Error()) } // Check individual addresses in rule lists f5.log.Debug("Checking AFM rule-lists") for _, ruleList := range f5.Config.AFMRuleLists { for _, rule := range ruleList.Rules { if rule.Dest != nil { if rule.Dest.Address != nil { for _, destAddress := range rule.Dest.Address { if destAddress.Address == nil { f5.log.Warnf("unable to inspect rule %s as destination address is nil", rule.Name) continue } for _, foundIP := range ipCIDRregex.FindAllStringSubmatch(*destAddress.Address, -1) { n := ipToCIDR(foundIP[0]) if n.String() == "0.0.0.0/0" { continue } if n.Contains(ip) { return fmt.Errorf("ip found in device: rule list: '%s'; rule: '%s'; destination: '%s'", ruleList.Name, *rule.Name, *destAddress.Address) } } } } } if rule.Source != nil { if rule.Source.Address != nil { for _, sourceAddress := range rule.Source.Address { if sourceAddress.Address == nil { f5.log.Warnf("unablet to inspect rule %s as source address is nil", rule.Name) continue } for _, foundIP := range ipCIDRregex.FindAllStringSubmatch(*sourceAddress.Address, -1) { n := ipToCIDR(foundIP[0]) if n.String() == "0.0.0.0/0" { continue } if n.Contains(ip) { return fmt.Errorf("ip found in device: rule list: '%s'; rule: '%s'; source: '%s'", ruleList.Name, *rule.Name, *sourceAddress.Address) } } } } } } } return nil } func asIRuleCheck(f5 *F5, ip net.IP) error { // Check iRules if err := f5.PopulateIRules(); err != nil { return err } for name, rule := range f5.LTM.IRules { for _, foundIP := range ipCIDRregex.FindAllStringSubmatch(rule.Name, -1) { n := ipToCIDR(foundIP[0]) if n.String() == "0.0.0.0/0" { continue } if n.Contains(ip) { return fmt.Errorf("ip found in device: irule '%s' contains ip in its name", name) } } for i, line := range rule.Contents { for _, foundIP := range ipCIDRregex.FindAllStringSubmatch(line, -1) { n := ipToCIDR(foundIP[0]) if n.String() == "0.0.0.0/0" { continue } if n.Contains(ip) { return fmt.Errorf("ip found in device: irule '%s' contains ip in line %d: %s", name, i, line) } } } } return nil } func asRouteCheck(f5 *F5, ip net.IP) error { // Check Routes if err := f5.RouteConfig("", defaultTimeout); err != nil { return fmt.Errorf("f5.RouteConfig: %s", err.Error()) } for _, route := range f5.Config.Routes { for _, foundIP := range ipCIDRregex.FindAllStringSubmatch(route.Network, -1) { n := ipToCIDR(foundIP[0]) if n.String() == "0.0.0.0/0" { continue } if n.Contains(ip) { return fmt.Errorf("ip found in device: route '%s' contains ip in network %s", route.Name, route.Network) } } for _, foundIP := range ipCIDRregex.FindAllStringSubmatch(route.Gateway, -1) { n := ipToCIDR(foundIP[0]) if n.String() == "0.0.0.0/0" { continue } if n.Contains(ip) { return fmt.Errorf("ip found in device: route '%s' contains ip in gateway %s", route.Name, route.Gateway) } } } return nil } func asBigIPConfCheck(f5 *F5, ip net.IP) error { // check the bigip.conf file f5.log.Debug("catting out bigip.conf file") res, err := f5.sshClient.PostCommand("run util bash -c 'cat /config/bigip.conf'", defaultPrompt, 60) // huge timeout for big configs if err != nil { return fmt.Errorf("error while cat'ing out the bigip.conf file: %s", err.Error()) } for i, line := range strings.Split(*res.Response, "\n") { for _, foundIP := range ipCIDRregex.FindAllStringSubmatch(line, -1) { n := ipToCIDR(foundIP[0]) if n.String() == "0.0.0.0/0" { continue } if n.Contains(ip) { return fmt.Errorf("ip found in device: bigip.conf file, line %d: %s", i, line) } } } return nil } func (f5 *F5) asStep4(ip net.IP) error { /* run security ip-intelligence category name AUTO_SHUN ip-ttl add { 172.99.99.10,90000 } */ f5.log.Debug("F5.asStep4() - Begin") defer f5.log.Debug("F5.asStep4() - Complete") cmd := fmt.Sprintf("run security ip-intelligence category name AUTO_SHUN ip-ttl add { %s,infinite }", ip.String()) res, err := f5.sshClient.PostCommand(cmd, defaultPrompt, defaultTimeout) if err != nil { return fmt.Errorf("run security ip-intelligence error: %s", err.Error()) } f5.log.WithFields(logrus.Fields{ "command": cmd, "response": *res.Response, }).Debug("step4 command sent") switch { case strings.Contains(strings.ToLower(*res.Response), "data input error"): return fmt.Errorf("error shunning ip, data input error. raw response: %s", *res.Response) case strings.Contains(strings.ToLower(*res.Response), "syntax error"): return fmt.Errorf("error shunning ip, syntax error. raw response: %s", *res.Response) default: return nil } } func (f5 *F5) asStep5(ip net.IP) error { defer f5.log.Debug("F5.asStep5() - Complete") c := fmt.Sprintf("show security ip-intelligence info address %s", ip.String()) res, err := f5.sshClient.PostCommand(c, defaultPrompt, defaultTimeout) if err != nil { return fmt.Errorf("show security ip-intelligence error: %s", err.Error()) } var asInfo AutoShunIP if err := asInfo.Parse(*res.Response); err != nil { return fmt.Errorf("asInfo.Parse: %s", err.Error()) } if strings.Contains(*res.Response, "Total records returned: 0") { return errors.New("verification reported total records returned: 0") } return nil } func (f5 *F5) IsIPPresent(ip net.IP) error { return f5.asStep3(ip) } // AutoShunInfo : Get information on an IP in the AUTO_SHUN blacklist func (f5 *F5) AutoShunInfo(ip string, pk *passkey.Client) (*AutoShunIP, error) { // Check Version if err := autoShunVersionCheck(f5); err != nil { return nil, err } cmd := fmt.Sprintf("show security ip-intelligence info address %s", ip) res, err := f5.sshClient.PostCommand(cmd, defaultPrompt, defaultTimeout) if err != nil { return nil, fmt.Errorf("show security ip-intelligence error: %s", err.Error()) } if strings.Contains(*res.Response, "Total records returned: 0") { return nil, errors.New("ip is not shunned") } var info AutoShunIP if err := info.Parse(*res.Response); err != nil { return nil, fmt.Errorf("info.Parse: %s", err.Error()) } db, err := autoshundb.New(false, pk, f5.log) if err != nil { return nil, fmt.Errorf("error initializing object rocket: %s", err.Error()) } dbDevice, err := db.FindDevice(strconv.Itoa(f5.Device)) if err != nil { if err == autoshundb.ErrNotFound { log.Warn("device not found in object rocket, may pre-date database") } else { return nil, fmt.Errorf("error looking for device in object rocket: %s", err.Error()) } } else { shun := dbDevice.FindShun(ip) if shun == nil { f5.log.WithError(err).Warn("error finding shun in object rocket") } else { info.DBInfo = shun } } return &info, nil } // AutoShunDelete : Remove an IP from the IP Intelligence AUTO_SHUN blacklist func (f5 *F5) AutoShunDelete(ip, user, reqID string, pk *passkey.Client) error { // Check Version if err := autoShunVersionCheck(f5); err != nil { return err } db, err := autoshundb.New(false, pk, f5.log) if err != nil { return fmt.Errorf("error connecting to object rocket: %s", err.Error()) } shun := new(autoshundb.Shun) dbDevice, err := db.FindDevice(strconv.Itoa(f5.Device)) if err != nil { if err == autoshundb.ErrNotFound { f5.log.Warn("device not found in object rocket") } else { return fmt.Errorf("error looking for device in object rocket: %s", err.Error()) } } else { shun = dbDevice.FindShun(ip) if shun == nil { f5.log.Warn("shun not found on device in object rocket") } } cmd := fmt.Sprintf("show security ip-intelligence info address %s", ip) res, err := f5.sshClient.PostCommand(cmd, defaultPrompt, defaultTimeout) if err != nil { return fmt.Errorf("show security ip-intelligence: %s", err.Error()) } if strings.Contains(*res.Response, "Total records returned: 0") { f5.log.Warn("ip is not shunned on device") } else { cmd = fmt.Sprintf("run security ip-intelligence category name AUTO_SHUN ip-ttl delete { %s }", ip) _, err = f5.sshClient.PostCommand(cmd, defaultPrompt, defaultTimeout) if err != nil { return fmt.Errorf("run security ip-intelligence: %s", err.Error()) } } // if the device is in the db, remove shun from device in db if dbDevice != nil && shun != nil { if err := db.RemoveShun(dbDevice, ip, user, reqID); err != nil { return fmt.Errorf("ip was removed successfully on device, but error during removal from object rocket: %s", err.Error()) } } return nil } func (f5 *F5) IPIPolicies(timeout int) error { f5.Security.IPIntelligence.Policies = make(map[string]IPIPolicy) res, err := f5.sshClient.PostCommand("list security ip-intelligence policy all-properties", defaultPrompt, timeout) if err != nil { return fmt.Errorf("list security ip-intelligence: %s", err.Error()) } // Define Regex outside of loop so only compiles once appServiceRE := regexp.MustCompile(`app-service (.*)`) defaultActionRE := regexp.MustCompile(`default-action (.*)`) defLogBlacklistHitRE := regexp.MustCompile(`default-log-blacklist-hit-only (.*)`) defLogBlacklistWhitelistHitRE := regexp.MustCompile(`default-log-blacklist-whitelist-hit (.*)`) descRE := regexp.MustCompile(`description (.*)`) feedListsRE := regexp.MustCompile(`feed-lists (.*)`) partRE := regexp.MustCompile(`partition (.*)`) policyNameRE := regexp.MustCompile(`\s+(\S+) {`) matchDirRE := regexp.MustCompile(`match-direction-override (.*)`) // Get a list of policy names policies := regexp.MustCompile(`security ip-intelligence policy (.*) {`).FindAllStringSubmatch(*res.Response, -1) for _, policy := range policies { var newPolicy IPIPolicy newPolicy.BlacklistCategories = make(map[string]IPIPolicyCategory) // Get details about a specific policy c := fmt.Sprintf("list security ip-intelligence policy %s all-properties", policy[1]) policyRes, err := f5.sshClient.PostCommand(c, defaultPrompt, timeout) if err != nil { return fmt.Errorf("list security ip-intelligence: %s", err.Error()) } inCategories := false inCategory := false var cat IPIPolicyCategory var catName string for _, line := range strings.Split(*policyRes.Response, "\n") { if line == " blacklist-categories {" { inCategories = true continue } if line == " }" { inCategories = false continue } if !inCategories { // if we're not in the assigned categories, find the policies information appService := appServiceRE.FindStringSubmatch(line) if len(appService) > 0 { newPolicy.AppService = appService[1] } defaultAction := defaultActionRE.FindStringSubmatch(line) if len(defaultAction) > 0 { newPolicy.DefaultAction = defaultAction[1] } defLogBlacklistHit := defLogBlacklistHitRE.FindStringSubmatch(line) if len(defLogBlacklistHit) > 0 { newPolicy.DefaultLogBlacklistHitOnly = defLogBlacklistHit[1] } defLogBlacklistWhitelistHit := defLogBlacklistWhitelistHitRE.FindStringSubmatch(line) if len(defLogBlacklistWhitelistHit) > 0 { newPolicy.DefaultLogBlacklistWhitelistHit = defLogBlacklistWhitelistHit[1] } desc := descRE.FindStringSubmatch(line) if len(desc) > 0 { newPolicy.Description = desc[1] } feedLists := feedListsRE.FindStringSubmatch(line) if len(feedLists) > 0 { newPolicy.FeedLists = strings.Split(feedLists[1], ",") } part := partRE.FindStringSubmatch(line) if len(part) > 0 { newPolicy.Partition = part[1] } } else { if !inCategory { policyName := policyNameRE.FindStringSubmatch(line) if len(policyName) > 0 { catName = policyName[1] // Record the category name inCategory = true } } else { if line == " }" { inCategory = false newPolicy.BlacklistCategories[catName] = cat cat = IPIPolicyCategory{} // Reset cat to a new one } matchDir := matchDirRE.FindStringSubmatch(line) if len(matchDir) > 0 { cat.MatchDirectionOverride = matchDir[1] } } } } f5.Security.IPIntelligence.Policies[policy[1]] = newPolicy } return nil } func (f5 *F5) IPIGlobalPolicy(timeout int) error { res, err := f5.sshClient.PostCommand("list security ip-intelligence global-policy", defaultPrompt, timeout) if err != nil { return fmt.Errorf("list security ip-intelligence: %s", err.Error()) } info := regexp.MustCompile(`ip-intelligence-policy (.*)`).FindStringSubmatch(*res.Response) if len(info) > 0 { f5.Security.IPIntelligence.GlobalPolicy.IPIntelligencePolicy = info[1] } return nil } func (f5 *F5) IPIBlacklistCategories(timeout int) error { f5.Security.IPIntelligence.BlacklistCategories = make(map[string]BlacklistCategory) res, err := f5.sshClient.PostCommand("list security ip-intelligence blacklist-category", defaultPrompt, timeout) if err != nil { return fmt.Errorf("list security ip-intelligence: %s", err.Error()) } re, err := regexp.Compile(`security ip-intelligence blacklist-category (\S+) {`) if err != nil { return fmt.Errorf("error compiling regex: %s", err.Error()) } // Define these here so they're only compiled once instead of every loop iteration desRE := regexp.MustCompile(`description "(.*)"`) blRE := regexp.MustCompile(`bl-match-direction (.*)`) asRE := regexp.MustCompile(`app-service (.*)`) partRE := regexp.MustCompile(`partition (.*)`) // Get a list of the categories results := re.FindAllStringSubmatch(*res.Response, -1) for _, cat := range results { var newCat BlacklistCategory // Get the specifics for a category c := fmt.Sprintf("list security ip-intelligence blacklist-category %s", cat[1]) catRes, err := f5.sshClient.PostCommand(c, defaultPrompt, timeout) if err != nil { return fmt.Errorf("list security ip-intelligence: %s", err.Error()) } // Look for a description des := desRE.FindStringSubmatch(*catRes.Response) if len(des) > 0 { newCat.Description = des[1] } // look for a blacklist match direction bl := blRE.FindStringSubmatch(*catRes.Response) if len(bl) > 0 { newCat.BLMatchDirection = bl[1] } as := asRE.FindStringSubmatch(*catRes.Response) if len(as) > 0 { newCat.AppService = as[1] } part := partRE.FindStringSubmatch(*catRes.Response) if len(part) > 0 { newCat.Partition = part[1] } // add this category to the overall list of categories f5.Security.IPIntelligence.BlacklistCategories[cat[1]] = newCat } return nil }