1

I want to extract a network group from a Cisco config using Python and regular expressions. The group starts with object-group network Cloudflare and just after that, there are one or more subnets like network-object 173.245.48.0 255.255.240.0. The text would be

object-group network Cloudflare
 network-object 173.245.48.0 255.255.240.0
 network-object 103.21.244.0 255.255.252.0
 network-object 103.22.200.0 255.255.252.0
 network-object 103.31.4.0 255.255.252.0
 network-object 141.101.64.0 255.255.192.0
 network-object 108.162.192.0 255.255.192.0
 network-object 190.93.240.0 255.255.240.0
 network-object 188.114.96.0 255.255.240.0
 network-object 197.234.240.0 255.255.252.0
 network-object 198.41.128.0 255.255.128.0
 network-object 162.158.0.0 255.254.0.0
 network-object 104.16.0.0 255.248.0.0
 network-object 104.24.0.0 255.252.0.0
 network-object 172.64.0.0 255.248.0.0
 network-object 131.0.72.0 255.255.252.0

While using this regex (?P<name>object-group network Cloudflare)\n(?P<subnets> network-object \d+\.\d+\.\d+\.\d+ \d+\.\d+\.\d+\.\d+\n)* I get 3 match groups, of which only the 1st is usable but it contains the header object-group .... Can it be improved? https://regex101.com/r/s925cq/1

7
  • 2
    What's the official name for this file format? Chances are pretty good that there's a python module ready to parse it and give you all the information you need from it without the need for regexs at all.
    – Ted Lyngmo
    Commented Jan 13, 2023 at 16:11
  • Your regex looks to work perfectly. What exactly are you trying to achieve? Please give some context and/or code.
    – PJProudhon
    Commented Jan 13, 2023 at 16:23
  • 2
    github.com/topics/cisco-asa seems to list quite a few repos. Perhaps there's something useful in there.
    – Ted Lyngmo
    Commented Jan 13, 2023 at 16:47
  • 1
    @mlazzarotto91 See regex demo ?
    – SaSkY
    Commented Jan 13, 2023 at 17:00
  • 1
    You can get all the network objects in named group subnets with (?P<name>object-group network Cloudflare)\n(?P<subnets>(?: network-object (?:\d+\.){3}\d+ (?:\d+\.){3}\d+\n)*) See regex101.com/r/OloB8E/1 Commented Jan 13, 2023 at 19:49

3 Answers 3

1
data = """
 network-object 1.1.1.1 0.0.0.0
object-group network Cloudflare
 network-object 173.245.48.0 255.255.240.0
 network-object 103.21.244.0 255.255.252.0
 network-object 103.22.200.0 255.255.252.0
 network-object 103.31.4.0 255.255.252.0
 network-object 141.101.64.0 255.255.192.0
 network-object 108.162.192.0 255.255.192.0
 network-object 190.93.240.0 255.255.240.0
 network-object 188.114.96.0 255.255.240.0
 network-object 197.234.240.0 255.255.252.0
 network-object 198.41.128.0 255.255.128.0
 network-object 162.158.0.0 255.254.0.0
 network-object 104.16.0.0 255.248.0.0
 network-object 104.24.0.0 255.252.0.0
 network-object 172.64.0.0 255.248.0.0
 network-object 131.0.72.0 255.255.252.0
access-list outside
"""

regex=r"object-group network (?P<network>\S+)|network-object\s(?P<subnet>\S+?)\s(?P<mask>\S+)"
matches = re.finditer(regex, data)
result = []
network_group = {"network": "Unknown"}


for cnt, match in enumerate(matches):
    info = match.groupdict()
    info1 = {k: v for k, v in info.items() if v is not None}
    if check:=info.get("network"):
        network_group =   {"network": check} if check else network_group
    if not info.get("network"):
        result.append(network_group | info1)
result

enter image description here

1

You can get all the network objects in named group subnets with:

(?P<name>object-group network Cloudflare)\n(?P<subnets>(?: network-object (?:\d+\.){3}\d+ (?:\d+\.){3}\d+\n)+)

Explanation

  • (?P<name>object-group network Cloudflare) Named group name, match literally
  • \n Match a newline
  • (?P<subnets> Named group subnets
    • (?: Non capture group to repeat as a whole
      • network-object
      • (?:\d+\.){3}\d+ (?:\d+\.){3}\d+\n match 2 times the digits with dot part followed by a newline
    • )+ Close the non capture group and repeat 1 or more times (to match at least 1 line)
  • ) Close group subnets

See a regex 101 demo.

1

Intro

As @Ted Lyngmo mentioned above, it's best to use a parser that is specific to this network configuration format. Once such parser is ciscoconfparse2 (full disclosure: I am the author)—despite the name, it reads more than Cisco configuration files.

The advantage of using ciscoconfparse2 over a raw regular expression is that the code is more readable and maintainable.

regex technique with ciscoconfparse2

To get the list of subnets in object-group network Cloudflare, read the format with ciscoconfparse2 and build a dict to hold the networks. There is no limit of object-group statements you can read with the technique below.

With ciscoconfparse2 it's pretty easy...

from pprint import pprint

from ciscoconfparse2 import CiscoConfParse, IPv4Obj

config = """
 network-object 1.1.1.1 0.0.0.0
object-group network Cloudflare
 network-object 173.245.48.0 255.255.240.0
 network-object 103.21.244.0 255.255.252.0
 network-object 103.22.200.0 255.255.252.0
 network-object 103.31.4.0 255.255.252.0
 network-object 141.101.64.0 255.255.192.0
 network-object 108.162.192.0 255.255.192.0
 network-object 190.93.240.0 255.255.240.0
 network-object 188.114.96.0 255.255.240.0
 network-object 197.234.240.0 255.255.252.0
 network-object 198.41.128.0 255.255.128.0
 network-object 162.158.0.0 255.254.0.0
 network-object 104.16.0.0 255.248.0.0
 network-object 104.24.0.0 255.252.0.0
 network-object 172.64.0.0 255.248.0.0
 network-object 131.0.72.0 255.255.252.0
access-list outside
"""

parse = CiscoConfParse(config)

# Store all object-group names in a dict to list mapping
#    key the dict by the object-group name and append the networks to each
object_groups = dict()

# If there are multiple object-groups, iterate over each one...
for object_group_cmd in parse.find_objects('^object-group'):
    name = object_group_cmd.split()[2]

    # Grab all object-group network-object commands at once with this regex...
    networks = object_group_cmd.re_list_iter_typed('network-object\s+(\d.+)')

    object_groups[name] = list()
    for cmd in networks:

        tmp = cmd.split()
        network, netmask = tmp[0], tmp[1]

        # Add each network and netmask to the list...
        object_groups[name].append({'network': network, 'netmask': netmask})

pprint(object_groups)

That will print:

{'Cloudflare': [{'netmask': '255.255.240.0', 'network': '173.245.48.0'},
                {'netmask': '255.255.252.0', 'network': '103.21.244.0'},
                {'netmask': '255.255.252.0', 'network': '103.22.200.0'},
                {'netmask': '255.255.252.0', 'network': '103.31.4.0'},
                {'netmask': '255.255.192.0', 'network': '141.101.64.0'},
                {'netmask': '255.255.192.0', 'network': '108.162.192.0'},
                {'netmask': '255.255.240.0', 'network': '190.93.240.0'},
                {'netmask': '255.255.240.0', 'network': '188.114.96.0'},
                {'netmask': '255.255.252.0', 'network': '197.234.240.0'},
                {'netmask': '255.255.128.0', 'network': '198.41.128.0'},
                {'netmask': '255.254.0.0', 'network': '162.158.0.0'},
                {'netmask': '255.248.0.0', 'network': '104.16.0.0'},
                {'netmask': '255.252.0.0', 'network': '104.24.0.0'},
                {'netmask': '255.248.0.0', 'network': '172.64.0.0'},
                {'netmask': '255.255.252.0', 'network': '131.0.72.0'}]}

It should be noted that this gets all object-group network statements at once...

Not the answer you're looking for? Browse other questions tagged or ask your own question.