DRIVERS-3329: Configurable DNS domain validation for SRV records#1950
DRIVERS-3329: Configurable DNS domain validation for SRV records#1950sleepyStick wants to merge 11 commits into
Conversation
rozza
left a comment
There was a problem hiding this comment.
It looks good, I have one comment about if an invalid srvAllowedHostsSuffix should throw an error explicitly? (if so should we update the yml?)
Also the yml and json has diverged for srvAllowedHostsSuffix-without_dot_pass - which is probably the lint error.
| example, `srvAllowedHostsSuffix=.mydomain.net`. If the value does not begin with a `.`, for example, | ||
| `srvAllowedHostsSuffix=mydomain.net`, the `.` MUST be automatically prepended prior to validation. If this option is not | ||
| present, the domain MUST be inferred from the hostname. This option MUST only be configurable at the level of a | ||
| `MongoClient`. |
There was a problem hiding this comment.
Just to clarify, if a host is invalid eg: ..example.com should this error or just return an empty list of hosts?
There was a problem hiding this comment.
oh good question, my current implementation has it eventually erroring (with a configuration error) when the driver goes to validate the SRV hosts -- i didn't add a new "check valid host" type of function. (though this probably wouldn't be too hard with some regex? if you think it'd be beneficial to add something like this.) My initial thought process was this new parameter is just a user configurable way to denote what the domain should be (as opposed to the previous, now default, logic) -- they should know what they're doing if they're using this param and if they happened to make a typo / silly mistake, the existing "invalid SRV host" error message that occurs when there is an invalid host given the previous logic would still apply.
There was a problem hiding this comment.
I think the eventually error part will cover this, so no need for a regex. I can never understand regexes and someone always seems to find a usecase where the regex fails where it shouldn't!
|
|
||
| #### srvAllowedHostsSuffix | ||
|
|
||
| This option is used to validate hosts. If present, its value MUST be treated as the domain for DNS validation. For |
There was a problem hiding this comment.
Optional: Consider linking to the Querying DNS section, which is where the DNS validation is described.
|
Assigned |
…covery.md Co-authored-by: Matt Dale <9760375+matthewdale@users.noreply.github.com>
…covery.md Co-authored-by: Matt Dale <9760375+matthewdale@users.noreply.github.com>
| @@ -0,0 +1,6 @@ | |||
| { | |||
| "uri": "mongodb+srv://test12.test.build.10gen.cc/?srvAllowedHostsSuffix=uild.10gen.cc", | |||
There was a problem hiding this comment.
Looks like the convention in yml is no quotes around uri
| This option is used to validate hosts. If present, its value MUST be treated as the `{domainname}` for | ||
| [DNS validation](#querying-dns). For example, `srvAllowedHostsSuffix=.mydomain.net`. If the value does not begin with a | ||
| `.`, for example, `srvAllowedHostsSuffix=mydomain.net`, the `.` MUST be automatically prepended prior to validation. If | ||
| this option is not present, the`{domainname}` MUST be inferred from the `{hostname}` (as described in |
| uri: "mongodb+srv://test12.test.build.10gen.cc/?srvAllowedHostsSuffix=test.build.10gen.cc" | ||
| seeds: [] | ||
| hosts: [] | ||
| error: true No newline at end of file |
There was a problem hiding this comment.
Also a convention apparently is new lines at the end of yaml files.
|
Setting srvAllowedHostsSuffix to a public suffix neuters SRV anti-spoofing, turning mongodb+srv:// into an unbounded DNS redirect. A merely broad private domain doesn't make it unbounded, but widens the redirect to anywhere under that domain. There is no PSL/minimum-label guard to prevent either. Example: Preconditions:
Step-by-step:
|
|
I think the parsing needs to be much better specified and tested. It's important to understand what DNS entries allow and how to safely handle these things. These should be specified at the spec level, so all drivers will get the parsing correct. For example, most of these are not specified or tested:
There needs to be much better test coverage here. |
|
ajcvickers
left a comment
There was a problem hiding this comment.
From going through the history here, it seems we have acknowledged the security concerns but consciously accepted them and shifted the risk to the customer to meet customer demand. This is a weak security posture: it puts a lot of responsibility on the customer to get everything right, and rejects the safer alternatives (CNAMEs, RFC 4985 SANs).
If we are going to do this, then the risks I have highlighted need to be made front and center so that anyone opting into this is aware that it relaxes the naming-layer anti-spoofing defense and considerably increases their attack surface.
Beyond this, I think there needs to be a product-level guard against the worst misconfigurations (e.g. rejecting public-suffix values) and a fully specified safe-configuration recipe, so "the customer's responsibility" is achievable rather than a trap.
We're exploring some additional safeguards with respect to disallowing public suffixes but short of having a decision on that at the moment, I've added that " [srvAllowedHostsSuffix] MUST contain at least two dot-separated labels (i.e. the portion after the leading
Great call out -- I definitely overlooked this for the spec. I relied on existing logic to handle the case insensitivity and trailing dots and overlooked adding the specifics of that to the specification. I've gone ahead and added a test for both these cases.
The newly added two label minimum on srvAllowedHostsSuffix should satasify this concern therefore its implicitly applicable.
ahh good point -- i had assumed the logic would be reused (because that's what pymongo does) -- this parameter should also be used in SRV polling and I've updated the parameter description accordingly to reflect this.
I don't believe there is any new interactions? Their logic is fairly separated and distinct to me? Am i missing something? |
This doesn't really help very much. Consider .co.uk. @damieng Suggested a different approach here. Rather than us trying to handle every case safely, we could instead provide a hook so that the customer could handle their specific case. It's much easier to handle very specific cases safely, because you are matching actual specific domains, rather than attempting to match a pattern. We could write the safe implementation ourselves and give it to important customers.
I don't know. I'm not an expert on this type of parsing, or on what DNS actually does with non-Ascii characters. Do we have any domain experts in DNS that we can ask?
Sounds good. |
DNS does not support non-ascii chars e.g. |
|
tldr: i don't think the hook is a better idea, the current approach achieves the same thing. but for more details:
Agreed that the current one wouldn't help and that more is needed. I believe we're still contemplating the best way to "manage" a public suffix list across the drivers but the goal is to address this concern.
I messaged @damieng this morning after i read your reply (mainly because i didn't understand what the other approach was and was hoping for a clearer explanation) -- it appears that Damien didn't have the full context of the problem when he came up with this suggestion but I'll clarify on what this suggestion is (for anyone else reading) and then after share my thoughts on it for further discussion. def is_allow_host(...) -> bool:
# whatever the current validation isand then cusomers can do the following: @override
def is_allow_host(host: str, ...) -> bool:
return host.endswith("<their allow host suffix>"))notably this idea wouldn't involve adding def is_allow_host_hook(...) -> bool:
# this is where we'd place "optional" / "recommended" validation that the customer can then override if they decide they know better
def is_allow_host(...) -> bool:
# whatever validation we deem to be an absolute minimum safeguard that all allowed hosts should satisfy
# AND if those pass then
return is_allow_host_hook(...)and then customers can do the following: @override
def is_allow_host_hook(host: str, ...) -> bool:
return host.endswith("<their allow host suffix>")) # or whatever they wantBut at this point, all that absolute minimum validation is the same as the validation that we need to figure out for |
Nothing, but they would have explicitly chosen to have relaxed that security. It gives them the ultimate flexibility and power but only if they clearly opt into it. The problem with doing things like srvAllowedHostsSuffix is it's very limited. The moment a customer wants to say - use two hosts under different suffixes perhaps on different cloud providers they're out of luck again. With a hook they can either hard-code it, read a comma-separated list in from their own configuration etc. If the user wants to also perform our existing validation that's easy enough to do by them calling down to the base method/hook either before adding their own validation or after having allowed through their exception. |
Please complete the following before merging:
Python PR: PYTHON-5814 Configurable DNS domain validation for SRV records mongo-python-driver#2868
clusters).