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, 'winrm_enable_basic_auth', default=True,
help='Enables basic authentication for the WinRM ' help='Enables basic authentication for the WinRM '
'HTTPS listener'), '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( cfg.ListOpt(
'volumes_to_extend', default=None, 'volumes_to_extend', default=None,
help='List of volumes that need to be extended ' 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): def post_password(self, enc_password_b64):
pass pass
def get_winrm_listeners_configuration(self):
pass
def get_client_auth_certs(self): def get_client_auth_certs(self):
pass pass

View File

@ -12,6 +12,8 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import contextlib
from oslo_log import log as oslo_logging from oslo_log import log as oslo_logging
from cloudbaseinit import conf as cloudbaseinit_conf from cloudbaseinit import conf as cloudbaseinit_conf
@ -50,38 +52,31 @@ class ConfigWinRMListenerPlugin(base.BasePlugin):
return True return True
def execute(self, service, shared_data): @contextlib.contextmanager
osutils = osutils_factory.get_os_utils() def _check_uac_remote_restrictions(self, osutils):
security_utils = security.WindowsSecurityUtils() 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 # 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 # the winrm service will fail with an "Access is denied" error if the
# User Account Control remote restrictions are enabled. # User Account Control remote restrictions are enabled.
# The solution to this issue is to temporarily disable the User Account # The solution to this issue is to temporarily disable the User Account
# Control remote restrictions. # Control remote restrictions.
# https://support.microsoft.com/kb/951016 # https://support.microsoft.com/kb/951016
disable_uac_remote_restrictions = (osutils.check_os_version(6, 0) and disable_uac_remote_restrictions = (
not osutils.check_os_version(6, 2) osutils.check_os_version(6, 0) and
and security_utils not osutils.check_os_version(6, 2) and
.get_uac_remote_restrictions()) security_utils.get_uac_remote_restrictions())
try: try:
if disable_uac_remote_restrictions: if disable_uac_remote_restrictions:
LOG.debug("Disabling UAC remote restrictions") LOG.debug("Disabling UAC remote restrictions")
security_utils.set_uac_remote_restrictions(enable=False) security_utils.set_uac_remote_restrictions(enable=False)
yield
finally:
if disable_uac_remote_restrictions:
LOG.debug("Enabling UAC remote restrictions")
security_utils.set_uac_remote_restrictions(enable=True)
winrm_config = winrmconfig.WinRMConfig() def _configure_winrm_listener(self, osutils, winrm_config, protocol,
winrm_config.set_auth_config(basic=CONF.winrm_enable_basic_auth) cert_thumbprint=None):
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): if winrm_config.get_listener(protocol=protocol):
winrm_config.delete_listener(protocol=protocol) winrm_config.delete_listener(protocol=protocol)
@ -95,9 +90,57 @@ class ConfigWinRMListenerPlugin(base.BasePlugin):
osutils.firewall_create_rule(rule_name, listener_port, osutils.firewall_create_rule(rule_name, listener_port,
osutils.PROTOCOL_TCP) osutils.PROTOCOL_TCP)
finally: def _get_winrm_listeners_config(self, service):
if disable_uac_remote_restrictions: listeners_config = service.get_winrm_listeners_configuration()
LOG.debug("Enabling UAC remote restrictions") if listeners_config is None:
security_utils.set_uac_remote_restrictions(enable=True) 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 return base.PLUGIN_EXECUTION_DONE, False

View File

@ -41,7 +41,8 @@ class ConfigWinRMListenerPluginTests(unittest.TestCase):
'ctypes.wintypes': self._mock_wintypes, 'ctypes.wintypes': self._mock_wintypes,
'pywintypes': self._mock_pywintypes, 'pywintypes': self._mock_pywintypes,
'win32com': self._mock_win32, 'win32com': self._mock_win32,
'six.moves': self._moves_mock}) 'six.moves': self._moves_mock
})
self._module_patcher.start() self._module_patcher.start()
self._winreg_mock = self._moves_mock.winreg self._winreg_mock = self._moves_mock.winreg
@ -93,69 +94,191 @@ class ConfigWinRMListenerPluginTests(unittest.TestCase):
def test_check_winrm_service_no_service(self): def test_check_winrm_service_no_service(self):
self._test_check_winrm_service(service_exists=False) 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.osutils.factory.get_os_utils')
@mock.patch('cloudbaseinit.plugins.windows.winrmlistener.' @mock.patch('cloudbaseinit.plugins.windows.winrmlistener.'
'ConfigWinRMListenerPlugin._check_winrm_service') 'ConfigWinRMListenerPlugin._check_winrm_service')
@mock.patch('cloudbaseinit.utils.windows.winrmconfig.WinRMConfig') @mock.patch('cloudbaseinit.utils.windows.winrmconfig.WinRMConfig')
@mock.patch('cloudbaseinit.utils.windows.x509.CryptoAPICertManager' @mock.patch('cloudbaseinit.plugins.windows.winrmlistener'
'.create_self_signed_cert') '.ConfigWinRMListenerPlugin._create_self_signed_certificate')
@mock.patch('cloudbaseinit.utils.windows.security.WindowsSecurityUtils' def _test_execute(self, mock_create_cert, mock_WinRMConfig,
'.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_check_winrm_service, mock_get_os_utils, mock_check_winrm_service, mock_get_os_utils,
service_status): mock_get_winrm_listeners, mock_check_restrictions,
mock_service = mock.MagicMock() mock_configure_listener,
mock_listener_config = mock.MagicMock() service_status=True, protocol=None,
mock_cert_thumbprint = mock.MagicMock() listeners_config=True, certificate_thumbprint=None):
shared_data = 'fake data' mock_winrm_config = mock.MagicMock()
mock_WinRMConfig.return_value = mock_winrm_config
mock_osutils = mock.MagicMock() mock_osutils = mock.MagicMock()
mock_get_os_utils.return_value = mock_osutils mock_get_os_utils.return_value = mock_osutils
mock_check_winrm_service.return_value = service_status mock_check_winrm_service.return_value = service_status
mock_create_cert.return_value = mock_cert_thumbprint if not service_status:
mock_WinRMConfig().get_listener.return_value = mock_listener_config expected_result = (base.PLUGIN_EXECUTE_ON_NEXT_BOOT, False)
mock_listener_config.get.return_value = 9999 elif not listeners_config:
mock_get_winrm_listeners.return_value = None
mock_osutils.check_os_version.side_effect = [True, False] expected_result = (base.PLUGIN_EXECUTION_DONE, False)
get_uac_rs.return_value = True else:
expected_result = (base.PLUGIN_EXECUTION_DONE, False)
expected_check_version_calls = [mock.call(6, 0), mock.call(6, 2)] if certificate_thumbprint is not None:
expected_set_token_calls = [mock.call(enable=False), certificate_thumbprint = \
mock.call(enable=True)] str(mock.sentinel.certificate_thumbprint)
listener_config = {
response = self._winrmlistener.execute(mock_service, shared_data) "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_get_os_utils.assert_called_once_with()
mock_check_winrm_service.assert_called_once_with(mock_osutils) mock_check_winrm_service.assert_called_once_with(mock_osutils)
if service_status:
if not service_status: mock_get_winrm_listeners.assert_called_once_with(
self.assertEqual((base.PLUGIN_EXECUTE_ON_NEXT_BOOT, mock.sentinel.service)
service_status), response) if listeners_config:
else: mock_check_restrictions.assert_called_once_with(mock_osutils)
self.assertEqual(expected_check_version_calls, mock_WinRMConfig.assert_called_once_with()
mock_osutils.check_os_version.call_args_list) mock_winrm_config.set_auth_config.assert_called_once_with(
self.assertEqual(expected_set_token_calls, basic=str(mock.sentinel.winrm_enable_basic_auth))
set_uac_rs.call_args_list) winrmconfig = importlib.import_module('cloudbaseinit.utils.'
mock_WinRMConfig().set_auth_config.assert_called_once_with( 'windows.winrmconfig')
basic=CONF.winrm_enable_basic_auth) if (protocol == winrmconfig.LISTENER_PROTOCOL_HTTPS and
mock_create_cert.assert_called_once_with( not certificate_thumbprint):
self._winrmlistener._cert_subject) certificate_thumbprint = mock_create_cert.return_value
mock_create_cert.assert_called_once_with()
mock_WinRMConfig().get_listener.assert_called_with( mock_configure_listener.assert_called_once_with(
protocol="HTTPS") mock_osutils, mock_winrm_config, protocol.upper(),
mock_WinRMConfig().delete_listener.assert_called_once_with( certificate_thumbprint)
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)
def test_execute_service_status_is_false(self): def test_execute_service_status_is_false(self):
self._test_execute(service_status=False) 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:CertificateThumbPrint>'
'<p:URLPrefix>wsman</p:URLPrefix>' '<p:URLPrefix>wsman</p:URLPrefix>'
'</p:Listener>' % {"enabled": True, '</p:Listener>' % {"enabled": True,
"cert_thumbprint": None}) "cert_thumbprint": ""})
@mock.patch('xml.etree.ElementTree.fromstring') @mock.patch('xml.etree.ElementTree.fromstring')
@mock.patch('xml.etree.ElementTree.tostring') @mock.patch('xml.etree.ElementTree.tostring')

View File

@ -164,7 +164,7 @@ class WinRMConfig(object):
'</p:CertificateThumbPrint>' '</p:CertificateThumbPrint>'
'<p:URLPrefix>wsman</p:URLPrefix>' '<p:URLPrefix>wsman</p:URLPrefix>'
'</p:Listener>' % {"enabled": self._get_xml_bool(enabled), '</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, def set_auth_config(self, basic=None, kerberos=None, negotiate=None,
certificate=None, credSSP=None, certificate=None, credSSP=None,