Skip to content

Commit fa39358

Browse files
authored
Merge pull request #325 from PerimeterX/release/v7.3.0
Release/v7.3.0
2 parents b135801 + 645a5a4 commit fa39358

11 files changed

+154
-36
lines changed

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,19 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
66
and this project adheres to [Semantic Versioning](http://semver.org/).
77

88

9+
## [7.3.0] - 2023-06-13
10+
### Added
11+
- CORS support
12+
- Set X-PX-COOKIES as the default custom cookie name
13+
- _M.px_login_creds_settings configuration, to allow specify CI settings in Lua configuration file
14+
15+
### Changed
16+
- rename "px_graphql_paths" to "px_graphql_routes"
17+
18+
### Fixed
19+
- correctly add GraphQL routes (requests must contain specified GraphQL Type/Name) to sensitive routes
20+
21+
922
## [7.2.1] - 2023-04-20
1023
### Added
1124
- `custom_sensitive_routes` a custom function to determine if url path is a sensitive route

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
# [PerimeterX](http://www.perimeterx.com) NGINX Lua Plugin
44

5-
> Latest stable version: [v7.2.1](https://luarocks.org/modules/bendpx/perimeterx-nginx-plugin/7.2.1-1)
5+
> Latest stable version: [v7.3.0](https://luarocks.org/modules/bendpx/perimeterx-nginx-plugin/7.3.0-1)
66
77
## [Introduction](#introduction)
88

lib/px/block/pxblock.lua

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,24 @@ function M.load(px_config)
5454
end
5555
end
5656

57+
local function append_cors_headers()
58+
if not px_config.px_cors_support_enabled then
59+
return
60+
end
61+
62+
if px_config.px_cors_create_custom_block_response_headers ~= nil then
63+
px_config.px_cors_create_custom_block_response_headers()
64+
else
65+
local origin_val = px_headers.get_header(px_constants.ORIGIN_HEADER_NAME)
66+
if origin_val ~= nil then
67+
ngx.header[px_constants.CORS_HEADER_KEY] = origin_val
68+
ngx.header[px_constants.CORS_ALLOW_CREDENTIALS_HEADER_KEY] = 'true'
69+
end
70+
end
71+
72+
73+
end
74+
5775
function _M.block(reason, creds, graphql, custom_params, jwt)
5876
local details = {}
5977
local ref_str = ''
@@ -137,6 +155,7 @@ function M.load(px_config)
137155
page = ngx.encode_base64(html),
138156
collectorUrl = collectorUrl
139157
}
158+
append_cors_headers()
140159
ngx.header["Content-Type"] = 'application/json'
141160
ngx.status = ngx_HTTP_FORBIDDEN
142161
ngx.say(cjson.encode(result))
@@ -160,13 +179,15 @@ function M.load(px_config)
160179
customLogo = px_config.customLogo,
161180
altBlockScript = props.altBlockScript
162181
}
182+
append_cors_headers()
163183
ngx.header["Content-Type"] = 'application/json'
164184
ngx.status = ngx_HTTP_FORBIDDEN
165185
ngx.say(cjson.encode(result))
166186
ngx_exit(ngx.OK)
167187
end
168188

169189
-- web scenarios
190+
append_cors_headers()
170191
ngx.header["Content-Type"] = 'text/html'
171192

172193
-- render advanced actions (js challange/rate limit)

lib/px/pxconfig.lua

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ _M.auth_token = 'PX_AUTH_TOKEN'
3232
-- _M.reverse_xhr_enabled = true
3333
-- _M.proxy_url = nil
3434
-- _M.proxy_authorization = nil
35-
-- _M.custom_cookie_header = nil
35+
-- _M.custom_cookie_header = 'X-PX-COOKIES'
3636
-- _M.bypass_monitor_header = nil
3737
-- _M.pxhd_secure_enabled = false
3838

@@ -83,6 +83,7 @@ _M.auth_token = 'PX_AUTH_TOKEN'
8383
-- ## Login Credentials extraction
8484
--_M.px_enable_login_creds_extraction = false
8585
--_M.px_login_creds_settings_filename = nil
86+
--_M.px_login_creds_settings = nil
8687
--_M.px_compromised_credentials_header_name = "px-compromised-credentials"
8788
--_M.px_login_successful_reporting_method = "none"
8889
--_M.px_login_successful_header_name = "x-px-login-successful"
@@ -100,7 +101,7 @@ _M.auth_token = 'PX_AUTH_TOKEN'
100101
-- ## GraphQL
101102
-- _M.px_sensitive_graphql_operation_types = {}
102103
-- _M.px_sensitive_graphql_operation_names = {}
103-
-- _M.px_graphql_paths = {'/graphql'}
104+
-- _M.px_graphql_routes = {'/graphql'}
104105

105106
-- ## User Identifiers
106107
-- _M.px_jwt_cookie_name = nil
@@ -109,4 +110,10 @@ _M.auth_token = 'PX_AUTH_TOKEN'
109110
-- _M.px_jwt_header_name = nil
110111
-- _M.px_jwt_header_user_id_field_name = nil
111112
-- _M.px_jwt_header_additional_field_names = {}
113+
114+
-- ## CORS support
115+
-- _M.px_cors_support_enabled = false
116+
-- _M.px_cors_custom_preflight_handler = nil
117+
-- _M.px_cors_preflight_request_filter_enabled = false
118+
-- _M.px_cors_create_custom_block_response_headers = nil
112119
return _M

lib/px/pxnginx.lua

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,36 @@ function M.application(px_configuration_table)
227227
return px_data
228228
end
229229

230+
-- filter preflight requests
231+
local method = ngx.req.get_method()
232+
method = method:upper()
233+
if px_config.px_cors_support_enabled and
234+
method == px_constants.OPTIONS_METHOD and
235+
(px_config.px_cors_custom_preflight_handler ~= nil or
236+
px_config.px_cors_preflight_request_filter_enabled) then
237+
238+
local origin_val = px_headers.get_header(px_constants.ORIGIN_HEADER_NAME)
239+
local req_method_val = px_headers.get_header(px_constants.CORS_ACCESS_CONTROL_REQUEST_METHOD_HEADER)
240+
241+
if origin_val ~= nil and req_method_val ~= nil then
242+
243+
if px_config.px_cors_custom_preflight_handler ~= nil then
244+
px_logger.debug("custom_enabled_routes was triggered")
245+
local ret = px_config.px_cors_custom_preflight_handler()
246+
if ret then
247+
px_headers.set_score_header(0)
248+
return px_data
249+
end
250+
end
251+
252+
if px_config.px_cors_preflight_request_filter_enabled then
253+
px_headers.set_score_header(0)
254+
return px_data
255+
end
256+
257+
end
258+
end
259+
230260
-- Clean any protected headers from the request.
231261
-- Prevents header spoofing to upstream application
232262
px_headers.clear_protected_headers()

lib/px/utils/config_builder.lua

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,15 +62,16 @@ PX_DEFAULT_CONFIGURATIONS["whitelist_uri_suffixes"] = { {'.css', '.bmp', '.tif',
6262
PX_DEFAULT_CONFIGURATIONS["whitelist_ip_addresses"] = { {}, "table"}
6363
PX_DEFAULT_CONFIGURATIONS["whitelist_ua_full"] = { {}, "table"}
6464
PX_DEFAULT_CONFIGURATIONS["whitelist_ua_sub"] = { {}, "table"}
65-
PX_DEFAULT_CONFIGURATIONS["custom_cookie_header"] = { nil, "string"}
65+
PX_DEFAULT_CONFIGURATIONS["custom_cookie_header"] = { 'X-PX-COOKIES', "string"}
6666
PX_DEFAULT_CONFIGURATIONS["bypass_monitor_header"] = { nil, "string"}
6767
PX_DEFAULT_CONFIGURATIONS["postpone_page_requested"] = { false, "boolean"}
6868
PX_DEFAULT_CONFIGURATIONS["hypesale_host"] = { "https://captcha.px-cdn.net", "string"}
6969
PX_DEFAULT_CONFIGURATIONS["px_sensitive_graphql_operation_types"] = { {}, "table"}
7070
PX_DEFAULT_CONFIGURATIONS["px_sensitive_graphql_operation_names"] = { {}, "table"}
71-
PX_DEFAULT_CONFIGURATIONS["px_graphql_paths"] = { {'/graphql'}, "table"}
71+
PX_DEFAULT_CONFIGURATIONS["px_graphql_routes"] = { {'/graphql'}, "table"}
7272
PX_DEFAULT_CONFIGURATIONS["px_enable_login_creds_extraction"] = { false, "boolean"}
7373
PX_DEFAULT_CONFIGURATIONS["px_login_creds_settings_filename"] = { nil, "string"}
74+
PX_DEFAULT_CONFIGURATIONS["px_login_creds_settings"] = { nil, "table"}
7475
PX_DEFAULT_CONFIGURATIONS["px_compromised_credentials_header_name"] = { "px-compromised-credentials", "string"}
7576
PX_DEFAULT_CONFIGURATIONS["px_login_successful_reporting_method"] = { "none", "string"}
7677
PX_DEFAULT_CONFIGURATIONS["px_login_successful_header_name"] = { "x-px-login-successful", "string"}
@@ -86,8 +87,13 @@ PX_DEFAULT_CONFIGURATIONS["px_jwt_cookie_additional_field_names"] = { {}, "table
8687
PX_DEFAULT_CONFIGURATIONS["px_jwt_header_name"] = { nil, "string"}
8788
PX_DEFAULT_CONFIGURATIONS["px_jwt_header_user_id_field_name"] = { nil, "string"}
8889
PX_DEFAULT_CONFIGURATIONS["px_jwt_header_additional_field_names"] = { {}, "table"}
90+
PX_DEFAULT_CONFIGURATIONS["px_cors_support_enabled"] = { false, "boolean"}
91+
PX_DEFAULT_CONFIGURATIONS["px_cors_preflight_request_filter_enabled"] = { false, "boolean"}
92+
PX_DEFAULT_CONFIGURATIONS["px_cors_custom_preflight_handler"] = { nil, "function"}
93+
PX_DEFAULT_CONFIGURATIONS["px_cors_create_custom_block_response_headers"] = { nil, "function"}
8994

9095
function _M.load(px_config)
96+
local px_constants = require("px.utils.pxconstants")
9197
local ngx_log = ngx.log
9298
local ngx_ERR = ngx.ERR
9399

@@ -124,6 +130,11 @@ function _M.load(px_config)
124130
end
125131
end
126132

133+
-- set default GraphQL route
134+
if not px_config.px_graphql_routes then
135+
table.insert(px_config.px_graphql_routes, px_constants.GRAPHQL_PATH)
136+
end
137+
127138
return px_config
128139
end
129140

lib/px/utils/pxconstants.lua

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
----------------------------------------------
44

55
local _M = {
6-
MODULE_VERSION = "NGINX Module v7.2.1",
6+
MODULE_VERSION = "NGINX Module v7.3.0",
77
RISK_PATH = "/api/v3/risk",
88
CAPTCHA_PATH = "/api/v2/risk/captcha",
99
ACTIVITIES_PATH = "/api/v1/collector/s2s",
@@ -20,7 +20,7 @@ local _M = {
2020
HSC_BLOCK_ACTION = 'hsc',
2121
HSC_DRC_PROPERTY = 7190,
2222
HSC_BLOCK_TYPE = 'pxHypeSaleChallenge',
23-
GRAPHQL_PATH = "graphql",
23+
GRAPHQL_PATH = "/graphql",
2424
GRAPHQL_QUERY = "query",
2525
GRAPHQL_MUTATION = "mutation",
2626
JSON_CONTENT_TYPE = "application/json",
@@ -34,7 +34,17 @@ local _M = {
3434
EMAIL_ADDRESS_REGEX = "[A-Za-z0-9%.%%%+%-]+@[A-Za-z0-9%.%%%+%-]+%.%w%w%w?%w?",
3535
GMAIL_DOMAIN = "gmail.com",
3636
BACKUP_CAPTCHA_HOST = "https://captcha.px-cloud.net",
37-
CTS_COOKIE = "pxcts"
37+
CTS_COOKIE = "pxcts",
38+
CORS_HEADER_KEY = "Access-Control-Allow-Origin",
39+
CORS_ALLOW_CREDENTIALS_HEADER_KEY = "Access-Control-Allow-Credentials",
40+
CORS_ACCESS_CONTROL_ALLOW_METHODS_KEY = "Access-Control-Allow-Methods",
41+
CORS_ACCESS_CONTROL_REQUEST_METHOD_HEADER = "Access-Control-Request-Method",
42+
CORS_ACCESS_CONTROL_ALLOW_METHODS_VALUE = "GET,POST,OPTIONS",
43+
CORS_ACCESS_CONTROL_ALLOW_HEADERS_KEY = "Access-Control-Allow-Headers",
44+
CORS_ACCESS_CONTROL_ALLOW_HEADERS_VALUE = "Content-Type,Authorization",
45+
ORIGIN_HEADER_NAME = "origin",
46+
OPTIONS_METHOD = "OPTIONS"
47+
3848
}
3949

4050
return _M

lib/px/utils/pxgraphql_extractor.lua

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,15 +96,15 @@ function M.load(px_config)
9696
local operationType = graphql["operationType"]
9797
local operationName = graphql["operationName"]
9898

99-
if next(px_config.px_sensitive_graphql_operation_names) ~= nil and operationType then
99+
if next(px_config.px_sensitive_graphql_operation_types) ~= nil and operationType then
100100
for i = 1, #px_config.px_sensitive_graphql_operation_types do
101101
if px_config.px_sensitive_graphql_operation_types[i] == operationType then
102102
return true
103103
end
104104
end
105105
end
106106

107-
if next(px_config.px_sensitive_graphql_operation_types) ~= nil and operationName then
107+
if next(px_config.px_sensitive_graphql_operation_names) ~= nil and operationName then
108108
for i = 1, #px_config.px_sensitive_graphql_operation_names do
109109
if px_config.px_sensitive_graphql_operation_names[i] == operationName then
110110
return true
@@ -116,9 +116,14 @@ function M.load(px_config)
116116
end
117117

118118
function _M.extract(lower_request_url)
119+
if not ((px_config.px_sensitive_graphql_operation_types and next(px_config.px_sensitive_graphql_operation_types) ~= nil) or
120+
(px_config.px_sensitive_graphql_operation_names and next(px_config.px_sensitive_graphql_operation_names) ~= nil)) then
121+
return nil
122+
end
123+
119124
local method = ngx.req.get_method()
120-
for i = 1, #px_config.px_graphql_paths do
121-
if string.find(lower_request_url, px_config.px_graphql_paths[i]) and method:lower() == "post" then
125+
for i = 1, #px_config.px_graphql_routes do
126+
if string.find(lower_request_url, px_config.px_graphql_routes[i]) and method:lower() == "post" then
122127
-- force Nginx to read body data
123128
ngx.req.read_body()
124129
local body = ngx.req.get_body_data()
@@ -138,8 +143,7 @@ function M.load(px_config)
138143
end
139144
end
140145

141-
return nul;
142-
146+
return nil
143147
end
144148

145149
return _M

lib/px/utils/pxlogin_credentials.lua

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,29 @@ function M.load(px_config)
358358
return pxdata
359359
end
360360

361+
-- try to load CI from a JSON file. Return True / False
362+
function _M.px_credentials_load_from_file()
363+
-- try to load JSON with creds information
364+
local f, err = io.open(px_config.px_login_creds_settings_filename, "r")
365+
if not f then
366+
px_logger.error("Failed to load login credentials JSON file: " .. err)
367+
return false
368+
end
369+
370+
local content = f:read("*a")
371+
f:close()
372+
373+
local success, creds_json = pcall(cjson.decode, content)
374+
if not success then
375+
px_logger.error("Could not decode login credentials JSON file")
376+
return false
377+
else
378+
if creds_json.features.credentials.items then
379+
px_config.creds = creds_json.features.credentials.items
380+
end
381+
end
382+
return true
383+
end
361384

362385
-- extract login information from client request
363386
-- return table or nil
@@ -395,7 +418,7 @@ function M.load(px_config)
395418
end
396419

397420
-- check if login credentials feature is enabled
398-
if not px_config.px_enable_login_creds_extraction or px_config.px_login_creds_settings_filename == nil then
421+
if not px_config.px_enable_login_creds_extraction then
399422
return _M
400423
end
401424

@@ -404,26 +427,23 @@ function M.load(px_config)
404427
return _M
405428
end
406429

407-
-- try to load JSON with creds information
408-
local f, err = io.open(px_config.px_login_creds_settings_filename, "r")
409-
if not f then
410-
px_logger.error("Failed to load login credentials JSON file: " .. err)
411-
return _M
430+
if px_config.px_login_creds_settings_filename ~= nil then
431+
local res = _M.px_credentials_load_from_file()
432+
if not res then
433+
return _M
434+
end
412435
end
413436

414-
local content = f:read("*a")
415-
f:close()
416-
417-
local success, creds_json = pcall(cjson.decode, content)
418-
if not success then
419-
px_logger.error("Could not decode login credentials JSON file")
420-
else
421-
if creds_json.features.credentials.items then
422-
px_config.creds = creds_json.features.credentials.items
423-
for _, p in pairs(px_config.creds) do
424-
if p["path"] then
425-
table.insert(px_config.sensitive_routes, p["path"])
426-
end
437+
-- Use settings from Enforcer configuration
438+
if px_config.px_login_creds_settings and next(px_config.px_login_creds_settings) ~= nil then
439+
px_config.creds = px_config.px_login_creds_settings
440+
end
441+
442+
-- update sensitive routes
443+
if px_config.creds and next(px_config.creds) ~= nil then
444+
for _, p in pairs(px_config.creds) do
445+
if p["path"] then
446+
table.insert(px_config.sensitive_routes, p["path"])
427447
end
428448
end
429449
end

perimeterx-nginx-plugin-7.2.1-1.rockspec renamed to perimeterx-nginx-plugin-7.3.0-1.rockspec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package = "perimeterx-nginx-plugin"
2-
version = "7.2.1-1"
2+
version = "7.3.0-1"
33
source = {
44
url = "git+https://github.com/PerimeterX/perimeterx-nginx-plugin.git",
5-
tag = "v7.2.1",
5+
tag = "v7.3.0",
66
}
77
description = {
88
summary = "PerimeterX NGINX Lua Middleware.",

0 commit comments

Comments
 (0)