diff --git a/cloudbaseinit/conf/default.py b/cloudbaseinit/conf/default.py
index 4218f3f3..bd75c026 100644
--- a/cloudbaseinit/conf/default.py
+++ b/cloudbaseinit/conf/default.py
@@ -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 '
diff --git a/cloudbaseinit/metadata/services/base.py b/cloudbaseinit/metadata/services/base.py
index a3d315c3..861acff7 100644
--- a/cloudbaseinit/metadata/services/base.py
+++ b/cloudbaseinit/metadata/services/base.py
@@ -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
diff --git a/cloudbaseinit/plugins/windows/winrmlistener.py b/cloudbaseinit/plugins/windows/winrmlistener.py
index 28da41c9..34ee337f 100644
--- a/cloudbaseinit/plugins/windows/winrmlistener.py
+++ b/cloudbaseinit/plugins/windows/winrmlistener.py
@@ -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
diff --git a/cloudbaseinit/tests/plugins/windows/test_winrmlistener.py b/cloudbaseinit/tests/plugins/windows/test_winrmlistener.py
index 9f1badb1..bb70691f 100644
--- a/cloudbaseinit/tests/plugins/windows/test_winrmlistener.py
+++ b/cloudbaseinit/tests/plugins/windows/test_winrmlistener.py
@@ -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")
diff --git a/cloudbaseinit/tests/utils/windows/test_winrmconfig.py b/cloudbaseinit/tests/utils/windows/test_winrmconfig.py
index 6592b160..4ce0d095 100644
--- a/cloudbaseinit/tests/utils/windows/test_winrmconfig.py
+++ b/cloudbaseinit/tests/utils/windows/test_winrmconfig.py
@@ -342,7 +342,7 @@ class WinRMConfigTests(unittest.TestCase):
''
'wsman'
'' % {"enabled": True,
- "cert_thumbprint": None})
+ "cert_thumbprint": ""})
@mock.patch('xml.etree.ElementTree.fromstring')
@mock.patch('xml.etree.ElementTree.tostring')
diff --git a/cloudbaseinit/utils/windows/winrmconfig.py b/cloudbaseinit/utils/windows/winrmconfig.py
index 8fb6a058..fe7e8410 100644
--- a/cloudbaseinit/utils/windows/winrmconfig.py
+++ b/cloudbaseinit/utils/windows/winrmconfig.py
@@ -164,7 +164,7 @@ class WinRMConfig(object):
''
'wsman'
'' % {"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,