Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/68211.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed ``salt.utils.vmware`` to use the supported ``token``/``tokenType`` arguments instead of the deprecated ``b64token``/``mechanism`` arguments when calling ``pyVim.connect.SmartConnect``. pyvmomi 9 raises an exception when either deprecated argument is truthy, which broke salt-cloud, the ``vsphere`` execution module, and other VMware integrations as soon as pyvmomi was upgraded.
20 changes: 14 additions & 6 deletions salt/utils/vmware.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,17 @@ def _get_service_instance(
"Please check the debug log for more information.".format(host)
)

# pyvmomi >= 9 removed support for the deprecated b64token/mechanism
# keyword arguments and unconditionally raises if either is truthy. The
# supported replacement keywords are token/tokenType, present since
# pyvmomi 8. Only forward them when we actually have a token to send -
# for the default userpass mechanism the credentials are passed through
# user/pwd and no token argument is needed.
token_kwargs = {}
if token is not None:
token_kwargs["token"] = token
token_kwargs["tokenType"] = mechanism

try:
if verify_ssl:
service_instance = SmartConnect(
Expand All @@ -280,8 +291,7 @@ def _get_service_instance(
pwd=password,
protocol=protocol,
port=port,
b64token=token,
mechanism=mechanism,
**token_kwargs,
)
except TypeError as exc:
if "unexpected keyword argument" in exc.message:
Expand Down Expand Up @@ -320,8 +330,7 @@ def _get_service_instance(
protocol=protocol,
port=port,
sslContext=ssl._create_unverified_context(),
b64token=token,
mechanism=mechanism,
**token_kwargs,
)
except Exception as exc: # pylint: disable=broad-except
# pyVmomi's SmartConnect() actually raises Exception in some cases.
Expand All @@ -336,8 +345,7 @@ def _get_service_instance(
protocol=protocol,
port=port,
sslContext=context,
b64token=token,
mechanism=mechanism,
**token_kwargs,
)
except Exception as exc: # pylint: disable=broad-except
log.exception(exc)
Expand Down
84 changes: 84 additions & 0 deletions tests/pytests/unit/utils/test_vmware.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,87 @@ def test_reconnect_when_disconnecting_dead_socket_raises(connection_params):
salt.utils.vmware.get_service_instance(**connection_params)
assert mock_disconnect.call_count == 1
assert mock_get_si.call_count == 2


def test_userpass_does_not_pass_deprecated_b64token_mechanism():
"""
pyvmomi 9 raises Exception('b64token and mechanism are no longer
supported') as soon as either keyword argument is truthy. For the
userpass mechanism Salt has no token at all, so SmartConnect must be
called without the deprecated b64token/mechanism keywords (issue
#68211).
"""
mock_sc = MagicMock()
with patch("salt.utils.vmware.SmartConnect", mock_sc), patch(
"salt.utils.vmware.Disconnect", MagicMock()
):
salt.utils.vmware._get_service_instance(
host="fake_host.fqdn",
username="fake_username",
password="fake_password",
protocol="fake_protocol",
port=1,
mechanism="userpass",
principal=None,
domain=None,
)
assert mock_sc.call_count == 1
kwargs = mock_sc.call_args.kwargs
assert "b64token" not in kwargs
assert "mechanism" not in kwargs


def test_sspi_uses_token_and_tokenType_not_b64token_mechanism():
"""
pyvmomi 9 replaced the deprecated b64token/mechanism keywords with
token/tokenType. For the sspi mechanism Salt now forwards the gssapi
token through the new keyword arguments so SmartConnect does not raise
(issue #68211).
"""
mock_sc = MagicMock()
mock_token = MagicMock(return_value="fake_token")
with patch("salt.utils.vmware.SmartConnect", mock_sc), patch(
"salt.utils.vmware.get_gssapi_token", mock_token
), patch("salt.utils.vmware.Disconnect", MagicMock()):
salt.utils.vmware._get_service_instance(
host="fake_host.fqdn",
username="fake_username",
password="fake_password",
protocol="fake_protocol",
port=1,
mechanism="sspi",
principal="fake_principal",
domain="fake_domain",
)
assert mock_sc.call_count == 1
kwargs = mock_sc.call_args.kwargs
assert "b64token" not in kwargs
assert "mechanism" not in kwargs
assert kwargs.get("token") == "fake_token"
assert kwargs.get("tokenType") == "sspi"


def test_userpass_verify_ssl_false_does_not_pass_b64token_mechanism():
"""
The verify_ssl=False code path also must not forward the deprecated
b64token/mechanism keywords to pyvmomi 9 SmartConnect (issue #68211).
"""
mock_sc = MagicMock()
with patch("salt.utils.vmware.SmartConnect", mock_sc), patch(
"salt.utils.vmware.Disconnect", MagicMock()
), patch("ssl._create_unverified_context", MagicMock()):
salt.utils.vmware._get_service_instance(
host="fake_host.fqdn",
username="fake_username",
password="fake_password",
protocol="fake_protocol",
port=1,
mechanism="userpass",
principal=None,
domain=None,
verify_ssl=False,
)
assert mock_sc.call_count == 1
kwargs = mock_sc.call_args.kwargs
assert "b64token" not in kwargs
assert "mechanism" not in kwargs
26 changes: 10 additions & 16 deletions tests/unit/utils/test_vmware.py
Original file line number Diff line number Diff line change
Expand Up @@ -1544,8 +1544,6 @@ def test_userpass_mechanism_no_domain(self):
pwd="fake_password",
protocol="fake_protocol",
port=1,
b64token=None,
mechanism="userpass",
)

def test_userpass_mech_domain_unused(self):
Expand All @@ -1567,8 +1565,6 @@ def test_userpass_mech_domain_unused(self):
pwd="fake_password",
protocol="fake_protocol",
port=1,
b64token=None,
mechanism="userpass",
)
mock_sc.reset_mock()
salt.utils.vmware._get_service_instance(
Expand All @@ -1587,8 +1583,6 @@ def test_userpass_mech_domain_unused(self):
pwd="fake_password",
protocol="fake_protocol",
port=1,
b64token=None,
mechanism="userpass",
)

def test_sspi_empty_principal(self):
Expand Down Expand Up @@ -1664,8 +1658,8 @@ def test_sspi_get_token_success_(self):
pwd="fake_password",
protocol="fake_protocol",
port=1,
b64token="fake_token",
mechanism="sspi",
token="fake_token",
tokenType="sspi",
)

def test_first_attempt_successful_connection(self):
Expand All @@ -1687,8 +1681,8 @@ def test_first_attempt_successful_connection(self):
pwd="fake_password",
protocol="fake_protocol",
port=1,
b64token="fake_token",
mechanism="sspi",
token="fake_token",
tokenType="sspi",
)

def test_first_attempt_successful_connection_verify_ssl_false(self):
Expand Down Expand Up @@ -1724,8 +1718,8 @@ def test_first_attempt_successful_connection_verify_ssl_false(self):
protocol="fake_protocol",
port=1,
sslContext=mock_ssl.return_value,
b64token="fake_token",
mechanism="sspi",
token="fake_token",
tokenType="sspi",
),
]
mock_sc.assert_has_calls(calls)
Expand Down Expand Up @@ -1764,8 +1758,8 @@ def test_second_attempt_successful_connection_verify_ssl_false(self):
protocol="fake_protocol",
port=1,
sslContext=mock_ssl_unverif.return_value,
b64token="fake_token",
mechanism="sspi",
token="fake_token",
tokenType="sspi",
),
call(
host="fake_host.fqdn",
Expand All @@ -1774,8 +1768,8 @@ def test_second_attempt_successful_connection_verify_ssl_false(self):
protocol="fake_protocol",
port=1,
sslContext=mock_ssl_context.return_value,
b64token="fake_token",
mechanism="sspi",
token="fake_token",
tokenType="sspi",
),
]
mock_sc.assert_has_calls(calls)
Expand Down
Loading