-
Notifications
You must be signed in to change notification settings - Fork 248
DRIVERS-3329: Configurable DNS domain validation for SRV records #1950
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
base: master
Are you sure you want to change the base?
Changes from all commits
8c7bc69
790505e
914c830
529ad3b
6556c30
871c812
c3f590c
2dd89df
86d1633
013594b
8122297
78ff515
403ec19
fde06d6
d2731ce
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -37,7 +37,8 @@ mongodb+srv://{hostname}/{options} | |
| `{options}` refers to the optional elements from the [Connection String](../connection-string/connection-string-spec.md) | ||
| specification following the `Host Information`. This includes the `Auth database` and `Connection Options`. | ||
|
|
||
| For the purposes of this document, `{hostname}` will be divided using the following terminology. If an SRV `{hostname}` | ||
| For the purposes of this document, `{hostname}` will be divided using the following terminology. If | ||
| `srvAllowedHostsSuffix` has been configured, then that will act as the `{domainname}`. Otherwise, if an SRV `{hostname}` | ||
|
Comment on lines
+40
to
+41
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Question for the rationale rather than a change request: nothing requires I initially thought that should be forbidden, but I don't think it should — it would break the vanity-CNAME shape, where the SRV name is a corporate alias and the hosts live in a provider domain, which seems like one of the cases this option might be used for. So I'd just like the rationale to say that decoupling is deliberate and why, since it's the point where the trust anchor stops being tied to the name the user typed.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. that is correct, I've added this to the rationale in d2731ce |
||
| has: | ||
|
|
||
| 1. Three or more `.` separated parts, then the left-most part is the `{subdomain}` and the remaining portion is the | ||
|
|
@@ -65,6 +66,27 @@ Only `{domainname}` is used during SRV record verification and `{subdomain}` is | |
|
|
||
| ### MongoClient Configuration | ||
|
|
||
| #### srvAllowedHostsSuffix | ||
|
|
||
| This option is used to validate hosts. If present, its value MUST be treated as the `{domainname}` for | ||
| [DNS validation](#querying-dns) and | ||
| [SRV polling](../polling-srv-records-for-mongos-discovery/polling-srv-records-for-mongos-discovery.md). For example, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The option description now says it applies to SRV polling but that spec document isn't touched by this PR and still carries its own normative sentence: More subtly, the two documents now use
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oo thanks for pointing that out! fixed in d2731ce |
||
| `srvAllowedHostsSuffix=.mydomain.net`. Drivers MUST apply the following normalization and validation to the value, in | ||
| this order: | ||
|
|
||
| 1. Any trailing `.` MUST be stripped. For example, `srvAllowedHostsSuffix=.mydomain.net.` is treated as `.mydomain.net`. | ||
| 2. The value MUST be converted to its A-label (Punycode) form, so that it is comparable against the A-label hostnames | ||
| returned by DNS. | ||
| 3. The value MUST be normalized to lowercase using ASCII case folding. | ||
| 4. The resulting value, with any leading `.` removed, MUST NOT be a public suffix, per the algorithm in | ||
| [Public Suffix List](../public-suffix-list/public-suffix-list.md). | ||
| 5. If the value does not begin with a `.`, a `.` MUST be prepended. For example, `srvAllowedHostsSuffix=mydomain.net` is | ||
| treated as `.mydomain.net`. | ||
|
|
||
| If this option is not present, the `{domainname}` MUST be inferred from the `{hostname}` (as described in | ||
| [Connection String Format](#connection-string-format)). This option MUST only be configurable at the level of a | ||
| `MongoClient`. | ||
|
rozza marked this conversation as resolved.
|
||
|
|
||
| #### srvMaxHosts | ||
|
|
||
| This option is used to limit the number of mongos connections that may be created for sharded topologies. This option | ||
|
|
@@ -84,9 +106,9 @@ requires a string value and defaults to "mongodb". This option MUST only be conf | |
|
|
||
| #### URI Validation | ||
|
|
||
| The driver MUST report an error if either the `srvServiceName` or `srvMaxHosts` URI options are specified with a non-SRV | ||
| URI (i.e. scheme other than `mongodb+srv`). The driver MUST allow specifying the `srvServiceName` and `srvMaxHosts` URI | ||
| options with an SRV URI (i.e. `mongodb+srv` scheme). | ||
| The driver MUST report an error if any of `srvServiceName`, `srvMaxHosts`, or `srvAllowedHostsSuffix` URI options are | ||
| specified with a non-SRV URI (i.e. scheme other than `mongodb+srv`). The driver MUST allow specifying the | ||
| `srvServiceName`, `srvMaxHosts`, and `srvAllowedHostsSuffix` URI options with an SRV URI (i.e. `mongodb+srv` scheme). | ||
|
|
||
| If `srvMaxHosts` is a positive integer, the driver MUST throw an error in the following cases: | ||
|
|
||
|
|
@@ -124,10 +146,16 @@ If the DNS result returns no SRV records, or no records at all, or a DNS error h | |
| indicating that the URI could not be used to find hostnames. The error SHALL include the reason why they could not be | ||
| found. | ||
|
|
||
| Before validating returned hostnames, drivers MUST normalize them as follows: | ||
|
|
||
| - Trailing dots MUST be stripped (e.g. `host.example.com.` becomes `host.example.com`). | ||
| - Hostnames MUST be normalized to lowercase using ASCII case folding. | ||
|
|
||
| A driver MUST verify that the host names returned through SRV records share the original SRV's `{domainname}`. In | ||
| addition, SRV records with fewer than three `.` separated parts, the returned hostname MUST have at least one more | ||
| domain level than the SRV record hostname. Drivers MUST raise an error and MUST NOT initiate a connection to any | ||
| returned hostname which does not fulfill these requirements. | ||
| addition, when `srvAllowedHostsSuffix` is not configured and the SRV record hostname has fewer than three `.` separated | ||
| parts, the returned hostname MUST have at least one more domain level than the SRV record hostname. Drivers MUST raise | ||
| an error and MUST NOT initiate a connection to any returned hostname which does not fulfill these requirements. This | ||
| additional requirement does not apply when `srvAllowedHostsSuffix` is configured. | ||
|
|
||
| The driver MUST NOT attempt to connect to any hosts until the DNS query has returned its results. | ||
|
|
||
|
|
@@ -247,6 +275,39 @@ Several of our users have asked for this through tickets: | |
| The design specifically calls for a pre-processing stage of the processing of connection URLs to minimize the impact on | ||
| existing functionality. | ||
|
|
||
| ### Rationale for `srvAllowedHostsSuffix` | ||
|
|
||
| By default, the parent domain that returned host names are checked against is inferred from the seed by treating its | ||
| leftmost label as the `{subdomain}`. A seed of `mongodb.mydomain.net` results in `mongodb1.us-east-1.mydomain.net` being | ||
| rejected, even when both are owned by the same user. `srvAllowedHostsSuffix` allows users to work around this limitation | ||
| by letting the user state the parent domain directly instead, admitting such nested names and hosts in a different | ||
| domain altogether. | ||
|
|
||
| Note that nothing requires the `{hostname}` itself to end with the configured suffix. For example, | ||
| `mongodb+srv://cluster.example.org/?srvAllowedHostsSuffix=.hosts.example.net` is valid, even though | ||
| `cluster.example.org` does not end with `.hosts.example.net`. This is intentional: the option exists so the user can | ||
| state the allowed parent domain explicitly, rather than having it inferred from the connection string. Requiring the two | ||
| to match would restore that coupling, and would rule out arrangements such as a vanity alias in one organization's | ||
| domain resolving to hosts in a provider's domain. | ||
|
|
||
| Because it replaces that default requirement, this option relaxes a DNS spoofing safeguard. An attacker able to forge | ||
| SRV responses is otherwise confined to host names under the seed's `{domainname}`; with the option set, they are | ||
| confined to host names under the configured suffix instead. The broader that suffix, the more hosts a forged response | ||
| can direct a driver to. | ||
|
|
||
| Requiring that the value not be a public suffix bounds how broad it can get. It rules out values such as `.com` or | ||
| `.co.uk`, which would place no meaningful limit on a forged response. It does not, however, make a given configuration | ||
| safe: `.example.com` is not a public suffix, but it still admits every host in a large organization's domain. | ||
|
|
||
| Users should be encouraged to configure the narrowest suffix that covers their deployment, and drivers should say so | ||
| wherever the option is documented. For a seed hostname of `cluster.test.internal.example.com`, prefer the second of | ||
| these: | ||
|
|
||
| ```text | ||
| mongodb+srv://cluster.test.internal.example.com/?srvAllowedHostsSuffix=.example.com | ||
| mongodb+srv://cluster.test.internal.example.com/?srvAllowedHostsSuffix=.internal.example.com | ||
| ``` | ||
|
|
||
| ## Justifications | ||
|
|
||
| ### Why Are Multiple Key-Value Pairs Allowed in One TXT Record? | ||
|
|
@@ -283,6 +344,8 @@ In the future we could consider using the priority and weight fields of the SRV | |
|
|
||
| ## ChangeLog | ||
|
|
||
| - 2026-08-24: Add `srvAllowedHostsSuffix` MongoClient option. | ||
|
|
||
| - 2024-09-24: Removed requirement for URI to have three '.' separated parts; these SRVs have stricter parent domain | ||
| matching requirements for security. Create terminology section. Remove usage of term `{TLD}`. The `{hostname}` now | ||
| refers to the entire hostname, not just the `{subdomain}`. | ||
|
|
||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| # Tests that srvAllowedHostsSuffix comparison is case-insensitive (ASCII case folding per RFC 4343). | ||
| # The suffix .BUILD.10GEN.CC is uppercase but should match the returned host localhost.build.10gen.cc. | ||
| uri: "mongodb+srv://test12.test.build.10gen.cc/?srvAllowedHostsSuffix=.BUILD.10GEN.CC" | ||
| seeds: | ||
| - localhost.build.10gen.cc:27017 | ||
| options: | ||
| srvAllowedHostsSuffix: .BUILD.10GEN.CC | ||
| ssl: true | ||
| ping: false |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| # DNS record for test12.test.build.10gen.cc returns localhost.build.10gen.cc which would not match test.build.10gen.cc | ||
| uri: "mongodb+srv://test12.test.build.10gen.cc/?srvAllowedHostsSuffix=test.build.10gen.cc" | ||
| seeds: [] | ||
| hosts: [] | ||
| error: true |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| # srvAllowedHostsSuffix MUST NOT be a public suffix. "cc" is an ordinary rule in the | ||
| # Public Suffix List, so ".cc" is a public suffix and MUST raise an error. | ||
| # Note: .cc is used (rather than an unrelated public suffix) because the DNS records | ||
| # return hosts ending in .cc, so the host suffix check passes and the public suffix | ||
| # check is the only thing that can fail here. | ||
| uri: "mongodb+srv://test1.test.build.10gen.cc/?srvAllowedHostsSuffix=.cc" | ||
| seeds: [] | ||
| hosts: [] | ||
| error: true |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| # A trailing dot in srvAllowedHostsSuffix must be stripped before comparison, | ||
| # so ".build.10gen.cc." is equivalent to ".build.10gen.cc". | ||
| uri: "mongodb+srv://test12.test.build.10gen.cc/?srvAllowedHostsSuffix=.build.10gen.cc." | ||
| seeds: | ||
| - localhost.build.10gen.cc:27017 | ||
| options: | ||
| srvAllowedHostsSuffix: .build.10gen.cc. | ||
| ssl: true | ||
| ping: false |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| # Also tests trailing dot normalization: DNS returns targets with a trailing dot | ||
| # (localhost.build.10gen.cc.) which must be stripped before suffix comparison. | ||
| uri: "mongodb+srv://test12.test.build.10gen.cc/?srvAllowedHostsSuffix=.build.10gen.cc" | ||
| seeds: | ||
| - localhost.build.10gen.cc:27017 | ||
| options: | ||
| srvAllowedHostsSuffix: .build.10gen.cc | ||
| ssl: true | ||
| ping: false |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| # dot should be prepended to `srvAllowedHostsSuffix` causing the host to be .uild.10gen.cc which does not match any available DNS records | ||
| uri: "mongodb+srv://test12.test.build.10gen.cc/?srvAllowedHostsSuffix=uild.10gen.cc" | ||
| seeds: [] | ||
| hosts: [] | ||
| error: true |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| uri: mongodb+srv://test12.test.build.10gen.cc/?srvAllowedHostsSuffix=build.10gen.cc | ||
| seeds: | ||
| - localhost.build.10gen.cc:27017 | ||
| options: | ||
| srvAllowedHostsSuffix: build.10gen.cc | ||
| ssl: true | ||
| ping: false |
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.
There's no security-considerations text for this new option. The Design Rationale section is a single pre-existing sentence and doesn't mention
srvAllowedHostsSuffix. I think we could benefit from a subsection covering: why the option exists; that the PSL bounds the blast radius but doesn't make a configuration safe; guidance to use the narrowest suffix that work. PyMongo already has good material for most of this in its MongoClient docstring warning. Also helps future spec readers.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.
added in d2731ce
honestly i feel a bit off about my wording though? so lmk if you have any suggestions on the wording here!