├── srx-list ├── Feed ├── schema.xml ├── manifest.xml ├── README.md └── dynamic-policy.py /srx-list: -------------------------------------------------------------------------------- 1 | 192.168.0.2 2 | -------------------------------------------------------------------------------- /Feed: -------------------------------------------------------------------------------- 1 | {"filter":{},"version":"1428028377.4-20","schema_version":"398490f187","previous_version":null} 2 | #del 3 | #add 4 | #end 5 | e9e635c8001b078ef30e9cd992401e32 6 | -------------------------------------------------------------------------------- /schema.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | /schema.xml 5 | 6 | 7 | 8 | / 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dynamic-policy - Dynamic creation of SRX address entries 2 | 3 | The SRX firewall can be configured to monitor a central web server for a list of IP Addresses to create a dynamic address book using the```security-intelligence```feature-set. 4 | 5 | This software will need to be installed on an existing web-server and the python file modified to point to the correct base directory of the web-server. Clone all files and copy the schema.xml and manifest.xml file to the base of the web server. 6 | 7 | Configure the SRX security-intelligence module to point to you web server - the auth-token is required to commit (32 ascii chars), but is only used by Junos Space to authenticate the device: 8 | 9 | ``` 10 | set services security-intelligence url http://192.168.1.1/manifest.xml 11 | set services security-intelligence authentication auth-token 12345678901234567890121234567890 12 | ``` 13 | The```manifest.xml```file provides a list of available "feeds" of IP addresses eg: 14 | 15 | ``` 16 | 17 | 18 | 19 | /schema.xml 20 | 21 | 22 | 23 | / 24 | 25 | 26 | 27 | 28 | ``` 29 | 30 | The SRX will refresh```manifest.xml``` every```update_interval```seconds, as specified in the manifest. 31 | 32 | A feed is essentially a list of IPs that have some common purpose eg: a blacklist, whitelist, prefixes that identify a cloud provider etc. 33 | 34 | Addresses are grouped into feeds and stored in independent feed files. 35 | 36 | Within the SRX, a dynamic-address is created as follows: 37 | 38 | ```set security dynamic-address address-name MY-BLACKLIST profile category IPFilter feed BADSITES``` 39 | 40 | The SRX then consults the```manifest.xml```file for a feed named BADSITES, downloads it from the web server and then installs it into the dynamic-address entry. 41 | 42 | The SRX will now consult the feed every update interval and dynamically add or remove addresses as they are available, all without requiring a configuration change. 43 | 44 | **NOTE:** As of this writing this has been tested on Junos 12.3X48D10 and 15.1X49-D110 code releases, dynamic-address entries can only be used in security policies; NAT rules only consult the global and zone-based address-books for address entries. 45 | 46 | # Usage 47 | **Create a new feed** 48 | 49 | ``` 50 | dynamic-policy.py new MYFEED 51 | ``` 52 | 53 | **Add an address to a feed** 54 | 55 | ``` 56 | dynamic-policy.py add MYFEED 192.168.88.0/24 57 | ``` 58 | 59 | **Remove an address from a feed** 60 | 61 | ``` 62 | dynamic-policy.py del MYFEED 192.168.88.0/24 63 | ``` 64 | -------------------------------------------------------------------------------- /dynamic-policy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | #import xml.etree.ElementTree as ET 4 | from xml.dom import minidom 5 | from xml.etree import ElementTree as ET 6 | from xml.etree.ElementTree import Element, SubElement, Comment, tostring 7 | 8 | import time 9 | import sys 10 | import re 11 | import netaddr 12 | import fileinput 13 | import hashlib 14 | import shutil 15 | import json 16 | from pprint import pprint 17 | from jnpr.junos import Device 18 | import getpass 19 | from jnpr.junos import Device 20 | from jnpr.junos.utils.config import Config 21 | 22 | 23 | 24 | 25 | feed_location='/var/www/' 26 | temp_location='/var/tmp/' 27 | 28 | 29 | def prettify(elem): 30 | rough_string = ET.tostring(elem, 'utf-8') 31 | reparsed = minidom.parseString(rough_string) 32 | return reparsed.toprettyxml(indent=" ") 33 | 34 | def modify_file(file_name,pattern,value=""): 35 | fh=fileinput.input(file_name,inplace=True) 36 | for line in fh: 37 | replacement=line + value 38 | line=re.sub(pattern,replacement,line) 39 | sys.stdout.write(line) 40 | fh.close() 41 | 42 | def delete_line(pattern): 43 | readFile = open(tempFeed,'r') 44 | lines = readFile.readlines() 45 | readFile.close() 46 | 47 | writeFile = open(tempFeed,'w') 48 | 49 | for line in lines: 50 | if line != pattern+"\n": 51 | #print "Not found--->" + line 52 | writeFile.write(line) 53 | 54 | writeFile.close() 55 | 56 | 57 | def update_manifest(feedname): 58 | ts = int(time.time()) 59 | print "Modifying total number of objects for -> " + feedname 60 | with open(tempFeed,'r') as feed: 61 | count = sum(1 for line in feed) - 5 62 | print "Revised address count of -> " + str(count) + "\n" 63 | feed.close() 64 | 65 | tree = ET.parse(tempManifest) 66 | root = tree.getroot() 67 | 68 | for feed in root.iter('feed'): 69 | name = feed.get('name') 70 | #print name 71 | if name == str(feedname): 72 | feed.set('data_ts',str(ts)) 73 | feed.set('objects',str(count)) 74 | 75 | tree.write(tempManifest) 76 | 77 | 78 | def create_manifest_entry(feedname): 79 | ts = str(int(time.time())) 80 | print "Inserting new feed into manifest file located at " + feed_location 81 | 82 | tree = ET.parse(tempManifest) 83 | root = tree.getroot() 84 | 85 | category = root.find('category') 86 | feed = ET.SubElement(category,'feed',dict(data_ts=ts, name=feedname, objects="0", options="", types="ip_addr ip_range", version=feedname)) 87 | data = ET.SubElement(feed,'data') 88 | url = ET.SubElement(data,'url') 89 | url.text='/' 90 | 91 | text=(prettify(root)) 92 | cleantext= "".join([s for s in text.strip().splitlines(True) if s.strip()]) 93 | 94 | with open(tempManifest,'w') as file: 95 | file.write(cleantext) 96 | 97 | 98 | 99 | 100 | 101 | def copy_feed_to_tempFeed(): 102 | shutil.copyfile(feed,tempFeed) 103 | readFile = open(tempFeed) 104 | lines = readFile.readlines() 105 | readFile.close() 106 | writeFile = open(tempFeed,'w') 107 | writeFile.writelines([item for item in lines[:-1]]) 108 | writeFile.close() 109 | 110 | def copy_tempFeed_to_feed(): 111 | shutil.copyfile(tempFeed,feed) 112 | 113 | def copy_tempManifest_to_Manifest(): 114 | shutil.copyfile(tempManifest,manifest) 115 | 116 | def copy_Manifest_to_tempManifest(): 117 | shutil.copyfile(manifest,tempManifest) 118 | 119 | def create_newFeed(name): 120 | shutil.copyfile('Feed',name) 121 | 122 | 123 | def calculate_md5(): 124 | with open(tempFeed) as file: 125 | data = file.read() 126 | md5_returned = hashlib.md5(data).hexdigest() 127 | file.close() 128 | 129 | writeFile = open(tempFeed,'a') 130 | writeFile.write(md5_returned) 131 | writeFile.close() 132 | 133 | 134 | 135 | if (sys.argv[1]=='add'): 136 | feed=feed_location + str(sys.argv[2]) 137 | tempFeed=temp_location + str(sys.argv[2]) 138 | manifest=feed_location+'manifest.xml' 139 | tempManifest=temp_location+'manifest.xml' 140 | copy_feed_to_tempFeed() 141 | copy_Manifest_to_tempManifest() 142 | ip = netaddr.IPNetwork(sys.argv[3]) 143 | feedname = sys.argv[2] 144 | address = ip.ip 145 | size = ip.size 146 | adj_size = size -1 147 | value = ip.value 148 | print "\nAdding address of -> " + str(sys.argv[3]) +" (including " + str(adj_size) + " subequent hosts)" 149 | 150 | if adj_size == 0: 151 | newentry = '{"1":' + str(value) +'}' 152 | else: 153 | newentry = '{"2":[' + str(value) + ',' + str(adj_size) +']}' 154 | 155 | #print newentry 156 | modify_file(tempFeed,'#add',newentry) 157 | calculate_md5() 158 | update_manifest(feedname) 159 | copy_tempFeed_to_feed() 160 | copy_tempManifest_to_Manifest() 161 | 162 | 163 | if sys.argv[1]=='del': 164 | feed=feed_location + str(sys.argv[2]) 165 | tempFeed=temp_location + str(sys.argv[2]) 166 | manifest=feed_location+'manifest.xml' 167 | tempManifest=temp_location+'manifest.xml' 168 | copy_feed_to_tempFeed() 169 | copy_Manifest_to_tempManifest() 170 | ip = netaddr.IPNetwork(sys.argv[3]) 171 | feedname = sys.argv[2] 172 | address = ip.ip 173 | size = ip.size 174 | adj_size = size -1 175 | value = ip.value 176 | print "\nRemoving address of -> " + str(sys.argv[3]) +" (including " + str(adj_size) + " subequent hosts)" 177 | 178 | if adj_size == 0: 179 | oldline = '{"1":' + str(value) +'}' 180 | else: 181 | oldline = '{"2":[' + str(value) + ',' + str(adj_size) +']}' 182 | delete_line(oldline) 183 | 184 | calculate_md5() 185 | update_manifest(feedname) 186 | copy_tempFeed_to_feed() 187 | copy_tempManifest_to_Manifest() 188 | 189 | 190 | if sys.argv[1]=='list': 191 | feed=feed_location + str(sys.argv[2]) 192 | 193 | pattern_network = '{"(\d+)":\[\d+,\d+\]}' 194 | pattern_host = '{"(\d+)":\d+}' 195 | pattern_ip_network = '{"\d+":\[(\d+),\d+]}' 196 | pattern_ip_host = '{"\d+":(\d+)}' 197 | pattern_range = '\d+":\[\d+,(\d+)]}' 198 | 199 | with open(feed,'r') as file: 200 | lines = file.readlines() 201 | 202 | for line in lines: 203 | host = re.search(pattern_host,line) 204 | network = re.search(pattern_network,line) 205 | if host: 206 | ip = str(netaddr.IPAddress(re.findall(pattern_ip_host,line)[0])) 207 | print "Host entry: " + ip 208 | 209 | elif network: 210 | #ip = re.findall(pattern_ip_network,line)[0] 211 | ip = str(netaddr.IPAddress(re.findall(pattern_ip_network,line)[0])) 212 | range = re.findall(pattern_range,line)[0] 213 | print "Network Entry: " + ip + " (+" + range + " hosts)" 214 | 215 | 216 | if sys.argv[1]=='new': 217 | name = str(sys.argv[2]) 218 | feed=feed_location + str(sys.argv[2]) 219 | manifest=feed_location+'manifest.xml' 220 | tempManifest=temp_location+'manifest.xml' 221 | copy_Manifest_to_tempManifest() 222 | print name 223 | create_newFeed(feed) 224 | create_manifest_entry(name) 225 | copy_tempManifest_to_Manifest() 226 | #print "Completed, add the following line to your SRX to accept feed:\n set security dynamic-address address-name "+name+ " profile category IPFilter feed "+name 227 | username = raw_input("Please enter your SRX Username:") 228 | password = getpass.getpass() 229 | srx_list = 'srx-list' 230 | srxs = open(srx_list,'r') 231 | for srx in srxs: 232 | print "Logging into SRX "+srx 233 | login=str(srx) 234 | dev = Device(host=login,user=username,password=password) 235 | dev.open() 236 | dev.timeout = 300 237 | cu = Config(dev) 238 | set_cmd = 'set security dynamic-address address-name '+name+' profile category IPFilter feed '+name 239 | cu.load(set_cmd, format='set') 240 | print "Applying changes, please wait...." 241 | cu.commit() 242 | dev.close() 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | if sys.argv[1]=='drop': 252 | name = feed_location + str(sys.argv[2]) 253 | print name 254 | print "do this next...." 255 | 256 | 257 | if sys.argv[1]=='setup': 258 | print "Kick off initial setup process, copy files to target directories etc" 259 | 260 | --------------------------------------------------------------------------------