Update the WinRM Listener plugin
This commit adds configurable options to enable HTTP or HTTPS WinRM transport and the possibility of the WinRM configuration to be retrieved from the metadata service. The following config options have been added: - winrm_configure_http_listener: Configures the WinRM HTTP listener type=bool; default=False - winrm_configure_https_listener: Configures the WinRM HTTPS listener type=bool; default=True Change-Id: Ief60cbb22d7b17f9858c86e8501c1b2eee8fe627 Implements: update-winrmlistener-plugin Co-Authored-By: Paula Madalina Crismaru <pcrismaru@cloudbasesolutions.com>
This commit is contained in:
parent
acc9769659
commit
78335113ba
@ -86,6 +86,12 @@ class GlobalOptions(conf_base.Options):
|
||||
'winrm_enable_basic_auth', default=True,
|
||||
help='Enables basic authentication for the WinRM '
|
||||
'HTTPS listener'),
|
||||
cfg.BoolOpt(
|
||||
'winrm_configure_http_listener', default=False,
|
||||
help='Configures the WinRM HTTP listener'),
|
||||
cfg.BoolOpt(
|
||||
'winrm_configure_https_listener', default=True,
|
||||
help='Configures the WinRM HTTPS listener'),
|
||||
cfg.ListOpt(
|
||||
'volumes_to_extend', default=None,
|
||||
help='List of volumes that need to be extended '
|
||||
|
@ -154,6 +154,9 @@ class BaseMetadataService(object):
|
||||
def post_password(self, enc_password_b64):
|
||||
pass
|
||||
|
||||
def get_winrm_listeners_configuration(self):
|
||||
pass
|
||||
|
||||
def get_client_auth_certs(self):
|
||||
pass
|
||||
|
||||
|
@ -12,6 +12,8 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import contextlib
|
||||
|
||||
from oslo_log import log as oslo_logging
|
||||
|
||||
from cloudbaseinit import conf as cloudbaseinit_conf
|
||||
@ -50,54 +52,95 @@ class ConfigWinRMListenerPlugin(base.BasePlugin):
|
||||
|
||||
return True
|
||||
|
||||
def execute(self, service, shared_data):
|
||||
osutils = osutils_factory.get_os_utils()
|
||||
@contextlib.contextmanager
|
||||
def _check_uac_remote_restrictions(self, osutils):
|
||||
security_utils = security.WindowsSecurityUtils()
|
||||
|
||||
if not self._check_winrm_service(osutils):
|
||||
return base.PLUGIN_EXECUTE_ON_NEXT_BOOT, False
|
||||
|
||||
# On Windows Vista, 2008, 2008 R2 and 7, changing the configuration of
|
||||
# the winrm service will fail with an "Access is denied" error if the
|
||||
# User Account Control remote restrictions are enabled.
|
||||
# The solution to this issue is to temporarily disable the User Account
|
||||
# Control remote restrictions.
|
||||
# https://support.microsoft.com/kb/951016
|
||||
disable_uac_remote_restrictions = (osutils.check_os_version(6, 0) and
|
||||
not osutils.check_os_version(6, 2)
|
||||
and security_utils
|
||||
.get_uac_remote_restrictions())
|
||||
|
||||
disable_uac_remote_restrictions = (
|
||||
osutils.check_os_version(6, 0) and
|
||||
not osutils.check_os_version(6, 2) and
|
||||
security_utils.get_uac_remote_restrictions())
|
||||
try:
|
||||
if disable_uac_remote_restrictions:
|
||||
LOG.debug("Disabling UAC remote restrictions")
|
||||
security_utils.set_uac_remote_restrictions(enable=False)
|
||||
|
||||
winrm_config = winrmconfig.WinRMConfig()
|
||||
winrm_config.set_auth_config(basic=CONF.winrm_enable_basic_auth)
|
||||
|
||||
cert_manager = x509.CryptoAPICertManager()
|
||||
cert_thumbprint = cert_manager.create_self_signed_cert(
|
||||
self._cert_subject)
|
||||
|
||||
protocol = winrmconfig.LISTENER_PROTOCOL_HTTPS
|
||||
|
||||
if winrm_config.get_listener(protocol=protocol):
|
||||
winrm_config.delete_listener(protocol=protocol)
|
||||
|
||||
winrm_config.create_listener(cert_thumbprint=cert_thumbprint,
|
||||
protocol=protocol)
|
||||
|
||||
listener_config = winrm_config.get_listener(protocol=protocol)
|
||||
listener_port = listener_config.get("Port")
|
||||
|
||||
rule_name = "WinRM %s" % protocol
|
||||
osutils.firewall_create_rule(rule_name, listener_port,
|
||||
osutils.PROTOCOL_TCP)
|
||||
|
||||
yield
|
||||
finally:
|
||||
if disable_uac_remote_restrictions:
|
||||
LOG.debug("Enabling UAC remote restrictions")
|
||||
security_utils.set_uac_remote_restrictions(enable=True)
|
||||
|
||||
def _configure_winrm_listener(self, osutils, winrm_config, protocol,
|
||||
cert_thumbprint=None):
|
||||
if winrm_config.get_listener(protocol=protocol):
|
||||
winrm_config.delete_listener(protocol=protocol)
|
||||
|
||||
winrm_config.create_listener(cert_thumbprint=cert_thumbprint,
|
||||
protocol=protocol)
|
||||
|
||||
listener_config = winrm_config.get_listener(protocol=protocol)
|
||||
listener_port = listener_config.get("Port")
|
||||
|
||||
rule_name = "WinRM %s" % protocol
|
||||
osutils.firewall_create_rule(rule_name, listener_port,
|
||||
osutils.PROTOCOL_TCP)
|
||||
|
||||
def _get_winrm_listeners_config(self, service):
|
||||
listeners_config = service.get_winrm_listeners_configuration()
|
||||
if listeners_config is None:
|
||||
listeners_config = []
|
||||
if CONF.winrm_configure_http_listener:
|
||||
listeners_config.append(
|
||||
{"protocol": winrmconfig.LISTENER_PROTOCOL_HTTP})
|
||||
if CONF.winrm_configure_https_listener:
|
||||
listeners_config.append(
|
||||
{"protocol": winrmconfig.LISTENER_PROTOCOL_HTTPS})
|
||||
return listeners_config
|
||||
|
||||
def _create_self_signed_certificate(self):
|
||||
LOG.info("Generating self signed certificate for WinRM HTTPS listener")
|
||||
cert_manager = x509.CryptoAPICertManager()
|
||||
cert_thumbprint = cert_manager.create_self_signed_cert(
|
||||
self._cert_subject)
|
||||
return cert_thumbprint
|
||||
|
||||
def execute(self, service, shared_data):
|
||||
osutils = osutils_factory.get_os_utils()
|
||||
|
||||
if not self._check_winrm_service(osutils):
|
||||
return base.PLUGIN_EXECUTE_ON_NEXT_BOOT, False
|
||||
|
||||
listeners_config = self._get_winrm_listeners_config(service)
|
||||
|
||||
if not listeners_config:
|
||||
LOG.info("No WinRM listener configuration provided")
|
||||
else:
|
||||
with self._check_uac_remote_restrictions(osutils):
|
||||
winrm_config = winrmconfig.WinRMConfig()
|
||||
winrm_config.set_auth_config(
|
||||
basic=CONF.winrm_enable_basic_auth)
|
||||
|
||||
for listener_config in listeners_config:
|
||||
protocol = listener_config["protocol"].upper()
|
||||
|
||||
cert_thumb = None
|
||||
if protocol == winrmconfig.LISTENER_PROTOCOL_HTTPS:
|
||||
cert_thumb = listener_config.get(
|
||||
"certificate_thumbprint")
|
||||
if not cert_thumb:
|
||||
cert_thumb = self._create_self_signed_certificate()
|
||||
|
||||
LOG.info("Configuring WinRM listener for protocol: "
|
||||
"%(protocol)s, certificate thumbprint: "
|
||||
"%(cert_thumb)s",
|
||||
{"protocol": protocol,
|
||||
"cert_thumb": cert_thumb})
|
||||
self._configure_winrm_listener(
|
||||
osutils, winrm_config, protocol, cert_thumb)
|
||||
|
||||
return base.PLUGIN_EXECUTION_DONE, False
|
||||
|
@ -41,7 +41,8 @@ class ConfigWinRMListenerPluginTests(unittest.TestCase):
|
||||
'ctypes.wintypes': self._mock_wintypes,
|
||||
'pywintypes': self._mock_pywintypes,
|
||||
'win32com': self._mock_win32,
|
||||
'six.moves': self._moves_mock})
|
||||
'six.moves': self._moves_mock
|
||||
})
|
||||
self._module_patcher.start()
|
||||
self._winreg_mock = self._moves_mock.winreg
|
||||
|
||||
@ -93,69 +94,191 @@ class ConfigWinRMListenerPluginTests(unittest.TestCase):
|
||||
def test_check_winrm_service_no_service(self):
|
||||
self._test_check_winrm_service(service_exists=False)
|
||||
|
||||
@mock.patch('cloudbaseinit.utils.windows.security.'
|
||||
'WindowsSecurityUtils')
|
||||
def _test_check_uac_remote_restrictions(self, mock_SecurityUtils,
|
||||
disable_uac_remote_restrictions):
|
||||
mock_security_utils = mock.MagicMock()
|
||||
mock_SecurityUtils.return_value = mock_security_utils
|
||||
mock_osutils = mock.Mock()
|
||||
mock_osutils.check_os_version.side_effect = [True, False]
|
||||
if disable_uac_remote_restrictions:
|
||||
mock_security_utils.get_uac_remote_restrictions.return_value = \
|
||||
disable_uac_remote_restrictions
|
||||
|
||||
with self._winrmlistener._check_uac_remote_restrictions(mock_osutils):
|
||||
mock_SecurityUtils.assert_called_once_with()
|
||||
mock_osutils.check_os_version.assert_has_calls(
|
||||
[mock.call(6, 0), mock.call(6, 2)])
|
||||
(mock_security_utils.get_uac_remote_restrictions.
|
||||
assert_called_once_with())
|
||||
if disable_uac_remote_restrictions:
|
||||
expected_set_token_calls = [mock.call(enable=True)]
|
||||
else:
|
||||
expected_set_token_calls = [mock.call(enable=False),
|
||||
mock.call(enable=True)]
|
||||
mock_security_utils.set_uac_remote_restrictions.has_calls(
|
||||
expected_set_token_calls)
|
||||
|
||||
def test_check_uac_remote_restrictions(self):
|
||||
self._test_check_uac_remote_restrictions(
|
||||
disable_uac_remote_restrictions=True)
|
||||
|
||||
def test_check_uac_remote_restrictions_no_disable_restrictions(self):
|
||||
self._test_check_uac_remote_restrictions(
|
||||
disable_uac_remote_restrictions=False)
|
||||
|
||||
def _test_configure_winrm_listener(self, has_listener=True):
|
||||
mock_listener_config = mock.MagicMock()
|
||||
mock_winrm_config = mock.MagicMock()
|
||||
mock_osutils = mock.MagicMock()
|
||||
mock_osutils.PROTOCOL_TCP = mock.sentinel.PROTOCOL_TCP
|
||||
mock_winrm_config.get_listener.side_effect = [
|
||||
has_listener, mock_listener_config]
|
||||
port = 9999
|
||||
protocol = mock.sentinel.protocol
|
||||
cert_thumbprint = mock.sentinel.cert_thumbprint
|
||||
mock_listener_config.get.return_value = port
|
||||
|
||||
self._winrmlistener._configure_winrm_listener(
|
||||
mock_osutils, mock_winrm_config, protocol, cert_thumbprint)
|
||||
|
||||
if has_listener:
|
||||
mock_winrm_config.delete_listener.assert_called_once_with(
|
||||
protocol=protocol)
|
||||
mock_winrm_config.create_listener.assert_called_once_with(
|
||||
cert_thumbprint=cert_thumbprint, protocol=protocol)
|
||||
mock_listener_config.get.assert_called_once_with("Port")
|
||||
mock_osutils.firewall_create_rule.assert_called_once_with(
|
||||
"WinRM %s" % protocol, port, mock_osutils.PROTOCOL_TCP)
|
||||
|
||||
def test_configure_winrm_listener(self):
|
||||
self._test_configure_winrm_listener()
|
||||
|
||||
def test_configure_winrm_listener_no_initial_listener(self):
|
||||
self._test_configure_winrm_listener(has_listener=False)
|
||||
|
||||
def _test_get_winrm_listeners_config(self, listeners_config=None,
|
||||
http_listener=None,
|
||||
https_listener=None):
|
||||
winrmconfig = importlib.import_module('cloudbaseinit.utils.'
|
||||
'windows.winrmconfig')
|
||||
mock_service = mock.MagicMock()
|
||||
mock_service.get_winrm_listeners_configuration.return_value = \
|
||||
listeners_config
|
||||
expected_result = listeners_config
|
||||
if listeners_config is None:
|
||||
expected_result = []
|
||||
if http_listener:
|
||||
expected_result.append(
|
||||
{"protocol": winrmconfig.LISTENER_PROTOCOL_HTTP})
|
||||
if https_listener:
|
||||
expected_result.append(
|
||||
{"protocol": winrmconfig.LISTENER_PROTOCOL_HTTPS})
|
||||
|
||||
with testutils.ConfPatcher("winrm_configure_http_listener",
|
||||
http_listener):
|
||||
with testutils.ConfPatcher("winrm_configure_https_listener",
|
||||
https_listener):
|
||||
result = self._winrmlistener._get_winrm_listeners_config(
|
||||
mock_service)
|
||||
|
||||
self.assertEqual(result, expected_result)
|
||||
|
||||
def test_get_winrm_listeners_config_has_listeners(self):
|
||||
self._test_get_winrm_listeners_config(
|
||||
listeners_config=mock.sentinel.listeners)
|
||||
|
||||
def test_get_winrm_listeners_config_http_listener(self):
|
||||
self._test_get_winrm_listeners_config(http_listener=True)
|
||||
|
||||
def test_get_winrm_listeners_config_https_listener(self):
|
||||
self._test_get_winrm_listeners_config(https_listener=True)
|
||||
|
||||
@mock.patch('cloudbaseinit.utils.windows.x509.CryptoAPICertManager')
|
||||
def test_create_self_signed_certificate(self, mock_CryptoAPICertManager):
|
||||
mock_cert_mgr = mock.MagicMock()
|
||||
mock_CryptoAPICertManager.return_value = mock_cert_mgr
|
||||
mock_cert_mgr.create_self_signed_cert.return_value = \
|
||||
mock.sentinel.cert_thumbprint
|
||||
result = self._winrmlistener._create_self_signed_certificate()
|
||||
self.assertEqual(result, mock.sentinel.cert_thumbprint)
|
||||
mock_CryptoAPICertManager.assert_called_once_with()
|
||||
mock_cert_mgr.create_self_signed_cert.assert_called_once_with(
|
||||
self._winrmlistener._cert_subject)
|
||||
|
||||
@mock.patch('cloudbaseinit.plugins.windows.winrmlistener.'
|
||||
'ConfigWinRMListenerPlugin._configure_winrm_listener')
|
||||
@mock.patch('cloudbaseinit.plugins.windows.winrmlistener.'
|
||||
'ConfigWinRMListenerPlugin._check_uac_remote_restrictions')
|
||||
@mock.patch('cloudbaseinit.plugins.windows.winrmlistener.'
|
||||
'ConfigWinRMListenerPlugin._get_winrm_listeners_config')
|
||||
@mock.patch('cloudbaseinit.osutils.factory.get_os_utils')
|
||||
@mock.patch('cloudbaseinit.plugins.windows.winrmlistener.'
|
||||
'ConfigWinRMListenerPlugin._check_winrm_service')
|
||||
@mock.patch('cloudbaseinit.utils.windows.winrmconfig.WinRMConfig')
|
||||
@mock.patch('cloudbaseinit.utils.windows.x509.CryptoAPICertManager'
|
||||
'.create_self_signed_cert')
|
||||
@mock.patch('cloudbaseinit.utils.windows.security.WindowsSecurityUtils'
|
||||
'.set_uac_remote_restrictions')
|
||||
@mock.patch('cloudbaseinit.utils.windows.security.WindowsSecurityUtils'
|
||||
'.get_uac_remote_restrictions')
|
||||
def _test_execute(self, get_uac_rs, set_uac_rs, mock_create_cert,
|
||||
mock_WinRMConfig,
|
||||
@mock.patch('cloudbaseinit.plugins.windows.winrmlistener'
|
||||
'.ConfigWinRMListenerPlugin._create_self_signed_certificate')
|
||||
def _test_execute(self, mock_create_cert, mock_WinRMConfig,
|
||||
mock_check_winrm_service, mock_get_os_utils,
|
||||
service_status):
|
||||
mock_service = mock.MagicMock()
|
||||
mock_listener_config = mock.MagicMock()
|
||||
mock_cert_thumbprint = mock.MagicMock()
|
||||
shared_data = 'fake data'
|
||||
mock_get_winrm_listeners, mock_check_restrictions,
|
||||
mock_configure_listener,
|
||||
service_status=True, protocol=None,
|
||||
listeners_config=True, certificate_thumbprint=None):
|
||||
mock_winrm_config = mock.MagicMock()
|
||||
mock_WinRMConfig.return_value = mock_winrm_config
|
||||
mock_osutils = mock.MagicMock()
|
||||
mock_get_os_utils.return_value = mock_osutils
|
||||
mock_check_winrm_service.return_value = service_status
|
||||
mock_create_cert.return_value = mock_cert_thumbprint
|
||||
mock_WinRMConfig().get_listener.return_value = mock_listener_config
|
||||
mock_listener_config.get.return_value = 9999
|
||||
|
||||
mock_osutils.check_os_version.side_effect = [True, False]
|
||||
get_uac_rs.return_value = True
|
||||
|
||||
expected_check_version_calls = [mock.call(6, 0), mock.call(6, 2)]
|
||||
expected_set_token_calls = [mock.call(enable=False),
|
||||
mock.call(enable=True)]
|
||||
|
||||
response = self._winrmlistener.execute(mock_service, shared_data)
|
||||
if not service_status:
|
||||
expected_result = (base.PLUGIN_EXECUTE_ON_NEXT_BOOT, False)
|
||||
elif not listeners_config:
|
||||
mock_get_winrm_listeners.return_value = None
|
||||
expected_result = (base.PLUGIN_EXECUTION_DONE, False)
|
||||
else:
|
||||
expected_result = (base.PLUGIN_EXECUTION_DONE, False)
|
||||
if certificate_thumbprint is not None:
|
||||
certificate_thumbprint = \
|
||||
str(mock.sentinel.certificate_thumbprint)
|
||||
listener_config = {
|
||||
"protocol": protocol,
|
||||
"certificate_thumbprint": certificate_thumbprint
|
||||
}
|
||||
mock_get_winrm_listeners.return_value = [listener_config]
|
||||
with testutils.ConfPatcher('winrm_enable_basic_auth',
|
||||
str(mock.sentinel.winrm_enable_basic_auth)):
|
||||
result = self._winrmlistener.execute(
|
||||
mock.sentinel.service, mock.sentinel.shared_data)
|
||||
|
||||
self.assertEqual(result, expected_result)
|
||||
mock_get_os_utils.assert_called_once_with()
|
||||
mock_check_winrm_service.assert_called_once_with(mock_osutils)
|
||||
|
||||
if not service_status:
|
||||
self.assertEqual((base.PLUGIN_EXECUTE_ON_NEXT_BOOT,
|
||||
service_status), response)
|
||||
else:
|
||||
self.assertEqual(expected_check_version_calls,
|
||||
mock_osutils.check_os_version.call_args_list)
|
||||
self.assertEqual(expected_set_token_calls,
|
||||
set_uac_rs.call_args_list)
|
||||
mock_WinRMConfig().set_auth_config.assert_called_once_with(
|
||||
basic=CONF.winrm_enable_basic_auth)
|
||||
mock_create_cert.assert_called_once_with(
|
||||
self._winrmlistener._cert_subject)
|
||||
|
||||
mock_WinRMConfig().get_listener.assert_called_with(
|
||||
protocol="HTTPS")
|
||||
mock_WinRMConfig().delete_listener.assert_called_once_with(
|
||||
protocol="HTTPS")
|
||||
mock_WinRMConfig().create_listener.assert_called_once_with(
|
||||
protocol="HTTPS", cert_thumbprint=mock_cert_thumbprint)
|
||||
mock_listener_config.get.assert_called_once_with("Port")
|
||||
mock_osutils.firewall_create_rule.assert_called_once_with(
|
||||
"WinRM HTTPS", 9999, mock_osutils.PROTOCOL_TCP)
|
||||
self.assertEqual((base.PLUGIN_EXECUTION_DONE, False), response)
|
||||
|
||||
def test_execute(self):
|
||||
self._test_execute(service_status=True)
|
||||
if service_status:
|
||||
mock_get_winrm_listeners.assert_called_once_with(
|
||||
mock.sentinel.service)
|
||||
if listeners_config:
|
||||
mock_check_restrictions.assert_called_once_with(mock_osutils)
|
||||
mock_WinRMConfig.assert_called_once_with()
|
||||
mock_winrm_config.set_auth_config.assert_called_once_with(
|
||||
basic=str(mock.sentinel.winrm_enable_basic_auth))
|
||||
winrmconfig = importlib.import_module('cloudbaseinit.utils.'
|
||||
'windows.winrmconfig')
|
||||
if (protocol == winrmconfig.LISTENER_PROTOCOL_HTTPS and
|
||||
not certificate_thumbprint):
|
||||
certificate_thumbprint = mock_create_cert.return_value
|
||||
mock_create_cert.assert_called_once_with()
|
||||
mock_configure_listener.assert_called_once_with(
|
||||
mock_osutils, mock_winrm_config, protocol.upper(),
|
||||
certificate_thumbprint)
|
||||
|
||||
def test_execute_service_status_is_false(self):
|
||||
self._test_execute(service_status=False)
|
||||
|
||||
def test_execute_no_listeners_config(self):
|
||||
self._test_execute(listeners_config=None)
|
||||
|
||||
def test_execute_http_protocol(self):
|
||||
self._test_execute(protocol=str(mock.sentinel.http))
|
||||
|
||||
def test_execute_https_protocol(self):
|
||||
self._test_execute(protocol="HTTPS")
|
||||
|
@ -342,7 +342,7 @@ class WinRMConfigTests(unittest.TestCase):
|
||||
'</p:CertificateThumbPrint>'
|
||||
'<p:URLPrefix>wsman</p:URLPrefix>'
|
||||
'</p:Listener>' % {"enabled": True,
|
||||
"cert_thumbprint": None})
|
||||
"cert_thumbprint": ""})
|
||||
|
||||
@mock.patch('xml.etree.ElementTree.fromstring')
|
||||
@mock.patch('xml.etree.ElementTree.tostring')
|
||||
|
@ -164,7 +164,7 @@ class WinRMConfig(object):
|
||||
'</p:CertificateThumbPrint>'
|
||||
'<p:URLPrefix>wsman</p:URLPrefix>'
|
||||
'</p:Listener>' % {"enabled": self._get_xml_bool(enabled),
|
||||
"cert_thumbprint": cert_thumbprint})
|
||||
"cert_thumbprint": cert_thumbprint or ""})
|
||||
|
||||
def set_auth_config(self, basic=None, kerberos=None, negotiate=None,
|
||||
certificate=None, credSSP=None,
|
||||
|
Loading…
x
Reference in New Issue
Block a user