Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remote config initial commit - fetchConfig, add risk parameter and in… #300

Merged
merged 10 commits into from
Jan 28, 2024
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,4 @@ module.exports = {
PxCdEnforcer: require('./lib/pxcdenforcer'),
PxCdFirstParty: require('./lib/pxcdfirstparty'),
addNonce: require('./lib/nonce')
};
};
2 changes: 1 addition & 1 deletion lib/enums/CIVersion.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ const CIVersion = {

module.exports = {
CIVersion
};
};
4 changes: 4 additions & 0 deletions lib/enums/ErrorType.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
const ErrorType = {
AsafAklerPX marked this conversation as resolved.
Show resolved Hide resolved
WRITE_REMOTE_CONFIG: 'write_remote_config',
};
module.exports = { ErrorType };
8 changes: 7 additions & 1 deletion lib/pxapi.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use strict';
const pxUtil = require('./pxutil');
const pxHttpc = require('./pxhttpc');

const os = require('os');
const S2SErrorInfo = require('./models/S2SErrorInfo');
const { ModuleMode } = require('./enums/ModuleMode');
const PassReason = require('./enums/PassReason');
Expand Down Expand Up @@ -66,6 +66,8 @@ function buildRequestData(ctx, config) {
cookie_origin: ctx.cookieOrigin,
request_cookie_names: ctx.requestCookieNames,
request_id: ctx.requestId,
px_remote_config_id: config.REMOTE_CONFIG_ID ? config.REMOTE_CONFIG_ID : '',
AsafAklerPX marked this conversation as resolved.
Show resolved Hide resolved
hostname: os.hostname()
AsafAklerPX marked this conversation as resolved.
Show resolved Hide resolved
},
};

Expand Down Expand Up @@ -190,6 +192,10 @@ function evalByServerCall(ctx, config, callback) {
return callback(ScoreEvaluateAction.UNEXPECTED_RESULT);
}
ctx.pxhdServer = res.pxhd;
if (res.remote_config && res.remote_config.id === config.REMOTE_CONFIG_ID) {
ctx.isRemoteConfigOutdated = res.remote_config.version > config.REMOTE_CONFIG_VERSION;
}

if (res.data_enrichment) {
ctx.pxde = res.data_enrichment;
ctx.pxdeVerified = true;
Expand Down
49 changes: 48 additions & 1 deletion lib/pxclient.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ const pxUtil = require('./pxutil');
const pxHttpc = require('./pxhttpc');
const { ActivityType } = require('./enums/ActivityType');
const { CIVersion } = require('./enums/CIVersion');
const p = require('agent-phin');
const { LoggerSeverity } = require('./enums/LoggerSeverity');
const { PxLogsParser } = require('./pxlogparser');
const PxLogger = require('./pxlogger');
const PxConfig = require('./pxconfig');
const {
CI_VERSION_FIELD,
CI_SSO_STEP_FIELD,
Expand All @@ -12,12 +17,13 @@ const {
GQL_OPERATIONS_FIELD,
APP_USER_ID_FIELD_NAME,
JWT_ADDITIONAL_FIELDS_FIELD_NAME,
CROSS_TAB_SESSION,
CROSS_TAB_SESSION, HOST_NAME, EXTERNAL_LOGGER_SERVICE_PATH,
} = require('./utils/constants');

class PxClient {
constructor() {
this.activitiesBuffer = [];
this.logsBuffer = []; // TODO: add buffer to logs and flush it at the End of the Enforcer flow
}

init() {
Expand All @@ -42,6 +48,7 @@ class PxClient {
vid: ctx.vid ? ctx.vid : undefined,
};
details['request_id'] = ctx.requestId;
details['px_remote_config_version'] = config.remoteConfigVersion;
AsafAklerPX marked this conversation as resolved.
Show resolved Hide resolved

this.addAdditionalFieldsToActivity(details, ctx);
if (activityType !== ActivityType.ADDITIONAL_S2S) {
Expand All @@ -57,6 +64,25 @@ class PxClient {
activity.details = details;
return activity;
}
async fetchRemoteConfig(config) {
try {
const callData = {
url: `https://sapi-${config.px_app_id}.perimeterx.net/config/`,
AsafAklerPX marked this conversation as resolved.
Show resolved Hide resolved
headers: { 'Authorization': `Bearer ${config.px_remote_config_secret}`, 'Accept-Encoding': '' },
timeout: 20000,
};
const res = await p({ url: callData.url, headers: callData.headers, timeout: callData.timeout, method: 'GET' });
AsafAklerPX marked this conversation as resolved.
Show resolved Hide resolved
const remoteConfigObject = JSON.parse(res.body);
AsafAklerPX marked this conversation as resolved.
Show resolved Hide resolved
return {
px_remote_config_id: remoteConfigObject.id,
px_remote_config_version: remoteConfigObject.version,
... remoteConfigObject.configValue
AsafAklerPX marked this conversation as resolved.
Show resolved Hide resolved
};
} catch (e) {
this.sendRemoteLog(`Error fetching remote configurations: ${e.message}`, LoggerSeverity.DEBUG, 'write_remote_config', config);
AsafAklerPX marked this conversation as resolved.
Show resolved Hide resolved
return undefined;
}
}

addAdditionalFieldsToActivity(details, ctx) {
if (ctx.additionalFields && ctx.additionalFields.loginCredentials) {
Expand Down Expand Up @@ -84,6 +110,7 @@ class PxClient {
}
}

details[HOST_NAME] = os.hostname();
AsafAklerPX marked this conversation as resolved.
Show resolved Hide resolved
if (ctx.cts) {
details[CROSS_TAB_SESSION] = ctx.cts;
}
Expand Down Expand Up @@ -182,5 +209,25 @@ class PxClient {
cb();
}
}

sendRemoteLog(message, severity, errorType, config) {
const enforcerConfig = new PxConfig(config, new PxLogger(config));
AsafAklerPX marked this conversation as resolved.
Show resolved Hide resolved
const reqHeaders = {
'Authorization': 'Bearer ' + enforcerConfig.config.LOGGER_AUTH_TOKEN,
'Content-Type': 'application/json',
};
const logParser = new PxLogsParser(config);
AsafAklerPX marked this conversation as resolved.
Show resolved Hide resolved
const logs = [{message, severity, errorType}];
logParser.enrichLogs(logs);
AsafAklerPX marked this conversation as resolved.
Show resolved Hide resolved
pxHttpc.callServer(
logs,
reqHeaders,
EXTERNAL_LOGGER_SERVICE_PATH,
'remote-log',
enforcerConfig.conf,
null,
false,
);
}
}
module.exports = PxClient;
29 changes: 25 additions & 4 deletions lib/pxconfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@ const { LoggerSeverity } = require('./enums/LoggerSeverity');
const { DEFAULT_COMPROMISED_CREDENTIALS_HEADER_NAME } = require('./utils/constants');
const { CIVersion } = require('./enums/CIVersion');
const { LoginSuccessfulReportingMethod } = require('./enums/LoginSuccessfulReportingMethod');

const { INVALID_VERSION_NUMBER } = require('./utils/constants');
class PxConfig {
constructor(params, logger) {
this.PX_INTERNAL = pxInternalConfig();
this.PX_DEFAULT = pxDefaultConfig();
this.logger = logger;
this.config = this.mergeParams(params);
this._remoteConfigOutdated = false;
this.config.FILTER_BY_METHOD = this.config.FILTER_BY_METHOD.map((v) => v.toUpperCase());
this.config.logger = this.logger;

this.config.WHITELIST_EXT = [...this.PX_INTERNAL.STATIC_FILES_EXT, ...this.PX_DEFAULT.WHITELIST_EXT];

if (this.PX_DEFAULT.TESTING_MODE) {
Expand All @@ -30,6 +30,17 @@ class PxConfig {
this.configLoader = null;
}

get remoteConfigVersion() {
return this.config.px_remote_config_version || INVALID_VERSION_NUMBER;
}

get isRemoteConfigOutdated() {
AsafAklerPX marked this conversation as resolved.
Show resolved Hide resolved
return this._remoteConfigOutdated;
}
set isRemoteConfigOutdated(value) {
this._remoteConfigOutdated = value;
}

mergeParams(params) {
params = this.mergeConfigFileParams(params);

Expand Down Expand Up @@ -104,7 +115,12 @@ class PxConfig {
['JWT_HEADER_ADDITIONAL_FIELD_NAMES', 'px_jwt_header_additional_field_names'],
['CUSTOM_IS_SENSITIVE_REQUEST', 'px_custom_is_sensitive_request'],
['FIRST_PARTY_TIMEOUT_MS', 'px_first_party_timeout_ms'],
['URL_DECODE_RESERVED_CHARACTERS', 'px_url_decode_reserved_characters']
['URL_DECODE_RESERVED_CHARACTERS', 'px_url_decode_reserved_characters'],
['REMOTE_CONFIG_ENABLED', 'px_remote_config_enabled'],
['REMOTE_CONFIG_AUTH_TOKEN', 'px_remote_config_auth_token'],
['REMOTE_CONFIG_ID', 'px_remote_config_id'],
['REMOTE_CONFIG_VERSION', 'px_remote_config_version'],
['LOGGER_AUTH_TOKEN', 'px_logger_auth_token']
];

configKeyMapping.forEach(([targetKey, sourceKey]) => {
Expand Down Expand Up @@ -365,7 +381,12 @@ function pxDefaultConfig() {
JWT_HEADER_ADDITIONAL_FIELD_NAMES: [],
CUSTOM_IS_SENSITIVE_REQUEST: '',
FIRST_PARTY_TIMEOUT_MS: 4000,
URL_DECODE_RESERVED_CHARACTERS: false
URL_DECODE_RESERVED_CHARACTERS: false,
REMOTE_CONFIG_ENABLED: false,
REMOTE_CONFIG_AUTH_TOKEN: '',
REMOTE_CONFIG_ID: '',
REMOTE_CONFIG_VERSION: INVALID_VERSION_NUMBER,
LOGGER_AUTH_TOKEN: ''
};
}

Expand Down
2 changes: 1 addition & 1 deletion lib/pxenforcer.js
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ class PxEnforcer {

handleVerification(ctx, req, res, cb) {
const verified = ctx.score < this._config.BLOCKING_SCORE;

this.config.isRemoteConfigOutdated = ctx.isRemoteConfigOutdated;
AsafAklerPX marked this conversation as resolved.
Show resolved Hide resolved
if (res) {
const setCookie = res.getHeader('Set-Cookie') ? res.getHeader('Set-Cookie') : '';
const secure = this._config.PXHD_SECURE ? '; Secure' : '';
Expand Down
3 changes: 3 additions & 0 deletions lib/pxhttpc.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ function callServer(data, headers, uri, callType, config, callback, failOnEmptyB

try {
request.post(callData, config, function (err, response) {
if (callType === 'remote-log') {
AsafAklerPX marked this conversation as resolved.
Show resolved Hide resolved
return;
}
if (err) {
if (err.toString().toLowerCase().includes('timeout')) {
return callback('timeout');
Expand Down
24 changes: 24 additions & 0 deletions lib/pxlogparser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const PxConfig = require('./pxconfig');

class PxLogsParser {
constructor(config) {
AsafAklerPX marked this conversation as resolved.
Show resolved Hide resolved
this.config = new PxConfig(config, null);

}
enrichLogs(logs) {
return logs.map((log) => this.enrichLogRecord(log));
}

enrichLogRecord(log) {
log.message = log.message.substring(0, this.config.px_external_logger_max_message_size || log.message.length);
AsafAklerPX marked this conversation as resolved.
Show resolved Hide resolved
Object.assign(log, {
messageTimestamp: new Date().toISOString(),
appID: this.config.config.PX_APP_ID,
container: 'enforcer',
configID: this.config.config.REMOTE_CONFIG_ID,
AsafAklerPX marked this conversation as resolved.
Show resolved Hide resolved
configVersion: this.config.config.REMOTE_CONFIG_VERSION
});
}
}

module.exports = { PxLogsParser };
2 changes: 1 addition & 1 deletion lib/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ function makeRequest(options, config, cb) {
options.agent = new http.Agent();
}
p(options, cb);
}
}
7 changes: 7 additions & 0 deletions lib/utils/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ const JWT_ADDITIONAL_FIELDS_FIELD_NAME = 'jwt_additional_fields';
const CROSS_TAB_SESSION = 'cross_tab_session';
const COOKIE_SEPARATOR = ';';

const EXTERNAL_LOGGER_SERVICE_PATH = '/enforcer-logs/';
AsafAklerPX marked this conversation as resolved.
Show resolved Hide resolved
const INVALID_VERSION_NUMBER = -1;
const HOST_NAME = 'hostname';

module.exports = {
MILLISECONDS_IN_SECOND,
SECONDS_IN_MINUTE,
Expand Down Expand Up @@ -66,4 +70,7 @@ module.exports = {
JWT_ADDITIONAL_FIELDS_FIELD_NAME,
CROSS_TAB_SESSION,
COOKIE_SEPARATOR,
EXTERNAL_LOGGER_SERVICE_PATH,
INVALID_VERSION_NUMBER,
HOST_NAME
};
Loading