diff --git a/azurelinuxagent/common/conf.py b/azurelinuxagent/common/conf.py index 5dd9f70..75a0248 100644 --- a/azurelinuxagent/common/conf.py +++ b/azurelinuxagent/common/conf.py @@ -86,11 +86,13 @@ def load_conf_from_file(conf_file_path, conf=__conf__): "").format(conf_file_path, err)) __SWITCH_OPTIONS__ = { + "OS.AllowHTTP" : False, + "OS.EnableFirewall" : False, + "OS.EnableFIPS" : False, "OS.EnableRDMA" : False, "OS.UpdateRdmaDriver" : False, "OS.CheckRdmaDriver" : False, "Logs.Verbose" : False, - "OS.EnableFIPS" : False, "Provisioning.Enabled" : True, "Provisioning.UseCloudInit" : False, "Provisioning.AllowResetSysUser" : False, @@ -105,8 +107,7 @@ __SWITCH_OPTIONS__ = { "ResourceDisk.Format" : False, "ResourceDisk.EnableSwap" : False, "AutoUpdate.Enabled" : True, - "EnableOverProvisioning" : False, - "OS.AllowHTTP" : False + "EnableOverProvisioning" : False } __STRING_OPTIONS__ = { @@ -149,6 +150,9 @@ def get_configuration(conf=__conf__): return options +def enable_firewall(conf=__conf__): + return conf.get_switch("OS.EnableFirewall", False) + def enable_rdma(conf=__conf__): return conf.get_switch("OS.EnableRDMA", False) or \ conf.get_switch("OS.UpdateRdmaDriver", False) or \ diff --git a/azurelinuxagent/common/osutil/default.py b/azurelinuxagent/common/osutil/default.py index ead1385..6fe4981 100644 --- a/azurelinuxagent/common/osutil/default.py +++ b/azurelinuxagent/common/osutil/default.py @@ -50,6 +50,10 @@ for all distros. Each concrete distro classes could overwrite default behavior if needed. """ +FIREWALL_ACCEPT = "iptables -t security -{0} OUTPUT -d {1} -p tcp -m owner --uid-owner {2} -j ACCEPT" +FIREWALL_DROP = "iptables -t security -{0} OUTPUT -d {1} -p tcp -j DROP" +FIREWALL_LIST = "iptables -t security -L" + DMIDECODE_CMD = 'dmidecode --string system-uuid' PRODUCT_ID_FILE = '/sys/class/dmi/id/product_uuid' UUID_PATTERN = re.compile( @@ -63,6 +67,40 @@ class DefaultOSUtil(object): self.selinux = None self.disable_route_warning = False + def enable_firewall(self, dst_ip=None, uid=None): + try: + if dst_ip is None or uid is None: + raise Exception("Missing arguments to enable_firewall") + + # If either firewall rule exists, make no changes + accept_rule = FIREWALL_ACCEPT.format("C", dst_ip, uid) + drop_rule = FIREWALL_DROP.format("C", dst_ip) + + if shellutil.run(accept_rule, chk_err=False) == 0 or \ + shellutil.run(drop_rule, chk_err=False) == 0: + return + + # Neither rule exists, append both rules + accept_rule = FIREWALL_ACCEPT.format("A", dst_ip, uid) + drop_rule = FIREWALL_DROP.format("A", dst_ip) + + if shellutil.run(accept_rule) != 0: + logger.warn("Unable to add ACCEPT firewall rule '{0}'".format( + accept_rule)) + return + + if shellutil.run(drop_rule) != 0: + logger.warn("Unable to add DROP firewall rule '{0}'".format( + drop_rule)) + return + + rc, output = shellutil.run_get_output(FIREWALL_LIST) + logger.info("Successfully added firewall rules:\n{0}".format( + output)) + + except Exception as e: + logger.info("Unable to establish firewall: {0}".format(ustr(e))) + def _correct_instance_id(self, id): ''' Azure stores the instance ID with an incorrect byte ordering for the diff --git a/azurelinuxagent/common/protocol/util.py b/azurelinuxagent/common/protocol/util.py index bb3500a..3071d7a 100644 --- a/azurelinuxagent/common/protocol/util.py +++ b/azurelinuxagent/common/protocol/util.py @@ -16,11 +16,14 @@ # # Requires Python 2.4+ and Openssl 1.0+ # + +import errno import os import re import shutil import time import threading + import azurelinuxagent.common.conf as conf import azurelinuxagent.common.logger as logger from azurelinuxagent.common.exception import ProtocolError, OSUtilError, \ @@ -231,6 +234,9 @@ class ProtocolUtil(object): try: os.remove(protocol_file_path) except IOError as e: + # Ignore file-not-found errors (since the file is being removed) + if e.errno == errno.ENOENT: + return logger.error("Failed to clear protocol endpoint: {0}", e) def get_protocol(self): diff --git a/azurelinuxagent/common/utils/restutil.py b/azurelinuxagent/common/utils/restutil.py index b671c31..39d2155 100644 --- a/azurelinuxagent/common/utils/restutil.py +++ b/azurelinuxagent/common/utils/restutil.py @@ -137,34 +137,33 @@ def _get_http_proxy(secure=False): def _http_request(method, host, rel_uri, port=None, data=None, secure=False, headers=None, proxy_host=None, proxy_port=None): - url, conn = None, None - if secure: - port = 443 if port is None else port - if proxy_host is not None and proxy_port is not None: - conn = httpclient.HTTPSConnection(proxy_host, - proxy_port, - timeout=10) - conn.set_tunnel(host, port) - # If proxy is used, full url is needed. - url = "https://{0}:{1}{2}".format(host, port, rel_uri) - else: - conn = httpclient.HTTPSConnection(host, - port, - timeout=10) - url = rel_uri + + headers = {} if headers is None else headers + use_proxy = proxy_host is not None and proxy_port is not None + + if port is None: + port = 443 if secure else 80 + + if use_proxy: + conn_host, conn_port = proxy_host, proxy_port + scheme = "https" if secure else "http" + url = "{0}://{1}:{2}{3}".format(scheme, host, port, rel_uri) + else: - port = 80 if port is None else port - if proxy_host is not None and proxy_port is not None: - conn = httpclient.HTTPConnection(proxy_host, - proxy_port, - timeout=10) - # If proxy is used, full url is needed. - url = "http://{0}:{1}{2}".format(host, port, rel_uri) - else: - conn = httpclient.HTTPConnection(host, - port, - timeout=10) - url = rel_uri + conn_host, conn_port = host, port + url = rel_uri + + if secure: + conn = httpclient.HTTPSConnection(conn_host, + conn_port, + timeout=10) + if use_proxy: + conn.set_tunnel(host, port) + + else: + conn = httpclient.HTTPConnection(conn_host, + conn_port, + timeout=10) logger.verbose("HTTP connection [{0}] [{1}] [{2}] [{3}]", method, @@ -172,10 +171,8 @@ def _http_request(method, host, rel_uri, port=None, data=None, secure=False, data, headers) - headers = {} if headers is None else headers conn.request(method=method, url=url, body=data, headers=headers) - resp = conn.getresponse() - return resp + return conn.getresponse() def http_request(method, diff --git a/azurelinuxagent/ga/env.py b/azurelinuxagent/ga/env.py index c81eed7..7273319 100644 --- a/azurelinuxagent/ga/env.py +++ b/azurelinuxagent/ga/env.py @@ -27,6 +27,7 @@ import azurelinuxagent.common.logger as logger from azurelinuxagent.common.dhcp import get_dhcp_handler from azurelinuxagent.common.osutil import get_osutil +from azurelinuxagent.common.protocol import get_protocol_util def get_env_handler(): return EnvHandler() @@ -42,6 +43,7 @@ class EnvHandler(object): def __init__(self): self.osutil = get_osutil() self.dhcp_handler = get_dhcp_handler() + self.protocol_util = get_protocol_util() self.stopped = True self.hostname = None self.dhcpid = None @@ -64,17 +66,28 @@ class EnvHandler(object): def monitor(self): """ + Monitor firewall rules Monitor dhcp client pid and hostname. If dhcp clinet process re-start has occurred, reset routes. """ + protocol = self.protocol_util.get_protocol() while not self.stopped: self.osutil.remove_rules_files() + + if conf.enable_firewall(): + self.osutil.enable_firewall( + dst_ip=protocol.endpoint, + uid=os.getuid()) + timeout = conf.get_root_device_scsi_timeout() if timeout is not None: self.osutil.set_scsi_disks_timeout(timeout) + if conf.get_monitor_hostname(): self.handle_hostname_update() + self.handle_dhclient_restart() + time.sleep(5) def handle_hostname_update(self): diff --git a/config/alpine/waagent.conf b/config/alpine/waagent.conf index 2e3f6a5..99495d5 100644 --- a/config/alpine/waagent.conf +++ b/config/alpine/waagent.conf @@ -81,3 +81,12 @@ OS.SshDir=/etc/ssh # handling until inVMArtifactsProfile.OnHold is false. # Default is disabled # EnableOverProvisioning=n + +# Allow fallback to HTTP if HTTPS is unavailable +# Note: Allowing HTTP (vs. HTTPS) may cause security risks +# OS.AllowHTTP=n + +# Add firewall rules to protect access to Azure host node services +# Note: +# - The default is false to protect the state of exising VMs +OS.EnableFirewall=y diff --git a/config/arch/waagent.conf b/config/arch/waagent.conf index 686b90c..200a458 100644 --- a/config/arch/waagent.conf +++ b/config/arch/waagent.conf @@ -107,3 +107,12 @@ OS.SshDir=/etc/ssh # handling until inVMArtifactsProfile.OnHold is false. # Default is disabled # EnableOverProvisioning=n + +# Allow fallback to HTTP if HTTPS is unavailable +# Note: Allowing HTTP (vs. HTTPS) may cause security risks +# OS.AllowHTTP=n + +# Add firewall rules to protect access to Azure host node services +# Note: +# - The default is false to protect the state of exising VMs +OS.EnableFirewall=y diff --git a/config/bigip/waagent.conf b/config/bigip/waagent.conf index a6a380b..9ff6ee1 100644 --- a/config/bigip/waagent.conf +++ b/config/bigip/waagent.conf @@ -76,4 +76,21 @@ OS.SshdConfigPath=/config/ssh/sshd_config OS.EnableRDMA=n # Enable or disable goal state processing auto-update, default is enabled -AutoUpdate.Enabled=y \ No newline at end of file +AutoUpdate.Enabled=y + +# Determine the update family, this should not be changed +# AutoUpdate.GAFamily=Prod + +# Determine if the overprovisioning feature is enabled. If yes, hold extension +# handling until inVMArtifactsProfile.OnHold is false. +# Default is disabled +# EnableOverProvisioning=n + +# Allow fallback to HTTP if HTTPS is unavailable +# Note: Allowing HTTP (vs. HTTPS) may cause security risks +# OS.AllowHTTP=n + +# Add firewall rules to protect access to Azure host node services +# Note: +# - The default is false to protect the state of exising VMs +OS.EnableFirewall=y diff --git a/config/clearlinux/waagent.conf b/config/clearlinux/waagent.conf index 6606cd7..8109425 100644 --- a/config/clearlinux/waagent.conf +++ b/config/clearlinux/waagent.conf @@ -79,3 +79,12 @@ AutoUpdate.GAFamily=Prod # handling until inVMArtifactsProfile.OnHold is false. # Default is disabled # EnableOverProvisioning=n + +# Allow fallback to HTTP if HTTPS is unavailable +# Note: Allowing HTTP (vs. HTTPS) may cause security risks +# OS.AllowHTTP=n + +# Add firewall rules to protect access to Azure host node services +# Note: +# - The default is false to protect the state of exising VMs +OS.EnableFirewall=y diff --git a/config/coreos/waagent.conf b/config/coreos/waagent.conf index ac19b5f..cbb327f 100644 --- a/config/coreos/waagent.conf +++ b/config/coreos/waagent.conf @@ -111,3 +111,8 @@ OS.OpensslPath=None # Allow fallback to HTTP if HTTPS is unavailable # Note: Allowing HTTP (vs. HTTPS) may cause security risks OS.AllowHTTP=y + +# Add firewall rules to protect access to Azure host node services +# Note: +# - The default is false to protect the state of exising VMs +OS.EnableFirewall=y diff --git a/config/freebsd/waagent.conf b/config/freebsd/waagent.conf index 5149573..6406c75 100644 --- a/config/freebsd/waagent.conf +++ b/config/freebsd/waagent.conf @@ -105,3 +105,12 @@ OS.SudoersDir=/usr/local/etc/sudoers.d # handling until inVMArtifactsProfile.OnHold is false. # Default is disabled # EnableOverProvisioning=n + +# Allow fallback to HTTP if HTTPS is unavailable +# Note: Allowing HTTP (vs. HTTPS) may cause security risks +# OS.AllowHTTP=n + +# Add firewall rules to protect access to Azure host node services +# Note: +# - The default is false to protect the state of exising VMs +OS.EnableFirewall=y diff --git a/config/gaia/waagent.conf b/config/gaia/waagent.conf index 75550a6..9c28ba3 100644 --- a/config/gaia/waagent.conf +++ b/config/gaia/waagent.conf @@ -104,3 +104,12 @@ AutoUpdate.Enabled=n # handling until inVMArtifactsProfile.OnHold is false. # Default is disabled # EnableOverProvisioning=n + +# Allow fallback to HTTP if HTTPS is unavailable +# Note: Allowing HTTP (vs. HTTPS) may cause security risks +# OS.AllowHTTP=n + +# Add firewall rules to protect access to Azure host node services +# Note: +# - The default is false to protect the state of exising VMs +OS.EnableFirewall=y diff --git a/config/openbsd/waagent.conf b/config/openbsd/waagent.conf index ec47a78..a39a9a5 100644 --- a/config/openbsd/waagent.conf +++ b/config/openbsd/waagent.conf @@ -103,3 +103,12 @@ OS.PasswordPath=/etc/master.passwd # handling until inVMArtifactsProfile.OnHold is false. # Default is disabled # EnableOverProvisioning=n + +# Allow fallback to HTTP if HTTPS is unavailable +# Note: Allowing HTTP (vs. HTTPS) may cause security risks +# OS.AllowHTTP=n + +# Add firewall rules to protect access to Azure host node services +# Note: +# - The default is false to protect the state of exising VMs +OS.EnableFirewall=y diff --git a/config/suse/waagent.conf b/config/suse/waagent.conf index b2e90a8..ba50be6 100644 --- a/config/suse/waagent.conf +++ b/config/suse/waagent.conf @@ -107,3 +107,12 @@ OS.SshDir=/etc/ssh # handling until inVMArtifactsProfile.OnHold is false. # Default is disabled # EnableOverProvisioning=n + +# Allow fallback to HTTP if HTTPS is unavailable +# Note: Allowing HTTP (vs. HTTPS) may cause security risks +# OS.AllowHTTP=n + +# Add firewall rules to protect access to Azure host node services +# Note: +# - The default is false to protect the state of exising VMs +OS.EnableFirewall=y diff --git a/config/ubuntu/waagent.conf b/config/ubuntu/waagent.conf index f9145ca..71f2c04 100644 --- a/config/ubuntu/waagent.conf +++ b/config/ubuntu/waagent.conf @@ -95,3 +95,12 @@ OS.SshDir=/etc/ssh # handling until inVMArtifactsProfile.OnHold is false. # Default is disabled # EnableOverProvisioning=n + +# Allow fallback to HTTP if HTTPS is unavailable +# Note: Allowing HTTP (vs. HTTPS) may cause security risks +# OS.AllowHTTP=n + +# Add firewall rules to protect access to Azure host node services +# Note: +# - The default is false to protect the state of exising VMs +OS.EnableFirewall=y diff --git a/config/waagent.conf b/config/waagent.conf index bd11d46..99f54d6 100644 --- a/config/waagent.conf +++ b/config/waagent.conf @@ -108,3 +108,8 @@ OS.SshDir=/etc/ssh # Allow fallback to HTTP if HTTPS is unavailable # Note: Allowing HTTP (vs. HTTPS) may cause security risks # OS.AllowHTTP=n + +# Add firewall rules to protect access to Azure host node services +# Note: +# - The default is false to protect the state of exising VMs +OS.EnableFirewall=y diff --git a/tests/common/osutil/test_default.py b/tests/common/osutil/test_default.py index 9a2628e..3013f65 100644 --- a/tests/common/osutil/test_default.py +++ b/tests/common/osutil/test_default.py @@ -488,5 +488,82 @@ Match host 192.168.1.2\n\ print("WRITING TO {0}".format(waagent_sudoers)) self.assertEqual(1, count) + @patch('os.getuid', return_value=42) + @patch('azurelinuxagent.common.utils.shellutil.run_get_output') + @patch('azurelinuxagent.common.utils.shellutil.run') + def test_enable_firewall(self, mock_run, mock_output, mock_uid): + util = osutil.DefaultOSUtil() + + dst = '1.2.3.4' + uid = 42 + + mock_run.side_effect = [1, 1, 0, 0] + mock_output.return_value = 0 + util.enable_firewall(dst_ip=dst, uid=uid) + + mock_run.assert_has_calls([ + call(osutil.FIREWALL_ACCEPT.format("C", dst, uid), chk_err=False), + call(osutil.FIREWALL_DROP.format("C", dst), chk_err=False), + call(osutil.FIREWALL_ACCEPT.format("A", dst, uid)), + call(osutil.FIREWALL_DROP.format("A", dst)) + ]) + mock_output.assert_called_with(osutil.FIREWALL_LIST) + + @patch('os.getuid', return_value=42) + @patch('azurelinuxagent.common.utils.shellutil.run_get_output') + @patch('azurelinuxagent.common.utils.shellutil.run') + def test_enable_firewall_skips_if_accept_exists(self, mock_run, mock_output, mock_uid): + util = osutil.DefaultOSUtil() + + dst = '1.2.3.4' + uid = 42 + + mock_run.side_effect = [0, 1, 0, 0] + mock_output.return_value = 0 + util.enable_firewall(dst_ip=dst, uid=uid) + + mock_run.assert_has_calls([ + call(osutil.FIREWALL_ACCEPT.format("C", dst, uid), chk_err=False) + ]) + mock_output.assert_not_called() + + @patch('os.getuid', return_value=42) + @patch('azurelinuxagent.common.utils.shellutil.run_get_output') + @patch('azurelinuxagent.common.utils.shellutil.run') + def test_enable_firewall_skips_if_drop_exists(self, mock_run, mock_output, mock_uid): + util = osutil.DefaultOSUtil() + + dst = '1.2.3.4' + uid = 42 + + mock_run.side_effect = [1, 0, 0, 0] + mock_output.return_value = 0 + util.enable_firewall(dst_ip=dst, uid=uid) + + mock_run.assert_has_calls([ + call(osutil.FIREWALL_ACCEPT.format("C", dst, uid), chk_err=False), + call(osutil.FIREWALL_DROP.format("C", dst), chk_err=False), + ]) + mock_output.assert_not_called() + + @patch('os.getuid', return_value=42) + @patch('azurelinuxagent.common.utils.shellutil.run_get_output') + @patch('azurelinuxagent.common.utils.shellutil.run') + def test_enable_firewall_ignores_exceptions(self, mock_run, mock_output, mock_uid): + util = osutil.DefaultOSUtil() + + dst = '1.2.3.4' + uid = 42 + + mock_run.side_effect = [1, 1, Exception] + util.enable_firewall(dst_ip=dst, uid=uid) + + mock_run.assert_has_calls([ + call(osutil.FIREWALL_ACCEPT.format("C", dst, uid), chk_err=False), + call(osutil.FIREWALL_DROP.format("C", dst), chk_err=False), + call(osutil.FIREWALL_ACCEPT.format("A", dst, uid)) + ]) + mock_output.assert_not_called() + if __name__ == '__main__': unittest.main() diff --git a/tests/common/test_conf.py b/tests/common/test_conf.py index 6a0beb0..93759de 100644 --- a/tests/common/test_conf.py +++ b/tests/common/test_conf.py @@ -63,7 +63,8 @@ class TestConf(AgentTestCase): "AutoUpdate.Enabled" : True, "AutoUpdate.GAFamily" : "Prod", "EnableOverProvisioning" : False, - "OS.AllowHTTP" : False + "OS.AllowHTTP" : False, + "OS.EnableFirewall" : True } def setUp(self): diff --git a/tests/data/test_waagent.conf b/tests/data/test_waagent.conf index c7bcc9e..edc3676 100644 --- a/tests/data/test_waagent.conf +++ b/tests/data/test_waagent.conf @@ -112,3 +112,12 @@ OS.SshDir=/notareal/path # handling until inVMArtifactsProfile.OnHold is false. # Default is disabled # EnableOverProvisioning=n + +# Allow fallback to HTTP if HTTPS is unavailable +# Note: Allowing HTTP (vs. HTTPS) may cause security risks +# OS.AllowHTTP=n + +# Add firewall rules to protect access to Azure host node services +# Note: +# - The default is false to protect the state of exising VMs +OS.EnableFirewall=y diff --git a/tests/test_agent.py b/tests/test_agent.py index e662264..77be07a 100644 --- a/tests/test_agent.py +++ b/tests/test_agent.py @@ -39,6 +39,7 @@ Logs.Verbose = False OS.AllowHTTP = False OS.CheckRdmaDriver = False OS.EnableFIPS = True +OS.EnableFirewall = True OS.EnableRDMA = False OS.HomeDir = /home OS.OpensslPath = /usr/bin/openssl diff --git a/tests/utils/test_rest_util.py b/tests/utils/test_rest_util.py index ae1d135..75c75f8 100644 --- a/tests/utils/test_rest_util.py +++ b/tests/utils/test_rest_util.py @@ -23,7 +23,7 @@ from azurelinuxagent.common.exception import BadRequestError, \ import azurelinuxagent.common.utils.restutil as restutil from azurelinuxagent.common.future import httpclient, ustr -from tests.tools import AgentTestCase, patch, Mock, MagicMock +from tests.tools import * class TestHttpOperations(AgentTestCase): @@ -118,46 +118,100 @@ class TestHttpOperations(AgentTestCase): self.assertEqual("foo.com", h) self.assertEqual(80, p) + @patch("azurelinuxagent.common.future.httpclient.HTTPSConnection") + @patch("azurelinuxagent.common.future.httpclient.HTTPConnection") + def test_http_request_direct(self, HTTPConnection, HTTPSConnection): + mock_conn = \ + MagicMock(getresponse=\ + Mock(return_value=\ + Mock(read=Mock(return_value="TheResults")))) + + HTTPConnection.return_value = mock_conn + + resp = restutil._http_request("GET", "foo", "/bar") + + HTTPConnection.assert_has_calls([ + call("foo", 80, timeout=10) + ]) + HTTPSConnection.assert_not_called() + mock_conn.request.assert_has_calls([ + call(method="GET", url="/bar", body=None, headers={}) + ]) + mock_conn.getresponse.assert_called_once() + self.assertNotEquals(None, resp) + self.assertEquals("TheResults", resp.read()) @patch("azurelinuxagent.common.future.httpclient.HTTPSConnection") @patch("azurelinuxagent.common.future.httpclient.HTTPConnection") - def test_http_request(self, HTTPConnection, HTTPSConnection): - mock_http_conn = MagicMock() - mock_http_resp = MagicMock() - mock_http_conn.getresponse = Mock(return_value=mock_http_resp) - HTTPConnection.return_value = mock_http_conn - HTTPSConnection.return_value = mock_http_conn + def test_http_request_direct_secure(self, HTTPConnection, HTTPSConnection): + mock_conn = \ + MagicMock(getresponse=\ + Mock(return_value=\ + Mock(read=Mock(return_value="TheResults")))) - mock_http_resp.read = Mock(return_value="_(:3| <)_") + HTTPSConnection.return_value = mock_conn - # Test http get - resp = restutil._http_request("GET", "foo", "bar") + resp = restutil._http_request("GET", "foo", "/bar", secure=True) + + HTTPConnection.assert_not_called() + HTTPSConnection.assert_has_calls([ + call("foo", 443, timeout=10) + ]) + mock_conn.request.assert_has_calls([ + call(method="GET", url="/bar", body=None, headers={}) + ]) + mock_conn.getresponse.assert_called_once() self.assertNotEquals(None, resp) - self.assertEquals("_(:3| <)_", resp.read()) + self.assertEquals("TheResults", resp.read()) - # Test https get - resp = restutil._http_request("GET", "foo", "bar", secure=True) - self.assertNotEquals(None, resp) - self.assertEquals("_(:3| <)_", resp.read()) + @patch("azurelinuxagent.common.future.httpclient.HTTPSConnection") + @patch("azurelinuxagent.common.future.httpclient.HTTPConnection") + def test_http_request_proxy(self, HTTPConnection, HTTPSConnection): + mock_conn = \ + MagicMock(getresponse=\ + Mock(return_value=\ + Mock(read=Mock(return_value="TheResults")))) - # Test http get with proxy - mock_http_resp.read = Mock(return_value="_(:3| <)_") - resp = restutil._http_request("GET", "foo", "bar", proxy_host="foo.bar", - proxy_port=23333) - self.assertNotEquals(None, resp) - self.assertEquals("_(:3| <)_", resp.read()) + HTTPConnection.return_value = mock_conn - # Test https get - resp = restutil._http_request("GET", "foo", "bar", secure=True) - self.assertNotEquals(None, resp) - self.assertEquals("_(:3| <)_", resp.read()) + resp = restutil._http_request("GET", "foo", "/bar", + proxy_host="foo.bar", proxy_port=23333) - # Test https get with proxy - mock_http_resp.read = Mock(return_value="_(:3| <)_") - resp = restutil._http_request("GET", "foo", "bar", proxy_host="foo.bar", - proxy_port=23333, secure=True) + HTTPConnection.assert_has_calls([ + call("foo.bar", 23333, timeout=10) + ]) + HTTPSConnection.assert_not_called() + mock_conn.request.assert_has_calls([ + call(method="GET", url="http://foo:80/bar", body=None, headers={}) + ]) + mock_conn.getresponse.assert_called_once() self.assertNotEquals(None, resp) - self.assertEquals("_(:3| <)_", resp.read()) + self.assertEquals("TheResults", resp.read()) + + @patch("azurelinuxagent.common.future.httpclient.HTTPSConnection") + @patch("azurelinuxagent.common.future.httpclient.HTTPConnection") + def test_http_request_proxy_secure(self, HTTPConnection, HTTPSConnection): + mock_conn = \ + MagicMock(getresponse=\ + Mock(return_value=\ + Mock(read=Mock(return_value="TheResults")))) + + HTTPSConnection.return_value = mock_conn + + resp = restutil._http_request("GET", "foo", "/bar", + proxy_host="foo.bar", proxy_port=23333, + secure=True) + + HTTPConnection.assert_not_called() + HTTPSConnection.assert_has_calls([ + call("foo.bar", 23333, timeout=10) + ]) + mock_conn.request.assert_has_calls([ + call(method="GET", url="https://foo:443/bar", body=None, headers={}) + ]) + mock_conn.getresponse.assert_called_once() + self.assertNotEquals(None, resp) + self.assertEquals("TheResults", resp.read()) @patch("time.sleep") @patch("azurelinuxagent.common.utils.restutil._http_request")