File tree

3 files changed

+165
-22
lines changed

3 files changed

+165
-22
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Created by .ignore support plugin (hsz.mobi)
22
.idea
33
out
4+
*.out
45
gen
5-
xforce.cfg
6+
test/xforce.cfg
67

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
31.5.39.53
2+
115.178.71.53
3+
58.76.176.146
4+
133.225.250.244
5+
230.207.999.12
6+
c69f:6c5:754c:d301:df05:ba81:76a8:ddc4
7+
cc85:e78:948c:2ad6:5959:102d:10cb:31c4
8+
0.0.0.0
9+
247.101.183.224
10+
127.0.0.1
11+
22.50.127.57
12+
212.159.206.7
13+
165.143.14.64
14+
7939:a348:460d:966f:a986:d0ba:1e9a:c67e
15+
dbb1:39b7:1e8f:1a2a:3737:9721:5d16:166
16+
131.isle.12.9
17+
1d67:bd71:56d9:13f3:5499:25b:cc84:f7e4
18+
f4bc:bab1:4d39:9c0b:8797:a867:e6ce:6629
19+
193.131.176.37
20+
239.244.72.99
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,71 @@
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
114
import pprint
215
import argparse
316
import requests
417
import IPy
518

619
XFORCE_API_BASE = 'https://api.xforce.ibmcloud.com'
720
XFORCE_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

1024
def 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

2147
def 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

2658
def 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+
39102
def 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

Comments
 (0)