Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
7a2b17e
Merge remote-tracking branch 'remotes/origin/dev-first-party-mode' in…
alexbpx Nov 24, 2018
9b850e5
Merge remote-tracking branch 'remotes/origin/dev' into dev-refactor-c…
alexbpx Nov 24, 2018
1159bbb
Fxied errors
alexbpx Nov 24, 2018
8e2a9d4
Config refactoring
alexbpx Nov 24, 2018
36e5e6e
Config
alexbpx Nov 25, 2018
b65dc14
merging confligcts
alexbpx Nov 25, 2018
c811d14
Deleted Notes
alexbpx Nov 25, 2018
12e2efb
Feactored static files check
alexbpx Nov 25, 2018
47f4b0e
Refactored literals
alexbpx Nov 25, 2018
694ce36
Block activity
alexbpx Nov 25, 2018
e0f9092
Block action added to block acitivity
alexbpx Nov 25, 2018
a89913a
Removed socket_ip from context
alexbpx Nov 25, 2018
4ca2ee2
property
alexbpx Nov 25, 2018
c69dd9e
Telemetry
alexbpx Nov 25, 2018
0385fe5
Whitelist and sensitive route capabilities
alexbpx Nov 25, 2018
2417a14
Merge branch 'dev-refactor-config' into dev-custom-params-enforcer-tel
alexbpx Nov 25, 2018
07c26f9
Changes
alexbpx Nov 26, 2018
63a43b5
px_blocker tests
alexbpx Nov 26, 2018
b685b68
px_utils tests
alexbpx Nov 26, 2018
2e58395
px_validator tests
alexbpx Nov 26, 2018
63332dd
px_validator tests
alexbpx Nov 26, 2018
4c8f7ae
Fixed a few files after running automation
alexbpx Nov 27, 2018
1897868
Merge remote-tracking branch 'remotes/origin/dev' into dev-custom-par…
alexbpx Nov 27, 2018
8b04726
Fixed unittests
alexbpx Nov 27, 2018
35efd5a
Fxied j challenge response
alexbpx Nov 27, 2018
d89edaf
Added px_proxy tests
alexbpx Nov 27, 2018
c0abed6
Fxied building process of telemetry config
alexbpx Nov 27, 2018
6a694ff
Changed telemtry uri
alexbpx Nov 27, 2018
009e13c
Empty tests
alexbpx Nov 27, 2018
7581c84
Empty tests
alexbpx Nov 27, 2018
8b9922a
Fxied captcha loop
alexbpx Nov 28, 2018
febc41d
Fixed captcha loops
alexbpx Nov 28, 2018
757745e
fixed unittests
alexbpx Nov 28, 2018
d827064
Fixed spacing
alexbpx Nov 29, 2018
1e08684
Added custom params - I have no idea where it went
alexbpx Nov 29, 2018
51aebe5
Removed printing of exception in httpc exception handling
alexbpx Nov 29, 2018
666ad59
fixed api_timeout to work with seconds
alexbpx Nov 29, 2018
fedd704
Moved the transformation of timeoit to the getter
alexbpx Nov 29, 2018
0335e20
Moved the transformation of timeoit to the getter
alexbpx Nov 29, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 36 additions & 27 deletions perimeterx/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import px_api
import px_constants
import px_utils
from px_proxy import PXProxy
from perimeterx.px_proxy_handler import PXProxy
from px_config import PXConfig


Expand All @@ -29,11 +29,10 @@ def __init__(self, app, config=None):
logger.error('PX Cookie Key is missing')
raise ValueError('PX Cookie Key is missing')
self.reverse_proxy_prefix = px_config.app_id[2:].lower()
if px_config.custom_request_handler:
self.handle_verification = px_config.custom_request_handler.__get__(self, PerimeterX)
self._PXBlocker = px_blocker.PXBlocker()
self._config = px_config
px_httpc.init(px_config)
px_activities_client.init_activities_configuration(px_config)
px_activities_client.send_enforcer_telemetry_activity(config=px_config, update_reason='initial_config')

def __call__(self, environ, start_response):
return self._verify(environ, start_response)
Expand All @@ -46,7 +45,7 @@ def _verify(self, environ, start_response):
uri = ctx.get('uri')
px_proxy = PXProxy(config)
if px_proxy.should_reverse_request(uri):
return px_proxy.handle_reverse_request(self.config, ctx, start_response)
return px_proxy.handle_reverse_request(self.config, ctx, start_response, environ)
if px_utils.is_static_file(ctx):
logger.debug('Filter static file request. uri: ' + uri)
return self.app(environ, start_response)
Expand All @@ -63,33 +62,46 @@ def _verify(self, environ, start_response):
# Server-to-Server verification fallback
if not px_api.verify(ctx, self.config):
return self.app(environ, start_response)
if config.custom_request_handler:
return config.custom_request_handler(ctx, self.config, environ, start_response)
return self.handle_verification(ctx, self.config, environ, start_response)
except:
logger.error("Cought exception, passing request")
self.pass_traffic(environ, start_response, ctx)
self.pass_traffic(ctx)
return self.app(environ, start_response)

def handle_verification(self, ctx, config, environ, start_response):
score = ctx.get('risk_score', -1)

score = ctx.get('score', -1)
result = None
headers = None
status = None
pass_request = True
if score < config.blocking_score:
return self.pass_traffic(environ, start_response, ctx)

if config.custom_block_handler:
px_activities_client.send_block_activity(ctx, config)
return config.custom_block_handler(ctx, start_response)
elif config.module_mode == px_constants.MODULE_MODE_BLOCKING:
return self.px_blocker.handle_blocking(ctx=ctx, config=config, start_response=start_response)
self.pass_traffic(ctx)
else:
return self.pass_traffic(environ, start_response, ctx)
pass_request = False
self.block_traffic(ctx)

if config.additional_activity_handler:
config.additional_activity_handler(ctx, config)

if config.module_mode == px_constants.MODULE_MODE_BLOCKING and result is None and not pass_request:
result, headers, status = self.px_blocker.handle_blocking(ctx=ctx, config=config)
if config.custom_request_handler:
custom_body, custom_headers, custom_status = config.custom_request_handler(ctx, self.config, environ)
if (custom_body is not None):
start_response(custom_status, custom_headers)
return custom_body

if headers is not None:
start_response(status, headers)
return result
else:
return self.app(environ, start_response)

def pass_traffic(self, ctx):
px_activities_client.send_page_requested_activity( ctx, self.config)

def pass_traffic(self, environ, start_response, ctx):
details = {}
if ctx.get('decoded_cookie', ''):
details = {"px_cookie": ctx['decoded_cookie']}
px_activities_client.send_to_perimeterx(px_constants.PAGE_REQUESTED_ACTIVITY, ctx, self.config, details)
return self.app(environ, start_response)
def block_traffic(self, ctx):
px_activities_client.send_block_activity(ctx, self.config)

@property
def config(self):
Expand All @@ -98,6 +110,3 @@ def config(self):
@property
def px_blocker(self):
return self._PXBlocker



56 changes: 46 additions & 10 deletions perimeterx/px_activities_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,41 @@
import threading
import traceback, sys
import px_constants
import socket
import json

ACTIVITIES_BUFFER = []
CONFIG = {}

def init_activities_configuration(config):
global CONFIG
CONFIG = config
t1 = threading.Thread(target=send_activities)
t1.daemon = True
t1.start()

def send_activities():
global ACTIVITIES_BUFFER
default_headers = {
'Authorization': 'Bearer ' + CONFIG.auth_token,
'Content-Type': 'application/json'
}
full_url = CONFIG.server_host + px_constants.API_ACTIVITIES
while True:
if len(ACTIVITIES_BUFFER) > 0:
chunk = ACTIVITIES_BUFFER[:10]
ACTIVITIES_BUFFER = ACTIVITIES_BUFFER[10:]
px_httpc.send('/api/v1/collector/s2s', chunk, CONFIG)
px_httpc.send(full_url=full_url, body=json.dumps(chunk), headers=default_headers, config=CONFIG, method='POST')
time.sleep(1)


t1 = threading.Thread(target=send_activities)
t1.daemon = True
t1.start()


def send_to_perimeterx(activity_type, ctx, config, detail):
global CONFIG
try:
if activity_type == 'page_requested' and not config.send_page_activities:
print 'Page activities disabled in config - skipping.'
return

if not CONFIG:
CONFIG = config

_details = {
'http_method': ctx.get('http_method', ''),
'http_version': ctx.get('http_version', ''),
Expand Down Expand Up @@ -72,5 +77,36 @@ def send_block_activity(ctx, config):
#'cookie_origin':,
'block_action': ctx.get('block_action', ''),
'module_version': px_constants.MODULE_VERSION,
'simulated_block': config.monitor_mode is 0,
'simulated_block': config.module_mode is px_constants.MODULE_MODE_MONITORING,
})

def send_page_requested_activity(ctx, config):
details = {}
if ctx.get('decoded_cookie', ''):
details = {"px_cookie": ctx['decoded_cookie']}
send_to_perimeterx(px_constants.PAGE_REQUESTED_ACTIVITY, ctx, config, details)

def send_enforcer_telemetry_activity(config, update_reason):
details = {
'enforcer_configs': config.telemetry_config,
'node_name': socket.gethostname(),
'os_name': sys.platform,
'update_reason': update_reason,
'module_version': config.module_version
}
body = {
'type': px_constants.TELEMETRY_ACTIVITY,
'timestamp': time.time(),
'px_app_id': config.app_id,
'details': details
}
headers = {
'Authorization': 'Bearer ' + config.auth_token,
'Content-Type': 'application/json'
}
config.logger.debug('Sending telemetry activity to PerimeterX servers')
px_httpc.send(full_url=config.server_host + px_constants.API_ENFORCER_TELEMETRY, body=json.dumps(body),
headers=headers, config=config, method='POST')



50 changes: 37 additions & 13 deletions perimeterx/px_api.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,30 @@
import sys
import px_httpc
import time
import px_constants
import json
import re


custom_params = {
'custom_param1': '',
'custom_param2': '',
'custom_param3': '',
'custom_param4': '',
'custom_param5': '',
'custom_param6': '',
'custom_param7': '',
'custom_param8': '',
'custom_param9': '',
'custom_param10': ''
}

def send_risk_request(ctx, config):
body = prepare_risk_body(ctx, config)
return px_httpc.send(px_constants.API_RISK, body, config)

default_headers = {
'Authorization': 'Bearer ' + config.auth_token,
'Content-Type': 'application/json'
}
response = px_httpc.send(full_url=config.server_host + px_constants.API_RISK,body=json.dumps(body),config=config, headers=default_headers,method='POST')
return json.loads(response.content)

def verify(ctx, config):
logger = config.logger
Expand All @@ -26,15 +42,16 @@ def verify(ctx, config):
ctx['block_action'] = response['action']
ctx['risk_rtt'] = risk_rtt
if score >= config.blocking_score:
logger.debug("PXVerify block score threshold reached, will initiate blocking")
ctx['block_reason'] = 's2s_high_score'
elif response['action'] is 'j' and response.get('action_data') is not None and response.get('action_data').get('body') is not None:
logger.debug("PXVerify received javascript challenge action")
ctx['block_action_data'] = response.get('action_data').get('body')
ctx['block_reason'] = 'challenge'
elif response['action'] is 'r':
logger.debug("PXVerify received javascript ratelimit action")
ctx['block_reason'] = 'exceeded_rate_limit'
if response['action'] == 'j' and response.get('action_data') is not None and response.get('action_data').get('body') is not None:
logger.debug("PXVerify received javascript challenge action")
ctx['block_action_data'] = response.get('action_data').get('body')
ctx['block_reason'] = 'challenge'
elif response['action'] is 'r':
logger.debug("PXVerify received javascript ratelimit action")
ctx['block_reason'] = 'exceeded_rate_limit'
else:
logger.debug("PXVerify block score threshold reached, will initiate blocking")
ctx['block_reason'] = 's2s_high_score'
else:
ctx['pass_reason'] = 's2s'

Expand Down Expand Up @@ -70,6 +87,13 @@ def prepare_risk_body(ctx, config):
}
}

if config.enrich_custom_parameters:
risk_custom_params = config.enrich_custom_parameters(custom_params)
for param in risk_custom_params:
if re.match('^custom_param\d$',param) and risk_custom_params[param]:
body['additional'][param] = risk_custom_params[param]


if ctx['s2s_call_reason'] == 'cookie_decryption_failed':
logger.debug('attaching orig_cookie to request')
body['additional']['px_cookie_orig'] = ctx.get('px_orig_cookie')
Expand Down
9 changes: 4 additions & 5 deletions perimeterx/px_blocker.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def __init__(self):
self.ratelimit_rendered_page = self.mustache_renderer.render(
px_template.get_template(px_constants.RATELIMIT_TEMPLATE), {})

def handle_blocking(self, ctx, config, start_response):
def handle_blocking(self, ctx, config):
action = ctx.get('block_action')
status = '403 Forbidden'

Expand All @@ -31,13 +31,12 @@ def handle_blocking(self, ctx, config, start_response):
blocking_props = self.prepare_properties(ctx, config)
blocking_response = self.mustache_renderer.render(px_template.get_template(px_constants.BLOCK_TEMPLATE),
blocking_props)
start_response(status, headers)
if is_json_response:
blocking_response = json.dumps(blocking_props)
return str(blocking_response)
return str(blocking_response), headers, status

def prepare_properties(self, ctx, config):
app_id = config.app_id.lower()
app_id = config.app_id
vid = ctx.get('vid') if ctx.get('vid') is not None else ''
uuid = ctx.get('uuid')
custom_logo = config.custom_logo
Expand Down Expand Up @@ -65,7 +64,7 @@ def prepare_properties(self, ctx, config):
'logoVisibility': 'visible' if custom_logo is not None else 'hidden',
'hostUrl': host_url,
'jsClientSrc': js_client_src,
'firstPartyEnabled': config.first_party,
'firstPartyEnabled': 'true' if config.first_party else 'false',
'blockScript': captcha_src
}

Expand Down
Loading