Blame cloudinit/sources/helpers/azure.py

Packit Service a04d08
# This file is part of cloud-init. See LICENSE file for license information.
Packit Service 9bfd13
import base64
Packit Service a04d08
import json
Packit Service a04d08
import logging
Packit Service a04d08
import os
Packit Service a04d08
import re
Packit Service a04d08
import socket
Packit Service a04d08
import struct
Packit Service a04d08
import time
Packit Service a04d08
import textwrap
Packit Service 9bfd13
import zlib
Packit Service a04d08
Packit Service 9bfd13
from cloudinit.settings import CFG_BUILTIN
Packit Service a04d08
from cloudinit.net import dhcp
Packit Service a04d08
from cloudinit import stages
Packit Service a04d08
from cloudinit import temp_utils
Packit Service a04d08
from contextlib import contextmanager
Packit Service a04d08
from xml.etree import ElementTree
Packit Service a04d08
Packit Service 9bfd13
from cloudinit import subp
Packit Service a04d08
from cloudinit import url_helper
Packit Service a04d08
from cloudinit import util
Packit Service a04d08
from cloudinit import version
Packit Service a04d08
from cloudinit import distros
Packit Service a04d08
from cloudinit.reporting import events
Packit Service a04d08
from cloudinit.net.dhcp import EphemeralDHCPv4
Packit Service a04d08
from datetime import datetime
Packit Service a04d08
Packit Service a04d08
LOG = logging.getLogger(__name__)
Packit Service a04d08
Packit Service a04d08
# This endpoint matches the format as found in dhcp lease files, since this
Packit Service a04d08
# value is applied if the endpoint can't be found within a lease file
Packit Service a04d08
DEFAULT_WIRESERVER_ENDPOINT = "a8:3f:81:10"
Packit Service a04d08
Packit Service a04d08
BOOT_EVENT_TYPE = 'boot-telemetry'
Packit Service a04d08
SYSTEMINFO_EVENT_TYPE = 'system-info'
Packit Service a04d08
DIAGNOSTIC_EVENT_TYPE = 'diagnostic'
Packit Service 9bfd13
COMPRESSED_EVENT_TYPE = 'compressed'
Packit Service 9bfd13
# Maximum number of bytes of the cloud-init.log file that can be dumped to KVP
Packit Service 9bfd13
# at once. This number is based on the analysis done on a large sample of
Packit Service 9bfd13
# cloud-init.log files where the P95 of the file sizes was 537KB and the time
Packit Service 9bfd13
# consumed to dump 500KB file was (P95:76, P99:233, P99.9:1170) in ms
Packit Service 9bfd13
MAX_LOG_TO_KVP_LENGTH = 512000
Packit Service 9bfd13
# Marker file to indicate whether cloud-init.log is pushed to KVP
Packit Service 9bfd13
LOG_PUSHED_TO_KVP_MARKER_FILE = '/var/lib/cloud/data/log_pushed_to_kvp'
Packit Service a04d08
azure_ds_reporter = events.ReportEventStack(
Packit Service a04d08
    name="azure-ds",
Packit Service a04d08
    description="initialize reporter for azure ds",
Packit Service a04d08
    reporting_enabled=True)
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def azure_ds_telemetry_reporter(func):
Packit Service a04d08
    def impl(*args, **kwargs):
Packit Service a04d08
        with events.ReportEventStack(
Packit Service a04d08
                name=func.__name__,
Packit Service a04d08
                description=func.__name__,
Packit Service a04d08
                parent=azure_ds_reporter):
Packit Service a04d08
            return func(*args, **kwargs)
Packit Service a04d08
    return impl
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def is_byte_swapped(previous_id, current_id):
Packit Service a04d08
    """
Packit Service a04d08
    Azure stores the instance ID with an incorrect byte ordering for the
Packit Service a04d08
    first parts. This corrects the byte order such that it is consistent with
Packit Service a04d08
    that returned by the metadata service.
Packit Service a04d08
    """
Packit Service a04d08
    if previous_id == current_id:
Packit Service a04d08
        return False
Packit Service a04d08
Packit Service a04d08
    def swap_bytestring(s, width=2):
Packit Service a04d08
        dd = [byte for byte in textwrap.wrap(s, 2)]
Packit Service a04d08
        dd.reverse()
Packit Service a04d08
        return ''.join(dd)
Packit Service a04d08
Packit Service a04d08
    parts = current_id.split('-')
Packit Service 9bfd13
    swapped_id = '-'.join(
Packit Service 9bfd13
        [
Packit Service a04d08
            swap_bytestring(parts[0]),
Packit Service a04d08
            swap_bytestring(parts[1]),
Packit Service a04d08
            swap_bytestring(parts[2]),
Packit Service a04d08
            parts[3],
Packit Service a04d08
            parts[4]
Packit Service 9bfd13
        ]
Packit Service 9bfd13
    )
Packit Service a04d08
Packit Service a04d08
    return previous_id == swapped_id
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
@azure_ds_telemetry_reporter
Packit Service a04d08
def get_boot_telemetry():
Packit Service a04d08
    """Report timestamps related to kernel initialization and systemd
Packit Service a04d08
       activation of cloud-init"""
Packit Service a04d08
    if not distros.uses_systemd():
Packit Service a04d08
        raise RuntimeError(
Packit Service a04d08
            "distro not using systemd, skipping boot telemetry")
Packit Service a04d08
Packit Service a04d08
    LOG.debug("Collecting boot telemetry")
Packit Service a04d08
    try:
Packit Service a04d08
        kernel_start = float(time.time()) - float(util.uptime())
Packit Service 9bfd13
    except ValueError as e:
Packit Service 9bfd13
        raise RuntimeError(
Packit Service 9bfd13
            "Failed to determine kernel start timestamp"
Packit Service 9bfd13
        ) from e
Packit Service a04d08
Packit Service a04d08
    try:
Packit Service 9bfd13
        out, _ = subp.subp(['/bin/systemctl',
Packit Service a04d08
                            'show', '-p',
Packit Service a04d08
                            'UserspaceTimestampMonotonic'],
Packit Service a04d08
                           capture=True)
Packit Service a04d08
        tsm = None
Packit Service a04d08
        if out and '=' in out:
Packit Service a04d08
            tsm = out.split("=")[1]
Packit Service a04d08
Packit Service a04d08
        if not tsm:
Packit Service a04d08
            raise RuntimeError("Failed to parse "
Packit Service a04d08
                               "UserspaceTimestampMonotonic from systemd")
Packit Service a04d08
Packit Service a04d08
        user_start = kernel_start + (float(tsm) / 1000000)
Packit Service 9bfd13
    except subp.ProcessExecutionError as e:
Packit Service 9bfd13
        raise RuntimeError(
Packit Service 9bfd13
            "Failed to get UserspaceTimestampMonotonic: %s" % e
Packit Service 9bfd13
        ) from e
Packit Service a04d08
    except ValueError as e:
Packit Service 9bfd13
        raise RuntimeError(
Packit Service 9bfd13
            "Failed to parse UserspaceTimestampMonotonic from systemd: %s" % e
Packit Service 9bfd13
        ) from e
Packit Service a04d08
Packit Service a04d08
    try:
Packit Service 9bfd13
        out, _ = subp.subp(['/bin/systemctl', 'show',
Packit Service a04d08
                            'cloud-init-local', '-p',
Packit Service a04d08
                            'InactiveExitTimestampMonotonic'],
Packit Service a04d08
                           capture=True)
Packit Service a04d08
        tsm = None
Packit Service a04d08
        if out and '=' in out:
Packit Service a04d08
            tsm = out.split("=")[1]
Packit Service a04d08
        if not tsm:
Packit Service a04d08
            raise RuntimeError("Failed to parse "
Packit Service a04d08
                               "InactiveExitTimestampMonotonic from systemd")
Packit Service a04d08
Packit Service a04d08
        cloudinit_activation = kernel_start + (float(tsm) / 1000000)
Packit Service 9bfd13
    except subp.ProcessExecutionError as e:
Packit Service 9bfd13
        raise RuntimeError(
Packit Service 9bfd13
            "Failed to get InactiveExitTimestampMonotonic: %s" % e
Packit Service 9bfd13
        ) from e
Packit Service a04d08
    except ValueError as e:
Packit Service 9bfd13
        raise RuntimeError(
Packit Service 9bfd13
            "Failed to parse InactiveExitTimestampMonotonic from systemd: %s"
Packit Service 9bfd13
            % e
Packit Service 9bfd13
        ) from e
Packit Service a04d08
Packit Service a04d08
    evt = events.ReportingEvent(
Packit Service a04d08
        BOOT_EVENT_TYPE, 'boot-telemetry',
Packit Service a04d08
        "kernel_start=%s user_start=%s cloudinit_activation=%s" %
Packit Service a04d08
        (datetime.utcfromtimestamp(kernel_start).isoformat() + 'Z',
Packit Service a04d08
         datetime.utcfromtimestamp(user_start).isoformat() + 'Z',
Packit Service a04d08
         datetime.utcfromtimestamp(cloudinit_activation).isoformat() + 'Z'),
Packit Service a04d08
        events.DEFAULT_EVENT_ORIGIN)
Packit Service a04d08
    events.report_event(evt)
Packit Service a04d08
Packit Service a04d08
    # return the event for unit testing purpose
Packit Service a04d08
    return evt
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
@azure_ds_telemetry_reporter
Packit Service a04d08
def get_system_info():
Packit Service a04d08
    """Collect and report system information"""
Packit Service a04d08
    info = util.system_info()
Packit Service a04d08
    evt = events.ReportingEvent(
Packit Service a04d08
        SYSTEMINFO_EVENT_TYPE, 'system information',
Packit Service a04d08
        "cloudinit_version=%s, kernel_version=%s, variant=%s, "
Packit Service a04d08
        "distro_name=%s, distro_version=%s, flavor=%s, "
Packit Service a04d08
        "python_version=%s" %
Packit Service a04d08
        (version.version_string(), info['release'], info['variant'],
Packit Service a04d08
         info['dist'][0], info['dist'][1], info['dist'][2],
Packit Service a04d08
         info['python']), events.DEFAULT_EVENT_ORIGIN)
Packit Service a04d08
    events.report_event(evt)
Packit Service a04d08
Packit Service a04d08
    # return the event for unit testing purpose
Packit Service a04d08
    return evt
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def report_diagnostic_event(str):
Packit Service a04d08
    """Report a diagnostic event"""
Packit Service a04d08
    evt = events.ReportingEvent(
Packit Service a04d08
        DIAGNOSTIC_EVENT_TYPE, 'diagnostic message',
Packit Service a04d08
        str, events.DEFAULT_EVENT_ORIGIN)
Packit Service a04d08
    events.report_event(evt)
Packit Service a04d08
Packit Service a04d08
    # return the event for unit testing purpose
Packit Service a04d08
    return evt
Packit Service a04d08
Packit Service a04d08
Packit Service 9bfd13
def report_compressed_event(event_name, event_content):
Packit Service 9bfd13
    """Report a compressed event"""
Packit Service 9bfd13
    compressed_data = base64.encodebytes(zlib.compress(event_content))
Packit Service 9bfd13
    event_data = {"encoding": "gz+b64",
Packit Service 9bfd13
                  "data": compressed_data.decode('ascii')}
Packit Service 9bfd13
    evt = events.ReportingEvent(
Packit Service 9bfd13
        COMPRESSED_EVENT_TYPE, event_name,
Packit Service 9bfd13
        json.dumps(event_data),
Packit Service 9bfd13
        events.DEFAULT_EVENT_ORIGIN)
Packit Service 9bfd13
    events.report_event(evt,
Packit Service 9bfd13
                        excluded_handler_types={"log", "print", "webhook"})
Packit Service 9bfd13
Packit Service 9bfd13
    # return the event for unit testing purpose
Packit Service 9bfd13
    return evt
Packit Service 9bfd13
Packit Service 9bfd13
Packit Service 9bfd13
@azure_ds_telemetry_reporter
Packit Service 9bfd13
def push_log_to_kvp(file_name=CFG_BUILTIN['def_log_file']):
Packit Service 9bfd13
    """Push a portion of cloud-init.log file or the whole file to KVP
Packit Service 9bfd13
    based on the file size.
Packit Service 9bfd13
    If called more than once, it skips pushing the log file to KVP again."""
Packit Service 9bfd13
Packit Service 9bfd13
    log_pushed_to_kvp = bool(os.path.isfile(LOG_PUSHED_TO_KVP_MARKER_FILE))
Packit Service 9bfd13
    if log_pushed_to_kvp:
Packit Service 9bfd13
        report_diagnostic_event("cloud-init.log is already pushed to KVP")
Packit Service 9bfd13
        return
Packit Service 9bfd13
Packit Service 9bfd13
    LOG.debug("Dumping cloud-init.log file to KVP")
Packit Service 9bfd13
    try:
Packit Service 9bfd13
        with open(file_name, "rb") as f:
Packit Service 9bfd13
            f.seek(0, os.SEEK_END)
Packit Service 9bfd13
            seek_index = max(f.tell() - MAX_LOG_TO_KVP_LENGTH, 0)
Packit Service 9bfd13
            report_diagnostic_event(
Packit Service 9bfd13
                "Dumping last {} bytes of cloud-init.log file to KVP".format(
Packit Service 9bfd13
                    f.tell() - seek_index))
Packit Service 9bfd13
            f.seek(seek_index, os.SEEK_SET)
Packit Service 9bfd13
            report_compressed_event("cloud-init.log", f.read())
Packit Service 9bfd13
        util.write_file(LOG_PUSHED_TO_KVP_MARKER_FILE, '')
Packit Service 9bfd13
    except Exception as ex:
Packit Service 9bfd13
        report_diagnostic_event("Exception when dumping log file: %s" %
Packit Service 9bfd13
                                repr(ex))
Packit Service 9bfd13
Packit Service 9bfd13
Packit Service a04d08
@contextmanager
Packit Service a04d08
def cd(newdir):
Packit Service a04d08
    prevdir = os.getcwd()
Packit Service a04d08
    os.chdir(os.path.expanduser(newdir))
Packit Service a04d08
    try:
Packit Service a04d08
        yield
Packit Service a04d08
    finally:
Packit Service a04d08
        os.chdir(prevdir)
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def _get_dhcp_endpoint_option_name():
Packit Service a04d08
    if util.is_FreeBSD():
Packit Service a04d08
        azure_endpoint = "option-245"
Packit Service a04d08
    else:
Packit Service a04d08
        azure_endpoint = "unknown-245"
Packit Service a04d08
    return azure_endpoint
Packit Service a04d08
Packit Service a04d08
Packit Service 9bfd13
class AzureEndpointHttpClient:
Packit Service a04d08
Packit Service a04d08
    headers = {
Packit Service a04d08
        'x-ms-agent-name': 'WALinuxAgent',
Packit Service a04d08
        'x-ms-version': '2012-11-30',
Packit Service a04d08
    }
Packit Service a04d08
Packit Service a04d08
    def __init__(self, certificate):
Packit Service a04d08
        self.extra_secure_headers = {
Packit Service a04d08
            "x-ms-cipher-name": "DES_EDE3_CBC",
Packit Service a04d08
            "x-ms-guest-agent-public-x509-cert": certificate,
Packit Service a04d08
        }
Packit Service a04d08
Packit Service a04d08
    def get(self, url, secure=False):
Packit Service a04d08
        headers = self.headers
Packit Service a04d08
        if secure:
Packit Service a04d08
            headers = self.headers.copy()
Packit Service a04d08
            headers.update(self.extra_secure_headers)
Packit Service 9bfd13
        return url_helper.readurl(url, headers=headers,
Packit Service 9bfd13
                                  timeout=5, retries=10, sec_between=5)
Packit Service a04d08
Packit Service a04d08
    def post(self, url, data=None, extra_headers=None):
Packit Service a04d08
        headers = self.headers
Packit Service a04d08
        if extra_headers is not None:
Packit Service a04d08
            headers = self.headers.copy()
Packit Service a04d08
            headers.update(extra_headers)
Packit Service 9bfd13
        return url_helper.readurl(url, data=data, headers=headers,
Packit Service 9bfd13
                                  timeout=5, retries=10, sec_between=5)
Packit Service 11b429
Packit Service 11b429
Packit Service 9bfd13
class InvalidGoalStateXMLException(Exception):
Packit Service 9bfd13
    """Raised when GoalState XML is invalid or has missing data."""
Packit Service 751c4a
Packit Service a04d08
Packit Service 9bfd13
class GoalState:
Packit Service 11b429
Packit Service 9bfd13
    def __init__(self, unparsed_xml, azure_endpoint_client):
Packit Service 9bfd13
        """Parses a GoalState XML string and returns a GoalState object.
Packit Service b1601c
Packit Service 9bfd13
        @param unparsed_xml: string representing a GoalState XML.
Packit Service 9bfd13
        @param azure_endpoint_client: instance of AzureEndpointHttpClient
Packit Service 9bfd13
        @return: GoalState object representing the GoalState XML string.
Packit Service 9bfd13
        """
Packit Service 9bfd13
        self.azure_endpoint_client = azure_endpoint_client
Packit Service b1601c
Packit Service 9bfd13
        try:
Packit Service 9bfd13
            self.root = ElementTree.fromstring(unparsed_xml)
Packit Service 9bfd13
        except ElementTree.ParseError as e:
Packit Service 9bfd13
            msg = 'Failed to parse GoalState XML: %s'
Packit Service 9bfd13
            LOG.warning(msg, e)
Packit Service 9bfd13
            report_diagnostic_event(msg % (e,))
Packit Service 9bfd13
            raise
Packit Service 9bfd13
Packit Service 9bfd13
        self.container_id = self._text_from_xpath('./Container/ContainerId')
Packit Service 9bfd13
        self.instance_id = self._text_from_xpath(
Packit Service b1601c
            './Container/RoleInstanceList/RoleInstance/InstanceId')
Packit Service 9bfd13
        self.incarnation = self._text_from_xpath('./Incarnation')
Packit Service 9bfd13
Packit Service 9bfd13
        for attr in ("container_id", "instance_id", "incarnation"):
Packit Service 9bfd13
            if getattr(self, attr) is None:
Packit Service 9bfd13
                msg = 'Missing %s in GoalState XML'
Packit Service 9bfd13
                LOG.warning(msg, attr)
Packit Service 9bfd13
                report_diagnostic_event(msg % (attr,))
Packit Service 9bfd13
                raise InvalidGoalStateXMLException(msg)
Packit Service 9bfd13
Packit Service 9bfd13
        self.certificates_xml = None
Packit Service 9bfd13
        url = self._text_from_xpath(
Packit Service 9bfd13
            './Container/RoleInstanceList/RoleInstance'
Packit Service 9bfd13
            '/Configuration/Certificates')
Packit Service 9bfd13
        if url is not None:
Packit Service 9bfd13
            with events.ReportEventStack(
Packit Service 9bfd13
                    name="get-certificates-xml",
Packit Service 9bfd13
                    description="get certificates xml",
Packit Service 9bfd13
                    parent=azure_ds_reporter):
Packit Service 9bfd13
                self.certificates_xml = \
Packit Service 9bfd13
                    self.azure_endpoint_client.get(
Packit Service 9bfd13
                        url, secure=True).contents
Packit Service 9bfd13
                if self.certificates_xml is None:
Packit Service 9bfd13
                    raise InvalidGoalStateXMLException(
Packit Service 9bfd13
                        'Azure endpoint returned empty certificates xml.')
Packit Service 11b429
Packit Service 9bfd13
    def _text_from_xpath(self, xpath):
Packit Service 9bfd13
        element = self.root.find(xpath)
Packit Service 9bfd13
        if element is not None:
Packit Service 9bfd13
            return element.text
Packit Service 9bfd13
        return None
Packit Service b1601c
Packit Service b1601c
Packit Service 9bfd13
class OpenSSLManager:
Packit Service a04d08
Packit Service a04d08
    certificate_names = {
Packit Service a04d08
        'private_key': 'TransportPrivate.pem',
Packit Service a04d08
        'certificate': 'TransportCert.pem',
Packit Service a04d08
    }
Packit Service a04d08
Packit Service a04d08
    def __init__(self):
Packit Service a04d08
        self.tmpdir = temp_utils.mkdtemp()
Packit Service a04d08
        self.certificate = None
Packit Service a04d08
        self.generate_certificate()
Packit Service a04d08
Packit Service a04d08
    def clean_up(self):
Packit Service a04d08
        util.del_dir(self.tmpdir)
Packit Service a04d08
Packit Service a04d08
    @azure_ds_telemetry_reporter
Packit Service a04d08
    def generate_certificate(self):
Packit Service a04d08
        LOG.debug('Generating certificate for communication with fabric...')
Packit Service a04d08
        if self.certificate is not None:
Packit Service a04d08
            LOG.debug('Certificate already generated.')
Packit Service a04d08
            return
Packit Service a04d08
        with cd(self.tmpdir):
Packit Service 9bfd13
            subp.subp([
Packit Service a04d08
                'openssl', 'req', '-x509', '-nodes', '-subj',
Packit Service a04d08
                '/CN=LinuxTransport', '-days', '32768', '-newkey', 'rsa:2048',
Packit Service a04d08
                '-keyout', self.certificate_names['private_key'],
Packit Service a04d08
                '-out', self.certificate_names['certificate'],
Packit Service a04d08
            ])
Packit Service a04d08
            certificate = ''
Packit Service a04d08
            for line in open(self.certificate_names['certificate']):
Packit Service a04d08
                if "CERTIFICATE" not in line:
Packit Service a04d08
                    certificate += line.rstrip()
Packit Service a04d08
            self.certificate = certificate
Packit Service a04d08
        LOG.debug('New certificate generated.')
Packit Service a04d08
Packit Service a04d08
    @staticmethod
Packit Service a04d08
    @azure_ds_telemetry_reporter
Packit Service a04d08
    def _run_x509_action(action, cert):
Packit Service a04d08
        cmd = ['openssl', 'x509', '-noout', action]
Packit Service 9bfd13
        result, _ = subp.subp(cmd, data=cert)
Packit Service a04d08
        return result
Packit Service a04d08
Packit Service a04d08
    @azure_ds_telemetry_reporter
Packit Service a04d08
    def _get_ssh_key_from_cert(self, certificate):
Packit Service a04d08
        pub_key = self._run_x509_action('-pubkey', certificate)
Packit Service a04d08
        keygen_cmd = ['ssh-keygen', '-i', '-m', 'PKCS8', '-f', '/dev/stdin']
Packit Service 9bfd13
        ssh_key, _ = subp.subp(keygen_cmd, data=pub_key)
Packit Service a04d08
        return ssh_key
Packit Service a04d08
Packit Service a04d08
    @azure_ds_telemetry_reporter
Packit Service a04d08
    def _get_fingerprint_from_cert(self, certificate):
Packit Service a04d08
        """openssl x509 formats fingerprints as so:
Packit Service a04d08
        'SHA1 Fingerprint=07:3E:19:D1:4D:1C:79:92:24:C6:A0:FD:8D:DA:\
Packit Service a04d08
        B6:A8:BF:27:D4:73\n'
Packit Service a04d08
Packit Service a04d08
        Azure control plane passes that fingerprint as so:
Packit Service a04d08
        '073E19D14D1C799224C6A0FD8DDAB6A8BF27D473'
Packit Service a04d08
        """
Packit Service a04d08
        raw_fp = self._run_x509_action('-fingerprint', certificate)
Packit Service a04d08
        eq = raw_fp.find('=')
Packit Service a04d08
        octets = raw_fp[eq+1:-1].split(':')
Packit Service a04d08
        return ''.join(octets)
Packit Service a04d08
Packit Service a04d08
    @azure_ds_telemetry_reporter
Packit Service a04d08
    def _decrypt_certs_from_xml(self, certificates_xml):
Packit Service a04d08
        """Decrypt the certificates XML document using the our private key;
Packit Service a04d08
           return the list of certs and private keys contained in the doc.
Packit Service a04d08
        """
Packit Service a04d08
        tag = ElementTree.fromstring(certificates_xml).find('.//Data')
Packit Service a04d08
        certificates_content = tag.text
Packit Service a04d08
        lines = [
Packit Service a04d08
            b'MIME-Version: 1.0',
Packit Service a04d08
            b'Content-Disposition: attachment; filename="Certificates.p7m"',
Packit Service a04d08
            b'Content-Type: application/x-pkcs7-mime; name="Certificates.p7m"',
Packit Service a04d08
            b'Content-Transfer-Encoding: base64',
Packit Service a04d08
            b'',
Packit Service a04d08
            certificates_content.encode('utf-8'),
Packit Service a04d08
        ]
Packit Service a04d08
        with cd(self.tmpdir):
Packit Service 9bfd13
            out, _ = subp.subp(
Packit Service a04d08
                'openssl cms -decrypt -in /dev/stdin -inkey'
Packit Service a04d08
                ' {private_key} -recip {certificate} | openssl pkcs12 -nodes'
Packit Service a04d08
                ' -password pass:'.format(**self.certificate_names),
Packit Service a04d08
                shell=True, data=b'\n'.join(lines))
Packit Service a04d08
        return out
Packit Service a04d08
Packit Service a04d08
    @azure_ds_telemetry_reporter
Packit Service a04d08
    def parse_certificates(self, certificates_xml):
Packit Service a04d08
        """Given the Certificates XML document, return a dictionary of
Packit Service a04d08
           fingerprints and associated SSH keys derived from the certs."""
Packit Service a04d08
        out = self._decrypt_certs_from_xml(certificates_xml)
Packit Service a04d08
        current = []
Packit Service a04d08
        keys = {}
Packit Service a04d08
        for line in out.splitlines():
Packit Service a04d08
            current.append(line)
Packit Service a04d08
            if re.match(r'[-]+END .*?KEY[-]+$', line):
Packit Service a04d08
                # ignore private_keys
Packit Service a04d08
                current = []
Packit Service a04d08
            elif re.match(r'[-]+END .*?CERTIFICATE[-]+$', line):
Packit Service a04d08
                certificate = '\n'.join(current)
Packit Service a04d08
                ssh_key = self._get_ssh_key_from_cert(certificate)
Packit Service a04d08
                fingerprint = self._get_fingerprint_from_cert(certificate)
Packit Service a04d08
                keys[fingerprint] = ssh_key
Packit Service a04d08
                current = []
Packit Service a04d08
        return keys
Packit Service a04d08
Packit Service a04d08
Packit Service 9bfd13
class GoalStateHealthReporter:
Packit Service 9bfd13
Packit Service 9bfd13
    HEALTH_REPORT_XML_TEMPLATE = textwrap.dedent('''\
Packit Service 9bfd13
        
Packit Service 9bfd13
        
Packit Service 9bfd13
         xmlns:xsd="http://www.w3.org/2001/XMLSchema">
Packit Service 9bfd13
          <GoalStateIncarnation>{incarnation}</GoalStateIncarnation>
Packit Service 9bfd13
          <Container>
Packit Service 9bfd13
            <ContainerId>{container_id}</ContainerId>
Packit Service 9bfd13
            <RoleInstanceList>
Packit Service 9bfd13
              <Role>
Packit Service 9bfd13
                <InstanceId>{instance_id}</InstanceId>
Packit Service 9bfd13
                <Health>
Packit Service 9bfd13
                  <State>{health_status}</State>
Packit Service 9bfd13
                  {health_detail_subsection}
Packit Service 9bfd13
                </Health>
Packit Service 9bfd13
              </Role>
Packit Service 9bfd13
            </RoleInstanceList>
Packit Service 9bfd13
          </Container>
Packit Service 9bfd13
        </Health>
Packit Service 9bfd13
        ''')
Packit Service 9bfd13
Packit Service 9bfd13
    HEALTH_DETAIL_SUBSECTION_XML_TEMPLATE = textwrap.dedent('''\
Packit Service 9bfd13
        <Details>
Packit Service 9bfd13
          <SubStatus>{health_substatus}</SubStatus>
Packit Service 9bfd13
          <Description>{health_description}</Description>
Packit Service 9bfd13
        </Details>
Packit Service 9bfd13
        ''')
Packit Service 9bfd13
Packit Service 9bfd13
    PROVISIONING_SUCCESS_STATUS = 'Ready'
Packit Service 9bfd13
Packit Service 9bfd13
    def __init__(self, goal_state, azure_endpoint_client, endpoint):
Packit Service 9bfd13
        """Creates instance that will report provisioning status to an endpoint
Packit Service 9bfd13
Packit Service 9bfd13
        @param goal_state: An instance of class GoalState that contains
Packit Service 9bfd13
            goal state info such as incarnation, container id, and instance id.
Packit Service 9bfd13
            These 3 values are needed when reporting the provisioning status
Packit Service 9bfd13
            to Azure
Packit Service 9bfd13
        @param azure_endpoint_client: Instance of class AzureEndpointHttpClient
Packit Service 9bfd13
        @param endpoint: Endpoint (string) where the provisioning status report
Packit Service 9bfd13
            will be sent to
Packit Service 9bfd13
        @return: Instance of class GoalStateHealthReporter
Packit Service 9bfd13
        """
Packit Service 9bfd13
        self._goal_state = goal_state
Packit Service 9bfd13
        self._azure_endpoint_client = azure_endpoint_client
Packit Service 9bfd13
        self._endpoint = endpoint
Packit Service 9bfd13
Packit Service 9bfd13
    @azure_ds_telemetry_reporter
Packit Service 9bfd13
    def send_ready_signal(self):
Packit Service 9bfd13
        document = self.build_report(
Packit Service 9bfd13
            incarnation=self._goal_state.incarnation,
Packit Service 9bfd13
            container_id=self._goal_state.container_id,
Packit Service 9bfd13
            instance_id=self._goal_state.instance_id,
Packit Service 9bfd13
            status=self.PROVISIONING_SUCCESS_STATUS)
Packit Service 9bfd13
        LOG.debug('Reporting ready to Azure fabric.')
Packit Service 9bfd13
        try:
Packit Service 9bfd13
            self._post_health_report(document=document)
Packit Service 9bfd13
        except Exception as e:
Packit Service 9bfd13
            msg = "exception while reporting ready: %s" % e
Packit Service 9bfd13
            LOG.error(msg)
Packit Service 9bfd13
            report_diagnostic_event(msg)
Packit Service 9bfd13
            raise
Packit Service 9bfd13
Packit Service 9bfd13
        LOG.info('Reported ready to Azure fabric.')
Packit Service 9bfd13
Packit Service 9bfd13
    def build_report(
Packit Service 9bfd13
            self, incarnation, container_id, instance_id,
Packit Service 9bfd13
            status, substatus=None, description=None):
Packit Service 9bfd13
        health_detail = ''
Packit Service 9bfd13
        if substatus is not None:
Packit Service 9bfd13
            health_detail = self.HEALTH_DETAIL_SUBSECTION_XML_TEMPLATE.format(
Packit Service 9bfd13
                health_substatus=substatus, health_description=description)
Packit Service 9bfd13
Packit Service 9bfd13
        health_report = self.HEALTH_REPORT_XML_TEMPLATE.format(
Packit Service 9bfd13
            incarnation=incarnation,
Packit Service 9bfd13
            container_id=container_id,
Packit Service 9bfd13
            instance_id=instance_id,
Packit Service 9bfd13
            health_status=status,
Packit Service 9bfd13
            health_detail_subsection=health_detail)
Packit Service 9bfd13
Packit Service 9bfd13
        return health_report
Packit Service 9bfd13
Packit Service 9bfd13
    @azure_ds_telemetry_reporter
Packit Service 9bfd13
    def _post_health_report(self, document):
Packit Service 9bfd13
        push_log_to_kvp()
Packit Service 9bfd13
Packit Service 9bfd13
        # Whenever report_diagnostic_event(diagnostic_msg) is invoked in code,
Packit Service 9bfd13
        # the diagnostic messages are written to special files
Packit Service 9bfd13
        # (/var/opt/hyperv/.kvp_pool_*) as Hyper-V KVP messages.
Packit Service 9bfd13
        # Hyper-V KVP message communication is done through these files,
Packit Service 9bfd13
        # and KVP functionality is used to communicate and share diagnostic
Packit Service 9bfd13
        # info with the Azure Host.
Packit Service 9bfd13
        # The Azure Host will collect the VM's Hyper-V KVP diagnostic messages
Packit Service 9bfd13
        # when cloud-init reports to fabric.
Packit Service 9bfd13
        # When the Azure Host receives the health report signal, it will only
Packit Service 9bfd13
        # collect and process whatever KVP diagnostic messages have been
Packit Service 9bfd13
        # written to the KVP files.
Packit Service 9bfd13
        # KVP messages that are published after the Azure Host receives the
Packit Service 9bfd13
        # signal are ignored and unprocessed, so yield this thread to the
Packit Service 9bfd13
        # Hyper-V KVP Reporting thread so that they are written.
Packit Service 9bfd13
        # time.sleep(0) is a low-cost and proven method to yield the scheduler
Packit Service 9bfd13
        # and ensure that events are flushed.
Packit Service 9bfd13
        # See HyperVKvpReportingHandler class, which is a multi-threaded
Packit Service 9bfd13
        # reporting handler that writes to the special KVP files.
Packit Service 9bfd13
        time.sleep(0)
Packit Service 9bfd13
Packit Service 9bfd13
        LOG.debug('Sending health report to Azure fabric.')
Packit Service 9bfd13
        url = "http://{}/machine?comp=health".format(self._endpoint)
Packit Service 9bfd13
        self._azure_endpoint_client.post(
Packit Service 9bfd13
            url,
Packit Service 9bfd13
            data=document,
Packit Service 9bfd13
            extra_headers={'Content-Type': 'text/xml; charset=utf-8'})
Packit Service 9bfd13
        LOG.debug('Successfully sent health report to Azure fabric')
Packit Service 9bfd13
Packit Service 9bfd13
Packit Service 9bfd13
class WALinuxAgentShim:
Packit Service a04d08
Packit Service a04d08
    def __init__(self, fallback_lease_file=None, dhcp_options=None):
Packit Service a04d08
        LOG.debug('WALinuxAgentShim instantiated, fallback_lease_file=%s',
Packit Service a04d08
                  fallback_lease_file)
Packit Service a04d08
        self.dhcpoptions = dhcp_options
Packit Service a04d08
        self._endpoint = None
Packit Service a04d08
        self.openssl_manager = None
Packit Service 9bfd13
        self.azure_endpoint_client = None
Packit Service a04d08
        self.lease_file = fallback_lease_file
Packit Service a04d08
Packit Service a04d08
    def clean_up(self):
Packit Service a04d08
        if self.openssl_manager is not None:
Packit Service a04d08
            self.openssl_manager.clean_up()
Packit Service a04d08
Packit Service a04d08
    @staticmethod
Packit Service a04d08
    def _get_hooks_dir():
Packit Service a04d08
        _paths = stages.Init()
Packit Service a04d08
        return os.path.join(_paths.paths.get_runpath(), "dhclient.hooks")
Packit Service a04d08
Packit Service a04d08
    @property
Packit Service a04d08
    def endpoint(self):
Packit Service a04d08
        if self._endpoint is None:
Packit Service a04d08
            self._endpoint = self.find_endpoint(self.lease_file,
Packit Service a04d08
                                                self.dhcpoptions)
Packit Service a04d08
        return self._endpoint
Packit Service a04d08
Packit Service a04d08
    @staticmethod
Packit Service a04d08
    def get_ip_from_lease_value(fallback_lease_value):
Packit Service a04d08
        unescaped_value = fallback_lease_value.replace('\\', '')
Packit Service a04d08
        if len(unescaped_value) > 4:
Packit Service a04d08
            hex_string = ''
Packit Service a04d08
            for hex_pair in unescaped_value.split(':'):
Packit Service a04d08
                if len(hex_pair) == 1:
Packit Service a04d08
                    hex_pair = '0' + hex_pair
Packit Service a04d08
                hex_string += hex_pair
Packit Service a04d08
            packed_bytes = struct.pack(
Packit Service a04d08
                '>L', int(hex_string.replace(':', ''), 16))
Packit Service a04d08
        else:
Packit Service a04d08
            packed_bytes = unescaped_value.encode('utf-8')
Packit Service a04d08
        return socket.inet_ntoa(packed_bytes)
Packit Service a04d08
Packit Service a04d08
    @staticmethod
Packit Service a04d08
    @azure_ds_telemetry_reporter
Packit Service a04d08
    def _networkd_get_value_from_leases(leases_d=None):
Packit Service a04d08
        return dhcp.networkd_get_option_from_leases(
Packit Service a04d08
            'OPTION_245', leases_d=leases_d)
Packit Service a04d08
Packit Service a04d08
    @staticmethod
Packit Service a04d08
    @azure_ds_telemetry_reporter
Packit Service a04d08
    def _get_value_from_leases_file(fallback_lease_file):
Packit Service a04d08
        leases = []
Packit Service a04d08
        try:
Packit Service a04d08
            content = util.load_file(fallback_lease_file)
Packit Service a04d08
        except IOError as ex:
Packit Service a04d08
            LOG.error("Failed to read %s: %s", fallback_lease_file, ex)
Packit Service a04d08
            return None
Packit Service a04d08
Packit Service a04d08
        LOG.debug("content is %s", content)
Packit Service a04d08
        option_name = _get_dhcp_endpoint_option_name()
Packit Service a04d08
        for line in content.splitlines():
Packit Service a04d08
            if option_name in line:
Packit Service a04d08
                # Example line from Ubuntu
Packit Service a04d08
                # option unknown-245 a8:3f:81:10;
Packit Service a04d08
                leases.append(line.strip(' ').split(' ', 2)[-1].strip(';\n"'))
Packit Service a04d08
        # Return the "most recent" one in the list
Packit Service a04d08
        if len(leases) < 1:
Packit Service a04d08
            return None
Packit Service a04d08
        else:
Packit Service a04d08
            return leases[-1]
Packit Service a04d08
Packit Service a04d08
    @staticmethod
Packit Service a04d08
    @azure_ds_telemetry_reporter
Packit Service a04d08
    def _load_dhclient_json():
Packit Service a04d08
        dhcp_options = {}
Packit Service a04d08
        hooks_dir = WALinuxAgentShim._get_hooks_dir()
Packit Service a04d08
        if not os.path.exists(hooks_dir):
Packit Service a04d08
            LOG.debug("%s not found.", hooks_dir)
Packit Service a04d08
            return None
Packit Service a04d08
        hook_files = [os.path.join(hooks_dir, x)
Packit Service a04d08
                      for x in os.listdir(hooks_dir)]
Packit Service a04d08
        for hook_file in hook_files:
Packit Service a04d08
            try:
Packit Service a04d08
                name = os.path.basename(hook_file).replace('.json', '')
Packit Service a04d08
                dhcp_options[name] = json.loads(util.load_file((hook_file)))
Packit Service 9bfd13
            except ValueError as e:
Packit Service a04d08
                raise ValueError(
Packit Service 9bfd13
                    '{_file} is not valid JSON data'.format(_file=hook_file)
Packit Service 9bfd13
                ) from e
Packit Service a04d08
        return dhcp_options
Packit Service a04d08
Packit Service a04d08
    @staticmethod
Packit Service a04d08
    @azure_ds_telemetry_reporter
Packit Service a04d08
    def _get_value_from_dhcpoptions(dhcp_options):
Packit Service a04d08
        if dhcp_options is None:
Packit Service a04d08
            return None
Packit Service a04d08
        # the MS endpoint server is given to us as DHPC option 245
Packit Service a04d08
        _value = None
Packit Service a04d08
        for interface in dhcp_options:
Packit Service a04d08
            _value = dhcp_options[interface].get('unknown_245', None)
Packit Service a04d08
            if _value is not None:
Packit Service a04d08
                LOG.debug("Endpoint server found in dhclient options")
Packit Service a04d08
                break
Packit Service a04d08
        return _value
Packit Service a04d08
Packit Service a04d08
    @staticmethod
Packit Service a04d08
    @azure_ds_telemetry_reporter
Packit Service a04d08
    def find_endpoint(fallback_lease_file=None, dhcp245=None):
Packit Service 9bfd13
        """Finds and returns the Azure endpoint using various methods.
Packit Service 9bfd13
Packit Service 9bfd13
        The Azure endpoint is searched in the following order:
Packit Service 9bfd13
        1. Endpoint from dhcp options (dhcp option 245).
Packit Service 9bfd13
        2. Endpoint from networkd.
Packit Service 9bfd13
        3. Endpoint from dhclient hook json.
Packit Service 9bfd13
        4. Endpoint from fallback lease file.
Packit Service 9bfd13
        5. The default Azure endpoint.
Packit Service 9bfd13
Packit Service 9bfd13
        @param fallback_lease_file: Fallback lease file that will be used
Packit Service 9bfd13
            during endpoint search.
Packit Service 9bfd13
        @param dhcp245: dhcp options that will be used during endpoint search.
Packit Service 9bfd13
        @return: Azure endpoint IP address.
Packit Service 9bfd13
        """
Packit Service a04d08
        value = None
Packit Service 9bfd13
Packit Service a04d08
        if dhcp245 is not None:
Packit Service a04d08
            value = dhcp245
Packit Service a04d08
            LOG.debug("Using Azure Endpoint from dhcp options")
Packit Service a04d08
        if value is None:
Packit Service a04d08
            report_diagnostic_event("No Azure endpoint from dhcp options")
Packit Service a04d08
            LOG.debug('Finding Azure endpoint from networkd...')
Packit Service a04d08
            value = WALinuxAgentShim._networkd_get_value_from_leases()
Packit Service a04d08
        if value is None:
Packit Service a04d08
            # Option-245 stored in /run/cloud-init/dhclient.hooks/<ifc>.json
Packit Service a04d08
            # a dhclient exit hook that calls cloud-init-dhclient-hook
Packit Service a04d08
            report_diagnostic_event("No Azure endpoint from networkd")
Packit Service a04d08
            LOG.debug('Finding Azure endpoint from hook json...')
Packit Service a04d08
            dhcp_options = WALinuxAgentShim._load_dhclient_json()
Packit Service a04d08
            value = WALinuxAgentShim._get_value_from_dhcpoptions(dhcp_options)
Packit Service a04d08
        if value is None:
Packit Service a04d08
            # Fallback and check the leases file if unsuccessful
Packit Service a04d08
            report_diagnostic_event("No Azure endpoint from dhclient logs")
Packit Service a04d08
            LOG.debug("Unable to find endpoint in dhclient logs. "
Packit Service a04d08
                      " Falling back to check lease files")
Packit Service a04d08
            if fallback_lease_file is None:
Packit Service a04d08
                LOG.warning("No fallback lease file was specified.")
Packit Service a04d08
                value = None
Packit Service a04d08
            else:
Packit Service a04d08
                LOG.debug("Looking for endpoint in lease file %s",
Packit Service a04d08
                          fallback_lease_file)
Packit Service a04d08
                value = WALinuxAgentShim._get_value_from_leases_file(
Packit Service a04d08
                    fallback_lease_file)
Packit Service a04d08
        if value is None:
Packit Service a04d08
            msg = "No lease found; using default endpoint"
Packit Service a04d08
            report_diagnostic_event(msg)
Packit Service a04d08
            LOG.warning(msg)
Packit Service a04d08
            value = DEFAULT_WIRESERVER_ENDPOINT
Packit Service a04d08
Packit Service a04d08
        endpoint_ip_address = WALinuxAgentShim.get_ip_from_lease_value(value)
Packit Service a04d08
        msg = 'Azure endpoint found at %s' % endpoint_ip_address
Packit Service a04d08
        report_diagnostic_event(msg)
Packit Service a04d08
        LOG.debug(msg)
Packit Service a04d08
        return endpoint_ip_address
Packit Service a04d08
Packit Service a04d08
    @azure_ds_telemetry_reporter
Packit Service a04d08
    def register_with_azure_and_fetch_data(self, pubkey_info=None):
Packit Service 9bfd13
        """Gets the VM's GoalState from Azure, uses the GoalState information
Packit Service 9bfd13
        to report ready/send the ready signal/provisioning complete signal to
Packit Service 9bfd13
        Azure, and then uses pubkey_info to filter and obtain the user's
Packit Service 9bfd13
        pubkeys from the GoalState.
Packit Service 9bfd13
Packit Service 9bfd13
        @param pubkey_info: List of pubkey values and fingerprints which are
Packit Service 9bfd13
            used to filter and obtain the user's pubkey values from the
Packit Service 9bfd13
            GoalState.
Packit Service 9bfd13
        @return: The list of user's authorized pubkey values.
Packit Service 9bfd13
        """
Packit Service a04d08
        if self.openssl_manager is None:
Packit Service a04d08
            self.openssl_manager = OpenSSLManager()
Packit Service 9bfd13
        if self.azure_endpoint_client is None:
Packit Service 9bfd13
            self.azure_endpoint_client = AzureEndpointHttpClient(
Packit Service 9bfd13
                self.openssl_manager.certificate)
Packit Service 9bfd13
        goal_state = self._fetch_goal_state_from_azure()
Packit Service 9bfd13
        ssh_keys = self._get_user_pubkeys(goal_state, pubkey_info)
Packit Service 9bfd13
        health_reporter = GoalStateHealthReporter(
Packit Service 9bfd13
            goal_state, self.azure_endpoint_client, self.endpoint)
Packit Service 9bfd13
        health_reporter.send_ready_signal()
Packit Service 9bfd13
        return {'public-keys': ssh_keys}
Packit Service 9bfd13
Packit Service 9bfd13
    @azure_ds_telemetry_reporter
Packit Service 9bfd13
    def _fetch_goal_state_from_azure(self):
Packit Service 9bfd13
        """Fetches the GoalState XML from the Azure endpoint, parses the XML,
Packit Service 9bfd13
        and returns a GoalState object.
Packit Service 9bfd13
Packit Service 9bfd13
        @return: GoalState object representing the GoalState XML
Packit Service 9bfd13
        """
Packit Service 9bfd13
        unparsed_goal_state_xml = self._get_raw_goal_state_xml_from_azure()
Packit Service 9bfd13
        return self._parse_raw_goal_state_xml(unparsed_goal_state_xml)
Packit Service 9bfd13
Packit Service 9bfd13
    @azure_ds_telemetry_reporter
Packit Service 9bfd13
    def _get_raw_goal_state_xml_from_azure(self):
Packit Service 9bfd13
        """Fetches the GoalState XML from the Azure endpoint and returns
Packit Service 9bfd13
        the XML as a string.
Packit Service 9bfd13
Packit Service 9bfd13
        @return: GoalState XML string
Packit Service 9bfd13
        """
Packit Service 9bfd13
Packit Service a04d08
        LOG.info('Registering with Azure...')
Packit Service 9bfd13
        url = 'http://{}/machine/?comp=goalstate'.format(self.endpoint)
Packit Service 9bfd13
        try:
Packit Service 9bfd13
            response = self.azure_endpoint_client.get(url)
Packit Service 9bfd13
        except Exception as e:
Packit Service 9bfd13
            msg = 'failed to register with Azure: %s' % e
Packit Service 9bfd13
            LOG.warning(msg)
Packit Service 9bfd13
            report_diagnostic_event(msg)
Packit Service 9bfd13
            raise
Packit Service a04d08
        LOG.debug('Successfully fetched GoalState XML.')
Packit Service 9bfd13
        return response.contents
Packit Service 9bfd13
Packit Service 9bfd13
    @azure_ds_telemetry_reporter
Packit Service 9bfd13
    def _parse_raw_goal_state_xml(self, unparsed_goal_state_xml):
Packit Service 9bfd13
        """Parses a GoalState XML string and returns a GoalState object.
Packit Service 9bfd13
Packit Service 9bfd13
        @param unparsed_goal_state_xml: GoalState XML string
Packit Service 9bfd13
        @return: GoalState object representing the GoalState XML
Packit Service 9bfd13
        """
Packit Service 9bfd13
        try:
Packit Service 9bfd13
            goal_state = GoalState(
Packit Service 9bfd13
                unparsed_goal_state_xml, self.azure_endpoint_client)
Packit Service 9bfd13
        except Exception as e:
Packit Service 9bfd13
            msg = 'Error processing GoalState XML: %s' % e
Packit Service 9bfd13
            LOG.warning(msg)
Packit Service 9bfd13
            report_diagnostic_event(msg)
Packit Service 9bfd13
            raise
Packit Service 9bfd13
        msg = ', '.join([
Packit Service 9bfd13
            'GoalState XML container id: %s' % goal_state.container_id,
Packit Service 9bfd13
            'GoalState XML instance id: %s' % goal_state.instance_id,
Packit Service 9bfd13
            'GoalState XML incarnation: %s' % goal_state.incarnation])
Packit Service 9bfd13
        LOG.debug(msg)
Packit Service 9bfd13
        report_diagnostic_event(msg)
Packit Service 9bfd13
        return goal_state
Packit Service 9bfd13
Packit Service 9bfd13
    @azure_ds_telemetry_reporter
Packit Service 9bfd13
    def _get_user_pubkeys(self, goal_state, pubkey_info):
Packit Service 9bfd13
        """Gets and filters the VM admin user's authorized pubkeys.
Packit Service 9bfd13
Packit Service 9bfd13
        The admin user in this case is the username specified as "admin"
Packit Service 9bfd13
        when deploying VMs on Azure.
Packit Service 9bfd13
        See https://docs.microsoft.com/en-us/cli/azure/vm#az-vm-create.
Packit Service 9bfd13
        cloud-init expects a straightforward array of keys to be dropped
Packit Service 9bfd13
        into the admin user's authorized_keys file. Azure control plane exposes
Packit Service 9bfd13
        multiple public keys to the VM via wireserver. Select just the
Packit Service 9bfd13
        admin user's key(s) and return them, ignoring any other certs.
Packit Service 9bfd13
Packit Service 9bfd13
        @param goal_state: GoalState object. The GoalState object contains
Packit Service 9bfd13
            a certificate XML, which contains both the VM user's authorized
Packit Service 9bfd13
            pubkeys and other non-user pubkeys, which are used for
Packit Service 9bfd13
            MSI and protected extension handling.
Packit Service 9bfd13
        @param pubkey_info: List of VM user pubkey dicts that were previously
Packit Service 9bfd13
            obtained from provisioning data.
Packit Service 9bfd13
            Each pubkey dict in this list can either have the format
Packit Service 9bfd13
            pubkey['value'] or pubkey['fingerprint'].
Packit Service 9bfd13
            Each pubkey['fingerprint'] in the list is used to filter
Packit Service 9bfd13
            and obtain the actual pubkey value from the GoalState
Packit Service 9bfd13
            certificates XML.
Packit Service 9bfd13
            Each pubkey['value'] requires no further processing and is
Packit Service 9bfd13
            immediately added to the return list.
Packit Service 9bfd13
        @return: A list of the VM user's authorized pubkey values.
Packit Service 9bfd13
        """
Packit Service a04d08
        ssh_keys = []
Packit Service a04d08
        if goal_state.certificates_xml is not None and pubkey_info is not None:
Packit Service a04d08
            LOG.debug('Certificate XML found; parsing out public keys.')
Packit Service a04d08
            keys_by_fingerprint = self.openssl_manager.parse_certificates(
Packit Service a04d08
                goal_state.certificates_xml)
Packit Service a04d08
            ssh_keys = self._filter_pubkeys(keys_by_fingerprint, pubkey_info)
Packit Service 9bfd13
        return ssh_keys
Packit Service a04d08
Packit Service 9bfd13
    @staticmethod
Packit Service 9bfd13
    def _filter_pubkeys(keys_by_fingerprint, pubkey_info):
Packit Service 9bfd13
        """ Filter and return only the user's actual pubkeys.
Packit Service 9bfd13
Packit Service 9bfd13
        @param keys_by_fingerprint: pubkey fingerprint -> pubkey value dict
Packit Service 9bfd13
            that was obtained from GoalState Certificates XML. May contain
Packit Service 9bfd13
            non-user pubkeys.
Packit Service 9bfd13
        @param pubkey_info: List of VM user pubkeys. Pubkey values are added
Packit Service 9bfd13
            to the return list without further processing. Pubkey fingerprints
Packit Service 9bfd13
            are used to filter and obtain the actual pubkey values from
Packit Service 9bfd13
            keys_by_fingerprint.
Packit Service 9bfd13
        @return: A list of the VM user's authorized pubkey values.
Packit Service a04d08
        """
Packit Service a04d08
        keys = []
Packit Service a04d08
        for pubkey in pubkey_info:
Packit Service a04d08
            if 'value' in pubkey and pubkey['value']:
Packit Service a04d08
                keys.append(pubkey['value'])
Packit Service a04d08
            elif 'fingerprint' in pubkey and pubkey['fingerprint']:
Packit Service a04d08
                fingerprint = pubkey['fingerprint']
Packit Service a04d08
                if fingerprint in keys_by_fingerprint:
Packit Service a04d08
                    keys.append(keys_by_fingerprint[fingerprint])
Packit Service a04d08
                else:
Packit Service a04d08
                    LOG.warning("ovf-env.xml specified PublicKey fingerprint "
Packit Service a04d08
                                "%s not found in goalstate XML", fingerprint)
Packit Service a04d08
            else:
Packit Service a04d08
                LOG.warning("ovf-env.xml specified PublicKey with neither "
Packit Service a04d08
                            "value nor fingerprint: %s", pubkey)
Packit Service a04d08
Packit Service a04d08
        return keys
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
@azure_ds_telemetry_reporter
Packit Service a04d08
def get_metadata_from_fabric(fallback_lease_file=None, dhcp_opts=None,
Packit Service a04d08
                             pubkey_info=None):
Packit Service a04d08
    shim = WALinuxAgentShim(fallback_lease_file=fallback_lease_file,
Packit Service a04d08
                            dhcp_options=dhcp_opts)
Packit Service a04d08
    try:
Packit Service a04d08
        return shim.register_with_azure_and_fetch_data(pubkey_info=pubkey_info)
Packit Service a04d08
    finally:
Packit Service a04d08
        shim.clean_up()
Packit Service a04d08
Packit Service a04d08
Packit Service 9bfd13
def dhcp_log_cb(out, err):
Packit Service 9bfd13
    report_diagnostic_event("dhclient output stream: %s" % out)
Packit Service 9bfd13
    report_diagnostic_event("dhclient error stream: %s" % err)
Packit Service 9bfd13
Packit Service 9bfd13
Packit Service 9bfd13
class EphemeralDHCPv4WithReporting:
Packit Service a04d08
    def __init__(self, reporter, nic=None):
Packit Service a04d08
        self.reporter = reporter
Packit Service 9bfd13
        self.ephemeralDHCPv4 = EphemeralDHCPv4(
Packit Service 9bfd13
            iface=nic, dhcp_log_func=dhcp_log_cb)
Packit Service a04d08
Packit Service a04d08
    def __enter__(self):
Packit Service a04d08
        with events.ReportEventStack(
Packit Service a04d08
                name="obtain-dhcp-lease",
Packit Service a04d08
                description="obtain dhcp lease",
Packit Service a04d08
                parent=self.reporter):
Packit Service a04d08
            return self.ephemeralDHCPv4.__enter__()
Packit Service a04d08
Packit Service a04d08
    def __exit__(self, excp_type, excp_value, excp_traceback):
Packit Service a04d08
        self.ephemeralDHCPv4.__exit__(
Packit Service a04d08
            excp_type, excp_value, excp_traceback)
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
# vi: ts=4 expandtab