Once a CSP has been correctly configured such that no violations occur in normal circumstances, it seems desirable to place some restrictions on the number of violations which will be logged (by the default Drupal handler) should violations start to occur.
Picking sensible default values for these limits is a bit tricky...
If the CSP is correct and there are violations, then the number of reports will depend on the violation(s) and of course the popularity of the page(s) in which they occur.
If the CSP is incorrect (or becomes incorrect with subsequent changes to the site), then there may be many unintentional violations. (e.g. if triggered by a new global page element on a popular site.)
In either case I don't think that SecKit can predict what those numbers might actually look like for any given site, so my broad inclination is to base restrictions on "what's liable to cause performance problems". Which of course is also incredibly variable, but is probably still a better basis for extracting some practical baseline figures from.
The numbers I've pulled out of the air are:
Maximum CSP reports by all users: 1000 in a 3600 second (1 hr) window
Maximum CSP reports by a single user: 100 in a 900 second (15 min) window
Maximum report size: 4096 bytes (which should be excessive for a real report).
These are high enough that they could still cause some undesirable effects (dblog's default row limit is 1000 for starters), but I don't want to try to detect and adjust figures based on whether any particular watchdog module is enabled and how it's configured.
(Any recommendations are welcomed.)
Each of those limits is configurable, and they can also be disabled entirely, but for sites with existing CSPs, the limits will be enforced by default.
People in the initial stages of configuring a CSP would almost immediately blow past those per-user limits, however, which would mean that useful reports for establishing a CSP would be lost; so hook_install overrides this default in order to allow unlimited reporting, so that we do not cause problems for users who newly install SecKit. Similarly if SecKit is already installed, but the CSP configuration was in its default state, we do the same thing (in an update hook) so that if/when they start working on a CSP, they will start out with unlimited reporting.
n.b. I don't feel there are any sensible values which would cater both for initial CSP development and for subsequent flooding of a stable CSP.
| Comment | File | Size | Author |
|---|---|---|---|
| #52 | interdiff-2694029-49-52.txt | 4.91 KB | jweowu |
| #52 | 2694029-support-flood-control-52.patch | 17.9 KB | jweowu |
| #49 | interdiff.txt | 3.73 KB | kmoll |
| #49 | 2694029-support-flood-control-49.patch | 17 KB | kmoll |
| #40 | 2694029-support-flood-control-40.patch | 16.7 KB | kmoll |
Issue fork seckit-2694029
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
jweowu commentedComment #3
jweowu commentedComment #4
jweowu commentedI've previously given this a fair amount of testing myself, and no one has commented in the past two months, so I've pushed this to 7.x-1.x.
Comment #6
jweowu commentedNeeds port to 8.x
Comment #7
kmoll commentedI can port this over to D8. Based on the description I feel this is a sensible way to do it. By default things are the same and open. People can then choose to adjust the setting to prevent any sort of performance issue.
Comment #8
kmoll commentedComment #9
kmoll commented@jweowu I was wondering why there is the disable seckit value in the config. Shouldn't users disable the module to ensure that functionality?
Comment #10
jweowu commentedMostly it's just a convenience which was trivial to add. It lets people toggle the functionality on and off when testing, without the annoyance of a full module disable/enable (which may be irritatingly slow on larger sites).
Comment #11
kmoll commentedFair enough.
One question though, I am trying to write a test for this, but it doesn't seem as if the simpletest browser test case sends the report, so not sure its possible to write automated test for this. Do you know if this is possible or not with simpletest?
Comment #12
kmoll commentedupdated patch for D8.
Comment #13
kmoll commentedComment #15
jribeiro commented+1 RTBC
Comment #16
jweowu commented> Do you know if this is possible or not with simpletest?
Not offhand, sorry. I never tried to write a test for this.
Comment #17
naveenvalechaThanks for the patch
We are adding more keys in the config. This needs an hook_post_update_NAME for adding new keys to the config store. and a test class that would extend UpdatePathTestBase
use injectable t instead of t i.e. use $this->t
Address it at other places as well.
Remove this commented line as the title makes more sense.
Inject the request stack instead of naked usage of $_POST
is commented code needed here ? if not remove it.
Comment #18
kmoll commentedI have updated the patch to reflect these comments.
Comment #19
kmoll commentedComment #20
jweowu commentedCould someone please make the unrelated
t()vs$this->t()changes into a separate issue/patch so that can be committed independently/first. The patch for this can depend on that one.If we can separate patches for general D8 changes from patches for specific features, the code history will be rather cleaner. I'm not too fussed for really trivial additions; but in this case the patch jumped from 15K to 53K, which is a lot of extra noise.
Comment #21
naveenvalechaRegarding #17.1,
Maintainers,
As the module has relesed a alpha yet and in the alpha phase the changes in config schema is possible as drupal core follows. So do we really need the hook_post_update_NAME ? If yes then I think no.
#20.1
I'm +1 to this. Let's limit the scope of this issue.
Comment #22
kmoll commentedAs far as the update, I don't think its completely necessary given the alpha state, but Its there in the patch, so I don't see any harm in keeping it in, unless there is some precedence that states that it absolutely shouldn't be there.
I am also +1 for removing the $this->t, I can create another issue for that and update this patch.
Comment #23
kmoll commentedComment #24
naveenvalechaI was more concerned about the hook_update_n in the module which is not unit-testable.So in my opinion instead of pushing the non-testable code, better to remove it if its not needed.Rest is all on maintainers.
Comment #25
kmoll commentedI've reverted the $this->t() so the patch doesn't have to put a dependency on another patch.
Comment #26
kmoll commentedactually, need to update the 'seckit_advanced' section to use $this->t() because that section is not in the patch for #2788701: Use t() using DI in SecKitSettingsForm
Comment #27
kmoll commentedComment #28
naveenvalechaGreat thanks for the patch! Few more that are needed to let me RTBC it
Move this function to a service with name may be "seckit.helper" choose a better name if you know.
What's the need of getting the seckit settings again. you can access it from $this->config which is getting set in constructor
same as above. access it from $this->config
+1
+1 Nice :)
use injectable $this->t and use single quotes
dot at the end of the comment.
use single quotes. Its not a standard yet but php gives performance hits using single quotes over doubel
Comment #30
jweowu commentedn.b. I've pushed a couple of improvements to the 7.x-1.x branch:
http://cgit.drupalcode.org/seckit/commit/?id=316bb9b506eee3d55064aad7291...
http://cgit.drupalcode.org/seckit/commit/?id=1331c952cb7fe0341c75f218fee...
Comment #31
kmoll commentedas far as #1, I think there are a lot of methods that need to go in this. I agree that we need a service and it should be implemented. I think its outside the scope of this 1 issue. I think we should create another issue for it. I've addressed all the other issues as well as implemented the patch that @jweowu just posted.
Comment #32
kmoll commentedComment #33
naveenvalecha+1 Create a followup issue for it
As specified above we need hook_post_update_NAME for the config update changes. Profit for this is hook_post_update_NAME is testable as well.
Move the comments to above. This is violating the coding standards.Address it at other places as well.
Comment #34
kmoll commentedRather than having the update at all, I am removing this since its an alpha release and we don't necessarily need to support this. I fixed #2 for the code added in the patch, but there is already an issue to fix coding style for the rest of the module here #2715611: Fix coding style to meet Drupal standards so I did not address it in other places.
Comment #35
kmoll commentedComment #36
naveenvalechayup we don't need during alpha phase
Thanks but we should not pollute more.Fix this at other places too. I'll be more comfortable to RTBC it after these gets fixed
Comment #37
jweowu commentedI recently edited a second 7.x commit into my comment #30, so that will need to be included too. Apologies for the reduced visibility on that change.
Comment #38
naveenvalecha@jweowu ,
could you share the link of the commit & issue ?
Comment #39
jweowu commentedI'm just referring to comment #30 above.
The first commit linked in that comment has been incorporated in the 8.x patch, but the second one (which is more recent) has not.
Comment #40
kmoll commentedI've updated this with the updates from the second patch.
@naveenvalecha, I updated the coding style for the sections that I updated with this patch to not pollute it more. I disagree with updating it in other places as that is already taken care of by the issue I linked to above.
Comment #41
jweowu commentedIt's not correct to
throw new NotFoundHttpException();when the module or CSP option is disabled. The violation report may be entirely valid (from a cached page).In the 7.x branch I never return a non-success response, even if I'm ignoring the report.
(Unless there's some beneficial standardised behaviour for user agents if they receive a 404 that you're aware of?)
Comment #42
kmoll commentedjweowu, I don't necessarily need to throw a 404, but I can not just return, The API controller requires a specific Response object, I can return a blank object I believe which will be a 200. But if CSP is not enabled, I would think that it should act as if the call back doesn't exist, which IMO would be a 404.
Comment #43
naveenvalechaI'm fine with that
RTBC +1 leaving this for @jweowu to see his changes in #30 got accomodated
Comment #44
jweowu commentedWe don't differentiate between "never been enabled" and "temporarily disabled" though, and I also don't know whether a user-agent might react differently if it gets a 404. If they don't, then there is no benefit to using an error response over a 200. If they do then there is a real potential to cause problems for CSP testing.
Consider that a user agent might decide not to send any more violation reports for some duration, if it has received a 404...
When the site admin enables the CSP feature, pages will start being cached with CSP headers and so browsers may send violation reports. If the feature is then disabled, the cached pages will still contain the headers, and the site then responds to subsequent reports with a 404. When the feature is re-enabled, the browser may fail to send the violation reports that it should send (which may or may not be apparent to the site admin, depending on the significance of the violation; but it won't be clear what's gone wrong either way).
In short, I don't think there's any problems with using a 200 response in all cases, whereas there might be problems not doing that, and this is why I haven't used a menu access callback, or returned errors.
Tangentially, while D8 code in general is all kinds of different to D7 code, I don't wish the essential behaviour of this module to diverge between the two versions. Behavioural changes really need to be flagged for discussion and porting (in one direction or the other).
Furthermore, I'd really prefer if this approach extended to not arbitrarily modifying the original code in areas where the two versions can actually coincide. Once the D8 version stabilises, we're going to want to maintain both versions together as much as possible, and the fewer unnecessary differences between the versions, the easier that's going to be.
A simple example in this patch being the change from:
to
Here we have a double-negative in "not unlimited" which hinders comprehension. I had purposefully countered that by assigning it the positive label "enforce limits" to use in the subsequent conditionals. I would argue therefore that this change is (a) worse for code readability and (b) worse (on account of being arbitrarily different) for subsequent module maintenance across versions, as any later changes need to take the inversion into account.
In the same patch, we have a sequential change. In the D7 code I check the server variables first, before loading the seckit config, in order to defer loading the config until we need to. In the D8 patch the order has changed. I don't think this specific instance is super important -- loading the config isn't going to be slow, and the violation requests aren't likely to fail those tests -- but it's different which in general means (a) we might have a logical bug in one version and not the other; and (b) when porting changes people need to question why it's different, and then expend mental effort considering whether the differences have consequences.
Multiply this by any number of unnecessary changes, and it has a practical effect on the ease of maintenance that I would be glad to avoid.
If the code can be improved by doing something differently to the D7 version, that's great -- but let's make those changes in both versions; and let's not just change things unnecessarily? Sometimes things are the way they are for a reason, after all.
Comment #45
kmoll commentedI completely agree. Makes sense about returning a 200 rather than a 404. I had not thought of the case of the browser agent response if it had already received a 404.
As far as the code, I see your point with the way the variables are named and used. There are a few methods in D8 that we used as improvements, i.e. loading things from the config rather than using the defaults array, I am not sure that affects functionality and there should be no performance hits to calling the settings from config, but I see your point about code readability and comprehension and can update those sections to ensure that the essence of the code remains intact with the D7 version. Do you prefer its update in this patch, or should we create an issue to review the code and update things like that?
Comment #46
jweowu commentedIf you could update the bits which are specific to this patch, that would be good.
Differences which are reflected throughout the D8 version of the module should be deferred to a later review, as presumably we'll either want to keep them all or change them all.
Comment #47
kmoll commentedComment #48
kmoll commented@jweowu sounds good, I will take a quick look to ensure the changes specific to this patch are updated, then we can do a deeper review of the entire port.
Comment #49
kmoll commentedI've updated the responses to be 200 instead of 404, and updated the logic in conditionals to exactly match D7 version.
Comment #50
kmoll commentedComment #51
kmoll commented@jweowu are you able to review the latest patch?
Comment #52
jweowu commented(edit: I should have marked that as "no-test" for the test bot -- I forgot that I'd needed to apply the original patch from #49 to HEAD^ , so this all needs re-rolling....)
I've just looked at SeckitExportController.php vs http://cgit.drupalcode.org/seckit/tree/seckit.module?h=7.x-1.x#n307 and have updated the D8 code to better align with the D7 code, re-organising the sequence of checks, updating comments, and eliminating the last NotFoundHttpException(). This is from eye-balling the code only; I have not tested the changes.
It occurs to me that I don't understand why this controller/method is named "export". To me, that term does not seem at all relevant to the behaviour. Why was that name used? (I feel that either there's some standard D8 use for the term "export" that I'm unaware of, or else that it should be changed. Something like SeckitCspViolationReportController::processReport() perhaps? I'm even wondering whether this was a typo -- "export" in place of "report" ??)
Lastly, the following comment appears twice:
I find the phrasing/grammar a bit awkward and unclear, but I'm also not sure exactly what it's referring to in context. Which exception? What code raises it? The only explicit throw in that method (which I've now removed) was happening before either of these comments.
Comment #54
chop commentedWouldn't registering two flood events would be safer and simpler than copying the Database Backed Flood service query into Seckit?
Couldn't you register a global and user flood event like this:
Then, couldn't you check a global and user flood event like this:
IMO this would be safer, as it doesn't need the Seckit module to assume / understand the schema and queries used by the Flood service.
Comment #55
chop commentedIncreasing priority.
For as long as this issue remains unresolved seckit with CSP on Drupal 8 represents a potential DDOS vector.
We have client sites where we've had to block the csp violation report URI at the edge WAF because misconfiguration of Seckit CSP rules (by the client) has lead to Database deadlock exceptions.
The core problem is that these POST requests bypass all cache layers used in a high availability architecture. They are passed through Cloudflare CDN, Varnish. A percentage of reports are also causing the HTTP 404 page, bypassing fast 404, because the browser didn't conform with the expected HTTP Request Content-Type headers.
We'll be applying this patch above as it currently stands and then testing some more. I've raised this with our team here and we'll try to get some resources focused on helping to progress with the fix.
Comment #56
jweowu commentedThe chosen solution isn't ideal, but neither is the alternative. I recommend that we keep things as they are. If the core enhancement is implemented then we can transition to that API. (Maybe you'd even be interested in implementing that API improvement?)
FYI my comments at the time to the security team were:
To which the response was:
Comment #57
chop commentedIt's arguably better to use the API we have and maintain the separation of concerns, rather than creating a tight coupling between Seckit and the current Flood service.
That's all.
Comment #58
mxr576What is missing to get this fixed?
Comment #59
jweowu commentedPrimarily #2472941: Facilitate global (spanning all users) flood tests, which addresses the API deficiency which was under dispute above. I started work on that as a result of this, but stalled when I couldn't work out how to run the tests manually so that I could work out what I was doing wrong. (The testing framework seemed to be an absolute beast of a thing, and after trying and failing with about three separate approaches to running them, I set it aside, and never got back to it. I don't work with Drupal 8, so I have no familiarity with these things.)
So you could help with that issue.
Comment #60
chop commentedLinking the related core enhancement issue for Flood control API.
Agree with @jweowu that if we're to use Drupal core Flood Control API it needs to be extended to support global event logging without duplicating stored events using a static key.
It is a pity that #2472941: Facilitate global (spanning all users) flood tests hasn't had any real attention. I think the proposal is solid and you said the security team were at least receptive to it. We just really need to get that core issue tested and sorted so this can proceed.
Comment #63
geoffreyr commentedWe're actively looking at using the patch from #52 in production so we've created a MR for it. It also incorporates some minor fixes for D10 compatibility; there may be more adjustments to come. Will look at 2472941 when I have the chance.
Comment #65
geoffreyr commentedRerolled for 2.x in MR 52.
Comment #66
geoffreyr commentedRevisiting this patch after a while on a 10.6 site. It's still got a reference to REQUEST_TIME in there which I'll have to remove.