A small script with a big name. How to send several commands to some devices if you do not know exact credential pair? This script is responding to this challenge.
$ python crawler.py -h
usage: crawler.py [-h] (-d DEVICE_FILE | -l DEVICE_LIST) -c CREDS_FILE -r
COMMAND_FILE [-t CONNECT_THREADS] [-p PING_PROCESS]
[--ping | --no-ping] [--debug | --no-debug]
[--brief | --no-brief]
optional arguments:
-h, --help show this help message and exit
-d DEVICE_FILE Path to device file
-l DEVICE_LIST List of IP addresses (ex. "10.10.1.2, 10.10.1.3")
-c CREDS_FILE Path to file with credentials
-r COMMAND_FILE Path to file with comamnds list to be executed
-t CONNECT_THREADS The amount of simultanious SSH connections (30 by
default)
-p PING_PROCESS The amount of ping processes (30 by default)
--ping Enable ping test (default)
--no-ping Skip ping test
--debug Enable debug.yml
--no-debug Disable debug.yml (default)
--brief Enable brief output with summary information
--no-brief Returning output of commands per device (default)
├── crawler_modules.py # All functions are stored here
├── crawler.py # The script itself
├── data # Folder with all supporting files
│ ├── commands # List of commands to be executed on every device
│ ├── creds.yml # YAML file with credentials
│ └── devices # Device list
└── debug.yml # Debug file which is generated by --debug argument
data/commands
username user1 secret user1
data/creds.yml
usernames:
- melhiour
- user1
- user2
passwords:
- password1
- password2
- melhiour
data/devices
192.168.30.1
192.168.30.2
192.168.30.3
192.168.30.4
192.168.30.5
192.168.30.6
192.168.30.7
192.168.30.8
debug.yml is a dictionary with the following keys and values
{'ARGS': provided arguments,
'PINGED_IPS': result of ping check,
'DEVICES': provided device list or the result of file parsing,
'RESULT': result of the script execution (list of dictionaries {IP:OUTPUT},
'TIME': [Start time, end time]}
Try to exhaust the list of credentials data/creds.yml on all devices from file data/devices and run commands specified in data/commands
python crawler.py -d data/devices -c data/creds.yml -r data/commads
Specify the list of devices insted of file.
python crawler.py -l "192.168.0.1, 192.168.0.2" -c data/creds.yml -r data/commads
Same as above but excluding ping check.
python crawler.py -l "192.168.0.1, 192.168.0.2" -c data/creds.yml -r data/commads --no-ping
Brief output instead of full output table.
python crawler.py -l "192.168.0.1, 192.168.0.2" -c data/creds.yml -r data/commads --no-ping --brief
Creating debug.yml file with some usefull information (could be easily parsed)
python crawler.py -l "192.168.0.1, 192.168.0.2" -c data/creds.yml -r data/commads --no-ping --brief
$ python crawler.py -l 192.168.0.1,192.168.30.3,192.168.30.27 -c data/creds.yml -r data/commands
+-------------------+----------------------+
| "Network crawler" | @2019-01-19 23:19:46 |
+-------------------+----------------------+
DONE | Arguments parsed and validated
DONE | Processing devices from provided list "['192.168.0.1', '192.168.30.3', '192.168.30.27']"
DONE | Pinging devices...
INFO | There are 2 alive devices noticed... Processing...
WARN | These devices are dead: ['192.168.0.1']
DONE | Connecting to devices and sending commands...
INFO | The following commands have been sent
╒═══════════════╤═══════════════════════════════════════════════════════════════╕
│ IP │ OUTPUT │
╞═══════════════╪═══════════════════════════════════════════════════════════════╡
│ 192.168.30.3 │ config term │
│ │ Enter configuration commands, one per line. End with CNTL/Z. │
│ │ R3(config)#username user priv 15 secret secret │
│ │ R3(config)#end │
│ │ R3# │
├───────────────┼───────────────────────────────────────────────────────────────┤
│ 192.168.30.27 │ config term │
│ │ Enter configuration commands, one per line. End with CNTL/Z. │
│ │ R27(config)#username user priv 15 secret secret │
│ │ R27(config)#end │
│ │ R27# │
╘═══════════════╧═══════════════════════════════════════════════════════════════╛
INFO | Showing statistics
STATUS COUNT
----------- -------
Succeeded 2
Unreachable 1
Execution time: 0:02:58.232124
$ python crawler.py -l 192.168.0.1,192.168.30.3,192.168.30.27 -c data/creds.yml -r data/commands --brief
+-------------------+----------------------+
| "Network crawler" | @2019-01-19 23:16:10 |
+-------------------+----------------------+
DONE | Arguments parsed and validated
DONE | Processing devices from provided list "['192.168.0.1', '192.168.30.3', '192.168.30.27']"
DONE | Pinging devices...
INFO | There are 2 alive devices noticed... Processing...
WARN | These devices are dead: ['192.168.0.1']
DONE | Connecting to devices and sending commands...
INFO | Showing summary information
============= ===========
IP STATUS
============= ===========
192.168.0.1 Unreachable
192.168.30.3 Succeeded
192.168.30.27 Succeeded
============= ===========
INFO | Showing statistics
STATUS COUNT
----------- -------
Unreachable 1
Succeeded 2
Execution time: 0:02:56.778946
- python3
- pyyaml # For file parsing
- tabulate # For output printing
- netmiko # For connecting to devices
- halo # For damn good spinners...
It's not that bad, but it's not very quick either. The performance mostly depends on the amount of credentials to check. Currently, script uses multiprocessing for device connection. It's "parallelized" by devices (not credential pairs). Script starts to gently "brute force" some devices simultaneously trying to use credentials in one-by-one manner on each device. Why? Because the script has been designed for old IOS devices which have around 4 vty lines... Probably, I will add that feature latter, but not sure.
Here are some metrics for 50 devices, 4 usernames, 4 passwords
STATUS COUNT
----------- -------
Timeout 2
Unreachable 1
Succeeded 47
Execution time: 0:04:50.479534
- Only Cisco IOS devices are currently supported.
- Only configuration mode commands are accepted. Show commands will be rejected by device if "do" keyword is not specified.