diff --git a/index.d.ts b/index.d.ts index 1f3d1363..0abd5d63 100644 --- a/index.d.ts +++ b/index.d.ts @@ -56,6 +56,8 @@ export class DDWAF { readonly disposed: boolean; + readonly configPaths: string[]; + readonly diagnostics: { ruleset_version?: string, rules?: diagnosticsResult, @@ -70,12 +72,13 @@ export class DDWAF { readonly knownAddresses: Set; readonly knownActions: Set; - constructor(rules: rules, config?: { + constructor(rules: rules, rulesPath: string, config?: { obfuscatorKeyRegex?: string, obfuscatorValueRegex?: string }); - update(rules: rules): void; + createOrUpdateConfig(config: rules, path: string): boolean; + removeConfig(path: string): boolean; createContext(): DDWAFContext; dispose(): void; diff --git a/package.json b/package.json index 218ac838..1431e73b 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "8.5.2", "description": "Node.js bindings for libddwaf", "main": "index.js", - "libddwaf_version": "1.22.0", + "libddwaf_version": "1.24.1", "scripts": { "install": "exit 0", "rebuild": "node-gyp rebuild", diff --git a/src/main.cpp b/src/main.cpp index 2a9696e8..e02d54ef 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -20,7 +20,9 @@ Napi::Object DDWAF::Init(Napi::Env env, Napi::Object exports) { mlog("Setting up class DDWAF"); Napi::Function func = DefineClass(env, "DDWAF", { StaticMethod<&DDWAF::version>("version"), - InstanceMethod<&DDWAF::update>("update"), + InstanceMethod<&DDWAF::update_config>("createOrUpdateConfig"), + InstanceMethod<&DDWAF::remove_config>("removeConfig"), + InstanceAccessor("configPaths", &DDWAF::GetConfigPaths, nullptr, napi_enumerable), InstanceMethod<&DDWAF::createContext>("createContext"), InstanceMethod<&DDWAF::dispose>("dispose"), InstanceAccessor("disposed", &DDWAF::GetDisposed, nullptr, napi_enumerable), @@ -42,29 +44,35 @@ Napi::Value DDWAF::GetDisposed(const Napi::CallbackInfo& info) { DDWAF::DDWAF(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) { Napi::Env env = info.Env(); size_t arg_len = info.Length(); - if (arg_len < 1) { - Napi::Error::New(env, "Wrong number of arguments, expected at least 1").ThrowAsJavaScriptException(); + if (arg_len < 2) { + Napi::Error::New(env, "Wrong number of arguments, expected at least 2").ThrowAsJavaScriptException(); return; } + if (!info[0].IsObject()) { Napi::TypeError::New(env, "First argument must be an object").ThrowAsJavaScriptException(); return; } + if (!info[1].IsString()) { + Napi::TypeError::New(env, "Second argument must be a string").ThrowAsJavaScriptException(); + return; + } + ddwaf_config waf_config{{0, 0, 0}, {nullptr, nullptr}, ddwaf_object_free}; // do not touch these strings after the c_str() assigment std::string key_regex_str; std::string value_regex_str; - if (arg_len >= 2) { // TODO(@simon-id): there is a bug here ? + if (arg_len >= 3) { // TODO(@simon-id): there is a bug here ? // TODO(@simon-id) make a macro here someday - if (!info[1].IsObject()) { + if (!info[2].IsObject()) { Napi::TypeError::New(env, "Second argument must be an object").ThrowAsJavaScriptException(); return; } - Napi::Object config = info[1].ToObject(); + Napi::Object config = info[2].ToObject(); if (config.Has("obfuscatorKeyRegex")) { Napi::Value key_regex = config.Get("obfuscatorKeyRegex"); @@ -94,11 +102,14 @@ DDWAF::DDWAF(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) { ddwaf_object rules; mlog("building rules"); to_ddwaf_object(&rules, env, info[0], 0, false, false, JsSet::Create(env), nullptr); + std::string config_path = info[1].As().Utf8Value(); ddwaf_object diagnostics; - mlog("Init WAF"); - ddwaf_handle handle = ddwaf_init(&rules, &waf_config, &diagnostics); + mlog("Init Builder"); + ddwaf_builder builder = ddwaf_builder_init(&waf_config); + bool result = ddwaf_builder_add_or_update_config(builder, LSTRARG(config_path.c_str()), &rules, &diagnostics); + ddwaf_object_free(&rules); Napi::Value diagnostics_js = from_ddwaf_object(&diagnostics, env); @@ -106,11 +117,20 @@ DDWAF::DDWAF(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) { ddwaf_object_free(&diagnostics); + if (!result) { + Napi::Error::New(env, "Invalid rules").ThrowAsJavaScriptException(); + return; + } + + mlog("Init WAF"); + ddwaf_handle handle = ddwaf_builder_build_instance(builder); + if (handle == nullptr) { Napi::Error::New(env, "Invalid rules").ThrowAsJavaScriptException(); return; } + this->_builder = builder; this->_handle = handle; this->_disposed = false; @@ -124,6 +144,7 @@ void DDWAF::Finalize(Napi::Env env) { return; } ddwaf_destroy(this->_handle); + ddwaf_builder_destroy(this->_builder); this->_disposed = true; } @@ -132,52 +153,133 @@ void DDWAF::dispose(const Napi::CallbackInfo& info) { return this->Finalize(info.Env()); } -void DDWAF::update(const Napi::CallbackInfo& info) { - mlog("calling update on DDWAF"); +Napi::Value DDWAF::update_config(const Napi::CallbackInfo& info) { + mlog("Calling update config on DDWAF"); Napi::Env env = info.Env(); if (this->_disposed) { Napi::Error::New(env, "Could not update a disposed WAF instance").ThrowAsJavaScriptException(); - return; + return env.Undefined(); } - if (info.Length() < 1) { - Napi::Error::New(env, "Wrong number of arguments, expected at least 1").ThrowAsJavaScriptException(); - return; + if (info.Length() < 2) { + Napi::Error::New(env, "Wrong number of arguments, expected at least 2").ThrowAsJavaScriptException(); + return env.Undefined(); } + if (!info[0].IsObject()) { Napi::TypeError::New(env, "First argument must be an object").ThrowAsJavaScriptException(); - return; + return env.Undefined(); + } + + if (!info[1].IsString()) { + Napi::TypeError::New(env, "Second argument must be a string").ThrowAsJavaScriptException(); + return env.Undefined(); } ddwaf_object update; - mlog("building rules update"); + mlog("Building config update"); to_ddwaf_object(&update, env, info[0], 0, false, false, JsSet::Create(env), nullptr); + mlog("Obtaining config update path"); + std::string config_path = info[1].As().Utf8Value(); + ddwaf_object diagnostics; - mlog("Update DDWAF instance"); - ddwaf_handle updated_handle = ddwaf_update(this->_handle, &update, &diagnostics); - ddwaf_object_free(&update); + mlog("Applying new config to builder"); + bool update_result = ddwaf_builder_add_or_update_config( + this->_builder, + LSTRARG(config_path.c_str()), + &update, &diagnostics); Napi::Value diagnostics_js = from_ddwaf_object(&diagnostics, env); info.This().As().Set("diagnostics", diagnostics_js); ddwaf_object_free(&diagnostics); - if (updated_handle == nullptr) { - mlog("DDWAF updated handle is null"); - Napi::Error::New(env, "WAF has not been updated").ThrowAsJavaScriptException(); - return; + if (!update_result) { + mlog("DDWAF Builder update config has failed"); + return Napi::Boolean::New(env, false); } - mlog("New DDWAF updated instance") - ddwaf_destroy(this->_handle); - this->_handle = updated_handle; + mlog("Update DDWAF instance"); + ddwaf_handle updated_handle = ddwaf_builder_build_instance(this->_builder); + ddwaf_object_free(&update); - this->update_known_addresses(info); - this->update_known_actions(info); + if (updated_handle != nullptr) { + mlog("New DDWAF updated instance") + ddwaf_destroy(this->_handle); + this->_handle = updated_handle; + + this->update_known_addresses(info); + this->update_known_actions(info); + } + + return Napi::Boolean::New(env, true); +} + +Napi::Value DDWAF::remove_config(const Napi::CallbackInfo& info) { + mlog("Calling remove config on DDWAF"); + + Napi::Env env = info.Env(); + + if (this->_disposed) { + Napi::Error::New(env, "Could not update a disposed WAF instance").ThrowAsJavaScriptException(); + return env.Undefined(); + } + + if (info.Length() < 1) { + Napi::Error::New(env, "Wrong number of arguments, expected at least 1").ThrowAsJavaScriptException(); + return env.Undefined(); + } + + if (!info[0].IsString()) { + Napi::TypeError::New(env, "First argument must be a string").ThrowAsJavaScriptException(); + return env.Undefined(); + } + + mlog("Obtaining config remove path"); + std::string config_path = info[0].As().Utf8Value(); + + mlog("Applying removed config to builder"); + bool remove_result = ddwaf_builder_remove_config(this->_builder, LSTRARG(config_path.c_str())); + + if (!remove_result) { + mlog("DDWAF Builder remove config has failed"); + return Napi::Boolean::New(env, false); + } + + mlog("Update DDWAF instance"); + ddwaf_handle updated_handle = ddwaf_builder_build_instance(this->_builder); + + if (updated_handle != nullptr) { + mlog("New DDWAF updated instance") + ddwaf_destroy(this->_handle); + this->_handle = updated_handle; + + this->update_known_addresses(info); + this->update_known_actions(info); + } + + return Napi::Boolean::New(env, true); +} + +Napi::Value DDWAF::GetConfigPaths(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + if (this->_disposed) { + return Napi::Array::New(env, 0); + } + + ddwaf_object config_paths; + ddwaf_builder_get_config_paths(this->_builder, &config_paths, nullptr, 0); + + Napi::Value config_paths_js = from_ddwaf_object(&config_paths, env); + + ddwaf_object_free(&config_paths); + + return config_paths_js; } void DDWAF::update_known_addresses(const Napi::CallbackInfo& info) { diff --git a/src/main.h b/src/main.h index 709ab881..493b7068 100644 --- a/src/main.h +++ b/src/main.h @@ -8,6 +8,8 @@ #include #include "src/metrics.h" +#define LSTRARG(value) value, static_cast(strlen(value)) + // TODO(@vdeturckheim): logs with ddwaf_set_log_cb // TODO(@vdeturckheim): fix issue when used with workers @@ -21,7 +23,9 @@ class DDWAF : public Napi::ObjectWrap { explicit DDWAF(const Napi::CallbackInfo& info); // JS instance methods - void update(const Napi::CallbackInfo& info); + Napi::Value update_config(const Napi::CallbackInfo& info); + Napi::Value remove_config(const Napi::CallbackInfo& info); + Napi::Value GetConfigPaths(const Napi::CallbackInfo& info); Napi::Value createContext(const Napi::CallbackInfo& info); void Finalize(Napi::Env env); Napi::Value GetDisposed(const Napi::CallbackInfo& info); @@ -32,6 +36,7 @@ class DDWAF : public Napi::ObjectWrap { void update_known_actions(const Napi::CallbackInfo& info); bool _disposed; + ddwaf_builder _builder; ddwaf_handle _handle; }; diff --git a/test/fuzz.js b/test/fuzz.js index 9b590dc2..4e934206 100644 --- a/test/fuzz.js +++ b/test/fuzz.js @@ -10,7 +10,7 @@ const blns = require('./blns.json') const TIMEOUT = 9999e3 -const waf = new DDWAF(rules) +const waf = new DDWAF(rules, 'recommended') const ENCODINGS = [ // from https://github.com/nodejs/node/blob/master/lib/buffer.js 'utf8', diff --git a/test/index.js b/test/index.js index e9091b2e..62497f94 100644 --- a/test/index.js +++ b/test/index.js @@ -20,12 +20,13 @@ describe('DDWAF', () => { }) it('should have diagnostics', () => { - const waf = new DDWAF(rules) + const waf = new DDWAF(rules, 'recommended') assert.deepStrictEqual(waf.diagnostics, { ruleset_version: '1.3.1', actions: { errors: {}, + warnings: {}, failed: [], loaded: [ 'customblock' @@ -33,18 +34,6 @@ describe('DDWAF', () => { skipped: [] }, rules: { - addresses: { - optional: [], - required: [ - 'http.client_ip', - 'server.request.headers.no_cookies', - 'server.response.status', - 'value_attack', - 'key_attack', - 'custom_value_attack', - 'server.request.body' - ] - }, loaded: [ 'block_ip', 'value_attack', @@ -65,13 +54,14 @@ describe('DDWAF', () => { 'invalid_2', 'invalid_3' ] - } + }, + warnings: {} } }) }) it('should have knownAddresses', () => { - const waf = new DDWAF(rules) + const waf = new DDWAF(rules, 'recommended') assert.deepStrictEqual(waf.knownAddresses, new Set([ 'http.client_ip', @@ -85,7 +75,7 @@ describe('DDWAF', () => { }) it('should have knownActions', () => { - const waf = new DDWAF(rules) + const waf = new DDWAF(rules, 'recommended') assert.deepStrictEqual(waf.knownActions, new Set([ 'block_request' @@ -93,7 +83,7 @@ describe('DDWAF', () => { }) it('should collect an attack and cleanup everything', () => { - const waf = new DDWAF(rules) + const waf = new DDWAF(rules, 'recommended') const context = waf.createContext() const payload = { persistent: { @@ -129,7 +119,7 @@ describe('DDWAF', () => { }) it('should collect different attacks on ephemeral addresses', () => { - const waf = new DDWAF(rules) + const waf = new DDWAF(rules, 'recommended') const context = waf.createContext() let result = context.run({ ephemeral: { @@ -161,25 +151,184 @@ describe('DDWAF', () => { }) describe('WAF update', () => { - it('should throw an error when updating a disposed WAF instance', () => { - const waf = new DDWAF(rules) - waf.dispose() - assert.throws(() => waf.update(rules), new Error('Could not update a disposed WAF instance')) - }) + describe('Update config', () => { + it('should throw an error when updating configuration on a disposed WAF instance', () => { + const waf = new DDWAF(rules, 'recommended') + waf.dispose() + assert.throws( + () => waf.createOrUpdateConfig(rules, 'config/update'), + new Error('Could not update a disposed WAF instance')) + }) + + it('should throw an error when updating configuration with no arguments', () => { + const waf = new DDWAF(rules, 'recommended') + assert.throws(() => waf.createOrUpdateConfig(), new Error('Wrong number of arguments, expected at least 2')) + }) + + it('should throw an error when updating configuration with just one argument', () => { + const waf = new DDWAF(rules, 'recommended') + assert.throws(() => waf.createOrUpdateConfig({}), new Error('Wrong number of arguments, expected at least 2')) + }) + + it('should throw a type error when updating configuration with invalid arguments', () => { + const waf = new DDWAF(rules, 'recommended') + assert.throws( + () => waf.createOrUpdateConfig('string', 'config/update'), + new TypeError('First argument must be an object') + ) + assert.strictEqual(waf.disposed, false) + }) + + it('should return false when updating configuration with invalid configuration', () => { + const waf = new DDWAF(rules, 'recommended') + assert.strictEqual(waf.createOrUpdateConfig({}, 'config/update'), false) + assert.strictEqual(waf.disposed, false) + }) + + it('should keep functional handle after updating an invalid configuration', () => { + const waf = new DDWAF(rules, 'recommended') + waf.createOrUpdateConfig({}, 'config/update') + + assert(!waf.disposed) + + const context = waf.createContext() + const payload = { + persistent: { + 'server.request.headers.no_cookies': 'value_ATTack' + } + } + + const result = context.run(payload, TIMEOUT) - it('should throw an error when updating a WAF instance with no arguments', () => { - const waf = new DDWAF(rules) - assert.throws(() => waf.update(), new Error('Wrong number of arguments, expected at least 1')) + assert.strictEqual(result.timeout, false) + assert.strictEqual(result.status, 'match') + assert(result.events) + assert.deepStrictEqual(result.actions, {}) + assert(!context.disposed) + }) + + it('should return true when updating configuration', () => { + const waf = new DDWAF(rules, 'recommended') + const newConfig = { + version: '2.2', + metadata: { + rules_version: '1.3.0' + }, + actions: [{ + id: 'customredirect', + type: 'redirect_request', + parameters: { + status_code: '301', + location: '/' + } + }], + rules: [{ + id: 'block_ip_original', + name: 'block ip', + tags: { + type: 'ip_addresses', + category: 'blocking' + }, + conditions: [ + { + parameters: { + inputs: [ + { address: 'http.client_ip' } + ], + data: 'blocked_ips' + }, + operator: 'ip_match' + } + ], + transformers: [], + on_match: [ + 'customredirect' + ] + }] + } + assert.strictEqual(waf.createOrUpdateConfig(newConfig, 'config/update'), true) + }) }) - it('should throw a type error when updating a WAF instance with invalid arguments', () => { - const waf = new DDWAF(rules) - assert.throws(() => waf.update('string'), new TypeError('First argument must be an object')) + describe('Remove config', () => { + it('should throw an error when removing a configuration on a disposed WAF instance', () => { + const waf = new DDWAF(rules, 'recommended') + waf.dispose() + assert.throws( + () => waf.removeConfig('config/update'), + new Error('Could not update a disposed WAF instance')) + }) + + it('should throw an error when removing a configuration with no arguments', () => { + const waf = new DDWAF(rules, 'recommended') + assert.throws(() => waf.removeConfig(), new Error('Wrong number of arguments, expected at least 1')) + }) + + it('should throw a type error when removing a configuration with invalid arguments', () => { + const waf = new DDWAF(rules, 'recommended') + assert.throws( + () => waf.removeConfig(null), + new TypeError('First argument must be a string') + ) + }) + + it('should return true when removing an existing configuration', () => { + const waf = new DDWAF(rules, 'recommended') + assert.strictEqual(waf.removeConfig('recommended'), true) + assert.strictEqual(waf.configPaths.length, 0) + }) + + it('should return false when removing a non-existing configuration', () => { + const waf = new DDWAF(rules, 'recommended') + assert.strictEqual(waf.removeConfig('config/update'), false) + assert.ok( + waf.configPaths.includes('recommended') && + waf.configPaths.length === 1 + ) + }) }) - it('should throw an exception when WAF update has not been updated - nothing to update', () => { - const waf = new DDWAF(rules) - assert.throws(() => waf.update({}), new Error('WAF has not been updated')) + describe('Config paths', () => { + it('should have no loaded configuration paths on WAF disposed instance', () => { + const waf = new DDWAF(rules, 'recommended') + waf.dispose() + assert.strictEqual(waf.configPaths.length, 0) + }) + + it('should have loaded configuration paths', () => { + const waf = new DDWAF(rules, 'recommended') + const newConfig = { + rules: [{ + id: 'block_ip_original', + name: 'block ip', + tags: { + type: 'ip_addresses', + category: 'blocking' + }, + conditions: [ + { + parameters: { + inputs: [ + { address: 'http.client_ip' } + ], + data: 'blocked_ips' + }, + operator: 'ip_match' + } + ], + transformers: [], + on_match: [ + 'customredirect' + ] + }] + } + waf.createOrUpdateConfig(newConfig, 'config/update') + assert.ok( + waf.configPaths.includes('recommended') && + waf.configPaths.includes('config/update') && + waf.configPaths.length === 2 + ) + }) }) it('should update diagnostics, knownAddresses, and knownActions when updating an instance with new ruleSet', () => { @@ -197,7 +346,7 @@ describe('DDWAF', () => { } }], rules: [{ - id: 'block_ip', + id: 'block_ip_original', name: 'block ip', tags: { type: 'ip_addresses', @@ -219,12 +368,13 @@ describe('DDWAF', () => { 'customredirect' ] }] - }) + }, 'new_ruleset') assert.deepStrictEqual(waf.diagnostics, { ruleset_version: '1.3.0', actions: { errors: {}, + warnings: {}, failed: [], loaded: [ 'customredirect' @@ -232,14 +382,11 @@ describe('DDWAF', () => { skipped: [] }, rules: { - addresses: { - optional: [], - required: ['http.client_ip'] - }, - loaded: ['block_ip'], + loaded: ['block_ip_original'], failed: [], skipped: [], - errors: {} + errors: {}, + warnings: {} } }) assert.deepStrictEqual(waf.knownAddresses, new Set([ @@ -249,11 +396,12 @@ describe('DDWAF', () => { 'redirect_request' ])) - waf.update(rules) + waf.createOrUpdateConfig(rules, 'config/update') assert.deepStrictEqual(waf.diagnostics, { ruleset_version: '1.3.1', actions: { errors: {}, + warnings: {}, failed: [], loaded: [ 'customblock' @@ -261,18 +409,6 @@ describe('DDWAF', () => { skipped: [] }, rules: { - addresses: { - optional: [], - required: [ - 'http.client_ip', - 'server.request.headers.no_cookies', - 'server.response.status', - 'value_attack', - 'key_attack', - 'custom_value_attack', - 'server.request.body' - ] - }, loaded: [ 'block_ip', 'value_attack', @@ -293,7 +429,8 @@ describe('DDWAF', () => { 'invalid_2', 'invalid_3' ] - } + }, + warnings: {} } }) assert.deepStrictEqual(waf.knownAddresses, new Set([ @@ -306,7 +443,8 @@ describe('DDWAF', () => { 'custom_value_attack' ])) assert.deepStrictEqual(waf.knownActions, new Set([ - 'block_request' + 'block_request', + 'redirect_request' ])) waf.dispose() @@ -320,7 +458,7 @@ describe('DDWAF', () => { } } - const waf = new DDWAF(rules) + const waf = new DDWAF(rules, 'recommended') const context = waf.createContext() const resultBeforeUpdatingRuleData = context.run(payload, TIMEOUT) assert(!resultBeforeUpdatingRuleData.status) @@ -335,7 +473,7 @@ describe('DDWAF', () => { ] } - waf.update(updateWithRulesData) + waf.createOrUpdateConfig(updateWithRulesData, 'config/update') const contextWithRuleData = waf.createContext() const resultAfterUpdatingRuleData = contextWithRuleData.run(payload, TIMEOUT) @@ -385,7 +523,7 @@ describe('DDWAF', () => { value_attack: 'matchall' } } - const waf = new DDWAF(rules) + const waf = new DDWAF(rules, 'recommended') const contextToggledOn = waf.createContext() const resultToggledOn = contextToggledOn.run(payload, TIMEOUT) @@ -398,7 +536,7 @@ describe('DDWAF', () => { rules_override: testData.rulesOverride } - waf.update(updateWithRulesOverride) + waf.createOrUpdateConfig(updateWithRulesOverride, 'config/update') const contextToggledOff = waf.createContext() const resultToggledOff = contextToggledOff.run(payload, TIMEOUT) @@ -443,7 +581,7 @@ describe('DDWAF', () => { } } - const waf = new DDWAF(rules) + const waf = new DDWAF(rules, 'recommended') const monitorContext = waf.createContext() const resultMonitor = monitorContext.run(payload, TIMEOUT) @@ -457,7 +595,7 @@ describe('DDWAF', () => { rules_override: testData.rulesOverride } - waf.update(updateWithRulesOverride) + waf.createOrUpdateConfig(updateWithRulesOverride, 'config/update') const blockContext = waf.createContext() const resultBlock = blockContext.run(payload, TIMEOUT) @@ -477,7 +615,7 @@ describe('DDWAF', () => { }) it('should support case_sensitive', () => { - const waf = new DDWAF(rules) + const waf = new DDWAF(rules, 'recommended') const context = waf.createContext() const result = context.run({ @@ -491,12 +629,12 @@ describe('DDWAF', () => { }) it('should refuse invalid rule', () => { - assert.throws(() => new DDWAF({}), new Error('Invalid rules')) - assert.throws(() => new DDWAF(''), new TypeError('First argument must be an object')) + assert.throws(() => new DDWAF({}, 'empty_rules'), new Error('Invalid rules')) + assert.throws(() => new DDWAF('', 'non_object_rules'), new TypeError('First argument must be an object')) }) it('should refuse to run with bad signatures', () => { - const waf = new DDWAF(rules) + const waf = new DDWAF(rules, 'recommended') const context = waf.createContext() const wronArgsError = new Error('Wrong number of arguments, 2 expected') @@ -546,7 +684,7 @@ describe('DDWAF', () => { [function fn () {}, 'function fn () {}'] ]) - const waf = new DDWAF(rules) + const waf = new DDWAF(rules, 'recommended') for (const [key, expected] of possibleKeys) { const context = waf.createContext() @@ -584,7 +722,7 @@ describe('DDWAF', () => { [function fn () {}, undefined] ]) - const waf = new DDWAF(rules) + const waf = new DDWAF(rules, 'recommended') for (const [value, expected] of possibleValues) { const context = waf.createContext() @@ -608,7 +746,7 @@ describe('DDWAF', () => { }) it('should obfuscate keys', () => { - const waf = new DDWAF(rules, { + const waf = new DDWAF(rules, 'recommended', { obfuscatorKeyRegex: 'password' }) const context = waf.createContext() @@ -630,7 +768,7 @@ describe('DDWAF', () => { }) it('should obfuscate values', () => { - const waf = new DDWAF(rules, { + const waf = new DDWAF(rules, 'recommended', { obfuscatorValueRegex: 'value_attack' }) const context = waf.createContext() @@ -650,13 +788,10 @@ describe('DDWAF', () => { }) it('should collect derivatives information when a rule match', () => { - const waf = new DDWAF(processor) + const waf = new DDWAF(processor, 'processor_rules') const context = waf.createContext() - assert(waf.diagnostics.processors.addresses.optional.includes('server.request.query')) - assert(waf.diagnostics.processors.addresses.optional.includes('server.request.body')) - assert(waf.diagnostics.processors.addresses.required.includes('waf.context.processor')) assert(waf.diagnostics.processors.loaded.includes('processor-001')) assert.equal(waf.diagnostics.processors.failed.length, 0) @@ -680,12 +815,9 @@ describe('DDWAF', () => { }) it('should collect derivatives information when a rule does not match', () => { - const waf = new DDWAF(processor) + const waf = new DDWAF(processor, 'processor_rules') const context = waf.createContext() - assert(waf.diagnostics.processors.addresses.optional.includes('server.request.query')) - assert(waf.diagnostics.processors.addresses.optional.includes('server.request.body')) - assert(waf.diagnostics.processors.addresses.required.includes('waf.context.processor')) assert(waf.diagnostics.processors.loaded.includes('processor-001')) assert.equal(waf.diagnostics.processors.failed.length, 0) @@ -708,12 +840,9 @@ describe('DDWAF', () => { }) it('should collect all derivatives types', () => { - const waf = new DDWAF(processor) + const waf = new DDWAF(processor, 'processor_rules') const context = waf.createContext() - assert(waf.diagnostics.processors.addresses.optional.includes('server.request.query')) - assert(waf.diagnostics.processors.addresses.optional.includes('server.request.body')) - assert(waf.diagnostics.processors.addresses.required.includes('waf.context.processor')) assert(waf.diagnostics.processors.loaded.includes('processor-001')) assert.equal(waf.diagnostics.processors.failed.length, 0) @@ -770,12 +899,9 @@ describe('DDWAF', () => { }) it('should collect derivatives in two consecutive calls', () => { - const waf = new DDWAF(processor) + const waf = new DDWAF(processor, 'processor_rules') const context = waf.createContext() - assert(waf.diagnostics.processors.addresses.optional.includes('server.request.query')) - assert(waf.diagnostics.processors.addresses.optional.includes('server.request.body')) - assert(waf.diagnostics.processors.addresses.required.includes('waf.context.processor')) assert(waf.diagnostics.processors.loaded.includes('processor-001')) assert.equal(waf.diagnostics.processors.failed.length, 0) @@ -815,7 +941,7 @@ describe('DDWAF', () => { describe('Action semantics', () => { it('should support action definition in initialisation', () => { - const waf = new DDWAF(rules) + const waf = new DDWAF(rules, 'recommended') const context = waf.createContext() const result = context.run({ @@ -838,7 +964,7 @@ describe('DDWAF', () => { }) it('should support action definition in update', () => { - const waf = new DDWAF(rules) + const waf = new DDWAF(rules, 'recommended') const updatedRules = Object.assign({}, rules) updatedRules.actions = [{ @@ -851,7 +977,8 @@ describe('DDWAF', () => { } }] - waf.update(updatedRules) + waf.removeConfig('recommended') + waf.createOrUpdateConfig(updatedRules, 'recommended_action_modified') const context = waf.createContext() const resultWithUpdatedAction = context.run({ @@ -877,7 +1004,7 @@ describe('DDWAF', () => { describe('limit tests', () => { it('should ignore elements too far in the objects', () => { - const waf = new DDWAF(rules) + const waf = new DDWAF(rules, 'recommended') const context1 = waf.createContext() const result1 = context1.run({ @@ -908,7 +1035,7 @@ describe('limit tests', () => { }) it('should match a moderately deeply nested object', () => { - const waf = new DDWAF(rules) + const waf = new DDWAF(rules, 'recommended') const context = waf.createContext() const result = context.run({ @@ -923,7 +1050,7 @@ describe('limit tests', () => { }) it('should set as invalid circular property dependency', () => { - const waf = new DDWAF(processor) + const waf = new DDWAF(processor, 'processor_rules') const context = waf.createContext() const payload = { key: 'value', @@ -954,7 +1081,7 @@ describe('limit tests', () => { }) it('should set as invalid circular property dependency in deeper level', () => { - const waf = new DDWAF(processor) + const waf = new DDWAF(processor, 'processor_rules') const context = waf.createContext() const payload = { key: 'value', @@ -985,7 +1112,7 @@ describe('limit tests', () => { }) it('should set as invalid circular array dependency', () => { - const waf = new DDWAF(processor) + const waf = new DDWAF(processor, 'processor_rules') const context = waf.createContext() const payload = [] payload.push(payload, payload, payload) @@ -1003,7 +1130,7 @@ describe('limit tests', () => { }) it('should set as invalid circular array dependency in deeper levels', () => { - const waf = new DDWAF(processor) + const waf = new DDWAF(processor, 'processor_rules') const context = waf.createContext() const payload = [] payload.push({ payload }) @@ -1023,7 +1150,7 @@ describe('limit tests', () => { }) it('should not set as invalid same instances in array', () => { - const waf = new DDWAF(processor) + const waf = new DDWAF(processor, 'processor_rules') const context = waf.createContext() const item = { key: 'value', @@ -1048,7 +1175,7 @@ describe('limit tests', () => { }) it('should not set as invalid same instance in different properties', () => { - const waf = new DDWAF(processor) + const waf = new DDWAF(processor, 'processor_rules') const context = waf.createContext() const prop = { key: 'value', @@ -1079,7 +1206,7 @@ describe('limit tests', () => { }) it('should not match an extremely deeply nested object', () => { - const waf = new DDWAF(rules) + const waf = new DDWAF(rules, 'recommended') const context = waf.createContext() const result = context.run({ @@ -1094,7 +1221,7 @@ describe('limit tests', () => { }) it('should not limit the rules object', () => { - const waf = new DDWAF(rules) + const waf = new DDWAF(rules, 'recommended') // test first item in big rule const context1 = waf.createContext() @@ -1118,7 +1245,7 @@ describe('limit tests', () => { }) it('should use custom toJSON function', () => { - const waf = new DDWAF(rules) + const waf = new DDWAF(rules, 'recommended') const body = { a: 'not_an_attack' } @@ -1148,7 +1275,7 @@ describe('limit tests', () => { }) it('should use custom toJSON function in arrays', () => { - const waf = new DDWAF(rules) + const waf = new DDWAF(rules, 'recommended') const body = ['not_an_attack'] @@ -1193,7 +1320,7 @@ describe('limit tests', () => { } } - const waf = new DDWAF(processor) + const waf = new DDWAF(processor, 'processor_rules') const context = waf.createContext() const result = context.run({ persistent: { @@ -1236,7 +1363,7 @@ describe('limit tests', () => { } } - const waf = new DDWAF(processor) + const waf = new DDWAF(processor, 'processor_rules') const context = waf.createContext() const result = context.run({ persistent: { @@ -1281,7 +1408,7 @@ describe('limit tests', () => { } } - const waf = new DDWAF(processor) + const waf = new DDWAF(processor, 'processor_rules') const context = waf.createContext() const result = context.run({ persistent: { @@ -1313,7 +1440,7 @@ describe('limit tests', () => { c: 'c' } - const waf = new DDWAF(processor) + const waf = new DDWAF(processor, 'processor_rules') const context = waf.createContext() const result = context.run({ persistent: { @@ -1335,7 +1462,7 @@ describe('limit tests', () => { }) it('should truncate string values exceeding maximum length', () => { - const waf = new DDWAF(rules) + const waf = new DDWAF(rules, 'recommended') const context1 = waf.createContext() const result1 = context1.run({ @@ -1362,7 +1489,7 @@ describe('limit tests', () => { }) it('should handle multiple truncations in complex nested structure', () => { - const waf = new DDWAF(rules) + const waf = new DDWAF(rules, 'recommended') const longValue1 = 'a'.repeat(5000) const longValue2 = 'b'.repeat(6000) @@ -1406,7 +1533,7 @@ describe('limit tests', () => { describe('Handle errors', () => { it('should handle invalid arguments number', () => { - const waf = new DDWAF(rules) + const waf = new DDWAF(rules, 'recommended') const context = waf.createContext() try { @@ -1419,7 +1546,7 @@ describe('Handle errors', () => { }) it('should handle invalid timeout arguments', () => { - const waf = new DDWAF(rules) + const waf = new DDWAF(rules, 'recommended') const context = waf.createContext() try { @@ -1440,7 +1567,7 @@ describe('Handle errors', () => { }) it('should handle invalid arguments', () => { - const waf = new DDWAF(rules) + const waf = new DDWAF(rules, 'recommended') const context = waf.createContext() try { diff --git a/test/worker.js b/test/worker.js index 0cb31c3b..9e377911 100644 --- a/test/worker.js +++ b/test/worker.js @@ -6,7 +6,7 @@ if (!isMainThread) { const { DDWAF } = require('..') const rules = require('./rules.json') - const waf = new DDWAF(rules) + const waf = new DDWAF(rules, 'recommended') const context = waf.createContext() const result = context.run({ diff --git a/test/worker_threads.js b/test/worker_threads.js index 00016a61..f36965a4 100644 --- a/test/worker_threads.js +++ b/test/worker_threads.js @@ -13,7 +13,7 @@ const WORKER_PATH = path.join(__dirname, 'worker.js') describe('worker threads', () => { it('should not crash when worker created after DDWAF', (done) => { - const waf = new DDWAF(rules) + const waf = new DDWAF(rules, 'recommended') const context = waf.createContext() const result1 = context.run({ @@ -47,7 +47,7 @@ describe('worker threads', () => { worker.on('message', (result1) => { assert.strictEqual(result1?.status, 'match') - const waf = new DDWAF(rules) + const waf = new DDWAF(rules, 'recommended') const context = waf.createContext() const result2 = context.run({