
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>
399 lines
17 KiB
Python
399 lines
17 KiB
Python
# Copyright 2013 Cloudbase Solutions Srl
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import importlib
|
|
import unittest
|
|
from xml.sax import saxutils
|
|
|
|
try:
|
|
import unittest.mock as mock
|
|
except ImportError:
|
|
import mock
|
|
|
|
from cloudbaseinit import exception
|
|
from cloudbaseinit.tests import fake
|
|
|
|
|
|
class WinRMConfigTests(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
self._pywintypes_mock = mock.MagicMock()
|
|
self._pywintypes_mock.com_error = fake.FakeComError
|
|
self._win32com_mock = mock.MagicMock()
|
|
self._module_patcher = mock.patch.dict(
|
|
'sys.modules',
|
|
{'win32com': self._win32com_mock,
|
|
'pywintypes': self._pywintypes_mock})
|
|
|
|
self._module_patcher.start()
|
|
|
|
winrmconfig = importlib.import_module(
|
|
"cloudbaseinit.utils.windows.winrmconfig")
|
|
self._winrmconfig = winrmconfig.WinRMConfig()
|
|
|
|
def tearDown(self):
|
|
self._module_patcher.stop()
|
|
|
|
def test_get_wsman_session(self):
|
|
mock_wsman = mock.MagicMock()
|
|
self._win32com_mock.client.Dispatch.return_value = mock_wsman
|
|
|
|
response = self._winrmconfig._get_wsman_session()
|
|
|
|
self._win32com_mock.client.Dispatch.assert_called_once_with(
|
|
'WSMan.Automation')
|
|
mock_wsman.CreateSession.assert_called_once_with()
|
|
self.assertEqual(mock_wsman.CreateSession.return_value, response)
|
|
|
|
@mock.patch('re.match')
|
|
def test_get_node_tag(self, mock_match):
|
|
mock_tag = mock.MagicMock()
|
|
|
|
response = self._winrmconfig._get_node_tag(mock_tag)
|
|
|
|
mock_match.assert_called_once_with("^{.*}(.*)$", mock_tag)
|
|
self.assertEqual(mock_match().groups().__getitem__(), response)
|
|
|
|
@mock.patch('xml.etree.ElementTree.fromstring')
|
|
@mock.patch('cloudbaseinit.utils.windows.winrmconfig.WinRMConfig.'
|
|
'_get_node_tag')
|
|
def _test_parse_listener_xml(self, mock_get_node_tag, mock_fromstring,
|
|
data_xml, tag=None, text='Fake'):
|
|
mock_node = mock.MagicMock()
|
|
mock_node.tag = tag
|
|
mock_node.text = text
|
|
fake_tree = [mock_node]
|
|
mock_get_node_tag.return_value = tag
|
|
mock_fromstring.return_value = fake_tree
|
|
|
|
response = self._winrmconfig._parse_listener_xml(data_xml=data_xml)
|
|
|
|
if data_xml is None:
|
|
self.assertEqual(None, response)
|
|
else:
|
|
mock_fromstring.assert_called_once_with(data_xml)
|
|
mock_get_node_tag.assert_called_once_with(tag)
|
|
if tag is "ListeningOn":
|
|
self.assertEqual({'ListeningOn': ['Fake']}, response)
|
|
elif tag is "Enabled":
|
|
if text is 'true':
|
|
self.assertEqual({'ListeningOn': [],
|
|
'Enabled': True}, response)
|
|
else:
|
|
self.assertEqual({'ListeningOn': [],
|
|
'Enabled': False}, response)
|
|
elif tag is 'Port':
|
|
self.assertEqual({'ListeningOn': [],
|
|
'Port': int(text)}, response)
|
|
else:
|
|
self.assertEqual({'ListeningOn': [],
|
|
tag: text}, response)
|
|
|
|
def test_parse_listener_xml_no_data(self):
|
|
self._test_parse_listener_xml(data_xml=None)
|
|
|
|
def test_parse_listener_xml_listening_on(self):
|
|
self._test_parse_listener_xml(data_xml='fake data', tag="ListeningOn")
|
|
|
|
def test_parse_listener_xml_enabled_true(self):
|
|
self._test_parse_listener_xml(data_xml='fake data',
|
|
tag="Enabled", text='true')
|
|
|
|
def test_parse_listener_xml_enabled_false(self):
|
|
self._test_parse_listener_xml(data_xml='fake data', tag='Enabled',
|
|
text='false')
|
|
|
|
def test_parse_listener_xml_port(self):
|
|
self._test_parse_listener_xml(data_xml='fake data', tag='Port',
|
|
text='9999')
|
|
|
|
def test_parse_listener_xml_other_tag(self):
|
|
self._test_parse_listener_xml(data_xml='fake data', tag='fake tag',
|
|
text='fake text')
|
|
|
|
@mock.patch('xml.etree.ElementTree.fromstring')
|
|
@mock.patch('cloudbaseinit.utils.windows.winrmconfig.WinRMConfig'
|
|
'._get_node_tag')
|
|
def _test_parse_cert_mapping_xml(self, mock_get_node_tag,
|
|
mock_fromstring, data_xml, tag=None,
|
|
text='Fake'):
|
|
mock_node = mock.MagicMock()
|
|
mock_node.tag = tag
|
|
mock_node.text = text
|
|
fake_tree = [mock_node]
|
|
mock_get_node_tag.return_value = tag
|
|
mock_fromstring.return_value = fake_tree
|
|
|
|
response = self._winrmconfig._parse_cert_mapping_xml(data_xml=data_xml)
|
|
|
|
if data_xml is None:
|
|
self.assertEqual(response, None)
|
|
else:
|
|
mock_fromstring.assert_called_once_with(data_xml)
|
|
mock_get_node_tag.assert_called_once_with(tag)
|
|
if tag is "Enabled":
|
|
if text is 'true':
|
|
self.assertEqual({'Enabled': True}, response)
|
|
else:
|
|
self.assertEqual({'Enabled': False}, response)
|
|
else:
|
|
self.assertEqual({tag: text}, response)
|
|
|
|
def test_parse_cert_mapping_xml_no_data(self):
|
|
self._test_parse_cert_mapping_xml(data_xml=None)
|
|
|
|
def test_parse_cert_mapping_xml_enabled_true(self):
|
|
self._test_parse_listener_xml(data_xml='fake data',
|
|
tag="Enabled", text='true')
|
|
|
|
def test_parse_cert_mapping_xml_enabled_false(self):
|
|
self._test_parse_listener_xml(data_xml='fake data', tag='Enabled',
|
|
text='false')
|
|
|
|
def test_parse_cert_mapping_xml_other_tag(self):
|
|
self._test_parse_listener_xml(data_xml='fake data', tag='fake tag',
|
|
text='fake text')
|
|
|
|
def _test_get_xml_bool(self, value):
|
|
response = self._winrmconfig._get_xml_bool(value)
|
|
if value:
|
|
self.assertEqual('true', response)
|
|
else:
|
|
self.assertEqual('false', response)
|
|
|
|
def test_get_xml_bool_true(self):
|
|
self._test_get_xml_bool(value='fake value')
|
|
|
|
def test_get_xml_bool_false(self):
|
|
self._test_get_xml_bool(value=None)
|
|
|
|
@mock.patch('cloudbaseinit.utils.windows.winrmconfig.WinRMConfig.'
|
|
'_get_wsman_session')
|
|
def _test_get_resource(self, mock_get_wsman_session, resource):
|
|
fake_session = mock.MagicMock()
|
|
fake_uri = 'fake:\\uri'
|
|
fake_session.Get.side_effect = [resource]
|
|
mock_get_wsman_session.return_value = fake_session
|
|
|
|
if resource is exception.CloudbaseInitException:
|
|
self.assertRaises(exception.CloudbaseInitException,
|
|
self._winrmconfig._get_resource,
|
|
fake_uri)
|
|
else:
|
|
response = self._winrmconfig._get_resource(fake_uri)
|
|
|
|
mock_get_wsman_session.assert_called_once_with()
|
|
fake_session.Get.assert_called_once_with(fake_uri)
|
|
self.assertEqual(resource, response)
|
|
|
|
def test_get_resource(self):
|
|
self._test_get_resource(resource='fake resource')
|
|
|
|
def test_get_resource_exception(self):
|
|
self._test_get_resource(resource=exception.CloudbaseInitException)
|
|
|
|
@mock.patch('cloudbaseinit.utils.windows.winrmconfig.WinRMConfig.'
|
|
'_get_wsman_session')
|
|
def test_delete_resource(self, mock_get_wsman_session):
|
|
fake_session = mock.MagicMock()
|
|
fake_uri = 'fake:\\uri'
|
|
mock_get_wsman_session.return_value = fake_session
|
|
|
|
self._winrmconfig._delete_resource(fake_uri)
|
|
|
|
fake_session.Delete.assert_called_once_with(fake_uri)
|
|
|
|
@mock.patch('cloudbaseinit.utils.windows.winrmconfig.WinRMConfig.'
|
|
'_get_wsman_session')
|
|
def test_create_resource(self, mock_get_wsman_session):
|
|
fake_session = mock.MagicMock()
|
|
fake_uri = 'fake:\\uri'
|
|
mock_get_wsman_session.return_value = fake_session
|
|
|
|
self._winrmconfig._create_resource(fake_uri, 'fake data')
|
|
|
|
fake_session.Create.assert_called_once_with(fake_uri, 'fake data')
|
|
|
|
@mock.patch('cloudbaseinit.utils.windows.winrmconfig.WinRMConfig.'
|
|
'_parse_cert_mapping_xml')
|
|
@mock.patch('cloudbaseinit.utils.windows.winrmconfig.WinRMConfig.'
|
|
'_get_resource')
|
|
def test_get_cert_mapping(self, mock_get_resource,
|
|
mock_parse_cert_mapping_xml):
|
|
fake_dict = {'issuer': 'issuer',
|
|
'subject': 'subject',
|
|
'uri': 'fake:\\uri'}
|
|
mock_parse_cert_mapping_xml.return_value = 'fake response'
|
|
mock_get_resource.return_value = 'fake resource'
|
|
|
|
response = self._winrmconfig.get_cert_mapping('issuer', 'subject',
|
|
uri='fake:\\uri')
|
|
|
|
mock_parse_cert_mapping_xml.assert_called_with('fake resource')
|
|
mock_get_resource.assert_called_with(
|
|
self._winrmconfig._SERVICE_CERTMAPPING_URI % fake_dict)
|
|
self.assertEqual('fake response', response)
|
|
|
|
@mock.patch('cloudbaseinit.utils.windows.winrmconfig.WinRMConfig.'
|
|
'_delete_resource')
|
|
def test_delete_cert_mapping(self, mock_delete_resource):
|
|
fake_dict = {'issuer': 'issuer',
|
|
'subject': 'subject',
|
|
'uri': 'fake:\\uri'}
|
|
|
|
self._winrmconfig.delete_cert_mapping('issuer', 'subject',
|
|
uri='fake:\\uri')
|
|
|
|
mock_delete_resource.assert_called_with(
|
|
self._winrmconfig._SERVICE_CERTMAPPING_URI % fake_dict)
|
|
|
|
@mock.patch('cloudbaseinit.utils.windows.winrmconfig.WinRMConfig.'
|
|
'_get_xml_bool')
|
|
@mock.patch('cloudbaseinit.utils.windows.winrmconfig.WinRMConfig.'
|
|
'_create_resource')
|
|
def test_create_cert_mapping(self, mock_create_resource,
|
|
mock_get_xml_bool):
|
|
fake_dict = {'issuer': 'issuer',
|
|
'subject': 'subject',
|
|
'uri': 'fake:\\uri'}
|
|
mock_get_xml_bool.return_value = True
|
|
fake_password = "Pa&ssw0rd!"
|
|
fake_username = 'fake user'
|
|
expected_password = saxutils.escape(fake_password)
|
|
expected_username = saxutils.escape(fake_username)
|
|
|
|
self._winrmconfig.create_cert_mapping(
|
|
issuer='issuer', subject='subject', username=fake_username,
|
|
password=fake_password, uri='fake:\\uri', enabled=True)
|
|
|
|
mock_get_xml_bool.assert_called_once_with(True)
|
|
mock_create_resource.assert_called_once_with(
|
|
self._winrmconfig._SERVICE_CERTMAPPING_URI % fake_dict,
|
|
'<p:certmapping xmlns:p="http://schemas.microsoft.com/wbem/wsman/'
|
|
'1/config/service/certmapping.xsd">'
|
|
'<p:Enabled>%(enabled)s</p:Enabled>'
|
|
'<p:Password>%(password)s</p:Password>'
|
|
'<p:UserName>%(username)s</p:UserName>'
|
|
'</p:certmapping>' % {'enabled': True,
|
|
'username': expected_username,
|
|
'password': expected_password})
|
|
|
|
@mock.patch('cloudbaseinit.utils.windows.winrmconfig.WinRMConfig.'
|
|
'_get_resource')
|
|
@mock.patch('cloudbaseinit.utils.windows.winrmconfig.WinRMConfig.'
|
|
'_parse_listener_xml')
|
|
def test_get_listener(self, mock_parse_listener_xml, mock_get_resource):
|
|
dict = {'protocol': 'HTTPS',
|
|
'address': 'fake:\\address'}
|
|
mock_get_resource.return_value = 'fake resource'
|
|
mock_parse_listener_xml.return_value = 'fake response'
|
|
|
|
response = self._winrmconfig.get_listener(protocol='HTTPS',
|
|
address="fake:\\address")
|
|
|
|
mock_get_resource.assert_called_with(
|
|
self._winrmconfig._SERVICE_LISTENER_URI % dict)
|
|
mock_parse_listener_xml.assert_called_once_with('fake resource')
|
|
self.assertEqual('fake response', response)
|
|
|
|
@mock.patch('cloudbaseinit.utils.windows.winrmconfig.WinRMConfig.'
|
|
'_delete_resource')
|
|
def test_delete_listener(self, mock_delete_resource):
|
|
dict = {'protocol': 'HTTPS',
|
|
'address': 'fake:\\address'}
|
|
|
|
self._winrmconfig.delete_listener(protocol='HTTPS',
|
|
address="fake:\\address")
|
|
|
|
mock_delete_resource.assert_called_with(
|
|
self._winrmconfig._SERVICE_LISTENER_URI % dict)
|
|
|
|
@mock.patch('cloudbaseinit.utils.windows.winrmconfig.WinRMConfig.'
|
|
'_create_resource')
|
|
@mock.patch('cloudbaseinit.utils.windows.winrmconfig.WinRMConfig.'
|
|
'_get_xml_bool')
|
|
def test_create_listener(self, mock_get_xml_bool, mock_create_resource):
|
|
dict = {'protocol': 'HTTPS',
|
|
'address': 'fake:\\address'}
|
|
mock_get_xml_bool.return_value = True
|
|
|
|
self._winrmconfig.create_listener(protocol='HTTPS',
|
|
cert_thumbprint=None,
|
|
address="fake:\\address",
|
|
enabled=True)
|
|
|
|
mock_create_resource.assert_called_once_with(
|
|
self._winrmconfig._SERVICE_LISTENER_URI % dict,
|
|
'<p:Listener xmlns:p="http://schemas.microsoft.com/'
|
|
'wbem/wsman/1/config/listener.xsd">'
|
|
'<p:Enabled>%(enabled)s</p:Enabled>'
|
|
'<p:CertificateThumbPrint>%(cert_thumbprint)s'
|
|
'</p:CertificateThumbPrint>'
|
|
'<p:URLPrefix>wsman</p:URLPrefix>'
|
|
'</p:Listener>' % {"enabled": True,
|
|
"cert_thumbprint": ""})
|
|
|
|
@mock.patch('xml.etree.ElementTree.fromstring')
|
|
@mock.patch('xml.etree.ElementTree.tostring')
|
|
@mock.patch('cloudbaseinit.utils.windows.winrmconfig.WinRMConfig.'
|
|
'_get_wsman_session')
|
|
@mock.patch('cloudbaseinit.utils.windows.winrmconfig.WinRMConfig.'
|
|
'_get_xml_bool')
|
|
def test_set_auth_config(self, mock_get_xml_bool, mock_get_wsman_session,
|
|
mock_tostring, mock_fromstring):
|
|
mock_session = mock.MagicMock()
|
|
mock_tree = mock.MagicMock()
|
|
mock_node = mock.MagicMock()
|
|
url = 'http://schemas.microsoft.com/wbem/wsman/1/config/service/auth'
|
|
|
|
expected_find = [
|
|
mock.call('.//cfg:Certificate', namespaces={'cfg': url}),
|
|
mock.call('.//cfg:Kerberos', namespaces={'cfg': url}),
|
|
mock.call('.//cfg:CbtHardeningLevel', namespaces={'cfg': url}),
|
|
mock.call('.//cfg:Negotiate', namespaces={'cfg': url}),
|
|
mock.call('.//cfg:CredSSP', namespaces={'cfg': url}),
|
|
mock.call('.//cfg:Basic', namespaces={'cfg': url})]
|
|
|
|
expected_get_xml_bool = [mock.call('certificate'),
|
|
mock.call('kerberos'),
|
|
mock.call('cbt_hardening_level'),
|
|
mock.call('negotiate'),
|
|
mock.call('credSSP'),
|
|
mock.call('basic')]
|
|
|
|
mock_get_wsman_session.return_value = mock_session
|
|
mock_session.Get.return_value = 'fake xml'
|
|
mock_fromstring.return_value = mock_tree
|
|
mock_get_xml_bool.return_value = 'true'
|
|
mock_tostring.return_value = 'fake xml'
|
|
mock_tree.find.return_value = mock_node
|
|
mock_node.text.lower.return_value = 'old value'
|
|
|
|
self._winrmconfig.set_auth_config(
|
|
basic='basic', kerberos='kerberos', negotiate='negotiate',
|
|
certificate='certificate', credSSP='credSSP',
|
|
cbt_hardening_level='cbt_hardening_level')
|
|
|
|
self.assertEqual(sorted(expected_find),
|
|
sorted(mock_tree.find.call_args_list))
|
|
self.assertEqual(sorted(expected_get_xml_bool),
|
|
sorted(mock_get_xml_bool.call_args_list))
|
|
|
|
mock_get_wsman_session.assert_called_once_with()
|
|
mock_session.Get.assert_called_with(
|
|
self._winrmconfig._SERVICE_AUTH_URI)
|
|
mock_fromstring.assert_called_once_with('fake xml')
|
|
mock_session.Put.assert_called_with(
|
|
self._winrmconfig._SERVICE_AUTH_URI, 'fake xml')
|