Skip to content

Commit ee044dd

Browse files
committed
PAN-OS: Enhancement to handle term source-port keyword.
If source-port is specified add it to a service object. If there is no destination-port then add range 0-65535 to the service destination port because it is required field. The port range is used vs. "any" due to recent changes which disallow any in the WebUI.
1 parent f25094a commit ee044dd

2 files changed

Lines changed: 95 additions & 18 deletions

File tree

capirca/lib/paloaltofw.py

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -141,10 +141,10 @@ class ServiceMap(object):
141141
def __init__(self):
142142
self.entries = {}
143143

144-
def get_service_name(self, term_name, ports, protocol):
144+
def get_service_name(self, term_name, src_ports, ports, protocol):
145145
"""Returns service name based on the provided ports and protocol."""
146-
if (ports, protocol) in self.entries:
147-
return self.entries[(ports, protocol)]["name"]
146+
if (src_ports, ports, protocol) in self.entries:
147+
return self.entries[(src_ports, ports, protocol)]["name"]
148148

149149
service_name = "service-%s-%s" % (term_name, protocol)
150150

@@ -158,7 +158,7 @@ def get_service_name(self, term_name, ports, protocol):
158158
"You have a duplicate service. A service named %s already exists." %
159159
service_name)
160160

161-
self.entries[(ports, protocol)] = {"name": service_name}
161+
self.entries[(src_ports, ports, protocol)] = {"name": service_name}
162162
return service_name
163163

164164

@@ -195,6 +195,16 @@ def ModifyOptions(self, terms, service_map):
195195
self.options["service"] = []
196196
self.options["logging"] = []
197197

198+
def pan_ports(ports):
199+
x = []
200+
for tup in ports:
201+
if len(tup) > 1 and tup[0] != tup[1]:
202+
x.append(str(tup[0]) + "-" + str(tup[1]))
203+
else:
204+
x.append(str(tup[0]))
205+
206+
return tuple(x)
207+
198208
# COMMENT
199209
if term.comment:
200210
self.options["description"] = term.comment
@@ -244,18 +254,17 @@ def ModifyOptions(self, terms, service_map):
244254
for pan_app in term.pan_application:
245255
self.options["application"].append(pan_app)
246256

247-
if term.destination_port:
248-
ports = []
249-
for tup in term.destination_port:
250-
if len(tup) > 1 and tup[0] != tup[1]:
251-
ports.append(str(tup[0]) + "-" + str(tup[1]))
252-
else:
253-
ports.append(str(tup[0]))
254-
ports = tuple(ports)
257+
if term.source_port or term.destination_port:
258+
src_ports = pan_ports(term.source_port)
259+
if term.destination_port:
260+
ports = pan_ports(term.destination_port)
261+
else:
262+
ports = pan_ports([("0", "65535")])
255263

256264
# check to see if this service already exists
257265
for p in term.protocol:
258-
service_name = service_map.get_service_name(term.name, ports, p)
266+
service_name = service_map.get_service_name(term.name,
267+
src_ports, ports, p)
259268
if service_name not in self.options["service"]:
260269
self.options["service"].append(service_name)
261270

@@ -757,12 +766,20 @@ def __str__(self):
757766
for k, v in self.service_map.entries.items():
758767
entry = etree.SubElement(service, "entry", {"name": v["name"]})
759768
proto0 = etree.SubElement(entry, "protocol")
760-
proto = etree.SubElement(proto0, k[1])
769+
proto = etree.SubElement(proto0, k[2])
770+
# destination port
761771
port = etree.SubElement(proto, "port")
762-
tup = str(k[0])[1:-1]
772+
tup = str(k[1])[1:-1]
763773
if tup[-1] == ",":
764774
tup = tup[:-1]
765775
port.text = tup.replace("'", "").replace(", ", ",")
776+
# source port
777+
if len(k[0]):
778+
sport = etree.SubElement(proto, "source-port")
779+
tup = str(k[0])[1:-1]
780+
if tup[-1] == ",":
781+
tup = tup[:-1]
782+
sport.text = tup.replace("'", "").replace(", ", ",")
766783

767784
# RULES
768785
vsys_entry.append(etree.Comment(" Rules "))

tests/lib/paloaltofw_test.py

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,7 @@
440440
PATH_VSYS = "./devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']"
441441
PATH_RULES = PATH_VSYS + '/rulebase/security/rules'
442442
PATH_TAG = PATH_VSYS + '/tag'
443+
PATH_SERVICE = PATH_VSYS + '/service'
443444

444445

445446
class PaloAltoFWTest(unittest.TestCase):
@@ -472,10 +473,10 @@ def testServiceMap(self):
472473
policy.ParsePolicy(GOOD_HEADER_1 + SVC_TERM_1, definitions), EXP_INFO)
473474
self.assertEqual(
474475
pol1.service_map.entries, {
475-
(('22',), 'tcp'): {
476+
((), ('22',), 'tcp'): {
476477
'name': 'service-ssh-term-1-tcp'
477478
},
478-
(('25',), 'tcp'): {
479+
((), ('25',), 'tcp'): {
479480
'name': 'service-smtp-term-1-tcp'
480481
}
481482
}, pol1.service_map.entries)
@@ -485,7 +486,7 @@ def testServiceMap(self):
485486
# The expectation is that there will be a single port mapped.
486487
self.assertEqual(
487488
pol2.service_map.entries, {
488-
(('25',), 'tcp'): {
489+
((), ('25',), 'tcp'): {
489490
'name': 'service-smtp-term-1-tcp'
490491
}
491492
}, pol2.service_map.entries)
@@ -869,6 +870,65 @@ def testPanApplication(self):
869870
apps = {elem.text for elem in x}
870871
self.assertEqual(APPS[i], apps, output)
871872

873+
def testPanPorts(self):
874+
POL = """
875+
header {
876+
target:: paloalto from-zone trust to-zone untrust
877+
}
878+
term rule-1 {
879+
%s
880+
action:: accept
881+
}"""
882+
883+
T = """
884+
protocol:: udp
885+
destination-port:: NTP
886+
"""
887+
888+
pol = policy.ParsePolicy(POL % T)
889+
paloalto = paloaltofw.PaloAltoFW(pol, EXP_INFO)
890+
output = str(paloalto)
891+
name = "service-rule-1-udp"
892+
path = "/entry[@name='%s']/protocol/udp/port" % name
893+
x = paloalto.config.findtext(PATH_SERVICE + path)
894+
self.assertEqual(x, "123", output)
895+
path = "/entry[@name='%s']/protocol/udp/source-port" % name
896+
x = paloalto.config.findtext(PATH_SERVICE + path)
897+
self.assertIsNone(x, output)
898+
899+
T = """
900+
protocol:: udp
901+
source-port:: NTP
902+
"""
903+
904+
pol = policy.ParsePolicy(POL % T)
905+
paloalto = paloaltofw.PaloAltoFW(pol, EXP_INFO)
906+
output = str(paloalto)
907+
name = "service-rule-1-udp"
908+
path = "/entry[@name='%s']/protocol/udp/port" % name
909+
x = paloalto.config.findtext(PATH_SERVICE + path)
910+
self.assertEqual(x, "0-65535", output)
911+
path = "/entry[@name='%s']/protocol/udp/source-port" % name
912+
x = paloalto.config.findtext(PATH_SERVICE + path)
913+
self.assertEqual(x, "123", output)
914+
915+
T = """
916+
protocol:: tcp
917+
source-port:: NTP
918+
destination-port:: NTP DNS
919+
"""
920+
921+
pol = policy.ParsePolicy(POL % T)
922+
paloalto = paloaltofw.PaloAltoFW(pol, EXP_INFO)
923+
output = str(paloalto)
924+
name = "service-rule-1-tcp"
925+
path = "/entry[@name='%s']/protocol/tcp/port" % name
926+
x = paloalto.config.findtext(PATH_SERVICE + path)
927+
self.assertEqual(x, "53,123", output)
928+
path = "/entry[@name='%s']/protocol/tcp/source-port" % name
929+
x = paloalto.config.findtext(PATH_SERVICE + path)
930+
self.assertEqual(x, "123", output)
931+
872932

873933
if __name__ == '__main__':
874934
unittest.main()

0 commit comments

Comments
 (0)