Skip to content
This repository was archived by the owner on Apr 24, 2019. It is now read-only.

Commit 9b34aac

Browse files
authored
Merge pull request #15 from deep-security/integration-v2.1
Integration v2.1.1 of deep-security-py
2 parents c46a191 + 4070ca1 commit 9b34aac

10 files changed

+889
-261
lines changed

lib/core.py

+7-4
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,12 @@
1010
# project libraries
1111
import lib.deepsecurity as deepsecurity
1212

13-
def get_arg_parser(prog='ds-to-aws-waf.py', description=None, add_help=False):
13+
def get_arg_parser(prog='ds-cli.py', description=None, add_help=False):
1414
"""
1515
Create a standardized argument parser
1616
"""
1717
if not description:
18-
description = """
19-
Create and update AWS WAF WACL rules based on information from a Deep Security installation
20-
"""
18+
description = "A command line interface to Trend Micro's Deep Security"
2119

2220
parser = argparse.ArgumentParser(prog=prog, description=description, add_help=add_help)
2321

@@ -33,6 +31,11 @@ def get_arg_parser(prog='ds-to-aws-waf.py', description=None, add_help=False):
3331
parser.add_argument('-s', '--aws-secret-key', action='store', dest='aws_secret_key', required=False, help='The secret key for an IAM identity in the AWS account to connect to')
3432
parser.add_argument('-r', '--aws-region', action='store', dest='aws_region', required=False, default='us-east-1', help='The name of AWS region to connect to')
3533

34+
# Azure arguments
35+
parser.add_argument('--azure-subscription-id', action='store', dest='azure_subscription_id', required=False, help='The subscription ID of the Azure subscription to connect to')
36+
parser.add_argument('--azure-certificate', action='store', dest='azure_certificate', required=False, help='The certificate associated with the Azure subscription to connect to')
37+
parser.add_argument('--azure-certificate-password', action='store', dest='azure_certificate_password', required=False, default='us-east-1', help='The password associated with the specified Azure certificate')
38+
3639
# general structure arguments
3740
parser.add_argument('--ignore-ssl-validation', action='store_true', dest='ignore_ssl_validation', required=False, help='Ignore SSL certification validation. Be careful when you use this as it disables a recommended security check. Required for Deep Security Managers using a self-signed SSL certificate')
3841
parser.add_argument('--dryrun', action='store_true', required=False, help='Do a dry run of the command. This will not make any changes to your AWS WAF service')

lib/deepsecurity/__init__.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,6 @@
66
sys.path.append(current_path)
77

88
# import project files as required
9-
import dsm
9+
import dsm
10+
import translation
11+
translation.Terms.read_terms_file()

lib/deepsecurity/core.py

+133-10
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,12 @@ def _set_logging(self):
7676

7777
return logger
7878

79-
def _get_request_format(self, api=None, call=None):
79+
def _get_request_format(self, api=None, call=None, use_cookie_auth=False):
8080
if not api: api = self.API_TYPE_SOAP
8181
return {
8282
'api': api,
8383
'call': call,
84+
'use_cookie_auth': use_cookie_auth,
8485
'query': None,
8586
'data': None,
8687
}
@@ -108,6 +109,9 @@ def _request(self, request, auth_required=True):
108109
REST API calls this will be a dict converted to JSON automatically
109110
by this method
110111
112+
use_cookie_auth
113+
Whether or not to use an HTTP Cookie in lieu of a querystring for authorization
114+
111115
## Output
112116
113117
Returns a dict:
@@ -139,9 +143,9 @@ def _request(self, request, auth_required=True):
139143
# add the authentication parameters
140144
if auth_required:
141145
if request['api'] == self.API_TYPE_REST:
142-
# sID is a query string
143-
if not request['query']: request['query'] = {}
144-
request['query']['sID'] = self._sessions[self.API_TYPE_REST]
146+
if not request['use_cookie_auth']: # sID is a query string
147+
if not request['query']: request['query'] = {}
148+
request['query']['sID'] = self._sessions[self.API_TYPE_REST]
145149
elif request['api'] == self.API_TYPE_SOAP:
146150
# sID is part of the data
147151
if not request['data']: request['data'] = {}
@@ -182,6 +186,11 @@ def _request(self, request, auth_required=True):
182186

183187
# authentication calls don't accept the Accept header
184188
if request['call'].startswith('authentication'): del(headers['Accept'])
189+
190+
# some rest calls use a cookie to pass the sID
191+
if request['api'] == self.API_TYPE_REST and request['use_cookie_auth']:
192+
headers['Cookie'] = 'sID="{}"'.format(self._sessions[self.API_TYPE_REST])
193+
185194
if request['api'] == self.API_TYPE_REST and request['call'] in [
186195
'apiVersion',
187196
'status/manager/ping'
@@ -229,6 +238,7 @@ def _request(self, request, auth_required=True):
229238
result = {
230239
'status': response.getcode() if response else None,
231240
'raw': response.read() if response else None,
241+
'headers': dict(response.headers) if response else dict(),
232242
'data': None
233243
}
234244
bytes_of_data = len(result['raw']) if result['raw'] else 0
@@ -254,12 +264,14 @@ def _request(self, request, auth_required=True):
254264
else:
255265
# JSON response
256266
try:
257-
if result['raw']:
258-
result['data'] = json.loads(result['raw'])
267+
if result['raw'] and result['status'] != 204:
268+
result['type'] = result['headers']['content-type']
269+
result['data'] = json.loads(result['raw']) if 'json' in result['type'] else None
259270
except Exception, json_err:
260271
# report the exception as 'info' because it's not fatal and the data is
261272
# still captured in result['raw']
262273
self.log("Could not convert response from call {} to JSON. Threw exception:\n\t{}".format(request['call'], json_err), level='info')
274+
263275
return result
264276

265277
def _prefix_keys(self, prefix, d):
@@ -270,7 +282,7 @@ def _prefix_keys(self, prefix, d):
270282
if not type(d) == type({}): return d
271283
new_d = d.copy()
272284
for k,v in d.items():
273-
new_key = "{}:{}".format(prefix, k)
285+
new_key = u"{}:{}".format(prefix, k)
274286
new_v = v
275287
if type(v) == type({}): new_v = self._prefix_keys(prefix, v)
276288
new_d[new_key] = new_v
@@ -283,7 +295,7 @@ def _prep_data_for_soap(self, call, details):
283295
Prepare the complete XML SOAP envelope
284296
"""
285297
data = xmltodict.unparse(self._prefix_keys('ns1', { call: details }), pretty=False, full_document=False)
286-
soap_xml = """
298+
soap_xml = u"""
287299
<?xml version="1.0" encoding="UTF-8"?>
288300
<SOAP-ENV:Envelope xmlns:ns0="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="urn:Manager" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
289301
<SOAP-ENV:Header/>
@@ -293,7 +305,10 @@ def _prep_data_for_soap(self, call, details):
293305
</SOAP-ENV:Envelope>
294306
""".format(data).strip()
295307

296-
return soap_xml
308+
# convert any nil values to the proper format
309+
soap_xml = re.sub(r'<([^>]+)></\1>', r'<\1 xsi:nil="true" />', soap_xml)
310+
311+
return soap_xml.encode('utf-8')
297312

298313
def log(self, message='', err=None, level='info'):
299314
"""
@@ -432,4 +447,112 @@ def _set_properties(self, api_response, log_func):
432447
setattr(self, new_key, val)
433448
except Exception, err:
434449
if log_func:
435-
log_func("Could not set property {} to value {} for object {}".format(k, v, s))
450+
log_func("Could not set property {} to value {} for object {}".format(k, v, s))
451+
try:
452+
setattr(self, log, log_func)
453+
except: pass
454+
455+
def to_dict(self):
456+
"""
457+
Convert the object properties to API keypairs
458+
"""
459+
result = {}
460+
461+
api_properties = translation.Terms.api_to_new.values()
462+
463+
for p in dir(self):
464+
if p in api_properties:
465+
key = translation.Terms.get_reverse(p)
466+
val = getattr(self, p)
467+
result[key] = val
468+
469+
return result
470+
471+
class CoreList(list):
472+
def __init__(self, *args):
473+
super(CoreList, self).__init__(args)
474+
self._exempt_from_find = []
475+
476+
def find(self, **kwargs):
477+
"""
478+
Find any items where the values match the cumulative kwargs patterns and return their indices
479+
480+
If a keyword's value is a list, .find will match on any value for that keyword
481+
482+
.find(id=1)
483+
>>> returns any item with a property 'id' and value in [1]
484+
possibilities:
485+
{ 'id': 1, 'name': 'One'}
486+
{ 'id': 1, 'name': 'Two'}
487+
488+
.find(id=[1,2])
489+
>>> returns any item with a property 'id' and value in [1,2]
490+
possibilities:
491+
{ 'id': 1, 'name': 'One'}
492+
{ 'id': 2, 'name': 'One'}
493+
{ 'id': 1, 'name': 'Two'}
494+
{ 'id': 2, 'name': 'Two'}
495+
496+
.find(id=1, name='One')
497+
>>> returns any item with a property 'id' and value in [1] AND a property 'name' and value in ['One']
498+
possibilities:
499+
{ 'id': 1, 'name': 'One'}
500+
501+
.find(id=[1,2], name='One')
502+
>>> returns any item with a property 'id' and value in [1,2] AND a property 'name' and value in ['One']
503+
possibilities:
504+
{ 'id': 1, 'name': 'One'}
505+
{ 'id': 2, 'name': 'One'}
506+
507+
.find(id=[1,2], name=['One,Two'])
508+
>>> returns any item with a property 'id' and value in [1,2] AND a property 'name' and value in ['One','Two']
509+
possibilities:
510+
{ 'id': 1, 'name': 'One'}
511+
{ 'id': 2, 'name': 'One'}
512+
{ 'id': 1, 'name': 'Two'}
513+
{ 'id': 2, 'name': 'Two'}
514+
"""
515+
results = []
516+
517+
if kwargs:
518+
for item_id, item in enumerate(self):
519+
item_matches = False
520+
for match_attr, match_attr_vals in kwargs.items():
521+
if not type(match_attr_vals) == type([]): match_attr_vals = [match_attr_vals]
522+
523+
# does the current item have the property
524+
attr_to_check = None
525+
if match_attr in dir(item):
526+
attr_to_check = getattr(item, match_attr)
527+
elif 'has_key' in dir(item) and item.has_key(match_attr):
528+
attr_to_check = item[match_attr]
529+
530+
if attr_to_check:
531+
# does the property match the specified values?
532+
for match_attr_val in match_attr_vals:
533+
if type(attr_to_check) in [type(''), type(u'')]:
534+
# string comparison
535+
match = re.search(r'{}'.format(match_attr_val), attr_to_check)
536+
if match:
537+
item_matches = True
538+
break # and move on to the new kwarg
539+
else:
540+
item_matches = False
541+
elif type(attr_to_check) == type([]):
542+
# check for the match in the list
543+
if match_attr_val in attr_to_check:
544+
item_matches = True
545+
break # and move on to the new kwarg
546+
else:
547+
item_matches = False
548+
else:
549+
# object comparison
550+
if attr_to_check == match_attr_val:
551+
item_matches = True
552+
break # and move on to the new kwarg
553+
else:
554+
item_matches = False
555+
556+
if item_matches: results.append(item_id)
557+
558+
return results

lib/deepsecurity/dsm.py

+22-10
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,16 @@ def __init__(self,
1818
tenant=None,
1919
username=None,
2020
password=None,
21+
prefix="",
2122
ignore_ssl_validation=False
2223
):
2324
core.CoreApi.__init__(self)
2425
self._hostname = None
2526
self._port = port
26-
self._tenant = tenant
27-
self._username = username
28-
self._password = password
27+
self._tenant = unicode(tenant, "utf-8") if tenant else None # no harm in converting to ensure compatibility with non-latin tenant names
28+
self._username = unicode(username, "utf-8") if username else None
29+
self._password = unicode(password, "utf-8") if password else None
30+
self._prefix = prefix
2931
self.ignore_ssl_validation = ignore_ssl_validation
3032
self.hostname = hostname
3133

@@ -48,7 +50,8 @@ def __str__(self):
4850
"""
4951
Return a better string representation
5052
"""
51-
return "Manager <{}:{}>".format(self.hostname, self.port)
53+
dsm_port = ":{}".format(self.port) if self.port else ""
54+
return "Manager <{}{}>".format(self.hostname, dsm_port)
5255

5356
# *******************************************************************
5457
# properties
@@ -68,7 +71,7 @@ def port(self): return self._port
6871

6972
@port.setter
7073
def port(self, value):
71-
self._port = int(value)
74+
self._port = int(value) if value else None
7275
self._set_endpoints()
7376

7477
@property
@@ -95,15 +98,24 @@ def password(self, value):
9598
self._password = value
9699
self._reset_session()
97100

101+
@property
102+
def prefix(self): return self._prefix
103+
104+
@prefix.setter
105+
def prefix(self, value):
106+
if not value or not type(value) in [type(''), type(u'')]: value = ""
107+
self._prefix = value
108+
98109
# *******************************************************************
99110
# methods
100111
# *******************************************************************
101112
def _set_endpoints(self):
102113
"""
103114
Set the API endpoints based on the current configuration
104115
"""
105-
self._rest_api_endpoint = "https://{}:{}/rest".format(self.hostname, self.port)
106-
self._soap_api_endpoint = "https://{}:{}/webservice/Manager".format(self.hostname, self.port)
116+
dsm_port = ":{}".format(self.port) if self.port else "" # allow for endpoints with no port specified
117+
self._rest_api_endpoint = "https://{}{}/{}rest".format(self.hostname, dsm_port, self.prefix)
118+
self._soap_api_endpoint = "https://{}{}/{}webservice/Manager".format(self.hostname, dsm_port, self.prefix)
107119

108120
def _reset_session(self):
109121
"""
@@ -170,10 +182,10 @@ def sign_out(self):
170182
response = self._request(rest_call)
171183
if response and response['status'] == 200: self._sessions[self.API_TYPE_REST] = None
172184

173-
if self._sessions[self.API_TYPE_REST] and self._sessions[self.API_TYPE_SOAP]:
174-
return True
175-
else:
185+
if self._sessions[self.API_TYPE_REST] or self._sessions[self.API_TYPE_SOAP]:
176186
return False
187+
else:
188+
return True
177189

178190
def get_api_version(self):
179191
"""

lib/deepsecurity/environments.py

+13-8
Original file line numberDiff line numberDiff line change
@@ -31,20 +31,25 @@ def add_aws_account(self, name, aws_access_key=None, aws_secret_key=None, region
3131
responses = {}
3232

3333
regions = {
34-
'us-east-1': 'amazon.cloud.region.key.1',
35-
'us-west-1': 'amazon.cloud.region.key.2',
36-
'us-west-2': 'amazon.cloud.region.key.3',
37-
'eu-west-1': 'amazon.cloud.region.key.4',
38-
'ap-southeast-1': 'amazon.cloud.region.key.5',
39-
'ap-northeast-1': 'amazon.cloud.region.key.6',
40-
'sa-east-1': 'amazon.cloud.region.key.7',
34+
'us-east-1': 'amazon.cloud.region.key.1', # N. Virginia
35+
'us-west-1': 'amazon.cloud.region.key.2', # N. California
36+
'us-west-2': 'amazon.cloud.region.key.3', # Oregon
37+
'eu-west-1': 'amazon.cloud.region.key.4', # Ireland
38+
'ap-southeast-1': 'amazon.cloud.region.key.5', # Singapore
39+
'ap-northeast-1': 'amazon.cloud.region.key.6', # Tokyo
40+
'sa-east-1': 'amazon.cloud.region.key.7', # Sao Paulo
41+
'ap-southeast-2': 'amazon.cloud.region.key.8', # Sydney
42+
# need to add:
43+
# ap-south-1 / Mumbai
44+
# ap-northeast-2 / Seoul
45+
# eu-central-1 / Frankfurt
4146
}
4247

4348
regions_to_add = []
4449
if regions.has_key(region):
4550
regions_to_add.append(region)
4651
elif region == 'all':
47-
regions_to_add.append(regions.keys())
52+
regions_to_add = regions.keys()
4853
else:
4954
self.log("A region must be specified when add an AWS account to Deep Security")
5055

0 commit comments

Comments
 (0)