[PL-133773] Add initial ModSecurity implementation#2196
[PL-133773] Add initial ModSecurity implementation#2196nichmoe wants to merge 1 commit intofc-25.11-devfrom
Conversation
| }; | ||
|
|
||
| config = { | ||
| configContent = mkModsecurityConfig config; |
There was a problem hiding this comment.
Ambiguous naming, which config is this supposed to refer to? My intuition would be cfg, but you provide config which refers to the whole top-level NixOS system config?
There was a problem hiding this comment.
It is probably a better idea to simplify the option definitions into one set of options, instead of deferring to multiple submodules, and then puzzel this together in the single config block with mkIf conditionals.
There was a problem hiding this comment.
This config attribute name refers to the top-level config of this submodule scope. Let's see if this could be renamed or has to be this way.
The general idea here is that the rulesetImplModule generates a modsecurity config that is standalone in a way that it could be used in several places like nginx, apache, or other supporting software. This is then expose dthrough the configContent option.
According to @dpausp, this is built this way to avoid infinite recursions encoutered when building otherwise. (together with lazyAttrs in line 96).
| }; | ||
|
|
||
| services.nginx.virtualHosts = mkOption { | ||
| type = types.attrsOf (types.submodule nginxIntegrationSubmodule); |
There was a problem hiding this comment.
What is the reason of deferring this to an own submodule? At least now, the nginxIntegrationSubmodule does not even provide an option interface on its own, so it would be straightforward to just inline the code here.
There was a problem hiding this comment.
Sensible point, will be set inline.
| options = with lib; { | ||
| flyingcircus.modsecurity = { | ||
|
|
||
| enable = mkEnableOption { }; |
There was a problem hiding this comment.
AFAIK this is the wrong type signature, usually an option description goes here. Did this ever evaluate successfully?
There was a problem hiding this comment.
It did evaluate, but it will most likely blow up, when something tries to get the description. This needs fixing.
There was a problem hiding this comment.
Yes, thhis is the wrong type signature, but currently only causes the description to fail to evaluate.
| inherit lib pkgs rules; | ||
| }; | ||
|
|
||
| rulesetOptionsModule = { |
There was a problem hiding this comment.
Why does the submodule definition have its own file, do you plan to re-use it somewhere else?
There was a problem hiding this comment.
Can be moved into the rulesetImplModule, or a renamed equivalent.
|
|
||
| mkModsecurityConfig = | ||
| rules: | ||
| (import ./mk-modsecurity-config.nix) { |
There was a problem hiding this comment.
Why does this function definition have its own file, do you plan to re-use it somewhere else?
There was a problem hiding this comment.
This isn't reused but could be in the future, see ModSecurity for Apache.
| @@ -0,0 +1,92 @@ | |||
| { lib, ... }: | |||
|
|
|||
| with lib; | |||
There was a problem hiding this comment.
top-level with declarations are strongly discouraged against.
I know when have plenty of those in our code, but for new modules I prefer starting with a cleaner slate.
| }; | ||
| }; | ||
|
|
||
| services.nginx.virtualHosts = mkOption { |
There was a problem hiding this comment.
This conflicts with the approach taken in
fc-nixos/nixos/services/nginx/default.nix
Lines 325 to 348 in 875c603
There was a problem hiding this comment.
The mkBefore approach taken here is probably even preferrable to the apply approach in the nginx module, as it composes better.
Just make sure to unify both sides to take the same approach, I suggest adjusting the code in ngix/default.nix.
| ... | ||
| }: | ||
| let | ||
| cfg = config.flyingcircus.modsecurity; |
There was a problem hiding this comment.
I suggest moving this downwards to flyingcircus.services.modsecurity or, as this is currently nginx-specific, even flyingcircus.services.nginx.modsecurity.
There was a problem hiding this comment.
Moving to flyingcircus.services.modsecurity is the best solutions, as ModSecurity can also be used with Apache.
There was a problem hiding this comment.
I am not really convinced by several architecture decisions taken here, these are a bit unusual at least.
Additionally, please provide an actual example config using this module so I can make sense of what this is used for in practice. Then we can simplify the module together.
| configContent = mkModsecurityConfig config; | ||
| rulesFile = pkgs.writeText name config.configContent; | ||
| nginxConfig = config.mkNginxConfig config.nginxMode ""; | ||
| mkNginxConfig = |
There was a problem hiding this comment.
The idea here was to expose this function as a handle for deployments to re-use, enabling more composability.
There was a problem hiding this comment.
Okay, I think it still makes sense to expose this for re-use in a deployment, but here is my idea:
- turn this into a pure function: add the ruleset to use as a 3rd argument
- then expose this as a function, similar to like generators are exposed in the NixOS lib code
Because this is not really an option in the option sense of "it makes use of the NixOS evalModules for achieving merging of option values, checking eligible option values, and keeping state. It's just a convenience function.
Update: At a closer look, it is apparently not that simple to expose plain non-options under the same naming scope as the surrounding options. Then at least mark the option as lib.types.raw.
| (lib.optionalString (nginxMode != "Transparent") "modsecurity ${(lib.toSentenceCase nginxMode)}") | ||
| "modsecurity_rules_file ${config.rulesFile}" | ||
| "modsecurity_transaction_id ${config.transactionID}" | ||
| extraConfig |
There was a problem hiding this comment.
extraConfig needs a trailing newline.
There was a problem hiding this comment.
flyingcircus.services.nginx.virtualHosts."modsectest.test.fcio.net" = {
forceSSL = true;
enableACME = true;
extraConfig = config.flyingcircus.modsecurity.rulesets."modsectest.test.fcio.net.foo.bar".mkNginxConfig "On" "##foo";
};
Creates
modsecurity On;
modsecurity_rules_file /nix/store/mxs83kyjnsv38k91cl6q3ynppjlvrph6-modsectest.test.fcio.net.foo.bar;
modsecurity_transaction_id $request_id;
##fooaccess_log /var/log/nginx/access-modsectest.test.fcio.net.log;
error_log /var/log/nginx/error-modsectest.test.fcio.net.log;
osnyx
left a comment
There was a problem hiding this comment.
After reviewing this together, here are the suggestions for restructuring this:
- Split this into 2 main modules, similar to how
security.acmedefines the certificates that are then used and referenced by several other modules, like in the nginx modules flyingcircus.services.modsecuritymodule: merge the rulesetOptionsModule and rulesetImplModule. Specifying a ruleset and then rendering it into an actual modsecurity config format are tightly coupled- we might even be able to get rid of the submodule-level
configin line 62, as this is now a module on its own, not a submodule
- we might even be able to get rid of the submodule-level
flyingcircus.(?)services.nginx.virtualHostsreceives an option that can then reference these rule sets and configure their particular usage within that nginx host- this is then also the place for helpers generating the nginx-specific config embedding
Avoid convoluting submodules unless they are really necessary. The nginxIntegrationSubmodule stops being a submodule and instead configures nginx directly.
@flyingcircusio/release-managers
PR release workflow (internal)
Design notes
onoroff. Example: rate limiting.Security implications
No specific requirements
Manual tests on dev server
This PR adds a minimal initial integration of ModSecurity. It is intended to be properly documented and supported with tests, after a bit of practical testing and further development.
See PL-133773