Skip to content
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

feat(DRIVERS-1803): add new canonicalize host name opts #1135

Merged
merged 5 commits into from
Feb 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
32 changes: 19 additions & 13 deletions source/auth/auth.rst
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,7 @@ mechanism_properties
Drivers MUST allow the user to specify a different service name. The default is "mongodb".

CANONICALIZE_HOST_NAME
Drivers MAY allow the user to request canonicalization of the hostname. This might be required when the hosts report different hostnames than what is used in the kerberos database. The default is "false".
Drivers MAY allow the user to request canonicalization of the hostname. This might be required when the hosts report different hostnames than what is used in the kerberos database. The value is a string of either "none", "forward", or "forwardAndReverse". "none" is the default and performs no canonicalization. "forward" performs a forward DNS lookup to canonicalize the hostname. "forwardAndReverse" performs a forward DNS lookup and then a reverse lookup on that value to canonicalize the hostname. The driver MUST error if any lookup errors or returns no results. Drivers MAY decide to also keep the legacy boolean values where `true` equals the "forwardAndReverse" behaviour and `false` equals "none".
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for explicitly specifying the options. I agree that keeping the legacy boolean values makes sense for backwards compatibility. However you say "Driver MAY decide to also keep the legacy boolean values" but later on have tests requiring this behaviour. We have to either require drivers to accept the boolean values or remove the test that asserts that boolean values work.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've gone and removed those tests now for the MAY wording.


SERVICE_REALM
Drivers MAY allow the user to specify a different realm for the service. This might be necessary to support cross-realm authentication where the user exists in one realm and the service in another.
Expand All @@ -471,7 +471,11 @@ mechanism_properties
Hostname Canonicalization
`````````````````````````

If CANONICALIZE_HOST_NAME is true, the client MUST canonicalize the name of each host it uses for authentication. There are two options. First, if the client's underlying GSSAPI library provides hostname canonicalization, the client MAY rely on it. For example, MIT Kerberos has `a configuration option for canonicalization <https://web.mit.edu/kerberos/krb5-1.13/doc/admin/princ_dns.html#service-principal-canonicalization>`_.
Valid values for CANONICALIZE_HOST_NAME are `true`, `false`, "none", "forward", "forwardAndReverse". If a value is provided that does not match one of these the driver MUST raise an error.

If CANONICALIZE_HOST_NAME is `false`, "none", or not provided, the driver MUST NOT canonicalize the host name.

If CANONICALIZE_HOST_NAME is `true`, "forward", or "forwardAndReverse", the client MUST canonicalize the name of each host it uses for authentication. There are two options. First, if the client's underlying GSSAPI library provides hostname canonicalization, the client MAY rely on it. For example, MIT Kerberos has `a configuration option for canonicalization <https://web.mit.edu/kerberos/krb5-1.13/doc/admin/princ_dns.html#service-principal-canonicalization>`_.

Second, the client MAY implement its own canonicalization. If so, the canonicalization algorithm MUST be::

Expand All @@ -489,8 +493,8 @@ Second, the client MAY implement its own canonicalization. If so, the canonicali
# Unspecified which CNAME is used if > 1.
host = one of the records in cnames

reversed = do a reverse DNS lookup for address
if reversed:
if forwardAndReverse or true:
reversed = do a reverse DNS lookup for address
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CANONICALIZE_HOST_NAME is currently equivalent to forwardAndReverse. This PR changes it to mean forward. Is that the correct behaviour? Why are we potentially breaking backward compat?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As well the Python impl below does not take true versus forward versus forwardAndReverse into account. It still implements the old pseudo code where only true was applicable and had the same meaning as forwardAndReverse.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that we are asking drivers to implement (or wrap libgssapi) to canonicalize host names, do we want to add some host name canonicalization tests to ensure that drivers are performing canonicalization correctly? It seems dangerous to leave this behaviour testing up to each driver individually as it may give rise to differences in implementation.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. I was basing this off the Node implementation which doesn't do the reverse lookup so I'll update to make forwardAndReverse the default. Also yes I will add some more tests as well and ping you when the changes are made.

Copy link
Member Author

@durran durran Feb 4, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated to default true to being "forwardAndReverse", updated the python impl, and added spec test for raising an error on invalid values.

Copy link
Contributor

@jyemin jyemin Feb 4, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Java driver calls java.net.InetAddress#getByName(java.lang.String), followed by java.net.InetAddress#getCanonicalHostName. I think that's the equivalent of forwardAndReverse.... Can you confirm?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that is the equivalent of forwardAndReverse.

canonicalized = lowercase(reversed)
else:
canonicalized = lowercase(host)
Expand All @@ -503,23 +507,25 @@ For example, here is a Python implementation of this algorithm using ``getaddrin
import sys


def canonicalize(host):
def canonicalize(host, mode):
# Get a CNAME for host, if any.
af, socktype, proto, canonname, sockaddr = getaddrinfo(
host, None, 0, 0, IPPROTO_TCP, AI_CANONNAME)[0]

print('address from getaddrinfo: [%s]' % (sockaddr[0],))
print('canonical name from getaddrinfo: [%s]' % (canonname,))

try:
# NI_NAMEREQD requests an error if getnameinfo fails.
name = getnameinfo(sockaddr, NI_NAMEREQD)
except gaierror as exc:
print('getname info failed: "%s"' % (exc,))
if (mode == true or mode == 'forwardAndReverse'):
try:
# NI_NAMEREQD requests an error if getnameinfo fails.
name = getnameinfo(sockaddr, NI_NAMEREQD)
except gaierror as exc:
print('getname info failed: "%s"' % (exc,))
return canonname.lower()
return name[0].lower()
else:
return canonname.lower()

return name[0].lower()


canonicalized = canonicalize(sys.argv[1])
print('canonicalized: [%s]' % (canonicalized,))
Expand Down Expand Up @@ -1063,7 +1069,7 @@ authSource
authMechanismProperties=PROPERTY_NAME:PROPERTY_VALUE,PROPERTY_NAME2:PROPERTY_VALUE2
A generic method to set mechanism properties in the connection string.

For example, to set REALM and CANONICALIZE_HOST_NAME, the option would be ``authMechanismProperties=CANONICALIZE_HOST_NAME:true,SERVICE_REALM:AWESOME``.
For example, to set REALM and CANONICALIZE_HOST_NAME, the option would be ``authMechanismProperties=CANONICALIZE_HOST_NAME:forward,SERVICE_REALM:AWESOME``.

gssapiServiceName (deprecated)
An alias for ``authMechanismProperties=SERVICE_NAME:mongodb``.
Expand Down
41 changes: 38 additions & 3 deletions source/auth/tests/connection-string.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
},
{
"description": "should accept generic mechanism property (GSSAPI)",
"uri": "mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:true,SERVICE_HOST:example.com",
"uri": "mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:forward,SERVICE_HOST:example.com",
"valid": true,
"credential": {
"username": "user@DOMAIN.COM",
Expand All @@ -89,11 +89,46 @@
"mechanism": "GSSAPI",
"mechanism_properties": {
"SERVICE_NAME": "other",
"CANONICALIZE_HOST_NAME": true,
"SERVICE_HOST": "example.com"
"SERVICE_HOST": "example.com",
"CANONICALIZE_HOST_NAME": "forward"
}
}
},
{
"description": "should accept forwardAndReverse hostname canonicalization (GSSAPI)",
"uri": "mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:forwardAndReverse",
"valid": true,
"credential": {
"username": "user@DOMAIN.COM",
"password": null,
"source": "$external",
"mechanism": "GSSAPI",
"mechanism_properties": {
"SERVICE_NAME": "other",
"CANONICALIZE_HOST_NAME": "forwardAndReverse"
}
}
},
{
"description": "should accept no hostname canonicalization (GSSAPI)",
"uri": "mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:none",
"valid": true,
"credential": {
"username": "user@DOMAIN.COM",
"password": null,
"source": "$external",
"mechanism": "GSSAPI",
"mechanism_properties": {
"SERVICE_NAME": "other",
"CANONICALIZE_HOST_NAME": "none"
}
}
},
{
"description": "must raise an error when the hostname canonicalization is invalid",
"uri": "mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:invalid",
"valid": false
},
{
"description": "should accept the password (GSSAPI)",
"uri": "mongodb://user%40DOMAIN.COM:password@localhost/?authMechanism=GSSAPI&authSource=$external",
Expand Down
32 changes: 30 additions & 2 deletions source/auth/tests/connection-string.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ tests:
SERVICE_NAME: "mongodb"
-
description: "should accept generic mechanism property (GSSAPI)"
uri: "mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:true,SERVICE_HOST:example.com"
uri: "mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:forward,SERVICE_HOST:example.com"
valid: true
credential:
username: "user@DOMAIN.COM"
Expand All @@ -73,8 +73,36 @@ tests:
mechanism: "GSSAPI"
mechanism_properties:
SERVICE_NAME: "other"
CANONICALIZE_HOST_NAME: true
SERVICE_HOST: "example.com"
CANONICALIZE_HOST_NAME: "forward"
-
description: "should accept forwardAndReverse hostname canonicalization (GSSAPI)"
uri: "mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:forwardAndReverse"
valid: true
credential:
username: "user@DOMAIN.COM"
password: ~
source: "$external"
mechanism: "GSSAPI"
mechanism_properties:
SERVICE_NAME: "other"
CANONICALIZE_HOST_NAME: "forwardAndReverse"
-
description: "should accept no hostname canonicalization (GSSAPI)"
uri: "mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:none"
valid: true
credential:
username: "user@DOMAIN.COM"
password: ~
source: "$external"
mechanism: "GSSAPI"
mechanism_properties:
SERVICE_NAME: "other"
CANONICALIZE_HOST_NAME: "none"
-
description: "must raise an error when the hostname canonicalization is invalid"
uri: "mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:invalid"
valid: false
-
description: "should accept the password (GSSAPI)"
uri: "mongodb://user%40DOMAIN.COM:password@localhost/?authMechanism=GSSAPI&authSource=$external"
Expand Down
6 changes: 3 additions & 3 deletions source/connection-string/tests/valid-auth.json
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@
},
{
"description": "Escaped username (GSSAPI)",
"uri": "mongodb://user%40EXAMPLE.COM:secret@localhost/?authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:true,SERVICE_HOST:example.com&authMechanism=GSSAPI",
"uri": "mongodb://user%40EXAMPLE.COM:secret@localhost/?authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:forward,SERVICE_HOST:example.com&authMechanism=GSSAPI",
"valid": true,
"warning": false,
"hosts": [
Expand All @@ -303,8 +303,8 @@
"authmechanism": "GSSAPI",
"authmechanismproperties": {
"SERVICE_NAME": "other",
"CANONICALIZE_HOST_NAME": true,
"SERVICE_HOST": "example.com"
"SERVICE_HOST": "example.com",
"CANONICALIZE_HOST_NAME": "forward"
}
}
},
Expand Down
4 changes: 2 additions & 2 deletions source/connection-string/tests/valid-auth.yml
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ tests:
authmechanism: "MONGODB-X509"
-
description: "Escaped username (GSSAPI)"
uri: "mongodb://user%40EXAMPLE.COM:secret@localhost/?authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:true,SERVICE_HOST:example.com&authMechanism=GSSAPI"
uri: "mongodb://user%40EXAMPLE.COM:secret@localhost/?authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:forward,SERVICE_HOST:example.com&authMechanism=GSSAPI"
valid: true
warning: false
hosts:
Expand All @@ -238,8 +238,8 @@ tests:
authmechanism: "GSSAPI"
authmechanismproperties:
SERVICE_NAME: "other"
CANONICALIZE_HOST_NAME: true,
SERVICE_HOST: "example.com"
CANONICALIZE_HOST_NAME: "forward"
-
description: "At-signs in options aren't part of the userinfo"
uri: "mongodb://alice:secret@example.com/admin?replicaset=my@replicaset"
Expand Down
6 changes: 3 additions & 3 deletions source/uri-options/tests/auth-options.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"tests": [
{
"description": "Valid auth options are parsed correctly (GSSAPI)",
"uri": "mongodb://foo:bar@example.com/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:true,SERVICE_HOST:example.com&authSource=$external",
"uri": "mongodb://foo:bar@example.com/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:forward,SERVICE_HOST:example.com&authSource=$external",
"valid": true,
"warning": false,
"hosts": null,
Expand All @@ -11,8 +11,8 @@
"authMechanism": "GSSAPI",
"authMechanismProperties": {
"SERVICE_NAME": "other",
"CANONICALIZE_HOST_NAME": true,
"SERVICE_HOST": "example.com"
"SERVICE_HOST": "example.com",
"CANONICALIZE_HOST_NAME": "forward"
},
"authSource": "$external"
}
Expand Down
4 changes: 2 additions & 2 deletions source/uri-options/tests/auth-options.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
tests:
-
description: "Valid auth options are parsed correctly (GSSAPI)"
uri: "mongodb://foo:bar@example.com/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:true,SERVICE_HOST:example.com&authSource=$external"
uri: "mongodb://foo:bar@example.com/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:forward,SERVICE_HOST:example.com&authSource=$external"
valid: true
warning: false
hosts: ~
Expand All @@ -10,8 +10,8 @@ tests:
authMechanism: "GSSAPI"
authMechanismProperties:
SERVICE_NAME: "other"
CANONICALIZE_HOST_NAME: true
SERVICE_HOST: "example.com"
CANONICALIZE_HOST_NAME: "forward"
authSource: "$external"
-
description: "Valid auth options are parsed correctly (SCRAM-SHA-1)"
Expand Down