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:
Alessandro Pilotti 2017-03-01 12:48:11 +02:00 committed by Paula Madalina Crismaru
parent acc9769659
commit 78335113ba
6 changed files with 264 additions and 89 deletions

View File

@ -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 '

View File

@ -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

View File

@ -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

View File

@ -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")

View File

@ -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')

View File

@ -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,