Пример #1
0
def _assert_args(resource_group, vm_name, ssh_ip, resource_type, cert_file, username):
    if resource_type and resource_type.lower() != "microsoft.compute" \
       and resource_type.lower() != "microsoft.hybridcompute":
        raise azclierror.InvalidArgumentValueError("--resource-type must be either \"Microsoft.Compute\" "
                                                   "for Azure VMs or \"Microsoft.HybridCompute\" for Arc Servers.")

    if not (resource_group or vm_name or ssh_ip):
        raise azclierror.RequiredArgumentMissingError(
            "The VM must be specified by --ip or --resource-group and "
            "--vm-name/--name")

    if resource_group and not vm_name or vm_name and not resource_group:
        raise azclierror.MutuallyExclusiveArgumentError(
            "--resource-group and --vm-name/--name must be provided together")

    if ssh_ip and (vm_name or resource_group):
        raise azclierror.MutuallyExclusiveArgumentError(
            "--ip cannot be used with --resource-group or --vm-name/--name")

    if cert_file and not username:
        raise azclierror.MutuallyExclusiveArgumentError(
            "To authenticate with a certificate you need to provide a --local-user")

    if cert_file and not os.path.isfile(cert_file):
        raise azclierror.FileOperationError(f"Certificate file {cert_file} not found")
Пример #2
0
def _check_or_create_public_private_files(public_key_file, private_key_file):
    # If nothing is passed in create a temporary directory with a ephemeral keypair
    if not public_key_file and not private_key_file:
        temp_dir = tempfile.mkdtemp(prefix="aadsshcert")
        public_key_file = os.path.join(temp_dir, "id_rsa.pub")
        private_key_file = os.path.join(temp_dir, "id_rsa")
        ssh_utils.create_ssh_keyfile(private_key_file)

    if not public_key_file:
        if private_key_file:
            public_key_file = private_key_file + ".pub"
        else:
            raise azclierror.RequiredArgumentMissingError(
                "Public key file not specified")

    if not os.path.isfile(public_key_file):
        raise azclierror.FileOperationError(
            f"Public key file {public_key_file} not found")

    # The private key is not required as the user may be using a keypair
    # stored in ssh-agent (and possibly in a hardware token)
    if private_key_file:
        if not os.path.isfile(private_key_file):
            raise azclierror.FileOperationError(
                f"Private key file {private_key_file} not found")

    return public_key_file, private_key_file
Пример #3
0
def ssh_cert(cmd, cert_path=None, public_key_file=None, ssh_client_folder=None):
    if not cert_path and not public_key_file:
        raise azclierror.RequiredArgumentMissingError("--file or --public-key-file must be provided.")
    if cert_path and not os.path.isdir(os.path.dirname(cert_path)):
        raise azclierror.InvalidArgumentValueError(f"{os.path.dirname(cert_path)} folder doesn't exist")

    if public_key_file:
        public_key_file = os.path.abspath(public_key_file)
    if cert_path:
        cert_path = os.path.abspath(cert_path)
    if ssh_client_folder:
        ssh_client_folder = os.path.abspath(ssh_client_folder)

    # If user doesn't provide a public key, save generated key pair to the same folder as --file
    keys_folder = None
    if not public_key_file:
        keys_folder = os.path.dirname(cert_path)

    public_key_file, _, _ = _check_or_create_public_private_files(public_key_file, None, keys_folder, ssh_client_folder)
    cert_file, _ = _get_and_write_certificate(cmd, public_key_file, cert_path, ssh_client_folder)

    if keys_folder:
        logger.warning("%s contains sensitive information (id_rsa, id_rsa.pub). "
                       "Please delete once this certificate is no longer being used.", keys_folder)
    # pylint: disable=broad-except
    try:
        cert_expiration = ssh_utils.get_certificate_start_and_end_times(cert_file, ssh_client_folder)[1]
        print_styled_text((Style.SUCCESS,
                           f"Generated SSH certificate {cert_file} is valid until {cert_expiration} in local time."))
    except Exception as e:
        logger.warning("Couldn't determine certificate validity. Error: %s", str(e))
        print_styled_text((Style.SUCCESS, f"Generated SSH certificate {cert_file}."))
Пример #4
0
def _check_or_create_public_private_files(public_key_file, private_key_file, credentials_folder):
    delete_keys = False
    # If nothing is passed, then create a directory with a ephemeral keypair
    if not public_key_file and not private_key_file:
        # We only want to delete the keys if the user hasn't provided their own keys
        # Only ssh vm deletes generated keys.
        delete_keys = True
        if not credentials_folder:
            # az ssh vm: Create keys on temp folder and delete folder once connection succeeds/fails.
            credentials_folder = tempfile.mkdtemp(prefix="aadsshcert")
        else:
            # az ssh config: Keys saved to the same folder as --file or to --keys-destination-folder.
            # az ssh cert: Keys saved to the same folder as --file.
            if not os.path.isdir(credentials_folder):
                os.makedirs(credentials_folder)
        public_key_file = os.path.join(credentials_folder, "id_rsa.pub")
        private_key_file = os.path.join(credentials_folder, "id_rsa")
        ssh_utils.create_ssh_keyfile(private_key_file)

    if not public_key_file:
        if private_key_file:
            public_key_file = private_key_file + ".pub"
        else:
            raise azclierror.RequiredArgumentMissingError("Public key file not specified")

    if not os.path.isfile(public_key_file):
        raise azclierror.FileOperationError(f"Public key file {public_key_file} not found")

    # The private key is not required as the user may be using a keypair
    # stored in ssh-agent (and possibly in a hardware token)
    if private_key_file:
        if not os.path.isfile(private_key_file):
            raise azclierror.FileOperationError(f"Private key file {private_key_file} not found")

    return public_key_file, private_key_file, delete_keys
Пример #5
0
def _assert_args(resource_group, vm_name, ssh_ip):
    if not (resource_group or vm_name or ssh_ip):
        raise azclierror.RequiredArgumentMissingError(
            "The VM must be specified by --ip or --resource-group and --vm-name/--name"
        )

    if resource_group and not vm_name or vm_name and not resource_group:
        raise azclierror.MutuallyExclusiveArgumentError(
            "--resource-group and --vm-name/--name must be provided together")

    if ssh_ip and (vm_name or resource_group):
        raise azclierror.MutuallyExclusiveArgumentError(
            "--ip cannot be used with --resource-group or --vm-name/--name")
Пример #6
0
def ssh_cert(cmd, cert_path=None, public_key_file=None):
    if not cert_path and not public_key_file:
        raise azclierror.RequiredArgumentMissingError("--file or --public-key-file must be provided.")
    if cert_path and not os.path.isdir(os.path.dirname(cert_path)):
        raise azclierror.InvalidArgumentValueError(f"{os.path.dirname(cert_path)} folder doesn't exist")
    # If user doesn't provide a public key, save generated key pair to the same folder as --file
    keys_folder = None
    if not public_key_file:
        keys_folder = os.path.dirname(cert_path)
        logger.warning("The generated SSH keys are stored at %s. Please delete SSH keys when the certificate "
                       "is no longer being used.", keys_folder)
    public_key_file, _, _ = _check_or_create_public_private_files(public_key_file, None, keys_folder)
    cert_file, _ = _get_and_write_certificate(cmd, public_key_file, cert_path)
    print(cert_file + "\n")
Пример #7
0
def _assert_args(resource_group, vm_name, ssh_ip, cert_file, username):
    if not (resource_group or vm_name or ssh_ip):
        raise azclierror.RequiredArgumentMissingError(
            "The VM must be specified by --ip or --resource-group and --vm-name/--name")

    if resource_group and not vm_name or vm_name and not resource_group:
        raise azclierror.MutuallyExclusiveArgumentError(
            "--resource-group and --vm-name/--name must be provided together")

    if ssh_ip and (vm_name or resource_group):
        raise azclierror.MutuallyExclusiveArgumentError(
            "--ip cannot be used with --resource-group or --vm-name/--name")

    if cert_file and not username:
        raise azclierror.MutuallyExclusiveArgumentError(
            "To authenticate with a certificate you need to provide a --local-user")

    if cert_file and not os.path.isfile(cert_file):
        raise azclierror.FileOperationError(f"Certificate file {cert_file} not found")
Пример #8
0
def ssh_config(cmd, config_path, resource_group_name=None, vm_name=None, ssh_ip=None,
               public_key_file=None, private_key_file=None, overwrite=False, use_private_ip=False,
               local_user=None, cert_file=None, port=None, resource_type=None, credentials_folder=None,
               ssh_proxy_folder=None, ssh_client_folder=None):

    # If user provides their own key pair, certificate will be written in the same folder as public key.
    if (public_key_file or private_key_file) and credentials_folder:
        raise azclierror.ArgumentUsageError("--keys-destination-folder can't be used in conjunction with "
                                            "--public-key-file/-p or --private-key-file/-i.")
    _assert_args(resource_group_name, vm_name, ssh_ip, resource_type, cert_file, local_user)

    config_session = ssh_info.ConfigSession(config_path, resource_group_name, vm_name, ssh_ip, public_key_file,
                                            private_key_file, overwrite, use_private_ip, local_user, cert_file, port,
                                            resource_type, credentials_folder, ssh_proxy_folder, ssh_client_folder)
    op_call = ssh_utils.write_ssh_config

    config_session.resource_type = _decide_resource_type(cmd, config_session)

    # if the folder doesn't exist, this extension won't create a new one.
    config_folder = os.path.dirname(config_session.config_path)
    if not os.path.isdir(config_folder):
        raise azclierror.InvalidArgumentValueError(f"Config file destination folder {config_folder} "
                                                   "does not exist.")
    if not credentials_folder:
        # * is not a valid name for a folder in Windows. Treat this as a special case.
        folder_name = config_session.ip if config_session.ip != "*" else "all_ips"
        if config_session.resource_group_name and config_session.vm_name:
            folder_name = config_session.resource_group_name + "-" + config_session.vm_name
        if not set(folder_name).isdisjoint(set(const.WINDOWS_INVALID_FOLDERNAME_CHARS)) and \
           platform.system() == "Windows" and (not config_session.local_user or config_session.is_arc()):
            folder_name = file_utils.remove_invalid_characters_foldername(folder_name)
            if folder_name == "":
                raise azclierror.RequiredArgumentMissingError("Can't create default folder for generated keys. "
                                                              "Please provide --keys-destination-folder.")
        config_session.credentials_folder = os.path.join(config_folder, os.path.join("az_ssh_config", folder_name))

    _do_ssh_op(cmd, config_session, op_call)
Пример #9
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