def _get_proxy_filename_and_url(arc_proxy_folder):
    import platform
    operating_system = platform.system()
    machine = platform.machine()

    logger.debug("Platform OS: %s", operating_system)
    logger.debug("Platform architecture: %s", machine)

    if machine.endswith('64'):
        architecture = 'amd64'
    elif machine.endswith('86'):
        architecture = '386'
    elif machine == '':
        raise azclierror.BadRequestError(
            "Couldn't identify the platform architecture.")
    else:
        raise azclierror.BadRequestError(
            f"Unsuported architecture: {machine} is not currently supported")

    # define the request url and install location based on the os and architecture
    proxy_name = f"sshProxy_{operating_system.lower()}_{architecture}"
    request_uri = (
        f"{consts.CLIENT_PROXY_STORAGE_URL}/{consts.CLIENT_PROXY_RELEASE}"
        f"/{proxy_name}_{consts.CLIENT_PROXY_VERSION}")
    install_location = proxy_name + "_" + consts.CLIENT_PROXY_VERSION.replace(
        '.', '_')
    older_location = proxy_name + "*"

    if operating_system == 'Windows':
        request_uri = request_uri + ".exe"
        install_location = install_location + ".exe"
        older_location = older_location + ".exe"
    elif operating_system not in ('Linux', 'Darwin'):
        raise azclierror.BadRequestError(
            f"Unsuported OS: {operating_system} platform is not currently supported"
        )

    if not arc_proxy_folder:
        install_location = os.path.expanduser(
            os.path.join('~', os.path.join(".clientsshproxy",
                                           install_location)))
        older_location = os.path.expanduser(
            os.path.join('~', os.path.join(".clientsshproxy", older_location)))
    else:
        install_location = os.path.join(arc_proxy_folder, install_location)
        older_location = os.path.join(arc_proxy_folder, older_location)

    return request_uri, install_location, older_location
예제 #2
0
def start_ssh_tunnel(op_info):
    # pylint: disable=consider-using-with
    env = os.environ.copy()
    if op_info.is_arc():
        env['SSHPROXY_RELAY_INFO'] = connectivity_utils.format_relay_info_string(
            op_info.relay_info)

    print_ssh_logs = False
    if not set(['-v', '-vv', '-vvv']).isdisjoint(op_info.ssh_args):
        print_ssh_logs = True
    else:
        op_info.ssh_args = ['-v'] + op_info.ssh_args

    if '-E' in op_info.ssh_args:
        raise azclierror.BadRequestError(
            "Can't use -E ssh parameter when using --rdp")

    command = [
        ssh_utils.get_ssh_client_path('ssh', op_info.ssh_client_folder),
        op_info.get_host()
    ]
    command = command + op_info.build_args() + op_info.ssh_args

    logger.debug("Running ssh command %s", ' '.join(command))
    ssh_sub = subprocess.Popen(command,
                               shell=True,
                               stderr=subprocess.PIPE,
                               env=env,
                               encoding='utf-8')
    return ssh_sub, print_ssh_logs
예제 #3
0
def ssh_vm(cmd, resource_group_name=None, vm_name=None, ssh_ip=None, public_key_file=None,
           private_key_file=None, use_private_ip=False, local_user=None, cert_file=None, port=None,
           ssh_client_folder=None, delete_credentials=False, resource_type=None, ssh_proxy_folder=None,
           winrdp=False, ssh_args=None):

    # delete_credentials can only be used by Azure Portal to provide one-click experience on CloudShell.
    if delete_credentials and os.environ.get("AZUREPS_HOST_ENVIRONMENT") != "cloud-shell/1.0":
        raise azclierror.ArgumentUsageError("Can't use --delete-private-key outside an Azure Cloud Shell session.")

    # include openssh client logs to --debug output to make it easier to users to debug connection issued.
    if '--debug' in cmd.cli_ctx.data['safe_params'] and set(['-v', '-vv', '-vvv']).isdisjoint(ssh_args):
        ssh_args = ['-vvv'] if not ssh_args else ['-vvv'] + ssh_args

    _assert_args(resource_group_name, vm_name, ssh_ip, resource_type, cert_file, local_user)

    # all credentials for this command are saved in temp folder and deleted at the end of execution.
    credentials_folder = None

    op_call = ssh_utils.start_ssh_connection
    if winrdp:
        if platform.system() != 'Windows':
            raise azclierror.BadRequestError("RDP connection is not supported for this platform. "
                                             "Supported platforms: Windows")
        logger.warning("RDP feature is in preview.")
        op_call = rdp_utils.start_rdp_connection

    ssh_session = ssh_info.SSHSession(resource_group_name, vm_name, ssh_ip, public_key_file,
                                      private_key_file, use_private_ip, local_user, cert_file, port,
                                      ssh_client_folder, ssh_args, delete_credentials, resource_type,
                                      ssh_proxy_folder, credentials_folder, winrdp)
    ssh_session.resource_type = _decide_resource_type(cmd, ssh_session)
    _do_ssh_op(cmd, ssh_session, op_call)
예제 #4
0
 def get_host(self):
     if not self.is_arc():
         if self.local_user and self.ip:
             return self.local_user + "@" + self.ip
     else:
         if self.local_user and self.vm_name:
             return self.local_user + "@" + self.vm_name
     raise azclierror.BadRequestError("Unable to determine host.")
예제 #5
0
def get_ssh_cert_info(cert_file, ssh_client_folder=None):
    sshkeygen_path = get_ssh_client_path("ssh-keygen", ssh_client_folder)
    command = [sshkeygen_path, "-L", "-f", cert_file]
    logger.debug("Running ssh-keygen command %s", ' '.join(command))
    try:
        return subprocess.check_output(command).decode().splitlines()
    except OSError as e:
        colorama.init()
        raise azclierror.BadRequestError(
            f"Failed to get certificate info with error: {str(e)}.",
            const.RECOMMENDATION_SSH_CLIENT_NOT_FOUND)
예제 #6
0
def create_ssh_keyfile(private_key_file, ssh_client_folder=None):
    sshkeygen_path = get_ssh_client_path("ssh-keygen", ssh_client_folder)
    command = [
        sshkeygen_path, "-f", private_key_file, "-t", "rsa", "-q", "-N", ""
    ]
    logger.debug("Running ssh-keygen command %s", ' '.join(command))
    try:
        subprocess.call(command)
    except OSError as e:
        colorama.init()
        raise azclierror.BadRequestError(
            f"Failed to create ssh key file with error: {str(e)}.",
            const.RECOMMENDATION_SSH_CLIENT_NOT_FOUND)
예제 #7
0
def _get_rdp_path(rdp_command="mstsc"):
    rdp_path = rdp_command
    if platform.system() == 'Windows':
        arch_data = platform.architecture()
        sys_path = 'System32'
        system_root = os.environ['SystemRoot']
        system32_path = os.path.join(system_root, sys_path)
        rdp_path = os.path.join(system32_path, (rdp_command + ".exe"))
        logger.debug("Platform architecture: %s", str(arch_data))
        logger.debug("System Root: %s", system_root)
        logger.debug("Attempting to run rdp from path %s", rdp_path)

        if not os.path.isfile(rdp_path):
            raise azclierror.BadRequestError(
                "Could not find " + rdp_command +
                ".exe. Is the rdp client installed?")
    else:
        raise azclierror.BadRequestError(
            "Platform is not supported for this command. Supported platforms: Windows"
        )

    return rdp_path
예제 #8
0
def start_ssh_connection(op_info, delete_keys, delete_cert):
    try:
        # Initialize these so that if something fails in the try block before these
        # are initialized, then the finally block won't fail.
        cleanup_process = None
        log_file = None
        connection_status = None

        ssh_arg_list = []
        if op_info.ssh_args:
            ssh_arg_list = op_info.ssh_args

        env = os.environ.copy()
        if op_info.is_arc():
            env['SSHPROXY_RELAY_INFO'] = connectivity_utils.format_relay_info_string(
                op_info.relay_info)

        # Get ssh client before starting the clean up process in case there is an error in getting client.
        command = [
            get_ssh_client_path('ssh', op_info.ssh_client_folder),
            op_info.get_host()
        ]

        if not op_info.cert_file and not op_info.private_key_file:
            # In this case, even if delete_credentials is true, there is nothing to clean-up.
            op_info.delete_credentials = False

        log_file, ssh_arg_list, cleanup_process = _start_cleanup(
            op_info.cert_file, op_info.private_key_file,
            op_info.public_key_file, op_info.delete_credentials, delete_keys,
            delete_cert, ssh_arg_list)
        command = command + op_info.build_args() + ssh_arg_list

        connection_duration = time.time()
        logger.debug("Running ssh command %s", ' '.join(command))

        # pylint: disable=subprocess-run-check
        try:
            if set(['-v', '-vv', '-vvv']).isdisjoint(ssh_arg_list) or log_file:
                connection_status = subprocess.run(
                    command,
                    shell=platform.system() == 'Windows',
                    env=env,
                    stderr=subprocess.PIPE,
                    encoding='utf-8')
            else:
                # Logs are sent to stderr. In that case, we shouldn't capture stderr.
                connection_status = subprocess.run(
                    command, shell=platform.system() == 'Windows', env=env)
        except OSError as e:
            colorama.init()
            raise azclierror.BadRequestError(
                f"Failed to run ssh command with error: {str(e)}.",
                const.RECOMMENDATION_SSH_CLIENT_NOT_FOUND)

        connection_duration = (time.time() - connection_duration) / 60
        ssh_connection_data = {
            'Context.Default.AzureCLI.SSHConnectionDurationInMinutes':
            connection_duration
        }
        if connection_status and connection_status.returncode == 0:
            ssh_connection_data[
                'Context.Default.AzureCLI.SSHConnectionStatus'] = "Success"
        telemetry.add_extension_event('ssh', ssh_connection_data)

    finally:
        # Even if something fails between the creation of the credentials and the end of the ssh connection, we
        # want to make sure that all credentials are cleaned up, and that the clean up process is terminated.
        _terminate_cleanup(delete_keys, delete_cert,
                           op_info.delete_credentials, cleanup_process,
                           op_info.cert_file, op_info.private_key_file,
                           op_info.public_key_file, log_file,
                           connection_status)
예제 #9
0
def get_ssh_client_path(ssh_command="ssh", ssh_client_folder=None):
    if ssh_client_folder:
        ssh_path = os.path.join(ssh_client_folder, ssh_command)
        if platform.system() == 'Windows':
            ssh_path = ssh_path + '.exe'
        if os.path.isfile(ssh_path):
            logger.debug("Attempting to run %s from path %s", ssh_command,
                         ssh_path)
            return ssh_path
        logger.warning(
            "Could not find %s in provided --ssh-client-folder %s. "
            "Attempting to get pre-installed OpenSSH bits.", ssh_command,
            ssh_client_folder)

    ssh_path = ssh_command

    if platform.system() == 'Windows':
        # If OS architecture is 64bit and python architecture is 32bit,
        # look for System32 under SysNative folder.
        machine = platform.machine()
        os_architecture = None
        # python interpreter architecture
        platform_architecture = platform.architecture()[0]
        sys_path = None

        if machine.endswith('64'):
            os_architecture = '64bit'
        elif machine.endswith('86'):
            os_architecture = '32bit'
        elif machine == '':
            raise azclierror.BadRequestError(
                "Couldn't identify the OS architecture.")
        else:
            raise azclierror.BadRequestError(
                f"Unsuported OS architecture: {machine} is not currently supported"
            )

        if os_architecture == "64bit":
            sys_path = 'SysNative' if platform_architecture == '32bit' else 'System32'
        else:
            sys_path = 'System32'

        system_root = os.environ['SystemRoot']
        system32_path = os.path.join(system_root, sys_path)
        ssh_path = os.path.join(system32_path, "openSSH",
                                (ssh_command + ".exe"))
        logger.debug("Platform architecture: %s", platform_architecture)
        logger.debug("OS architecture: %s", os_architecture)
        logger.debug("System Root: %s", system_root)
        logger.debug("Attempting to run %s from path %s", ssh_command,
                     ssh_path)

        if not os.path.isfile(ssh_path):
            raise azclierror.UnclassifiedUserFault(
                "Could not find " + ssh_command + ".exe on path " + ssh_path +
                ". ", colorama.Fore.YELLOW +
                "Make sure OpenSSH is installed correctly: "
                "https://docs.microsoft.com/en-us/windows-server/administration/openssh/openssh_install_firstuse . "
                "Or use --ssh-client-folder to provide folder path with ssh executables. "
                + colorama.Style.RESET_ALL)

    return ssh_path
예제 #10
0
def _decide_resource_type(cmd, op_info):
    # If the user provides an IP address the target will be treated as an Azure VM even if it is an
    # Arc Server. Which just means that the Connectivity Proxy won't be used to establish connection.
    is_arc_server = False
    is_azure_vm = False

    if op_info.ip:
        is_azure_vm = True
        vm = None

    elif op_info.resource_type:
        if op_info.resource_type.lower() == "microsoft.hybridcompute":
            arc, arc_error, is_arc_server = _check_if_arc_server(cmd, op_info.resource_group_name, op_info.vm_name)
            if not is_arc_server:
                colorama.init()
                if isinstance(arc_error, ResourceNotFoundError):
                    raise azclierror.ResourceNotFoundError(f"The resource {op_info.vm_name} in the resource group "
                                                           f"{op_info.resource_group_name} was not found.",
                                                           const.RECOMMENDATION_RESOURCE_NOT_FOUND)
                raise azclierror.BadRequestError("Unable to determine that the target machine is an Arc Server. "
                                                 f"Error:\n{str(arc_error)}", const.RECOMMENDATION_RESOURCE_NOT_FOUND)

        elif op_info.resource_type.lower() == "microsoft.compute":
            vm, vm_error, is_azure_vm = _check_if_azure_vm(cmd, op_info.resource_group_name, op_info.vm_name)
            if not is_azure_vm:
                colorama.init()
                if isinstance(vm_error, ResourceNotFoundError):
                    raise azclierror.ResourceNotFoundError(f"The resource {op_info.vm_name} in the resource group "
                                                           f"{op_info.resource_group_name} was not found.",
                                                           const.RECOMMENDATION_RESOURCE_NOT_FOUND)
                raise azclierror.BadRequestError("Unable to determine that the target machine is an Azure VM. "
                                                 f"Error:\n{str(vm_error)}", const.RECOMMENDATION_RESOURCE_NOT_FOUND)

    else:
        vm, vm_error, is_azure_vm = _check_if_azure_vm(cmd, op_info.resource_group_name, op_info.vm_name)
        arc, arc_error, is_arc_server = _check_if_arc_server(cmd, op_info.resource_group_name, op_info.vm_name)

        if is_azure_vm and is_arc_server:
            colorama.init()
            raise azclierror.BadRequestError(f"{op_info.resource_group_name} has Azure VM and Arc Server with the "
                                             f"same name: {op_info.vm_name}.",
                                             colorama.Fore.YELLOW + "Please provide a --resource-type." +
                                             colorama.Style.RESET_ALL)
        if not is_azure_vm and not is_arc_server:
            colorama.init()
            if isinstance(arc_error, ResourceNotFoundError) and isinstance(vm_error, ResourceNotFoundError):
                raise azclierror.ResourceNotFoundError(f"The resource {op_info.vm_name} in the resource group "
                                                       f"{op_info.resource_group_name} was not found. ",
                                                       const.RECOMMENDATION_RESOURCE_NOT_FOUND)
            raise azclierror.BadRequestError("Unable to determine the target machine type as Azure VM or "
                                             f"Arc Server. Errors:\n{str(arc_error)}\n{str(vm_error)}",
                                             const.RECOMMENDATION_RESOURCE_NOT_FOUND)

    # Note: We are not able to determine the os of the target if the user only provides an IP address.
    os_type = None
    if is_azure_vm and vm and vm.storage_profile and vm.storage_profile.os_disk and vm.storage_profile.os_disk.os_type:
        os_type = vm.storage_profile.os_disk.os_type

    if is_arc_server and arc and arc.properties and arc.properties and arc.properties.os_name:
        os_type = arc.properties.os_name

    if os_type:
        telemetry.add_extension_event('ssh', {'Context.Default.AzureCLI.TargetOSType': os_type})

    # Note 2: This is a temporary check while AAD login is not enabled for Windows.
    if os_type and os_type.lower() == 'windows' and not op_info.local_user:
        colorama.init()
        raise azclierror.RequiredArgumentMissingError("SSH Login using AAD credentials is not currently supported "
                                                      "for Windows.",
                                                      colorama.Fore.YELLOW + "Please provide --local-user." +
                                                      colorama.Style.RESET_ALL)

    target_resource_type = "Microsoft.Compute"
    if is_arc_server:
        target_resource_type = "Microsoft.HybridCompute"
    telemetry.add_extension_event('ssh', {'Context.Default.AzureCLI.TargetResourceType': target_resource_type})

    return target_resource_type