├── .gitignore ├── README.md ├── src ├── README.md ├── __init__.py ├── dns-update │ ├── DNS_update.py │ ├── README.md │ └── fqdn-poller ├── eem-policy │ └── config_check.py ├── pci-tool │ ├── README.md │ ├── __init__.py │ ├── pci_check.py │ └── show_int.textfsm ├── requirements.txt └── utils │ ├── __init__.py │ ├── __init__.pyc │ ├── spark_utils.py │ └── spark_utils.pyc └── utils └── update_git.sh /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # On Box Python Example Scripts 2 | 3 | These scripts are related to a blog post https://blogs.cisco.com/developer/on-box-python-for-devices 4 | 5 | -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | #On-Box Python scripts 2 | This is a collection of sample on box-python scripts for Cisco devices. 3 | 4 | each can be run standalone. They illustrate different use cases for on-box Python, namely 5 | 6 | - Scale - see pci-tool 7 | - Security - see dns-update 8 | - Autonomy - see eem-policy 9 | 10 | Each script uses the Embeded Event Manager (EEM) to launch it in a slightly different way 11 | 12 | There is also a utility script that can be schedulled to keep these scripts updated on your device 13 | 14 | You need to add the following EEM configuration to your network device. 15 | This example runs the update_git.sh script at the top and bottom of the hour. 16 | This requires git to be installed on the device. 17 | 18 | ```buildoutcfg 19 | event manager applet GIT-sync 20 |  event timer cron cron-entry "0,30 * * * *" 21 |  action 1.0 cli command "enable" 22 |  action 1.1 cli command "guestshell run bootflash/gs_script/src/util/update_git/sh" 23 | 24 | ``` 25 | 26 | ## Enabling GuestShell 27 | Guestshell is a hosted application on IOS devices. In order to use it, you require the IOS application hosting 28 | framework called IOX 29 | 30 | This needs to be configured: 31 | ```buildoutcfg 32 | 9300# conf t 33 | 9300(config)#iox 34 | ``` 35 | 36 | You can then enable guestshell from exec mode. 37 | ```buildoutcfg 38 | 9300# guestshell enable 39 | Management Interface will be selected if configured 40 | Please wait for completion 41 | 42 | ``` 43 | 44 | It will take a a minute or so to complete. Once it has you can either run a command 45 | inline or just open a shell. 46 | 47 | ```buildoutcfg 48 | 9300#guestshell run echo "hello world" 49 | hello world 50 | 51 | or 52 | 53 | 9300#guestshell 54 | [guestshell@guestshell ~]$  55 | 56 | ``` 57 | 58 | Python can be run in a similat way: 59 | ```buildoutcfg 60 | 9300#guestshell run python -c "print 'hello world'" 61 | 62 | or 63 | 64 | 9300#guestshell 65 | [guestshell@guestshell ~]$python  66 | 67 | ``` 68 | 69 | ##Optimizing Python 70 | One of the first things you will want to do is install new python modules. 71 | The guestshell does not have dns setup, so the first thing to do is to edit /etc/resolv.conf 72 | 73 | Use you favorite unix editor, or you can just run the following command to update the file. 74 | ```buildoutcfg 75 | echo 'echo -e "nameserver 8.8.8.8\ndomain cisco.com" > /etc/resolv.conf' | sudo sh 76 | 77 | ``` 78 | Now you should have access to the internet. If your device needs to connect out via a proxy, then you will need to 79 | update the proxy environment variables. I normally store these in ~/.bashrc so they persist between guestshell sessions. 80 | ```buildoutcfg 81 | export http_proxy=http://proxy.abc.com:80/ 82 | export https_proxy=http://proxy.abc.com:80/ 83 | export ftp_proxy=http://proxy.abc.com:80/ 84 | export no_proxy=.abccom 85 | export HTTP_PROXY=http://proxy.abc.com:80/ 86 | export HTTPS_PROXY=http://proxy.abc.com:80/ 87 | export FTP_PROXY=http://proxy.abc.com:80/ 88 | ``` 89 | Next you can install some python modules. NOTE: if you are using a proxy you will need to use "sudo -E" 90 | to use the environment variables above with Pip. I am installing these into the global library, so I need to "sudo". 91 | I could also use a virtualenv if I wanted to. 92 | 93 | ```buildoutcfg 94 | [guestshell@guestshell ~]$ sudo -E  pip install netaddr 95 | Collecting netaddr 96 |   Downloading netaddr-0.7.19-py2.py3-none-any.whl (1.6MB) 97 |     100% |################################| 1.6MB 257kB/s  98 | Installing collected packages: netaddr 99 | Successfully installed netaddr-0.7.19 100 | 101 | ``` 102 | *NOTE: Catalyst 3k platforms have a limiation in that there is no gcc compiler installed. You will not (easily) be 103 | able to install modules that require C compilation* 104 | 105 | ## Installing Git 106 | Git is an extremely useful tool for sharing, collaborating and updating code. 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aradford123/on-box-python/f487e798bdc9b665bfaf7e298214eb2d13018919/src/__init__.py -------------------------------------------------------------------------------- /src/dns-update/DNS_update.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | import time 4 | import re 5 | from dns import resolver 6 | from dns.resolver import NXDOMAIN 7 | from dns.resolver import NoAnswer 8 | from dns.resolver import NoNameservers 9 | from dns.exception import Timeout 10 | from cli import cli 11 | from cli import configure 12 | 13 | ACLNAME = "canary_ip_in" 14 | 15 | 16 | def log(message, severity): 17 | ''' 18 | this can also log to spark. need to check if a token is present 19 | :param message: 20 | :param severity: 21 | :return: 22 | ''' 23 | cli('send log %d "%s"' % (severity, message)) 24 | 25 | 26 | def dns_lookup(name): 27 | log("Looking up %s" % name, 5) 28 | try: 29 | answers = resolver.query(name, 'A') 30 | return answers.rrset.ttl, [answer.address for answer in answers] 31 | except NXDOMAIN: 32 | pass 33 | except NoAnswer: 34 | pass 35 | except NoNameservers: 36 | pass 37 | except Timeout: 38 | pass 39 | 40 | 41 | def get_acl_ip(): 42 | ''' 43 | returns a list of IPS found in ACL 44 | :return: 45 | ''' 46 | acl = cli("show ip access-list %s" % ACLNAME).split('\n') 47 | p = re.compile('[\d]+\.[\d\.]+') 48 | return p.findall(''.join(acl)) 49 | 50 | 51 | # [ConfigResult(success=True, command='', line=1, output='', notes=None), 52 | # ConfigResult(success=True, command='ip access-list extended canary_ip_in', line=2, output='', notes=None), 53 | # ConfigResult(success=True, command='no deny ip any any', line=3, output='', notes=None), 54 | # ConfigResult(success=True, command='permit ip any host 1.1.1.1', line=4, output='', notes=None), 55 | # ConfigResult(success=True, command='deny ip any any', line=5, output='', notes=None)] 56 | 57 | def add_acl(ip): 58 | ''' 59 | add an entry to the ACL. look at success or not of the commands 60 | :param ip: 61 | :return: 62 | ''' 63 | UPDATE_ACL_COMMANDS = """ 64 | ip access-list extended %s 65 | no deny ip any any 66 | remark %s 67 | permit ip any host %s 68 | deny ip any any 69 | """ 70 | localtime = time.asctime(time.localtime(time.time())) 71 | remark = "Added %s @%s" % (ip, localtime) 72 | responses = configure(UPDATE_ACL_COMMANDS % (ACLNAME, remark, ip)) 73 | success = reduce(lambda x, y: x and y, [r.success for r in responses]) 74 | status = "Success" if success else "Fail" 75 | log("adding IP: %s to ACL: status: %s" % (ip, status), 5) 76 | 77 | 78 | def reschedule(seconds, *args): 79 | ''' 80 | set an EEM countdown timer to run the script again 81 | :param seconds: 82 | :param *args: the initial args that were passed to the script 83 | :return: 84 | ''' 85 | UPDATE_SCRIPT_FIRING_COMMANDS = """ 86 | event manager applet DNS_update 87 | event timer countdown time %s 88 | action 1.0 cli command "enable" 89 | action 1.1 cli command "guestshell run python %s 90 | """ 91 | responses = configure(UPDATE_SCRIPT_FIRING_COMMANDS % (seconds, " ".join(args))) 92 | success = reduce(lambda x, y: x and y, [r.success for r in responses]) 93 | status = "Success" if success else "Fail" 94 | log("reschedule in : %s seconds: status: %s" % (str(seconds), status), 5) 95 | 96 | 97 | def main(argv): 98 | ''' 99 | takes a list of DNS names, resolves them and gets all of the IP addresses as well as TTL 100 | TTL is used to calculate the next run time. =min(ttl) +1 101 | :param argv: domain names to lookup 102 | :return: 103 | ''' 104 | acl = get_acl_ip() 105 | minttl = 30 106 | for arg in argv[1:]: 107 | ttl, ips = dns_lookup(arg) 108 | missing_ip = list(set(ips) - set(acl)) 109 | 110 | for ip in missing_ip: 111 | add_acl(ip) 112 | 113 | if ttl < minttl: 114 | minttl = ttl 115 | 116 | # update with all the args including argv[0] which is how the script was called. 117 | reschedule(minttl, *argv) 118 | 119 | 120 | if __name__ == "__main__": 121 | if len(sys.argv) == 1: 122 | print "Usage: %s domain-name ..." 123 | sys.exit(1) 124 | 125 | main(sys.argv) 126 | -------------------------------------------------------------------------------- /src/dns-update/README.md: -------------------------------------------------------------------------------- 1 | #DNS_update 2 | 3 | This python script runs on a Cisco IOS-XE guestshell and will monitor DNS name to keep an ACL updated. 4 | 5 | To install required libraries (you need -E if you have proxy settings in your environment variables): 6 | 7 | ```buildoutcfg 8 | sudo -E pip install dnspython 9 | 10 | ``` 11 | 12 | install the script in the /flash/gs_script/src/dns_update directory. 13 | If you are doing a git clone, this is easy. 14 | ```buildoutcfg 15 | git clone https://github.com/aradford123/on-box-python.git /flash/gs_script 16 | ``` 17 | 18 | Run with the domain names you want to monitor (the domain names below are just examples): 19 | ```buildoutcfg 20 | /flash/gs_script/src/dns-update/DNS_update.py cisco.com amazon.com 21 | ``` 22 | 23 | The script will update an named access-list called "canary_ip_in" (this can be changed) 24 | It assumes the ACL already exists. By default you should create it as: 25 | 26 | ```buildoutcfg 27 | ip access-list extended canary_ip_in 28 | deny ip any any 29 | ``` 30 | The script will look for the lowest TTL in the DNS responses and use that to rechedulle the next time it runs. 31 | An EEM countdown timer is used for schedulling 32 | 33 | ```buildoutcfg 34 | 9300#show run | sec canary 35 | ip access-list extended canary_ip_in 36 | remark Added 72.163.4.161 @Wed Jul 12 11:44:11 2017 37 | permit ip any host 72.163.4.161 38 | remark Added 54.239.25.208 @Wed Jul 12 11:44:11 2017 39 | permit ip any host 54.239.25.208 40 | remark Added 54.239.17.6 @Wed Jul 12 11:44:11 2017 41 | permit ip any host 54.239.17.6 42 | remark Added 54.239.26.128 @Wed Jul 12 11:44:11 2017 43 | permit ip any host 54.239.26.128 44 | remark Added 54.239.17.7 @Wed Jul 12 11:44:11 2017 45 | permit ip any host 54.239.17.7 46 | remark Added 54.239.25.200 @Wed Jul 12 11:44:11 2017 47 | permit ip any host 54.239.25.200 48 | remark Added 54.239.25.192 @Wed Jul 12 11:44:11 2017 49 | permit ip any host 54.239.25.192 50 | deny ip any any 51 | ``` 52 | 53 | This is the log information for an initial update. 54 | ``` 55 | show logging 56 | *Jul 12 11:44:11.174: %SYS-5-USERLOG_NOTICE: Message from tty4(user id: ): "Looking up cisco.com" 57 | *Jul 12 11:44:11.253: %SYS-5-USERLOG_NOTICE: Message from tty4(user id: ): "Looking up amazon.com" 58 | *Jul 12 11:44:11.341: %SYS-5-USERLOG_NOTICE: Message from tty4(user id: ): "adding IP: 72.163.4.161 to ACL: status: Success" 59 | *Jul 12 11:44:11.351: %SYS-5-USERLOG_NOTICE: Message from tty4(user id: ): "adding IP: 54.239.25.208 to ACL: status: Success" 60 | *Jul 12 11:44:11.360: %SYS-5-USERLOG_NOTICE: Message from tty4(user id: ): "adding IP: 54.239.17.6 to ACL: status: Success" 61 | *Jul 12 11:44:11.370: %SYS-5-USERLOG_NOTICE: Message from tty4(user id: ): "adding IP: 54.239.26.128 to ACL: status: Success" 62 | *Jul 12 11:44:11.379: %SYS-5-USERLOG_NOTICE: Message from tty4(user id: ): "adding IP: 54.239.17.7 to ACL: status: Success" 63 | *Jul 12 11:44:11.389: %SYS-5-USERLOG_NOTICE: Message from tty4(user id: ): "adding IP: 54.239.25.200 to ACL: status: Success" 64 | *Jul 12 11:44:11.398: %SYS-5-USERLOG_NOTICE: Message from tty4(user id: ): "adding IP: 54.239.25.192 to ACL: status: Success" 65 | *Jul 12 11:44:11.407: %SYS-5-USERLOG_NOTICE: Message from tty4(user id: ): "reschedule in : 5 seconds: status: Success" 66 | *Jul 12 11:44:17.431: %SYS-5-USERLOG_NOTICE: Message from tty5(user id: ): "Looking up cisco.com" 67 | *Jul 12 11:44:17.509: %SYS-5-USERLOG_NOTICE: Message from tty5(user id: ): "Looking up amazon.com" 68 | *Jul 12 11:44:17.605: %SYS-5-USERLOG_NOTICE: Message from tty5(user id: ): "reschedule in : 56 seconds: status: Success" 69 | ``` 70 | 71 | The script will use an Embedded Event Manager (EEM) script countdown timer to trigger the next time it runs. This is based on the TTL of the 72 | dns response. This is an example of the EEM configuration that is added into IOS. The script remembers the arguments and the 73 | path from which it was invoked. 74 | 75 | ```buildoutcfg 76 | 9300#show run | sec even 77 | event manager applet DNS_update 78 | event timer countdown time 57 79 | action 1.0 cli command "enable" 80 | action 1.1 cli command "guestshell run python /bootflash/DNS_update.py cisco.com amazon.com" 81 | ``` 82 | 83 | If you want to change the domains that are being monitored, you should re-run the script using the guestshell command. 84 | The EEM config will be overwritten with a new countdown and correct command line arguments. 85 | 86 | 87 | ## fqdn-poller 88 | is a slightly different version of the script. It was written by Vicente De Luca. 89 | It uses Object groups (which are not supported on switches) 90 | and derives the domains to poll based on the name of the object group. 91 | The IP address are then added to the object group. 92 | 93 | It is also a more industrialised version as it uses threading to run multiple requests in parallel. 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /src/dns-update/fqdn-poller: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | import time 4 | import subprocess 5 | import requests 6 | import json 7 | from dns import reversename, resolver 8 | from dns.resolver import Resolver 9 | from dns.resolver import NXDOMAIN 10 | from dns.resolver import NoAnswer 11 | from dns.resolver import NoNameservers 12 | from dns.exception import Timeout 13 | from cli import cli 14 | from cli import configure 15 | from multiprocessing.dummy import Pool as ThreadPool 16 | 17 | ''' 18 | This code is executed as a one-shot script and takes no parameters. 19 | It fetches all object-groups containing the description field 20 | starting with FQDN: and resolves to the 21 | corresponding IPs, re-scheduling the next iteration at the min(TTL) 22 | 23 | Note: IOS-XE does not allow creation of empty object-groups, so adding 24 | new FQDN based objects must be done as the following: 25 | 26 | object-group network ACME-WWW 27 | host 127.0.0.1 28 | description FQDN:www.acme.com 29 | 30 | After first iteration 127.0.0.1 will be replaced by the resolved IP(s) 31 | ''' 32 | 33 | class config(): 34 | DEBUG_LOG = False 35 | MAX_THREADS = 8 36 | MIN_TTL = 30 37 | RESOLVERS = [ 38 | "8.8.8.8", 39 | "8.8.4.4" 40 | ] 41 | 42 | 43 | def log(message, severity): 44 | cli('send log %d "%s"' % (severity, message)) 45 | 46 | 47 | def dns_lookup_Parallel(object_groups, threads=config.MAX_THREADS): 48 | pool = ThreadPool(threads) 49 | results = pool.map(dns_lookup, object_groups) 50 | pool.close() 51 | pool.join() 52 | return results 53 | 54 | 55 | def dns_lookup(object_group): 56 | fqdn = objects[object_group]["fqdn"] 57 | if config.DEBUG_LOG: 58 | log("Looking up %s" % fqdn, 5) 59 | 60 | try: 61 | answers = resolver.query(fqdn, 'A') 62 | objects[object_group]["resolved"] = [answer.address for answer in answers] 63 | objects[object_group]["ttl"] = find_ttl(fqdn) 64 | 65 | pass 66 | 67 | except NXDOMAIN: 68 | pass 69 | except NoAnswer: 70 | pass 71 | except NoNameservers: 72 | pass 73 | except Timeout: 74 | pass 75 | 76 | def find_ttl(zone): 77 | resolver = Resolver() 78 | tokens = zone.split(".") 79 | 80 | # Find Authoritative DNS Servers 81 | while True: 82 | if not tokens: 83 | break 84 | 85 | try: 86 | answers = resolver.query(".".join(tokens), 'NS') 87 | authoritatives = [resolver.query(str(answer))[0].address for answer in answers] 88 | if authoritatives: 89 | break 90 | 91 | except NXDOMAIN: 92 | pass 93 | except NoAnswer: 94 | pass 95 | except NoNameservers: 96 | pass 97 | except Timeout: 98 | pass 99 | 100 | tokens.pop(0) 101 | 102 | 103 | # Query TTL against authoritatives 104 | resolver.nameservers = authoritatives 105 | try: 106 | ttl = resolver.query(zone, 'A').rrset.ttl 107 | # Return zone TTL 108 | return ttl 109 | 110 | except NXDOMAIN: 111 | pass 112 | except NoAnswer: 113 | pass 114 | except NoNameservers: 115 | pass 116 | except Timeout: 117 | pass 118 | 119 | # Return default TTL in case not found 120 | return config.MIN_TTL 121 | 122 | 123 | def restconf(method,resource): 124 | # can't use because REST API returns a single element instead of list of all elements 125 | # in a future when Cisco fixes it'll be preferred over the current object_group_lookup() parser 126 | 127 | self_ip = cli("show ip interface GigabitEthernet 0").split("\n")[2].split(" ")[-1].split("/")[0] 128 | rest_url = "https://%s/restconf/data/Cisco-IOS-XE-native:native/Cisco-IOS-XE-native:" % (SELF_IP) 129 | headers = { 130 | 'accept': "application/yang-data+json" 131 | } 132 | query = {"with-defaults":"report-all"} 133 | query = {"depth":"4"} 134 | response = requests.request(method, rest_url + resource, params=query, headers=headers, verify=False, auth=('admin', 'ldaldalda111')) 135 | return json.loads(response.text) 136 | 137 | 138 | def object_group_lookup(): 139 | text = cli("show object-group").splitlines() 140 | object_groups = {} 141 | 142 | for i in range(len(text)): 143 | line = text[i] 144 | if "Description FQDN:" in line: 145 | fqdn = line.split("Description FQDN:")[1] 146 | name = text[i-1].split("Network object group ")[1] 147 | 148 | object_groups[name] = { 149 | "fqdn": fqdn, 150 | "hosts": [] 151 | } 152 | 153 | try: 154 | for x in xrange(16): 155 | cur_line = text[i+x+1] 156 | 157 | if "host" in cur_line: 158 | object_groups[name]["hosts"].append(cur_line.split("host ")[1]) 159 | 160 | if "Network" in cur_line: 161 | break 162 | 163 | if "object" in cur_line: 164 | break 165 | 166 | if cur_line == "": 167 | break 168 | 169 | except: 170 | continue 171 | 172 | return object_groups 173 | 174 | 175 | def object_group_configure(object_group,ip,action): 176 | OBJECT_GROUP_ADD = """ 177 | object-group network %s 178 | host %s 179 | """ 180 | 181 | OBJECT_GROUP_DEL = """ 182 | object-group network %s 183 | no host %s 184 | """ 185 | localtime = time.asctime(time.localtime(time.time())) 186 | remark = "Added %s @%s" % (ip,localtime) 187 | 188 | if action == 'add': 189 | responses = configure(OBJECT_GROUP_ADD % (object_group,ip)) 190 | 191 | if action == 'del': 192 | responses = configure(OBJECT_GROUP_DEL % (object_group,ip)) 193 | 194 | success = reduce (lambda x, y : x and y, [r.success for r in responses]) 195 | status = "Success" if success else "Fail" 196 | log("RESOLVER(%s): %s IP: %s - status: %s" % (object_group, action, ip, status), 5) 197 | 198 | 199 | def reschedule(seconds): 200 | UPDATE_SCRIPT_FIRING_COMMANDS = """ 201 | event manager applet FQDN-POLLER 202 | event timer watchdog time %s 203 | action 1.0 cli command "enable" 204 | action 1.1 cli command "guestshell run /home/guestshell/fqdn-poller 205 | """ 206 | responses = configure(UPDATE_SCRIPT_FIRING_COMMANDS % (seconds)) 207 | success = reduce(lambda x, y: x and y, [r.success for r in responses]) 208 | status = "Success" if success else "Fail" 209 | if config.DEBUG_LOG: 210 | log("reschedule in : %s seconds: status: %s" % (str(seconds), status), 5) 211 | 212 | 213 | 214 | def main(): 215 | # Fix guestshell resolvers 216 | p = subprocess.Popen("printf \"nameserver %s\nnameserver %s\n\" | sudo tee /etc/resolv.conf" % (config.RESOLVERS[0],config.RESOLVERS[1]), shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 217 | 218 | # Fetch and resolve FQDN object groups 219 | global objects 220 | objects = object_group_lookup() 221 | 222 | dns_lookup_Parallel(list(objects)) 223 | 224 | for item in objects: 225 | 226 | # Diff resolved hosts vs. current object-group hosts 227 | include = list(set(objects[item]["resolved"]) - set(objects[item]["hosts"])) 228 | exclude = list(set(objects[item]["hosts"]) - set(objects[item]["resolved"])) 229 | 230 | # Synchronize config 231 | for ip in include: 232 | object_group_configure(item,ip,"add") 233 | 234 | for ip in exclude: 235 | object_group_configure(item,ip,"del") 236 | 237 | # Polling interval should consider the minimum original TTL 238 | ttl = objects[item]["ttl"] 239 | 240 | min_ttl = config.MIN_TTL 241 | 242 | if ttl < min_ttl >= 5: 243 | min_ttl = ttl + 1 244 | 245 | reschedule(min_ttl) 246 | 247 | 248 | if __name__ == "__main__": 249 | main() 250 | -------------------------------------------------------------------------------- /src/eem-policy/config_check.py: -------------------------------------------------------------------------------- 1 | ::cisco::eem::event_register_syslog pattern "CONFIG_I" maxrun 60 2 | # this is an example of an EEM policy trigger 3 | # based of Joe Clarke version 4 | # https://github.com/CiscoDevNet/python_code_samples_network/blob/master/eem_configdiff_to_spark/sl_config_diff_to_spark.py 5 | # think there is a permissions isssue in ios. need to create directory in IOs, but then cannot update it with git 6 | # need to copy the script to flash: 7 | ''' 8 | !need to have the following config in IOS 9 | event manager directory user policy flash: 10 | event manager directory user policy flash:gs_script/src/eem-policy 11 | 12 | do guestshell run bootflash:gs_script/utils/update_git.sh 13 | 14 | do copy flash:gs_script/src/eem-policy/config_check.py flash: 15 | 16 | Y 17 | no event manager policy config_check.py 18 | event manager policy config_check.py 19 | ''' 20 | 21 | import eem 22 | import os 23 | import sys 24 | from cli import cli 25 | import re 26 | sys.path.append("/flash/gs_script/src") 27 | from utils.spark_utils import getRoomId, postMessage 28 | 29 | # cli version 30 | BACKUP = "flash:run.last" 31 | # python version 32 | PY_BACKUP = "/flash/run.last" 33 | 34 | def logSpark(message): 35 | sparktoken = os.environ.get("SPARKTOKEN") 36 | if sparktoken is not None: 37 | roomId = getRoomId("Sanity", sparktoken) 38 | postMessage(message, roomId, sparktoken) 39 | 40 | def sanity(): 41 | ''' 42 | Sanity check to see if device is working properly. quick check of an IP Address, but could be much more 43 | :return: 44 | ''' 45 | # will return the following if shutdown managment IP address 46 | # '\n% VRF Mgmt-vrf does not have a usable source address\n' 47 | # result = cli('ping vrf Mgmt-vrf ip 10.10.10.100') 48 | result = cli('ping 1.1.1.1') 49 | eem.action_syslog('Sanity' + result, priority=3) 50 | return False if '0/5' in result else True 51 | 52 | def create_backup(): 53 | result = cli('copy run {bak}'.format(bak=BACKUP)) 54 | if not "copied " in result: 55 | raise IOError('Cannot create backup {}'.format(BACKUP)) 56 | 57 | def get_diff(): 58 | # first time there will be no backup to compare to 59 | if os.path.exists(PY_BACKUP): 60 | diffs = cli('show archive config diff {bak} system:running-config'.format(bak=BACKUP)) 61 | if 'No changes were found' in diffs: 62 | eem.action_syslog('No changes',priority=5) 63 | return 64 | diff_lines = re.split(r'\r?\n', diffs) 65 | msg = 'Configuration differences between the running config and last backup:\n' 66 | msg += '``` {} \n```'.format('\n'.join(diff_lines[:-1])) 67 | logSpark(msg) 68 | create_backup() 69 | 70 | 71 | def main(): 72 | #eem.action_syslog("config changed") 73 | #logSpark('config changed') 74 | if not sanity() and os.path.exists(PY_BACKUP): 75 | result = cli('configure replace {bak} force'.format(bak=BACKUP)) 76 | eem.action_syslog(result, priority=3) 77 | sys.exit(0) 78 | try: 79 | get_diff() 80 | except IOError as e: 81 | eem.action_syslog(e, priority=3) 82 | 83 | if __name__ == "__main__": 84 | main() -------------------------------------------------------------------------------- /src/pci-tool/README.md: -------------------------------------------------------------------------------- 1 | #pci-check 2 | 3 | This is a PCI-compliance use case. The desired outcome is that any switch ports that have been inactive for > 7 days should be 4 | administratively shutdown. 5 | 6 | This script will check the interface status on all switch ports. Those that have not been active for > 7 days 7 | and are not administratively disabled, will be administratively shutdown. 8 | 9 | This script can be installed to run 15 mins past the hour with the following Embedded Event Manager (EEM) 10 | configuration on your switch 11 | 12 | ```buildoutcfg 13 | event manager applet PCI-check 14 |  event timer cron cron-entry "15 * * * 1-5" 15 |  action 1.0 cli command "enable" 16 |  action 1.1 cli command "guestshell run python bootflash:gs_script/src/pci-tool/pci_check.py --apply" 17 | 18 | ``` 19 | 20 | The script can be run in "test" mode by leaving off the "--apply" flag 21 | 22 | The script will log to syslog on box as well as Cisco SparkRoom. In order to send to a sparkroom, you need to 23 | define the name of the room and insert a SPARKTOKEN into the environment variables in the guest shell 24 | 25 | A simple way of making this persist is to edit the file "~/.bashrc" and add the following line: 26 | 27 | ```buildoutcfg 28 | export SPARKTOKEN="Bearer XXXXXXXXXXX 29 | ``` 30 | To install required libraries (you need -E if you have proxy settings in your environment variables): 31 | 32 | ```buildoutcfg 33 | sudo -E pip install dnspython 34 | 35 | ``` 36 | -------------------------------------------------------------------------------- /src/pci-tool/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aradford123/on-box-python/f487e798bdc9b665bfaf7e298214eb2d13018919/src/pci-tool/__init__.py -------------------------------------------------------------------------------- /src/pci-tool/pci_check.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from __future__ import print_function 3 | import jtextfsm as textfsm 4 | from cli import cli, configure 5 | import re 6 | import time 7 | from argparse import ArgumentParser 8 | import sys 9 | import os 10 | #change path to allow import from parent directory 11 | sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir)) 12 | from utils.spark_utils import getRoomId, postMessage 13 | 14 | 15 | SPARKROOM = "PCI" 16 | def is_idle_value(string): 17 | ''' 18 | looks at the string to determine idle state. never is also a valid entry. 19 | :param string: 20 | :return: 21 | ''' 22 | # can be 'never' or 00:00:00 or 7d23h etc 23 | match = re.match(r'([n]+)d.*|(never)', string) 24 | if match: 25 | if int(match.group(1) >= 7) or string == "never": 26 | return True 27 | else: 28 | return False 29 | 30 | 31 | def is_idle(input_time, output_time): 32 | ''' 33 | decided if the interface has been idle for more than seven days. 34 | :param input_time: 35 | :param output_time: 36 | :return: 37 | ''' 38 | return is_idle_value(input_time) and is_idle_value(output_time) 39 | 40 | def log(message, severity): 41 | print(message) 42 | cli('send log %d "%s"' % (severity, message)) 43 | 44 | def spark(message): 45 | ''' 46 | If there is a spark token in the environment, send a message to Cisco Spark 47 | :param message: 48 | :return: 49 | ''' 50 | sparktoken = os.environ.get("SPARKTOKEN") 51 | if sparktoken is not None: 52 | roomId = getRoomId(SPARKROOM, sparktoken) 53 | postMessage('\n```\n' + message +'\n```', roomId, sparktoken) 54 | 55 | def apply_commands(commands): 56 | response = configure(commands) 57 | for r in response: 58 | log(r.__str__(), 5) 59 | if len(response) > 1: 60 | spark('\n'.join([r.__str__() for r in response])) 61 | 62 | def process(re_table, apply_change): 63 | exec_commands = [] 64 | re_table.Reset() 65 | 66 | output = cli("show int | inc Last in|line pro") 67 | #print (output) 68 | localtime = time.asctime(time.localtime(time.time())) 69 | description = "description PCIShutdown: %s" % localtime 70 | fsm_results = re_table.ParseText(output) 71 | for interface in fsm_results: 72 | # skip non Ethernet 73 | if is_idle(interface[2], interface[3]) and ("Ethernet" in interface[0]): 74 | if interface[1] != "administratively": 75 | if apply_change: 76 | exec_commands.extend(['interface ' + interface[0], description, 'shutdown']) 77 | else: 78 | print("(testmode) would have SHUT %s (%s,%s)" % (interface[0], interface[2], interface[3])) 79 | 80 | print('Commands to run:') 81 | print(exec_commands) 82 | apply_commands(exec_commands) 83 | 84 | 85 | if __name__ == "__main__": 86 | parser = ArgumentParser(description='Select PCI-TOOL args: reads a list of devices and credentials from STDIN') 87 | parser.add_argument('-a', '--apply', action='store_true', 88 | help="Apply commands to device. no longer run in testing mode.") 89 | 90 | args = parser.parse_args() 91 | template = open("/flash/gs_script/src/pci-tool/show_int.textfsm") 92 | re_table = textfsm.TextFSM(template) 93 | process(re_table, args.apply) -------------------------------------------------------------------------------- /src/pci-tool/show_int.textfsm: -------------------------------------------------------------------------------- 1 | Value Interface (\S*) 2 | Value Admin (\S*) 3 | Value Input (\S*) 4 | Value Output (\S*) 5 | 6 | Start 7 | ^${Interface} is ${Admin} 8 | ^\s*Last input ${Input}, output ${Output}, -> Record 9 | -------------------------------------------------------------------------------- /src/requirements.txt: -------------------------------------------------------------------------------- 1 | dnspython 2 | jtextfsm -------------------------------------------------------------------------------- /src/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aradford123/on-box-python/f487e798bdc9b665bfaf7e298214eb2d13018919/src/utils/__init__.py -------------------------------------------------------------------------------- /src/utils/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aradford123/on-box-python/f487e798bdc9b665bfaf7e298214eb2d13018919/src/utils/__init__.pyc -------------------------------------------------------------------------------- /src/utils/spark_utils.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from cli import cli 3 | SPARK_API='https://api.ciscospark.com/v1/' 4 | 5 | 6 | def getRoomId(roomName, token): 7 | ''' 8 | finds a roomId from the name. Raises exceptions if the REST API call fails or the room is not found 9 | :param roomName: 10 | :param token: 11 | :return: 12 | ''' 13 | 14 | url = SPARK_API + 'rooms' 15 | HEADERS = { 16 | 'authorization': token, 17 | 'content-type': 'application/json' 18 | } 19 | 20 | r = requests.request('GET', url, headers=HEADERS) 21 | r.raise_for_status() 22 | 23 | 24 | room_id = None 25 | for room in r.json()['items']: 26 | if room['title'] == roomName: 27 | room_id = room['id'] 28 | break 29 | if room_id is None: 30 | raise ValueError("no spark room {} found".format(roomName)) 31 | return room_id 32 | 33 | def postMessage(message, roomId, token): 34 | ''' 35 | sends a message to the room. Uses the "hostname" of the device to indicate where the message originated 36 | :param message: 37 | :param roomId: 38 | :param token: 39 | :return: 40 | ''' 41 | try: 42 | hostname = cli("show run | inc hostname").split()[1] 43 | except IndexError: 44 | raise ValueError("Cannot get device hostname") 45 | 46 | url = SPARK_API + 'messages' 47 | HEADERS = { 48 | 'authorization': token, 49 | 'content-type': 'application/json' 50 | } 51 | payload = {'roomId': roomId, 'markdown': '**{host}:** {mess}'.format(host=hostname, mess=message)} 52 | print payload 53 | r = requests.request( 54 | 'POST', url, json=payload, headers=HEADERS) 55 | r.raise_for_status() 56 | -------------------------------------------------------------------------------- /src/utils/spark_utils.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aradford123/on-box-python/f487e798bdc9b665bfaf7e298214eb2d13018919/src/utils/spark_utils.pyc -------------------------------------------------------------------------------- /utils/update_git.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | (cd /flash/gs_script; git pull) 3 | --------------------------------------------------------------------------------