package asaModels import ( "net" "reflect" "regexp" "strings" ) var ( afterAutoRE = regexp.MustCompile(`(after-auto)`) autoNATRE = regexp.MustCompile(`(?i)object\s+network\s+(\S+)\n\s+nat\s+\((.*?),(.*?)\)\s+(.*?)\s+(.*?)(\n|$)`) interfacesRE = regexp.MustCompile(`\((.*),(.*)\)`) manualNATRE = regexp.MustCompile(`(?i)nat\s\(.*,.*\)\s(?:after-auto|source).*?\n`) natDestRE = regexp.MustCompile(`destination\s(static|dynamic)\s([^\s]+)\s([^\s]+)`) natSourceRE = regexp.MustCompile(`source\s(static|dynamic)\s([^\s]+)\s([^\s]+)(?: service\s([^\s]+)\s([^\s]+)|)`) proxyARPRE = regexp.MustCompile(`(no-proxy-arp)`) routeLookupRE = regexp.MustCompile(`(route-lookup)`) serviceRE = regexp.MustCompile(`(?i)(\S+)\s+service\s+(\S+)\s+(.*?)$`) ) type ( Auto struct { Destination string `json:"destination"` Dns bool `json:"dns"` Name string `json:"name"` NoProxyArp bool `json:"no_proxy_arp"` RouteLookup bool `json:"route_lookup"` Service *NatService `json:"service"` Source string `json:"source"` Type string `json:"type"` Xlate string `json:"xlate"` } DestGroup struct { Mapped string `json:"mapped"` Real string `json:"real"` Type string `json:"type"` } ipStuct struct { IP net.IP // network number Mask string // network mask } Manual struct { AfterAuto bool `json:"after_auto"` DestinationGroup DestGroup `json:"destination_group"` DestinationInt string `json:"destination_interface"` NoProxyArp bool `json:"no_proxy_arp"` RouteLookUp bool `json:"route_lookup"` SourceGroup SourceGroup `json:"source_group"` SourceInt string `json:"source_interface"` } Nat struct { Auto []*Auto `json:"nat"` Manual []*Manual `json:"manual"` Static []Static `json:"static"` } NatMapService struct { Mapped string `json:"mapped"` Real string `json:"real"` } NatService struct { Ports []string `json:"ports"` Protocol string `json:"protocol"` } SourceGroup struct { Mapped string `json:"mapped"` Real string `json:"real"` Service NatMapService `json:"service"` Type string `json:"type"` } Static struct { DestinationInt string `json:"destination_interface"` DestinationIP net.IP `json:"destination_ip"` SourceInt string `json:"source_interface"` SourceIP ipStuct `json:"source_ip"` } ) func FindAutoNat(o string, auto []*Auto) *Auto { for _, n := range auto { if n.Name == o { return n } } return nil } func ParseNat(s string) *Nat { n := new(Nat) n.Parse(s) return n } func (n *Nat) autoNat(s []string) { srvc := serviceRE.FindStringSubmatch(strings.TrimRight((s)[5], " ")) var newAuto Auto newAuto.Name = (s)[1] newAuto.Source = (s)[2] newAuto.Destination = (s)[3] newAuto.Type = (s)[4] newAuto.Xlate = func() string { if len(srvc) != 0 { return srvc[1] } //if len(r) == 0 { if strings.Contains(s[5], " dns") { newAuto.Dns = true } if strings.Contains((s)[5], "no-proxy-arp") { newAuto.NoProxyArp = true } if strings.Contains(s[5], "rouute-lookup") { newAuto.RouteLookup = true } // check if this is a [no-proxy-arp|route-lookup] entry, if so, record location of the space if location := strings.Index(strings.TrimSpace(s[5]), " "); location != -1 { // strings.TrimSpace is so we don't match a space at the end of the line // append everything before the space (i.e. just the ip address) return s[5][:location] } return (s)[5] }() newAuto.Service = func() *NatService { if len(srvc) == 0 { return nil } return &NatService{ Protocol: srvc[2], Ports: strings.Split(srvc[3], " "), } }() n.Auto = append(n.Auto, &newAuto) } func (n *Nat) FindAutoNat(o string) *Auto { return FindAutoNat(o, n.Auto) } // Find all Manual Nat statements on the device. func (n *Nat) manualNAT(s string) { i := interfacesRE.FindStringSubmatch(s) so := natSourceRE.FindStringSubmatch(s) ds := natDestRE.FindStringSubmatch(s) rm := map[string][]string{ "AfterAuto": afterAutoRE.FindStringSubmatch(s), "NoProxyArp": proxyARPRE.FindStringSubmatch(s), "RouteLookUp": routeLookupRE.FindStringSubmatch(s), } m := Manual{ SourceInt: i[1], DestinationInt: i[2], } for k, v := range rm { if len(v) > 0 { reflect.ValueOf(&m).Elem().FieldByName(k).SetBool(true) } else { reflect.ValueOf(&m).Elem().FieldByName(k).SetBool(false) } } if len(so) > 0 { m.SourceGroup = SourceGroup{ Type: so[1], Real: so[2], Mapped: so[3], } if len(so) > 4 { m.SourceGroup.Service.Real = so[4] m.SourceGroup.Service.Mapped = so[5] } } if len(ds) > 0 { m.DestinationGroup = DestGroup{ Type: ds[1], Real: ds[2], Mapped: ds[3], } } n.Manual = append(n.Manual, &m) } func (n *Nat) Parse(s string) { // Static parsing, for ver < 8.3 // Simple fix added to get the netmask out of the ASA API. We will need to work on the parsing here // to support multiple types of statics. // TODO: Better parsing for static NAT (support for options below) // Policy NAT // static (real_interface,mapped_interface) {mapped_ip | interface} access-list acl_name [dns] [norandomseq] [[tcp] tcp_max_conns [emb_limit]] [udp udp_max_conns] // Regular Static NAT // static (real_interface,mapped_interface) {mapped_ip | interface} real_ip [netmask mask][dns] [norandomseq] [[tcp] tcp_max_conns [emb_limit]] [udp udp_max_conns] // Policy PAT // static (real_interface,mapped_interface) {tcp | udp} {mapped_ip | interface} mapped_port access-list acl_name [dns] [norandomseq] [[tcp] tcp_max_conns [emb_limit]] [udp udp_max_conns] // Regular Static PAT // static (real_interface,mapped_interface) {tcp | udp} {mapped_ip | interface} mapped_port real_ip real_port [netmask mask] [dns] [norandomseq] [[tcp] tcp_max_conns [emb_limit]] [udp udp_max_conns] staticRegex := regexp.MustCompile(`static \((\S+),(\S+)\) (\S+) (\S+) netmask (\S+)`) for _, static := range staticRegex.FindAllStringSubmatch(s, -1) { n.Static = append(n.Static, Static{ SourceIP: ipStuct{ IP: net.ParseIP(static[3]), Mask: static[5], }, SourceInt: static[1], DestinationInt: static[2], DestinationIP: net.ParseIP(static[4]), }) } // Object Nat parsing for >= 8.4 for _, v := range autoNATRE.FindAllStringSubmatch(s, -1) { n.autoNat(v) } // Manual Nat parsing for >= 8.4 for _, m := range manualNATRE.FindAllStringSubmatch(s, -1) { n.manualNAT(m[0]) } }