azurelinuxagent/common/protocol/metadata_server_migration_util.py (93 lines of code) (raw):
# Microsoft Azure Linux Agent
#
# Copyright 2020 Microsoft Corporation
#
# 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.
#
# Requires Python 2.6+ and Openssl 1.0+
#
import re
import os
import azurelinuxagent.common.conf as conf
import azurelinuxagent.common.logger as logger
from azurelinuxagent.common.event import add_event, WALAEventOperation
from azurelinuxagent.common.future import ustr
from azurelinuxagent.common.utils import shellutil
from azurelinuxagent.common.utils.flexible_version import FlexibleVersion
# Name for Metadata Server Protocol
_METADATA_PROTOCOL_NAME = "MetadataProtocol"
# MetadataServer Certificates for Cleanup
_LEGACY_METADATA_SERVER_TRANSPORT_PRV_FILE_NAME = "V2TransportPrivate.pem"
_LEGACY_METADATA_SERVER_TRANSPORT_CERT_FILE_NAME = "V2TransportCert.pem"
_LEGACY_METADATA_SERVER_P7B_FILE_NAME = "Certificates.p7b"
# MetadataServer Endpoint
_KNOWN_METADATASERVER_IP = "169.254.169.254"
def is_metadata_server_artifact_present():
metadata_artifact_path = os.path.join(conf.get_lib_dir(), _LEGACY_METADATA_SERVER_TRANSPORT_CERT_FILE_NAME)
return os.path.isfile(metadata_artifact_path)
def cleanup_metadata_server_artifacts():
logger.info("Clean up for MetadataServer to WireServer protocol migration: removing MetadataServer certificates and resetting firewall rules.")
_cleanup_metadata_protocol_certificates()
_reset_firewall_rules()
def _cleanup_metadata_protocol_certificates():
"""
Removes MetadataServer Certificates.
"""
lib_directory = conf.get_lib_dir()
_ensure_file_removed(lib_directory, _LEGACY_METADATA_SERVER_TRANSPORT_PRV_FILE_NAME)
_ensure_file_removed(lib_directory, _LEGACY_METADATA_SERVER_TRANSPORT_CERT_FILE_NAME)
_ensure_file_removed(lib_directory, _LEGACY_METADATA_SERVER_P7B_FILE_NAME)
def _reset_firewall_rules():
"""
Removes MetadataServer firewall rule so IMDS can be used.
"""
try:
_remove_firewall(dst_ip=_KNOWN_METADATASERVER_IP, uid=os.getuid(), wait=_get_firewall_will_wait())
except Exception as e:
add_event(op=WALAEventOperation.Firewall, message="Failed to remove firewall rule for MetadataServer: {0}".format(e), is_success=False)
def _ensure_file_removed(directory, file_name):
"""
Removes files if they are present.
"""
path = os.path.join(directory, file_name)
if os.path.isfile(path):
os.remove(path)
#
# NOTE: The code below was taken almost verbatim from the old firewall code that use to reside in osutil (default.py), with only very minor edits
#
_IPTABLES_VERSION_PATTERN = re.compile(r"^[^\d\.]*([\d\.]+).*$")
_IPTABLES_LOCKING_VERSION = FlexibleVersion('1.4.21')
def _add_wait(wait, command):
"""
If 'wait' is True, adds the wait option (-w) to the given iptables command line
"""
if wait:
command.insert(1, "-w")
return command
def _get_iptables_version_command():
return ["iptables", "--version"]
# Precisely delete the rules created by the agent. This rule was used <= 2.2.25. This rule helped to validate our change, and determine impact.
def _get_firewall_delete_conntrack_accept_command(wait, destination):
return _add_wait(
wait,
["iptables", "-t", "security", "-D", "OUTPUT", "-d", destination, "-p", "tcp", "-m", "conntrack", "--ctstate", "INVALID,NEW", "-j", "ACCEPT"])
def _get_delete_accept_tcp_rule(wait, destination):
return _add_wait(
wait,
["iptables", "-t", "security", "-D", "OUTPUT", "-d", destination, "-p", "tcp", "--destination-port", "53", "-j", "ACCEPT"])
def _get_firewall_delete_owner_accept_command(wait, destination, owner_uid):
return _add_wait(
wait,
["iptables", "-t", "security", "-D", "OUTPUT", "-d", destination, "-p", "tcp", "-m", "owner", "--uid-owner", str(owner_uid), "-j", "ACCEPT"])
def _get_firewall_delete_conntrack_drop_command(wait, destination):
return _add_wait(
wait,
["iptables", "-t", "security", "-D", "OUTPUT", "-d", destination, "-p", "tcp", "-m", "conntrack", "--ctstate", "INVALID,NEW", "-j", "DROP"])
def _get_firewall_will_wait():
# Determine if iptables will serialize access
try:
output = shellutil.run_command(_get_iptables_version_command())
except Exception as e:
msg = "Unable to determine version of iptables: {0}".format(ustr(e))
logger.warn(msg)
raise Exception(msg)
m = _IPTABLES_VERSION_PATTERN.match(output)
if m is None:
msg = "iptables did not return version information: {0}".format(output)
logger.warn(msg)
raise Exception(msg)
wait = "-w" \
if FlexibleVersion(m.group(1)) >= _IPTABLES_LOCKING_VERSION \
else ""
return wait
def _delete_rule(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): # pylint: disable=W0612
try:
rc = shellutil.run_command(rule) # pylint: disable=W0612
except shellutil.CommandError as e:
if e.returncode == 1:
return
if e.returncode == 2:
raise Exception("invalid firewall deletion rule '{0}'".format(rule))
def _remove_firewall(dst_ip, uid, wait):
try:
# This rule was <= 2.2.25 only, and may still exist on some VMs. Until 2.2.25
# has aged out, keep this cleanup in place.
_delete_rule(_get_firewall_delete_conntrack_accept_command(wait, dst_ip))
_delete_rule(_get_delete_accept_tcp_rule(wait, dst_ip))
_delete_rule(_get_firewall_delete_owner_accept_command(wait, dst_ip, uid))
_delete_rule(_get_firewall_delete_conntrack_drop_command(wait, dst_ip))
return True
except Exception as e:
logger.info("Unable to remove firewall -- no further attempts will be made: {0}".format(ustr(e)))
return False