1+ #!/usr/bin/env python
2+
3+ """
4+ @author Casey Boettcher
5+ @date 2017-09-26
6+
7+ This script accepts an ip address or addresses, on the command line or via newline-delimited file, and calls the ipr
8+ method of IBM's X-Force API to retrieve data describing the address's reputation.
9+
10+ """
11+
12+ import sys
13+ import re
114import pprint
215import argparse
316import requests
417import IPy
518
619XFORCE_API_BASE = 'https://api.xforce.ibmcloud.com'
720XFORCE_API_IP_REP = 'ipr'
21+ XFORCE_CRED_PATTERN = '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'
822
923
1024def parse_args ():
11- parser = argparse .ArgumentParser ()
12- parser .add_argument ('api_authN_file' , type = argparse .FileType ('r' ),
25+ """
26+ Parse the command line.
27+
28+ :return: a Namespace object of parsed arguments
29+ """
30+ parser = argparse .ArgumentParser (description = "Use the X-Force API to check IP address reputation." )
31+ parser .add_argument ('-o' , '--out' , metavar = 'output_file' ,
32+ type = argparse .FileType ('w' ), help = "Write result of X-Force call to file." )
33+ parser .add_argument ('authN' , type = argparse .FileType ('r' ),
1334 help = 'Path to a file containing your X-Force credentials, key and password on first and second '
1435 'lines, respectively.' )
15- parser .add_argument ('address' , nargs = '?' , metavar = 'ip_address' , help = 'An IP address to be checked via X-Force. If '
16- 'the IP address is omitted or invalid, the '
17- 'user will be prompted for one.' )
36+
37+ # user should not be able to specify both IP on cmdline and in a file
38+ ip_group = parser .add_mutually_exclusive_group ()
39+ # TODO: nargs='N' and loop through list
40+ ip_group .add_argument ('-i' , '--ip' , nargs = '?' , metavar = 'ip_address' , help = 'An IP address to be checked via '
41+ 'X-Force. If the IP address is omitted or invalid, the user will be prompted for one.' )
42+ ip_group .add_argument ('-I' , '--Ips' , type = argparse .FileType ('r' ), metavar = 'file_of_ip_addresses' ,
43+ help = 'A file containing IP addresses, one per line.' )
1844 return parser .parse_args ()
1945
2046
2147def request_valid_ip ():
48+ """
49+ Prompts the user for a valid IP, then validates it, returning None if invalid
50+
51+ :return:
52+ a valid IP or None if the user supplied an invalid or private address
53+ """
2254 ip = input ("Enter a valid IP address you would like to check: " )
2355 return validate_ip (ip )
2456
2557
2658def validate_ip (ip ):
59+ """
60+ Validate an address using IPy
61+
62+ :param ip: a string representation of an IP address
63+ :return: return None if IP is invalid or within a private network range
64+ """
2765 try :
2866 ipobj = IPy .IP (ip )
2967 if ipobj .iptype () == 'PRIVATE' :
30- print ("IP addresses should not be in private network ranges" )
68+ print ("IP addresses {} will be ignored as it is in a private network range." . format ( ip ) )
3169 ip = None
3270 except ValueError as ve :
3371 print ("Invalid IP: {}" .format (ve .args ))
@@ -36,33 +74,117 @@ def validate_ip(ip):
3674 return ip
3775
3876
77+ def read_in_address_file (file ):
78+ """
79+ Reads a file of IP addresses and returns only those that are valid in a list
80+
81+ :param file: a plaintext file of IP addresses, one per line
82+ :return address_list: a list of valid IP addresses
83+ """
84+ address_list = list ()
85+ lines = 0
86+ valid_ips = 0
87+ with file as f :
88+ for n in file :
89+ lines += 1
90+ if validate_ip (n .strip ()):
91+ address_list .append (n .strip ())
92+ valid_ips += 1
93+ if valid_ips < lines :
94+ print ("Of the {} lines in the file you supplied, only {} were valid. The latter will be used to call the "
95+ "API." .format (lines , valid_ips ))
96+ if valid_ips == 0 :
97+ print ("Please supply a valid IP address." )
98+ address_list = None
99+ return address_list
100+
101+
39102def read_in_xforce_keys (file ):
103+ """
104+ Read a plaintext file of two lines and return X-Force credentials in the form of a tuple, validating general form
105+ of the key and password in the process
106+
107+ :param file: a two-line plaintext files; the first line contains the X-Force API key and the second the password
108+ :return: a tuple of (key, password)
109+ """
110+ matcher = re .compile (XFORCE_CRED_PATTERN )
40111 for x in range (0 , 2 ):
41112 if x == 0 :
42113 key = file .readline ().strip ()
114+ if not matcher .match (key ):
115+ print ("API key invalid. Exiting..." )
116+ sys .exit (1 )
43117 if x == 1 :
44118 password = file .readline ().strip ()
119+ if not matcher .match (password ):
120+ print ("API password invalid. Exiting..." )
121+ sys .exit (1 )
45122 return key , password
46123
47124
48- def main ():
49- ip = None
50- args = parse_args ()
125+ def call_xforce_api (address_list , key , password ):
126+ """
127+ Call the ipr method of the X-Force API using the IP address(es) contained in the parameter. Results are written
128+ to a file or stdout (default).
129+
130+ :param address_list: a list of IP addresses
131+ :return: a list of json objects
132+ """
133+ results = []
134+ for a in address_list :
135+ url = "{}/{}/{}" .format (XFORCE_API_BASE , XFORCE_API_IP_REP , a )
136+ results .append (requests .get (url , auth = (key , password )).json ())
137+ return results
138+
139+
140+ def print_json_stdout (results ):
141+ """
142+ Print a list of json objects to the console
143+
144+ :param results: a list of json objects
145+ """
146+ for json in results :
147+ print ("\n ########## Result for IP {} ##########" .format (json ['ip' ]))
148+ pprint .pprint (json )
149+ print ('######################################' )
150+ print ()
151+
51152
52- # get user-supplied IP address
53- if args .address :
54- ip = validate_ip (args .address )
55- while not ip :
56- ip = request_valid_ip ()
153+ def print_json_file (results , file ):
154+ """
155+ Print a list of json objects to the console
57156
58- url = "{}/{}/{}" .format (XFORCE_API_BASE , XFORCE_API_IP_REP , ip )
157+ :param results: a list of json objects
158+ :param file: the destination file for the printed list of json objects passed in
159+ """
160+ print ("Writing results to file..." )
161+ for json in results :
162+ file .write ("\n ########## Result for IP {} ##########\n " .format (json ['ip' ]))
163+ pprint .pprint (json , stream = file )
164+ file .write ('######################################\n ' )
59165
60- # get X-Force API keys
61- creds = read_in_xforce_keys (args .api_authN_file )
62- result = requests .get (url , auth = (creds [0 ], creds [1 ]))
63- # maybe user swapped key and password in api creds file?
64- # if result.status_code == '401':
65- pprint .pprint (result .json ())
166+
167+ def main ():
168+
169+ ip = None
170+ addresses = list ()
171+ args = parse_args ()
172+ if args .Ips :
173+ addresses = read_in_address_file (args .Ips )
174+ else :
175+ # get user-supplied IP address from the cmd line
176+ if args .ip :
177+ ip = validate_ip (args .ip )
178+ # prompt user for valid IP in case of typo on cmdline
179+ while not ip :
180+ ip = request_valid_ip ()
181+ addresses .append (ip )
182+ creds = read_in_xforce_keys (args .authN )
183+ results = call_xforce_api (addresses , creds [0 ], creds [1 ])
184+ if args .out :
185+ print_json_file (results , args .out )
186+ else :
187+ print_json_stdout (results )
66188
67189 return 0
68190
0 commit comments