Example #1
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}."))
Example #2
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")
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, credentials_folder=None, ssh_client_folder=None):

    _assert_args(resource_group_name, vm_name, ssh_ip, cert_file, local_user)
    # 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.")

    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, ssh_client_folder)

    op_call = ssh_utils.write_ssh_config

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

    # Default credential location
    # Add logic to test if credentials folder is valid
    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
        credentials_folder = os.path.join(config_folder, os.path.join("az_ssh_config", folder_name))
    else:
        credentials_folder = os.path.abspath(credentials_folder)

    _do_ssh_op(cmd, config_session, credentials_folder, op_call)
def _get_and_write_certificate(cmd, public_key_file, cert_file, ssh_client_folder):
    cloudtoscope = {
        "azurecloud": "https://pas.windows.net/CheckMyAccess/Linux/.default",
        "azurechinacloud": "https://pas.chinacloudapi.cn/CheckMyAccess/Linux/.default",
        "azureusgovernment": "https://pasff.usgovcloudapi.net/CheckMyAccess/Linux/.default"
    }
    scope = cloudtoscope.get(cmd.cli_ctx.cloud.name.lower(), None)
    if not scope:
        raise azclierror.InvalidArgumentValueError(
            f"Unsupported cloud {cmd.cli_ctx.cloud.name.lower()}",
            "Supported clouds include azurecloud,azurechinacloud,azureusgovernment")

    scopes = [scope]
    data = _prepare_jwk_data(public_key_file)
    from azure.cli.core._profile import Profile
    profile = Profile(cli_ctx=cmd.cli_ctx)

    # We currently are using the presence of get_msal_token to detect if we are running on an older azure cli client
    # TODO: Remove when adal has been deprecated for a while
    if hasattr(profile, "get_msal_token"):
        # we used to use the username from the token but now we throw it away
        _, certificate = profile.get_msal_token(scopes, data)
    else:
        credential, _, _ = profile.get_login_credentials(subscription_id=profile.get_subscription()["id"])
        certificatedata = credential.get_token(*scopes, data=data)
        certificate = certificatedata.token

    if not cert_file:
        cert_file = public_key_file + "-aadcert.pub"

    logger.debug("Generating certificate %s", cert_file)
    _write_cert_file(certificate, cert_file)
    # instead we use the validprincipals from the cert due to mismatched upn and email in guest scenarios
    username = ssh_utils.get_ssh_cert_principals(cert_file, ssh_client_folder)[0]
    return cert_file, username.lower()
Example #5
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")
Example #6
0
    def __getitem__(self, data):
        key = data
        if isinstance(self.items, dict):
            # convert data, it can be key, value or key.lower() when not case sensitive
            for k, v in self.items.items():
                if v == data or k == data or not self._case_sensitive and k.lower(
                ) == data.lower():
                    key = k
                    break

        if key in self.items:
            if isinstance(self.items, (list, tuple, set)):
                return key
            if isinstance(self.items, dict):
                return self.items[key]
            raise NotImplementedError()
        raise azclierror.InvalidArgumentValueError(
            f"unrecognized value '{data}' from choices '{self.to_choices()}' ")
Example #7
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, credentials_folder=None):
    _assert_args(resource_group_name, vm_name, ssh_ip, cert_file, local_user)
    # 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.")

    op_call = functools.partial(ssh_utils.write_ssh_config, config_path, resource_group_name, vm_name, overwrite, port)
    # Default credential location
    if not credentials_folder:
        config_folder = os.path.dirname(config_path)
        if not os.path.isdir(config_folder):
            raise azclierror.InvalidArgumentValueError(f"Config file destination folder {config_folder} "
                                                       "does not exist.")
        folder_name = ssh_ip
        if resource_group_name and vm_name:
            folder_name = resource_group_name + "-" + vm_name
        credentials_folder = os.path.join(config_folder, os.path.join("az_ssh_config", folder_name))

    _do_ssh_op(cmd, resource_group_name, vm_name, ssh_ip, public_key_file, private_key_file, use_private_ip,
               local_user, cert_file, credentials_folder, op_call)
Example #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)
Example #9
0
def handle_exception(ex):  # pylint: disable=too-many-locals, too-many-statements, too-many-branches
    # For error code, follow guidelines at https://docs.python.org/2/library/sys.html#sys.exit,
    from jmespath.exceptions import JMESPathTypeError
    from msrestazure.azure_exceptions import CloudError
    from msrest.exceptions import HttpOperationError, ValidationError, ClientRequestError
    from azure.cli.core.azlogging import CommandLoggerContext
    from azure.common import AzureException
    from azure.core.exceptions import AzureError
    from requests.exceptions import SSLError, HTTPError
    import azure.cli.core.azclierror as azclierror
    import traceback

    logger.debug(
        "azure.cli.core.util.handle_exception is called with an exception:")
    # Print the traceback and exception message
    logger.debug(traceback.format_exc())

    with CommandLoggerContext(logger):
        error_msg = getattr(ex, 'message', str(ex))
        exit_code = 1

        if isinstance(ex, azclierror.AzCLIError):
            az_error = ex

        elif isinstance(ex, JMESPathTypeError):
            error_msg = "Invalid jmespath query supplied for `--query`: {}".format(
                error_msg)
            az_error = azclierror.InvalidArgumentValueError(error_msg)
            az_error.set_recommendation(QUERY_REFERENCE)

        elif isinstance(ex, SSLError):
            az_error = azclierror.AzureConnectionError(error_msg)
            az_error.set_recommendation(SSLERROR_TEMPLATE)

        elif isinstance(ex, CloudError):
            if extract_common_error_message(ex):
                error_msg = extract_common_error_message(ex)
            status_code = str(getattr(ex, 'status_code', 'Unknown Code'))
            AzCLIErrorType = get_error_type_by_status_code(status_code)
            az_error = AzCLIErrorType(error_msg)

        elif isinstance(ex, ValidationError):
            az_error = azclierror.ValidationError(error_msg)

        elif isinstance(ex, CLIError):
            # TODO: Fine-grained analysis here for Unknown error
            az_error = azclierror.UnknownError(error_msg)

        elif isinstance(ex, AzureError):
            if extract_common_error_message(ex):
                error_msg = extract_common_error_message(ex)
            AzCLIErrorType = get_error_type_by_azure_error(ex)
            az_error = AzCLIErrorType(error_msg)

        elif isinstance(ex, AzureException):
            if is_azure_connection_error(error_msg):
                az_error = azclierror.AzureConnectionError(error_msg)
            else:
                # TODO: Fine-grained analysis here for Unknown error
                az_error = azclierror.UnknownError(error_msg)

        elif isinstance(ex, ClientRequestError):
            if is_azure_connection_error(error_msg):
                az_error = azclierror.AzureConnectionError(error_msg)
            else:
                az_error = azclierror.ClientRequestError(error_msg)

        elif isinstance(ex, HttpOperationError):
            message, status_code = extract_http_operation_error(ex)
            if message:
                error_msg = message
            AzCLIErrorType = get_error_type_by_status_code(status_code)
            az_error = AzCLIErrorType(error_msg)

        elif isinstance(ex, HTTPError):
            status_code = str(
                getattr(ex.response, 'status_code', 'Unknown Code'))
            AzCLIErrorType = get_error_type_by_status_code(status_code)
            az_error = AzCLIErrorType(error_msg)

        elif isinstance(ex, KeyboardInterrupt):
            error_msg = 'Keyboard interrupt is captured.'
            az_error = azclierror.ManualInterrupt(error_msg)

        else:
            error_msg = "The command failed with an unexpected error. Here is the traceback:"
            az_error = azclierror.CLIInternalError(error_msg)
            az_error.set_exception_trace(ex)
            az_error.set_recommendation(
                "To open an issue, please run: 'az feedback'")

        if isinstance(az_error, azclierror.ResourceNotFoundError):
            exit_code = 3

        az_error.print_error()
        az_error.send_telemetry()

        return exit_code