-
Notifications
You must be signed in to change notification settings - Fork 6
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
WAF Builder: independent configuration manager to generate WAF instances #363
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #363 +/- ##
==========================================
+ Coverage 85.13% 85.32% +0.18%
==========================================
Files 157 163 +6
Lines 7993 8169 +176
Branches 3551 3600 +49
==========================================
+ Hits 6805 6970 +165
+ Misses 459 439 -20
- Partials 729 760 +31
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
BenchmarksBenchmark execution time: 2025-01-30 13:30:14 Comparing candidate commit fd841fc in PR branch Found 1 performance improvements and 0 performance regressions! Performance is the same for 0 metrics, 0 unstable metrics. scenario:global-benchmark.random
|
} | ||
} | ||
|
||
ancillary_tags_.merge(spec_.tags); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do you really want to modify spec_.tags
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned below, the rule builders are single use, I added a comment to make sure it's clear.
|
||
return std::make_shared<core_rule>(std::move(id_), std::move(spec_.name), | ||
std::move(ancillary_tags_), std::move(spec_.expr), std::move(spec_.actions), | ||
spec_.enabled, spec_.source, verdict); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
your builders get to a bad state after calls to build()
. Perhaps this is something you want to tackle in the future to prevent misusage
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In reference to this comment and the one about tags.
This particular builder is currently single-use so after build it's expected that it won't be useful (e.g. same as after move). I guess I could reset the state and throw if a second build is attempted, but it seems unnecessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added a comment to make it clear that after build it should be considered invalid.
I did consider caching the rule builders and reusing them later but I'll leave that for a future improvement.
} | ||
|
||
auto rs = std::make_shared<ruleset>(); | ||
rs->insert_rules(final_base_rules_.items(), final_user_rules_.items()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why are rules individually managed through shared_ptr
instead of final_base_rules_
being themselves behind a shared pointer (like the actions). Because AFAICT, whenever we have a base_rule_update
, we rebuild the rules from scratch:
if ((current_changes & base_rule_update) != change_set::none) {
final_base_rules_.clear();
// ...
for (const auto &builder : rule_builders) {
if (builder->is_enabled()) {
final_base_rules_.emplace(builder->build(*actions_));
}
}
Same applies to the user rules.
As mentioned earlier, I would feel much safer if these rules had their immutability enforced by the compiler, to prevent races.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll address this in the next PR.
rs->insert_filters(rule_filters_); | ||
rs->insert_filters(input_filters_); | ||
rs->insert_preprocessors(preprocessors_); | ||
rs->insert_postprocessors(postprocessors_); | ||
rs->rule_matchers = rule_matchers_; | ||
rs->exclusion_matchers = exclusion_matchers_; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same comment as with the rules. These are always build all together, so they could all be behind one single (for instance) shared_ptr<const map<string, unique_ptr<matcher::base>>>
.
It seems the only case where this doesn't apply is the scanners
, where a copy of the vector is needed because global_config.scanners
can be modified at any point and actions
, which already do it that way
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above, I'll address this in the next PR.
The remaining comments will be addressed in the next PR as this one is just too large at this stage. |
This PR introduces a large change which is hereby referred to as the WAF builder. This new builder allows for the generation of WAF instances through independent configurations, while keeping track of all independent configurations so that they can be updated and / or removed in order to update an existing instance.
Configurations are uniquely identified by their path and can provide any and all of the supported primitives, which will be subsequently consolidated in order to generate a consistent instance. Generally, the WAF builder holds both the configurations (and specifications), as well as common evaluation primitives, for example, generated rules are kept and reused on each new instance for as long as they don't need to be updated. Similarly, expressions within filters, rules and processors are also preserved across updates, when relevant.
The change has also involved a large refactoring of the ruleset parser into multiple independent parsers all feeding into a single configuration, while keeping track of their individual changes. This refactor has resulted in configuration parsers and a separate set of "builders" which can be used to generate instances of each evaluation primitive based on all of the required elements that feed into their creation.
An example of using the new builder interface can be the following:
Pending tasks (to be addressed in new PRs)
Required:
error
,warning
)Optional:
Related Jira: APPSEC-55064