Implementing idempotency in Unicon part 7, Special case: Crypto ACL.

Hello

One special case I’m dealing with at the moment is that when expanding the existing VPNs based on crypto ACLs, I get an excel sheet and I need to decide if the new connection between company A and company B necessitates a new crypto SA, in other words: do I need to add an entry in the crypto ACL, or perhaps this new connection is part of the existing old SAs.

This also forms part of the idempotency check that I’m trying to implement, because it doesn’t make sense to add a new ACL entry, if the new entries is a subset of any of the existing entries. So the new entry would need to be compared to each and every one of the existing entries.

An example: I have the following crypto ACL:

10 permit ip 1.1.1.0 0.0.0.255 2.2.2.0 0.0.0.255

This creates a single crypto SA.

The client now sends me an excel file, with the following change:
Please add a connection between 1.1.1.64 to 3.3.3.1

I would now need to create a new SA:

20 permit ip 1.1.1.0 0.0.0.255 3.3.3.0 0.0.0.255

Of course, i could add an SA for single hosts but then if the client sends a similar request for 1.1.1.72 to 3.3.3.55, the crypto ACL needs to be modified on both sides. I don’t want (or don’t need) redundant communication so i prefer to add /24 subnets (or even /16 if i can).

Here comes my pain point. Looking at the excel sheet and deciding whether the new entry is needed or not is manual work, so if I have a non-technical coordinator, they’re not able to say whether the workflow should be: coordinator>VPN specialist>inform other VPN specialist (other GW)> FW specialist>back to coordinator>process people or simply Coordinator>FW specialist>Coordinator.

The logic should be as follows: For the following change when entry 20 is added, when are we allowed to skip entry 20?
10 permit ip A1 B1

20 permit ip A2 B2

Entry 20 is not needed only if:

A2==A1 && B2==B1
OR
A2 is part of A1 && B2 is part of B1.

Let’s look at the netaddr python library documentation:

>>> s1 = IPSet(['192.0.2.0', '192.0.2.1', '192.0.2.2'])
>>> s2 = IPSet(['192.0.2.2', '192.0.2.3', '192.0.2.4'])
>>> s1 & s2
IPSet(['192.0.2.2/32'])
>>> s1.isdisjoint(s2)
False
>>> s1 = IPSet(['192.0.2.0', '192.0.2.1'])
>>> s2 = IPSet(['192.0.2.3', '192.0.2.4'])
>>> s1 & s2
IPSet([])
>>> s1.isdisjoint(s2)
True 

So if we use the disjoint method, our ACL idempotency check should now be (relatively) easy to write up.

In the end I implemented it in a different way:

from cisco_acl.regexes import ace_match
from netaddr import IPSet
from netaddr import IPNetwork
import iplib

source1 = input('what is the source ip ?')
wildcard = input('what is the wildcard ?')
wildcard_converted = iplib.convert_nm(wildcard, 'bit')
source1 = source1 + '/' + wildcard_converted

dest1 = input('what is the dest ip ?')
wildcard = input('what is the wildcard ?')
wildcard_converted = iplib.convert_nm(wildcard, 'bit')
dest1 = dest1 + '/' + wildcard_converted

source2 = input('what is the source ip ?')
wildcard = input('what is the wildcard ?')
wildcard_converted = iplib.convert_nm(wildcard, 'bit')
source2 = source2 + '/' + wildcard_converted

dest2 = input('what is the dest ip ?')
wildcard = input('what is the wildcard ?')
wildcard_converted = iplib.convert_nm(wildcard, 'bit')
dest2 = dest2 + '/' + wildcard_converted


result1 = IPNetwork(source2) in IPSet([source1])
result2 = IPNetwork(dest2) in IPSet([dest1])

if result1 and result2:
  print('we should not add this')
else:
  print('we should add this')
tode@DESKTOP:~$ python3 aclscript.py
 what is the source ip ?192.168.0.0
 what is the wildcard ?0.0.0.255
 
 what is the dest ip ?172.16.1.0
 what is the wildcard ?0.0.0.127
 
 what is the source ip ?192.168.0.0
 what is the wildcard ?0.0.0.31
 
 what is the dest ip ?172.16.1.0
 what is the wildcard ?0.0.0.3
 
 we should not add this

Here i needed to think how I would get the input and compare it with the new input. I came up with this. I will gather existing ACL entries as one dictionary and compare it with the new dictionary of new entries.

from cisco_acl.regexes import ace_match
from netaddr import IPSet
from netaddr import IPNetwork
import iplib

dictionary1 = {"192.168.0.0/24": "10.0.0.0/24", "192.168.1.0/24": "10.0.0.0/30"}

dictionary3 = {"192.168.3.0/24": "10.0.0.0/29", "192.168.4.0/24": "10.0.1.0/27"}

for newkey in dictionary3:
    for oldkey in dictionary1:
        if IPNetwork(newkey) in IPSet([oldkey]) and IPNetwork(dictionary3[newkey]) in IPSet([dictionary1[oldkey]]):
           print('nothing to do')
        else:
           print('something to do')

Now i need to populate the dictionaries with fields from an excel sheet.


If the value of row 1, column C = OLD, i should populate olddictionary,
if the value of row 1, column C = NEW, i should populate newdictionary,

from openpyxl import load_workbook
from netaddr import IPSet
from netaddr import IPNetwork


# load excel with its path
wrkbk = load_workbook("bpadb.xlsx")
sh = wrkbk["Sheet1"]
# to iterate through cells in Column A and print value in console
src = ''
dst = ''
olddict = {}
newdict = {}
changedict = {}
removaldict = {}
for i in range(1,10):
    src = "A" + str(i)
    dst = "B" + str(i)
    comment = "C" + str(i)
    src_cell = sh[src]
    dst_cell = sh[dst]
    comment_cell = sh[comment]
    if comment_cell.value == "OLD":
      olddict[src_cell.value] = dst_cell.value
    if comment_cell.value == "NEW":
      newdict[src_cell.value] = dst_cell.value
print('this is old dictionary ' + str(olddict))
print('this is new dictionary ' + str(newdict))

for newkey in newdict:

    for oldkey in olddict:
        if IPNetwork(newkey) in IPSet([oldkey]) and IPNetwork(newdict[newkey]) in IPSet([olddict[oldkey]]):
           #print('for this new entry ' + str(newkey) + ' ' + str(newdict[newkey]) + ' when compared with entry ' + str(oldkey) + ' ' + str(olddict[oldkey]) + ' there is nothing to do')
           removaldict[newkey] = newdict[newkey]
        else:
           #print(' you need to implement this new acl entry ' + str(newkey) + ' ' + str(newdict[newkey]) + ' because it was not inside ' + str(oldkey) + ' ' + str(olddict[oldkey]))
           changedict[newkey] = newdict[newkey]

print('this is change dictionary ' + str(changedict))
print('this is removal dictionary ' + str(removaldict))


intersect = []
for item in changedict.keys():
    if item in removaldict:
        intersect.append(item)
        #intersect.append(removaldict[item])

print('Two dictionaries intersect here so these entries should be removed: ' + str(intersect))
#print(intersect[0])
#print(intersect[1])
for i in range(0,len(intersect)):
  changedict.pop(intersect[i])
print('final form of changedict is ' + str(changedict))
C:\Users\tode\Desktop>python bpa_script.py
 this is old dictionary {'192.168.0.0/24': '172.16.0.1/32', '192.168.1.0/24': '10.0.0.0/29', '192.168.3.0/26': '169.254.0.0/31'}
 this is new dictionary {'192.168.1.0/27': '10.0.0.0/30', '192.168.3.0/27': '169.254.1.0/31', '192.168.3.0/28': '169.254.0.0/31', '192.168.3.0/29': '169.254.3.0/31'}
 this is change dictionary {'192.168.1.0/27': '10.0.0.0/30', '192.168.3.0/27': '169.254.1.0/31', '192.168.3.0/28': '169.254.0.0/31', '192.168.3.0/29': '169.254.3.0/31'}
 this is removal dictionary {'192.168.1.0/27': '10.0.0.0/30', '192.168.3.0/28': '169.254.0.0/31'}
 Two dictionaries intersect here so these entries should be removed: ['192.168.1.0/27', '192.168.3.0/28']
 final form of changedict is {'192.168.3.0/27': '169.254.1.0/31', '192.168.3.0/29': '169.254.3.0/31'}

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Wyloguj /  Zmień )

Zdjęcie na Google

Komentujesz korzystając z konta Google. Wyloguj /  Zmień )

Zdjęcie z Twittera

Komentujesz korzystając z konta Twitter. Wyloguj /  Zmień )

Zdjęcie na Facebooku

Komentujesz korzystając z konta Facebook. Wyloguj /  Zmień )

Połączenie z %s