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
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
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)
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.")
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)
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)
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
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)
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
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