PYTHON-3906 & PYTHON-2867 Implement GSSAPI ServiceHost support and expand canonicalization options (#1983)

This commit is contained in:
Steven Silvester 2024-10-30 14:06:54 -05:00 committed by GitHub
parent ad3292e39b
commit 92d6a732c5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 78 additions and 12 deletions

View File

@ -77,7 +77,7 @@ MongoCredential = namedtuple(
GSSAPIProperties = namedtuple(
"GSSAPIProperties", ["service_name", "canonicalize_host_name", "service_realm"]
"GSSAPIProperties", ["service_name", "canonicalize_host_name", "service_realm", "service_host"]
)
"""Mechanism properties for GSSAPI authentication."""
@ -86,6 +86,16 @@ _AWSProperties = namedtuple("_AWSProperties", ["aws_session_token"])
"""Mechanism properties for MONGODB-AWS authentication."""
def _validate_canonicalize_host_name(value: str | bool) -> str | bool:
valid_names = [False, True, "none", "forward", "forwardAndReverse"]
if value in ["true", "false", True, False]:
return value in ["true", True]
if value not in valid_names:
raise ValueError(f"CANONICALIZE_HOST_NAME '{value}' not in valid options: {valid_names}")
return value
def _build_credentials_tuple(
mech: str,
source: Optional[str],
@ -102,12 +112,15 @@ def _build_credentials_tuple(
raise ValueError("authentication source must be $external or None for GSSAPI")
properties = extra.get("authmechanismproperties", {})
service_name = properties.get("SERVICE_NAME", "mongodb")
canonicalize = bool(properties.get("CANONICALIZE_HOST_NAME", False))
service_host = properties.get("SERVICE_HOST", None)
canonicalize = properties.get("CANONICALIZE_HOST_NAME", "false")
canonicalize = _validate_canonicalize_host_name(canonicalize)
service_realm = properties.get("SERVICE_REALM")
props = GSSAPIProperties(
service_name=service_name,
canonicalize_host_name=canonicalize,
service_realm=service_realm,
service_host=service_host,
)
# Source is always $external.
return MongoCredential(mech, "$external", user, passwd, props, None)

View File

@ -139,6 +139,9 @@ SRV_SERVICE_NAME = "mongodb"
# Default value for serverMonitoringMode
SERVER_MONITORING_MODE = "auto" # poll/stream/auto
# Auth mechanism properties that must raise an error instead of warning if they invalidate.
_MECH_PROP_MUST_RAISE = ["CANONICALIZE_HOST_NAME"]
def partition_node(node: str) -> tuple[str, int]:
"""Split a host:port string into (host, int(port)) pair."""
@ -423,6 +426,7 @@ def validate_read_preference_tags(name: str, value: Any) -> list[dict[str, str]]
_MECHANISM_PROPS = frozenset(
[
"SERVICE_NAME",
"SERVICE_HOST",
"CANONICALIZE_HOST_NAME",
"SERVICE_REALM",
"AWS_SESSION_TOKEN",
@ -476,7 +480,9 @@ def validate_auth_mechanism_properties(option: str, value: Any) -> dict[str, Uni
)
if key == "CANONICALIZE_HOST_NAME":
props[key] = validate_boolean_or_string(key, val)
from pymongo.auth_shared import _validate_canonicalize_host_name
props[key] = _validate_canonicalize_host_name(val)
else:
props[key] = val
@ -867,6 +873,10 @@ def get_validated_options(
validator = _get_validator(opt, URI_OPTIONS_VALIDATOR_MAP, normed_key=normed_key)
validated = validator(opt, value)
except (ValueError, TypeError, ConfigurationError) as exc:
if normed_key == "authmechanismproperties" and any(
p in str(exc) for p in _MECH_PROP_MUST_RAISE
):
raise
if warn:
warnings.warn(str(exc), stacklevel=2)
else:

View File

@ -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",
"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",
@ -89,10 +89,46 @@
"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": 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",
@ -433,14 +469,14 @@
}
},
{
"description": "should throw an exception if username and password is specified for test environment (MONGODB-OIDC)",
"description": "should throw an exception if supplied a password (MONGODB-OIDC)",
"uri": "mongodb://user:pass@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:test",
"valid": false,
"credential": null
},
{
"description": "should throw an exception if username is specified for test environment (MONGODB-OIDC)",
"uri": "mongodb://principalName@localhost/?authMechanism=MONGODB-OIDC&ENVIRONMENT:test",
"description": "should throw an exception if username is specified for test (MONGODB-OIDC)",
"uri": "mongodb://principalName@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:test",
"valid": false,
"credential": null
},
@ -451,11 +487,17 @@
"credential": null
},
{
"description": "should throw an exception if neither provider nor callbacks specified (MONGODB-OIDC)",
"description": "should throw an exception if neither environment nor callbacks specified (MONGODB-OIDC)",
"uri": "mongodb://localhost/?authMechanism=MONGODB-OIDC",
"valid": false,
"credential": null
},
{
"description": "should throw an exception when unsupported auth property is specified (MONGODB-OIDC)",
"uri": "mongodb://localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=UnsupportedProperty:unexisted",
"valid": false,
"credential": null
},
{
"description": "should recognise the mechanism with azure provider (MONGODB-OIDC)",
"uri": "mongodb://localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:foo",
@ -586,4 +628,4 @@
"credential": null
}
]
}
}

View File

@ -263,7 +263,7 @@
},
{
"description": "Escaped username (GSSAPI)",
"uri": "mongodb://user%40EXAMPLE.COM:secret@localhost/?authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:true&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": [
@ -282,7 +282,8 @@
"authmechanism": "GSSAPI",
"authmechanismproperties": {
"SERVICE_NAME": "other",
"CANONICALIZE_HOST_NAME": true
"SERVICE_HOST": "example.com",
"CANONICALIZE_HOST_NAME": "forward"
}
}
},