summaryrefslogtreecommitdiff
path: root/azurelinuxagent/common/osutil
diff options
context:
space:
mode:
Diffstat (limited to 'azurelinuxagent/common/osutil')
-rw-r--r--azurelinuxagent/common/osutil/default.py97
-rw-r--r--azurelinuxagent/common/osutil/factory.py9
-rw-r--r--azurelinuxagent/common/osutil/ubuntu.py37
3 files changed, 121 insertions, 22 deletions
diff --git a/azurelinuxagent/common/osutil/default.py b/azurelinuxagent/common/osutil/default.py
index f0b1050..16b7444 100644
--- a/azurelinuxagent/common/osutil/default.py
+++ b/azurelinuxagent/common/osutil/default.py
@@ -57,10 +57,21 @@ IPTABLES_VERSION = "iptables --version"
IPTABLES_LOCKING_VERSION = FlexibleVersion('1.4.21')
FIREWALL_ACCEPT = "iptables {0} -t security -{1} OUTPUT -d {2} -p tcp -m owner --uid-owner {3} -j ACCEPT"
-FIREWALL_DROP = "iptables {0} -t security -{1} OUTPUT -d {2} -p tcp -j DROP"
-FIREWALL_LIST = "iptables {0} -t security -L"
+# Note:
+# -- Initially "flight" the change to ACCEPT packets and develop a metric baseline
+# A subsequent release will convert the ACCEPT to DROP
+FIREWALL_DROP = "iptables {0} -t security -{1} OUTPUT -d {2} -p tcp -m conntrack --ctstate INVALID,NEW -j ACCEPT"
+# FIREWALL_DROP = "iptables {0} -t security -{1} OUTPUT -d {2} -p tcp -m conntrack --ctstate INVALID,NEW -j DROP"
+FIREWALL_LIST = "iptables {0} -t security -L -nxv"
+FIREWALL_PACKETS = "iptables {0} -t security -L OUTPUT --zero OUTPUT -nxv"
FIREWALL_FLUSH = "iptables {0} -t security --flush"
+# Precisely delete the rules created by the agent.
+FIREWALL_DELETE_CONNTRACK = "iptables {0} -t security -D OUTPUT -d {1} -p tcp -m conntrack --ctstate INVALID,NEW -j ACCEPT"
+FIREWALL_DELETE_OWNER = "iptables {0} -t security -D OUTPUT -d {1} -p tcp -m owner --uid-owner {2} -j ACCEPT"
+
+PACKET_PATTERN = "^\s*(\d+)\s+(\d+)\s+DROP\s+.*{0}[^\d]*$"
+
_enable_firewall = True
DMIDECODE_CMD = 'dmidecode --string system-uuid'
@@ -69,13 +80,45 @@ UUID_PATTERN = re.compile(
r'^\s*[A-F0-9]{8}(?:\-[A-F0-9]{4}){3}\-[A-F0-9]{12}\s*$',
re.IGNORECASE)
-class DefaultOSUtil(object):
+class DefaultOSUtil(object):
def __init__(self):
self.agent_conf_file_path = '/etc/waagent.conf'
self.selinux = None
self.disable_route_warning = False
+ def get_firewall_dropped_packets(self, dst_ip=None):
+ # If a previous attempt failed, do not retry
+ global _enable_firewall
+ if not _enable_firewall:
+ return 0
+
+ try:
+ wait = self.get_firewall_will_wait()
+
+ rc, output = shellutil.run_get_output(FIREWALL_PACKETS.format(wait))
+ if rc == 3:
+ # Transient error that we ignore. This code fires every loop
+ # of the daemon (60m), so we will get the value eventually.
+ return 0
+
+ if rc != 0:
+ return -1
+
+ pattern = re.compile(PACKET_PATTERN.format(dst_ip))
+ for line in output.split('\n'):
+ m = pattern.match(line)
+ if m is not None:
+ return int(m.group(1))
+
+ return 0
+
+ except Exception as e:
+ _enable_firewall = False
+ logger.warn("Unable to retrieve firewall packets dropped"
+ "{0}".format(ustr(e)))
+ return -1
+
def get_firewall_will_wait(self):
# Determine if iptables will serialize access
rc, output = shellutil.run_get_output(IPTABLES_VERSION)
@@ -95,31 +138,46 @@ class DefaultOSUtil(object):
else ""
return wait
- def remove_firewall(self):
- # If a previous attempt threw an exception, do not retry
+ def _delete_rule(self, rule):
+ """
+ Continually execute the delete operation until the return
+ code is non-zero or the limit has been reached.
+ """
+ for i in range(1, 100):
+ rc = shellutil.run(rule, chk_err=False)
+ if rc == 1:
+ return
+ elif rc == 2:
+ raise Exception("invalid firewall deletion rule '{0}'".format(rule))
+
+ def remove_firewall(self, dst_ip=None, uid=None):
+ # If a previous attempt failed, do not retry
global _enable_firewall
if not _enable_firewall:
return False
try:
+ if dst_ip is None or uid is None:
+ msg = "Missing arguments to enable_firewall"
+ logger.warn(msg)
+ raise Exception(msg)
+
wait = self.get_firewall_will_wait()
- flush_rule = FIREWALL_FLUSH.format(wait)
- if shellutil.run(flush_rule, chk_err=False) != 0:
- logger.warn("Failed to flush firewall")
+ self._delete_rule(FIREWALL_DELETE_CONNTRACK.format(wait, dst_ip))
+ self._delete_rule(FIREWALL_DELETE_OWNER.format(wait, dst_ip, uid))
return True
except Exception as e:
_enable_firewall = False
- logger.info("Unable to flush firewall -- "
+ logger.info("Unable to remove firewall -- "
"no further attempts will be made: "
"{0}".format(ustr(e)))
return False
def enable_firewall(self, dst_ip=None, uid=None):
-
- # If a previous attempt threw an exception, do not retry
+ # If a previous attempt failed, do not retry
global _enable_firewall
if not _enable_firewall:
return False
@@ -134,10 +192,15 @@ class DefaultOSUtil(object):
# If the DROP rule exists, make no changes
drop_rule = FIREWALL_DROP.format(wait, "C", dst_ip)
-
- if shellutil.run(drop_rule, chk_err=False) == 0:
+ rc = shellutil.run(drop_rule, chk_err=False)
+ if rc == 0:
logger.verbose("Firewall appears established")
return True
+ elif rc == 2:
+ self.remove_firewall(dst_ip, uid)
+ msg = "please upgrade iptables to a version that supports the -C option"
+ logger.warn(msg)
+ raise Exception(msg)
# Otherwise, append both rules
accept_rule = FIREWALL_ACCEPT.format(wait, "A", dst_ip, uid)
@@ -309,11 +372,11 @@ class DefaultOSUtil(object):
else:
sudoer = "{0} ALL=(ALL) ALL".format(username)
if not os.path.isfile(sudoers_wagent) or \
- fileutil.findstr_in_file(sudoers_wagent, sudoer) is None:
+ fileutil.findstr_in_file(sudoers_wagent, sudoer) is False:
fileutil.append_file(sudoers_wagent, "{0}\n".format(sudoer))
fileutil.chmod(sudoers_wagent, 0o440)
else:
- #Remove user from sudoers
+ # remove user from sudoers
if os.path.isfile(sudoers_wagent):
try:
content = fileutil.read_file(sudoers_wagent)
@@ -440,7 +503,7 @@ class DefaultOSUtil(object):
conf_file = fileutil.read_file(conf_file_path).split("\n")
textutil.set_ssh_config(conf_file, "PasswordAuthentication", option)
textutil.set_ssh_config(conf_file, "ChallengeResponseAuthentication", option)
- textutil.set_ssh_config(conf_file, "ClientAliveInterval", "180")
+ textutil.set_ssh_config(conf_file, "ClientAliveInterval", str(conf.get_ssh_client_alive_interval()))
fileutil.write_file(conf_file_path, "\n".join(conf_file))
logger.info("{0} SSH password-based authentication methods."
.format("Disabled" if disable_password else "Enabled"))
@@ -965,7 +1028,7 @@ class DefaultOSUtil(object):
if not os.path.exists(hostname_record):
# this file is created at provisioning time with agents >= 2.2.3
hostname = socket.gethostname()
- logger.warn('Hostname record does not exist, '
+ logger.info('Hostname record does not exist, '
'creating [{0}] with hostname [{1}]',
hostname_record,
hostname)
diff --git a/azurelinuxagent/common/osutil/factory.py b/azurelinuxagent/common/osutil/factory.py
index 43aa6a7..1b4e2cb 100644
--- a/azurelinuxagent/common/osutil/factory.py
+++ b/azurelinuxagent/common/osutil/factory.py
@@ -27,7 +27,8 @@ from .freebsd import FreeBSDOSUtil
from .openbsd import OpenBSDOSUtil
from .redhat import RedhatOSUtil, Redhat6xOSUtil
from .suse import SUSEOSUtil, SUSE11OSUtil
-from .ubuntu import UbuntuOSUtil, Ubuntu12OSUtil, Ubuntu14OSUtil, UbuntuSnappyOSUtil
+from .ubuntu import UbuntuOSUtil, Ubuntu12OSUtil, Ubuntu14OSUtil, \
+ UbuntuSnappyOSUtil, Ubuntu16OSUtil
from .alpine import AlpineOSUtil
from .bigip import BigIpOSUtil
from .gaia import GaiaOSUtil
@@ -46,10 +47,12 @@ def get_osutil(distro_name=DISTRO_NAME,
return ClearLinuxUtil()
if distro_name == "ubuntu":
- if Version(distro_version) == Version("12.04") or Version(distro_version) == Version("12.10"):
+ if Version(distro_version) in [Version("12.04"), Version("12.10")]:
return Ubuntu12OSUtil()
- elif Version(distro_version) == Version("14.04") or Version(distro_version) == Version("14.10"):
+ elif Version(distro_version) in [Version("14.04"), Version("14.10")]:
return Ubuntu14OSUtil()
+ elif Version(distro_version) in [Version('16.04'), Version('16.10'), Version('17.04')]:
+ return Ubuntu16OSUtil()
elif distro_full_name == "Snappy Ubuntu Core":
return UbuntuSnappyOSUtil()
else:
diff --git a/azurelinuxagent/common/osutil/ubuntu.py b/azurelinuxagent/common/osutil/ubuntu.py
index 3c353cf..8dacc75 100644
--- a/azurelinuxagent/common/osutil/ubuntu.py
+++ b/azurelinuxagent/common/osutil/ubuntu.py
@@ -16,9 +16,14 @@
# Requires Python 2.4+ and Openssl 1.0+
#
+import time
+
+import azurelinuxagent.common.logger as logger
import azurelinuxagent.common.utils.shellutil as shellutil
+
from azurelinuxagent.common.osutil.default import DefaultOSUtil
+
class Ubuntu14OSUtil(DefaultOSUtil):
def __init__(self):
super(Ubuntu14OSUtil, self).__init__()
@@ -41,6 +46,7 @@ class Ubuntu14OSUtil(DefaultOSUtil):
def get_dhcp_lease_endpoint(self):
return self.get_endpoint_from_leases_path('/var/lib/dhcp/dhclient.*.leases')
+
class Ubuntu12OSUtil(Ubuntu14OSUtil):
def __init__(self):
super(Ubuntu12OSUtil, self).__init__()
@@ -50,9 +56,13 @@ class Ubuntu12OSUtil(Ubuntu14OSUtil):
ret = shellutil.run_get_output("pidof dhclient3", chk_err=False)
return ret[1] if ret[0] == 0 else None
-class UbuntuOSUtil(Ubuntu14OSUtil):
+
+class Ubuntu16OSUtil(Ubuntu14OSUtil):
+ """
+ Ubuntu 16.04, 16.10, and 17.04.
+ """
def __init__(self):
- super(UbuntuOSUtil, self).__init__()
+ super(Ubuntu16OSUtil, self).__init__()
def register_agent_service(self):
return shellutil.run("systemctl unmask walinuxagent", chk_err=False)
@@ -60,6 +70,29 @@ class UbuntuOSUtil(Ubuntu14OSUtil):
def unregister_agent_service(self):
return shellutil.run("systemctl mask walinuxagent", chk_err=False)
+
+class UbuntuOSUtil(Ubuntu16OSUtil):
+ def __init__(self):
+ super(UbuntuOSUtil, self).__init__()
+
+ def restart_if(self, ifname, retries=3, wait=5):
+ """
+ Restart an interface by bouncing the link. systemd-networkd observes
+ this event, and forces a renew of DHCP.
+ """
+ retry_limit=retries+1
+ for attempt in range(1, retry_limit):
+ return_code=shellutil.run("ip link set {0} down && ip link set {0} up".format(ifname))
+ if return_code == 0:
+ return
+ logger.warn("failed to restart {0}: return code {1}".format(ifname, return_code))
+ if attempt < retry_limit:
+ logger.info("retrying in {0} seconds".format(wait))
+ time.sleep(wait)
+ else:
+ logger.warn("exceeded restart retries")
+
+
class UbuntuSnappyOSUtil(Ubuntu14OSUtil):
def __init__(self):
super(UbuntuSnappyOSUtil, self).__init__()