Skip to content

Commit c76b9fb

Browse files
author
Johnny Tordgeman
authored
Merge pull request #12 from PerimeterX/dev
Version 1.1.0
2 parents 61ce9bb + f517c82 commit c76b9fb

14 files changed

+361
-40
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](http://keepachangelog.com/)
66
and this project adheres to [Semantic Versioning](http://semver.org/).
77

8+
## [1.1.0] - 2020-09-02
9+
### Added
10+
- Support for `monitored_specific_routes`
11+
- Support for Regex patters in sensitive/whitelist/monitored/enforced routes
12+
813
## [1.0.1] - 2019-11-10
914
### Fixed
1015
- Using hashlib pbkdf2 implementation.

README.md

Lines changed: 99 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
[![Build Status](https://travis-ci.org/PerimeterX/perimeterx-python-3-wsgi.svg?branch=master)](https://travis-ci.org/PerimeterX/perimeterx-python-3-wsgi)
22
[![Known Vulnerabilities](https://snyk.io/test/github/PerimeterX/perimeterx-python-3-wsgi/badge.svg)](https://snyk.io/test/github/PerimeterX/perimeterx-python-3-wsgi)
33

4-
![image](https://s.perimeterx.net/logo.png)
4+
![image](https://storage.googleapis.com/perimeterx-logos/primary_logo_red_cropped.png)
55

66
[PerimeterX](http://www.perimeterx.com) Python 3 Middleware
77
=============================================================
8-
> Latest stable version: [v1.0.1](https://pypi.org/project/perimeterx-python-3-wsgi/)
8+
> Latest stable version: [v1.1.0](https://pypi.org/project/perimeterx-python-3-wsgi/)
99
1010
Table of Contents
1111
-----------------
@@ -20,15 +20,20 @@ Table of Contents
2020
* [Send Page Activities](#send_page_activities)
2121
* [Debug Mode](#debug_mode)
2222
* [Sensitive Routes](#sensitive_routes)
23+
* [Sensitive Routes Regex](#sensitive_routes_regex)
2324
* [Whitelist Routes](#whitelist_routes)
25+
* [Whitelist Routes Regex](#whitelist_routes_regex)
26+
* [Enforce Specific Routes](#enforce_specific_routes)
27+
* [Enforce Specific Routes Regex](#enforce_specific_routes_regex)
28+
* [Monitor Specific Routes](#monitor_specific_routes)
29+
* [Monitor Specific Routes Regex](#monitor_specific_routes_regex)
2430
* [Sensitive Headers](#sensitive_headers)
2531
* [IP Headers](#ip_headers)
2632
* [First-Party Enabled](#first_party_enabled)
2733
* [Custom Request Handler](#custom_request_handler)
2834
* [Additional Activity Handler](#additional_activity_handler)
2935
* [Px Disable Request](#px_disable_request)
3036
* [Test Block Flow on Monitoring Mode](#bypass_monitor_header)
31-
* [Enforce Specific Routes](#enforce_specific_routes)
3237

3338
## <a name="installation"></a> Installation
3439

@@ -59,10 +64,10 @@ px_config = {
5964
application = get_wsgi_application()
6065
application = PerimeterX(application, px_config)
6166
```
62-
- The PerimeterX **Application ID** / **AppId** and PerimeterX **Token** / **Auth Token** can be found in the Portal, in [Applications](https://console.perimeterx.com/#/app/applicationsmgmt).
63-
- PerimeterX **Risk Cookie** / **Cookie Key** can be found in the portal, in [Policies](https://console.perimeterx.com/#/app/policiesmgmt).
67+
- The PerimeterX **Application ID** / **AppId** and PerimeterX **Token** / **Auth Token** can be found in the Portal, in [Applications](https://console.perimeterx.com/botDefender/admin?page=applicationsmgmt).
68+
- PerimeterX **Risk Cookie** / **Cookie Key** can be found in the portal, in [Policies](https://console.perimeterx.com/botDefender/admin?page=policiesmgmt).
6469
The Policy from where the **Risk Cookie** / **Cookie Key** is taken must correspond with the Application from where the **Application ID** / **AppId** and PerimeterX **Token** / **Auth Token**.
65-
For details on how to create a custom Captcha page, refer to the [documentation](https://console.perimeterx.com/docs/server_integration_new.html#custom-captcha-section)
70+
For details on how to create a custom Captcha page, refer to the [documentation](https://docs.perimeterx.com/pxconsole/docs/customize-challenge-page)
6671

6772
## <a name="configuration"></a>Optional Configuration
6873
In addition to the basic installation configuration [above](#required_config), the following configurations options are available:
@@ -152,6 +157,21 @@ config = {
152157
...
153158
}
154159
```
160+
161+
#### <a name="sensitive_routes_regex"></a> Sensitive Routes Regex
162+
163+
An array of regex patterns that trigger a server call to PerimeterX servers every time the page is viewed, regardless of viewing history.
164+
165+
**Default:** Empty
166+
167+
```python
168+
config = {
169+
...
170+
sensitive_routes_regex: [r'^/login$', r'^/user']
171+
...
172+
}
173+
```
174+
155175
#### <a name="whitelist_routes"></a> Whitelist Routes
156176

157177
An array of route prefixes which will bypass enforcement (will never get scored).
@@ -166,6 +186,78 @@ config = {
166186
}
167187
```
168188

189+
#### <a name="whitelist_routes_regex"></a> Whitelist Routes Regex
190+
191+
An array of regex patterns which will bypass enforcement (will never get scored).
192+
193+
**Default:** Empty
194+
195+
```python
196+
config = {
197+
...
198+
whitelist_routes_regex: [r'^/about']
199+
...
200+
}
201+
```
202+
203+
#### <a name="enforce_specific_routes"></a> Enforce Specific Routes
204+
205+
An array of route prefixes that are always validated by the PerimeterX Worker (as opposed to whitelisted routes).
206+
When this property is set, any route which is not added - will be whitelisted.
207+
208+
**Default:** Empty
209+
210+
```python
211+
config = {
212+
...
213+
enforced_specific_routes: ['/profile']
214+
...
215+
};
216+
```
217+
218+
#### <a name="enforce_specific_routes_regex"></a> Enforce Specific Routes Regex
219+
220+
An array of regex patterns that are always validated by the PerimeterX Worker (as opposed to whitelisted routes).
221+
When this property is set, any route which is not added - will be whitelisted.
222+
223+
**Default:** Empty
224+
225+
```python
226+
config = {
227+
...
228+
enforced_specific_routes_regex: [r'^/profile$']
229+
...
230+
};
231+
```
232+
233+
#### <a name="monitor_specific_routes"></a> Monitor Specific Routes
234+
235+
An array of route prefixes that are always set to be in [monitor mode](#module_mode). This configuration is effective only when the module is enabled and in blocking mode.
236+
237+
**Default:** Empty
238+
239+
```python
240+
config = {
241+
...
242+
monitored_specific_routes: ['/profile']
243+
...
244+
};
245+
```
246+
247+
#### <a name="monitor_specific_routes_regex"></a> Monitor Specific Routes Regex
248+
249+
An array of regex patterns that are always set to be in [monitor mode](#module_mode). This configuration is effective only when the module is enabled and in blocking mode.
250+
251+
**Default:** Empty
252+
253+
```python
254+
config = {
255+
...
256+
monitored_specific_routes_regex: [r'^/profile/me$']
257+
...
258+
};
259+
```
260+
169261
#### <a name="sensitive_headers"></a>Sensitive Headers
170262

171263
An array of headers that are not sent to PerimeterX servers on API calls.
@@ -279,18 +371,4 @@ config = {
279371
bypass_monitor_header: 'x-px-block',
280372
...
281373
}
282-
```
283-
284-
#### <a name="enforce_specific_routes"></a> Enforced Specific Routes
285-
286-
An array of route prefixes that are always validated by the PerimeterX Worker (as opposed to whitelisted routes).
287-
When this property is set, any route which is not added - will be whitelisted.
288-
289-
**Default:** Empty
290-
```python
291-
config = {
292-
...
293-
enforced_specific_routes: ['/profile']
294-
...
295-
};
296-
```
374+
```

dev-requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
mock==3.0.5
22
requests_mock==1.7.0
3-
pylint==2.4.2
3+
pylint==2.5.0

perimeterx/middleware.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ def __init__(self, app, config=None):
3232
self._config = px_config
3333
self._request_verifier = PxRequestVerifier(px_config)
3434
px_activities_client.init_activities_configuration(px_config)
35-
px_activities_client.send_enforcer_telemetry_activity(config=px_config, update_reason='initial_config')
3635

3736
def __call__(self, environ, start_response):
3837
px_activities_client.send_activities_in_thread()
@@ -53,7 +52,7 @@ def __call__(self, environ, start_response):
5352
return verified_response(environ, pxhd_callback)
5453

5554
except Exception as err:
56-
self._config.logger.error("Caught exception, passing request1111. Exception: {}".format(err))
55+
self._config.logger.error("Caught exception, passing request. Exception: {}".format(err))
5756
if context:
5857
self.report_pass_traffic(context)
5958
else:
@@ -63,7 +62,7 @@ def __call__(self, environ, start_response):
6362
def verify(self, request):
6463
config = self.config
6564
logger = config.logger
66-
logger.debug('Starting request verification')
65+
logger.debug("Starting request verification {}".format(request.path))
6766
ctx = None
6867
try:
6968
if not config._module_enabled:
@@ -72,7 +71,7 @@ def verify(self, request):
7271
ctx = PxContext(request, config)
7372
return ctx, self._request_verifier.verify_request(ctx, request)
7473
except Exception as err:
75-
logger.error("Caught exception222, passing request. Exception: {}".format(err))
74+
logger.error("Caught exception in verify, passing request. Exception: {}".format(err))
7675
if ctx:
7776
self.report_pass_traffic(ctx)
7877
else:

perimeterx/px_activities_client.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ def send_to_perimeterx(activity_type, ctx, config, detail):
4747
'http_method': ctx.http_method,
4848
'http_version': ctx.http_version,
4949
'module_version': config.module_version,
50-
'risk_mode': config.module_mode,
5150
}
5251

5352
if len(detail.keys()) > 0:
@@ -84,7 +83,7 @@ def send_block_activity(ctx, config):
8483
'risk_rtt': ctx.risk_rtt,
8584
'cookie_origin': ctx.cookie_origin,
8685
'block_action': ctx.block_action,
87-
'simulated_block': config.module_mode is px_constants.MODULE_MODE_MONITORING,
86+
'simulated_block': config.module_mode is px_constants.MODULE_MODE_MONITORING or ctx.monitored_route,
8887
})
8988

9089

perimeterx/px_api.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ def verify(ctx, config):
112112

113113
def prepare_risk_body(ctx, config):
114114
logger = config.logger
115+
risk_mode = 'monitor' if config.module_mode == px_constants.MODULE_MODE_MONITORING or ctx.monitored_route else 'active_blocking'
115116
body = {
116117
'request': {
117118
'ip': ctx.ip,
@@ -125,7 +126,7 @@ def prepare_risk_body(ctx, config):
125126
'http_method': ctx.http_method,
126127
'http_version': ctx.http_version,
127128
'module_version': config.module_version,
128-
'risk_mode': config.module_mode,
129+
'risk_mode': risk_mode,
129130
'cookie_origin': ctx.cookie_origin
130131
}
131132
}

perimeterx/px_config.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,31 @@ def __init__(self, config_dict):
5454
raise TypeError('enforced_specific_routes must be a list')
5555
self._enforced_specific_routes = enforced_routes
5656

57+
monitored_routes = config_dict.get('monitored_specific_routes', [])
58+
if not isinstance(monitored_routes, list):
59+
raise TypeError('monitored_specific_routes must be a list')
60+
self._monitored_specific_routes = monitored_routes
61+
62+
sensitive_routes_regex = config_dict.get('sensitive_routes_regex', [])
63+
if not isinstance(sensitive_routes_regex, list):
64+
raise TypeError('sensitive_routes_regex must be a list')
65+
self._sensitive_routes_regex = sensitive_routes_regex
66+
67+
whitelist_routes_regex = config_dict.get('whitelist_routes_regex', [])
68+
if not isinstance(whitelist_routes_regex, list):
69+
raise TypeError('whitelist_routes_regex must be a list')
70+
self._whitelist_routes_regex = whitelist_routes_regex
71+
72+
enforced_routes_regex = config_dict.get('enforced_specific_routes_regex', [])
73+
if not isinstance(enforced_routes_regex, list):
74+
raise TypeError('enforced_specific_routes must be a list')
75+
self._enforced_specific_routes_regex = enforced_routes_regex
76+
77+
monitored_routes_regex = config_dict.get('monitored_specific_routes_regex', [])
78+
if not isinstance(monitored_routes_regex, list):
79+
raise TypeError('monitored_specific_routes_regex must be a list')
80+
self._monitored_specific_routes_regex = monitored_routes_regex
81+
5782
self._block_html = 'BLOCK'
5883
self._logo_visibility = 'visible' if custom_logo is not None else 'hidden'
5984
self._telemetry_config = self.__create_telemetry_config()
@@ -165,6 +190,14 @@ def sensitive_routes(self):
165190
def whitelist_routes(self):
166191
return self._whitelist_routes
167192

193+
@property
194+
def sensitive_routes_regex(self):
195+
return self._sensitive_routes_regex
196+
197+
@property
198+
def whitelist_routes_regex(self):
199+
return self._whitelist_routes_regex
200+
168201
@property
169202
def block_html(self):
170203
return self._block_html
@@ -205,6 +238,18 @@ def bypass_monitor_header(self):
205238
def enforced_specific_routes(self):
206239
return self._enforced_specific_routes
207240

241+
@property
242+
def monitored_specific_routes(self):
243+
return self._monitored_specific_routes
244+
245+
@property
246+
def enforced_specific_routes_regex(self):
247+
return self._enforced_specific_routes_regex
248+
249+
@property
250+
def monitored_specific_routes_regex(self):
251+
return self._monitored_specific_routes_regex
252+
208253
def __instantiate_user_defined_handlers(self, config_dict):
209254
self._custom_request_handler = self.__set_handler('custom_request_handler', config_dict)
210255
self._get_user_ip = self.__set_handler('get_user_ip', config_dict)

perimeterx/px_constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
EMPTY_GIF_B64 = 'R0lGODlhAQABAPAAAAAAAAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=='
2828
COLLECTOR_HOST = 'collector.perimeterx.net'
2929
FIRST_PARTY_FORWARDED_FOR = 'X-FORWARDED-FOR'
30-
MODULE_VERSION = 'Python 3 WSGI Module v1.0.1'
30+
MODULE_VERSION = 'Python 3 WSGI Module v1.1.0'
3131
API_RISK = '/api/v3/risk'
3232
PAGE_REQUESTED_ACTIVITY = 'page_requested'
3333
BLOCK_ACTIVITY = 'block'

perimeterx/px_context.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
import re
2+
13
from requests.structures import CaseInsensitiveDict
24

35
from perimeterx.px_constants import *
46
from perimeterx.px_data_enrichment_cookie import PxDataEnrichmentCookie
57

6-
78
class PxContext(object):
89

910
def __init__(self, request, config):
@@ -49,9 +50,10 @@ def __init__(self, request, config):
4950
uri = request.path
5051
full_url = request.url
5152
hostname = request.host
52-
sensitive_route = sum(1 for _ in filter(lambda sensitive_route_item: uri.startswith(sensitive_route_item), config.sensitive_routes)) > 0
53-
whitelist_route = sum(1 for _ in filter(lambda whitelist_route_item: uri.startswith(whitelist_route_item), config.whitelist_routes)) > 0
54-
enforced_route = sum(1 for _ in filter(lambda enforced_route_item: uri.startswith(enforced_route_item), config.enforced_specific_routes)) > 0
53+
sensitive_route = sum(1 for _ in filter(lambda sensitive_route_item: re.search(sensitive_route_item, uri), config.sensitive_routes_regex)) > 0 or sum(1 for _ in filter(lambda sensitive_route_item: uri.startswith(sensitive_route_item), config.sensitive_routes)) > 0
54+
whitelist_route = sum(1 for _ in filter(lambda whitelist_route_item: re.search(whitelist_route_item, uri), config.whitelist_routes_regex)) > 0 or sum(1 for _ in filter(lambda whitelist_route_item: uri.startswith(whitelist_route_item), config.whitelist_routes)) > 0
55+
enforced_route = sum(1 for _ in filter(lambda enforced_route_item: re.search(enforced_route_item, uri), config.enforced_specific_routes_regex)) > 0 or sum(1 for _ in filter(lambda enforced_route_item: uri.startswith(enforced_route_item), config.enforced_specific_routes)) > 0
56+
monitored_route = not enforced_route and (sum(1 for _ in filter(lambda monitored_route_item: re.search(monitored_route_item, uri), config.monitored_specific_routes_regex)) > 0 or sum(1 for _ in filter(lambda monitored_route_item: uri.startswith(monitored_route_item), config.monitored_specific_routes)) > 0)
5557

5658
protocol_split = request.environ.get('SERVER_PROTOCOL', '').split('/')
5759
if protocol_split[0].startswith('HTTP'):
@@ -78,6 +80,7 @@ def __init__(self, request, config):
7880
self._sensitive_route = sensitive_route
7981
self._whitelist_route = whitelist_route
8082
self._enforced_route = enforced_route
83+
self._monitored_route = monitored_route
8184
self._s2s_call_reason = 'none'
8285
self._cookie_origin = cookie_origin
8386
self._is_mobile = cookie_origin == "header"
@@ -259,6 +262,14 @@ def whitelist_route(self):
259262
def whitelist_route(self, whitelist_route):
260263
self._whitelist_route = whitelist_route
261264

265+
@property
266+
def monitored_route(self):
267+
return self._monitored_route
268+
269+
@monitored_route.setter
270+
def monitored_route(self, monitored_route):
271+
self._monitored_route = monitored_route
272+
262273
@property
263274
def s2s_call_reason(self):
264275
return self._s2s_call_reason

perimeterx/px_cookie.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ def decrypt_cookie(self):
6464
if iterations < 1 or iterations > 10000:
6565
return False
6666
data = base64.b64decode(parts[2])
67-
dk = hashlib.pbkdf2_hmac(hash_name='sha256', password=config.cookie_key.encode(), salt=salt, iterations=iterations, dklen=48)
67+
dk = hashlib.pbkdf2_hmac(hash_name='sha256', password=self._config.cookie_key.encode(), salt=salt, iterations=iterations, dklen=48)
6868
key = dk[:32]
6969
iv = dk[32:]
7070
cipher = AES.new(key, AES.MODE_CBC, iv)

0 commit comments

Comments
 (0)