Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 46 additions & 16 deletions src/ssh/azext_ssh/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@
helps['ssh vm'] = """
type: command
short-summary: SSH into Azure VMs or Arc Servers.
long-summary: Users can login using AAD issued certificates or using local user credentials. We recommend login using AAD issued certificates. To SSH as a local user in the target machine, you must provide the local user name using the --local-user argument.
long-summary: Users can login using AAD issued certificates or using local user credentials. We recommend login using AAD issued certificates. To SSH using local user credentials, you must provide the local user name using the --local-user parameter.
examples:
- name: Give a resource group and VM to SSH using AAD issued certificates
- name: Give a resource group name and resource name to SSH using AAD issued certificates
Comment thread
vthiebaut10 marked this conversation as resolved.
Outdated
text: |
az ssh vm --resource-group myResourceGroup --vm-name myVM
az ssh vm --resource-group myResourceGroup --name myVM

- name: Give the public IP (or hostname) of a VM to SSH to SSH using AAD issued certificates
- name: Give the public IP (or hostname) of a VM to SSH using AAD issued certificates
text: |
az ssh vm --ip 1.2.3.4
az ssh vm --hostname example.com
Expand All @@ -32,36 +32,66 @@
text: |
az ssh vm --ip 1.2.3.4 -- -A -o ForwardX11=yes

- name: Give the Resource Type of a VM to SSH using AAD issued certificates. Using the resource type is useful when there is an Azure VM and a Arc Server with the same name in the same resource group.
- name: Give the Resource Type of the target. Useful when there is an Azure VM and an Arc Server with the same name in the same resource group. Resource type can be either "Microsoft.HybridCompute" for Arc Servers or "Microsoft.Compute" for Azure Virtual Machines.
text: |
az ssh vm --resource-type Microsoft.Compute --resource-group myResourceGroup --vm-name myVM
# Azure Virtual Machine
az ssh vm --resource-type Microsoft.Compute --resource-group myResourceGroup --name myVM
Comment thread
vthiebaut10 marked this conversation as resolved.
Outdated
# Arc Enbled Server
az ssh vm --resource-type Microsoft.HybridCompute --resource-group myResourceGroup --name myMachine

- name: Give a local user name to SSH using local user credentials on the target machine using certificate based authentication.
- name: Give a local user name to SSH with local user credentials using certificate based authentication.
text: |
az ssh vm --local-user username --ip 1.2.3.4 --certificate-file cert.pub --private-key key

- name: Give a local user name to SSH using local user credentials on the target machine using key based authentication.
- name: Give a local user name to SSH with local user credentials using key based authentication.
text: |
az ssh vm --local-user username --resource-group myResourceGroup --vm-name myVM --private-key-file key
az ssh vm --local-user username --resource-group myResourceGroup --name myVM --private-key-file key

- name: Give a local user name to SSH using local user credentials on the target machine using password based authentication.
- name: Give a local user name to SSH with local user credentials using password based authentication.
text: |
az ssh vm --local-user username --resource-group myResourceGroup --vm-name myArcServer
az ssh vm --local-user username --resource-group myResourceGroup --name myArcServer

- name: Give a SSH Client Folder to use the ssh executables (ssh-keyge.exe, ssh.exe, etc) in that folder. If not provided, the extension will look for pre-installed ssh client (for Windows, it will look for ssh executables under C:\\Windows\\System32\\OpenSSH).
Comment thread
vthiebaut10 marked this conversation as resolved.
Outdated
text: |
az ssh vm --resource-group myResourceGroup --name myVM --ssh-client-folder "C:\\Program Files\\OpenSSH"

- name: When connecting to an Arc Server, optionally give a SSH Proxy Folder to indicate the folder where the SSH proxy will be stored. If not provided, the proxy will be saved in .clientsshproxy folder in user\'s home directory.
Comment thread
vthiebaut10 marked this conversation as resolved.
Outdated
- text: |
az ssh vm --resource-group myResourceGroup --name myMachine --ssh-proxy-folder "/home/user/myproxyfolder"
"""

helps['ssh config'] = """
type: command
short-summary: Create an SSH config for resources (Azure VMs, etc) which can then be used by clients that support OpenSSH configs and certificates
long-summary: Other software (git/rsync/etc) that support setting an SSH command can be set to use the config file by setting the command to 'ssh -F /path/to/config' e.g. rsync -e 'ssh -F /path/to/config'
short-summary: Create an SSH config for resources (Azure VMs, Arc Servers, etc) which can then be used by clients that support OpenSSH configs and certificates
long-summary: Other software (git/rsync/etc) that support setting an SSH command can be set to use the config file by setting the command to 'ssh -F /path/to/config' e.g. rsync -e 'ssh -F /path/to/config'. Users can create ssh config files that use AAD issued certificates or local user credentials.
examples:
- name: Give a resource group and VM for which to create a config, and save in a local file
- name: Give a resource group and resource name for which to create a config using AAD issued certificates, save in a local file, and then ssh into that resource
text: |
az ssh config --resource-group myResourceGroup --name myVm --file ./sshconfig
ssh -F ./sshconfig myResourceGroup-myVM

- name: Give a resource group, resource name, and local user to create a config using local user credentials, save in local file, and then ssh into that resource
text: |
az ssh config --resource-group myResourceGroup --vm-name myVm --file ./sshconfig
# password based authentication
az ssh config --resource-group myResourceGroup --name myVM --local-user username1 --file ./sshconfig
ssh -F ./sshconfig MyResourceGroup-myVM-username1
Comment thread
vthiebaut10 marked this conversation as resolved.
Outdated

# key based authentication
az ssh config --resource-group myResourceGroup --name myVM --local-user username2 --private-key-file key --file ./sshconfig
ssh -F ./sshconfig MyResourceGroup-myVM-username2

# certificate based authentication
az ssh config --resource-group myResourceGroup --name myVM --local-user username3 --certificate-file cert.pub --private-key-file key --file ./sshconfig
ssh -F ./sshconfig MyResourceGroup-myVM-username3

- name: Give the public IP (or hostname) of a VM for which to create a config and then ssh
text: |
az ssh config --ip 1.2.3.4 --file ./sshconfig
ssh -F ./sshconfig 1.2.3.4

- name: Give Keys Destination Folder to indicate where new generated keys and certificates will be saved. If not provided, keys will be saved to a new folder "az_ssh_config" next to the config file.
Comment thread
vthiebaut10 marked this conversation as resolved.
Outdated
text: |
az ssh config --ip 1.2.3.4 --file ./sshconfig --keys-destination-folder /home/user/mykeys

- name: Create a generic config for use with any host
text: |
Expand Down Expand Up @@ -90,7 +120,7 @@
helps['ssh arc'] = """
type: command
short-summary: SSH into Azure Arc Servers
long-summary: Users can now login using AAD issued certificates or using local user credentials. We recommend login using AAD issued certificates as azure automatically rotate SSH CA keys. To SSH as a local user in the target machine, you must provide the local user name using the --local-user argument.
long-summary: Users can login using AAD issued certificates or using local user credentials. We recommend login using AAD issued certificates as azure automatically rotate SSH CA keys. To SSH as a local user in the target machine, you must provide the local user name using the --local-user argument.
examples:
- name: Give a resource group and Arc Server Name to SSH using AAD issued certificates
text: |
Expand Down
14 changes: 0 additions & 14 deletions src/ssh/azext_ssh/connectivity_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,8 @@ def get_relay_information(cmd, resource_group, vm_name, certificate_validity_in_
time_elapsed = time.time() - t0
telemetry.add_extension_event('ssh', {'Context.Default.AzureCLI.SSHListCredentialsTime': time_elapsed})
except Exception as e:
telemetry.set_exception(exception='Call to listCredentials failed',
fault_type=consts.LIST_CREDENTIALS_FAILED_FAULT_TYPE,
summary=f'listCredentials failed with error: {str(e)}.')
raise azclierror.ClientRequestError(f"Request for Azure Relay Information Failed:\n{str(e)}")
except Exception as e:
telemetry.set_exception(exception='Call to listCredentials failed',
fault_type=consts.LIST_CREDENTIALS_FAILED_FAULT_TYPE,
summary=f'listCredentials failed with error: {str(e)}.')
raise azclierror.ClientRequestError(f"Request for Azure Relay Information Failed:\n{str(e)}")
return result

Expand Down Expand Up @@ -91,8 +85,6 @@ def get_client_side_proxy(arc_proxy_folder):
response_content = response.read()
response.close()
except Exception as e:
telemetry.set_exception(exception=e, fault_type=consts.PROXY_DOWNLOAD_FAILED_FAULT_TYPE,
summary=f'Failed to download proxy from {request_uri}')
raise azclierror.ClientRequestError(f"Failed to download client proxy executable from {request_uri}. "
"Error: " + str(e)) from e
time_elapsed = time.time() - t0
Expand Down Expand Up @@ -136,9 +128,6 @@ def _get_proxy_filename_and_url(arc_proxy_folder):
elif machine == '':
raise azclierror.BadRequestError("Couldn't identify the platform architecture.")
else:
telemetry.set_exception(exception='Unsuported architecture for installing proxy',
fault_type=consts.PROXY_UNSUPPORTED_ARCH_FAULT_TYPE,
summary=f'{machine} is not supported for installing client proxy')
raise azclierror.BadRequestError(f"Unsuported architecture: {machine} is not currently supported")

# define the request url and install location based on the os and architecture
Expand All @@ -153,9 +142,6 @@ def _get_proxy_filename_and_url(arc_proxy_folder):
install_location = install_location + ".exe"
older_location = older_location + ".exe"
elif operating_system not in ('Linux', 'Darwin'):
telemetry.set_exception(exception='Unsuported OS for installing ssh client proxy',
fault_type=consts.PROXY_UNSUPPORTED_OS_FAULT_TYPE,
summary=f'{operating_system} is not supported for installing client proxy')
raise azclierror.BadRequestError(f"Unsuported OS: {operating_system} platform is not currently supported")

if not arc_proxy_folder:
Expand Down
4 changes: 0 additions & 4 deletions src/ssh/azext_ssh/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,4 @@
CLEANUP_TIME_INTERVAL_IN_SECONDS = 10
CLEANUP_AWAIT_TERMINATION_IN_SECONDS = 30
RELAY_INFO_MAXIMUM_DURATION_IN_SECONDS = 3600
PROXY_UNSUPPORTED_ARCH_FAULT_TYPE = 'client-proxy-unsupported-architecture-error'
PROXY_UNSUPPORTED_OS_FAULT_TYPE = 'client-proxy-unsupported-os-error'
PROXY_DOWNLOAD_FAILED_FAULT_TYPE = 'client-proxy-download-failed-error'
LIST_CREDENTIALS_FAILED_FAULT_TYPE = 'get-relay-information-failed-error'
WINDOWS_INVALID_FOLDERNAME_CHARS = "\\/*:<>?\"|"
3 changes: 3 additions & 0 deletions src/ssh/azext_ssh/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,9 @@ def _decide_resource_type(cmd, op_info):
raise azclierror.RequiredArgumentMissingError("SSH Login to AAD user is not currently supported for Windows. "
"Please provide --local-user.")

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

if is_arc_server:
return "Microsoft.HybridCompute"
return "Microsoft.Compute"
Expand Down
61 changes: 39 additions & 22 deletions src/ssh/azext_ssh/ssh_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ def start_ssh_connection(op_info, delete_keys, delete_cert):

connection_duration = time.time()
logger.debug("Running ssh command %s", ' '.join(command))
connection_status = subprocess.call(command, shell=platform.system() == 'Windows', env=env)

# pylint: disable=subprocess-run-check
connection_status = subprocess.run(command, env=env, stderr=subprocess.PIPE, text=True)

connection_duration = (time.time() - connection_duration) / 60
ssh_connection_data = {'Context.Default.AzureCLI.SSHConnectionDurationInMinutes': connection_duration}
Expand Down Expand Up @@ -143,6 +145,9 @@ def get_ssh_cert_principals(cert_file, ssh_client_folder=None):


def _print_error_messages_from_ssh_log(log_file, connection_status, delete_cert):
if connection_status:
connection_status = connection_status.returncode
Comment thread
vthiebaut10 marked this conversation as resolved.
Outdated

with open(log_file, 'r', encoding='utf-8') as ssh_log:
log_text = ssh_log.read()
log_lines = log_text.splitlines()
Expand Down Expand Up @@ -302,27 +307,37 @@ def _start_cleanup(cert_file, private_key_file, public_key_file, delete_credenti

def _terminate_cleanup(delete_keys, delete_cert, delete_credentials, cleanup_process, cert_file,
private_key_file, public_key_file, log_file, connection_status):

if delete_keys or delete_cert or delete_credentials:
if cleanup_process and cleanup_process.is_alive():
cleanup_process.terminate()
# wait for process to terminate
t0 = time.time()
while cleanup_process.is_alive() and (time.time() - t0) < const.CLEANUP_AWAIT_TERMINATION_IN_SECONDS:
time.sleep(1)

if log_file and os.path.isfile(log_file):
_print_error_messages_from_ssh_log(log_file, connection_status, delete_cert)

# Make sure all files have been properly removed.
do_cleanup(delete_keys or delete_credentials, delete_cert or delete_credentials,
cert_file, private_key_file, public_key_file)
if log_file:
file_utils.delete_file(log_file, f"Couldn't delete temporary log file {log_file}. ", True)
if delete_keys:
# This is only true if keys were generated, so they must be in a temp folder.
temp_dir = os.path.dirname(cert_file)
file_utils.delete_folder(temp_dir, f"Couldn't delete temporary folder {temp_dir}", True)
try:
if connection_status:
if connection_status.returncode != 0:
# Check if stderr is a proxy error
regex = ("{\"level\":\"fatal\",\"msg\":\"sshproxy: error copying information from the connection: "
".*\",\"time\":\".*\"}.*")
if re.search(regex, connection_status.stderr):
logger.error("SSH Proxy Error. Check if incoming connections are enabled on the hybrid agent "
Comment thread
vthiebaut10 marked this conversation as resolved.
Outdated
"and SSHD is running in the target machine.")
print(connection_status.stderr)
finally:
if delete_keys or delete_cert or delete_credentials:
if cleanup_process and cleanup_process.is_alive():
cleanup_process.terminate()
# wait for process to terminate
t0 = time.time()
while cleanup_process.is_alive() and (time.time() - t0) < const.CLEANUP_AWAIT_TERMINATION_IN_SECONDS:
time.sleep(1)

if log_file and os.path.isfile(log_file):
_print_error_messages_from_ssh_log(log_file, connection_status, delete_cert)

# Make sure all files have been properly removed.
do_cleanup(delete_keys or delete_credentials, delete_cert or delete_credentials,
cert_file, private_key_file, public_key_file)
if log_file:
file_utils.delete_file(log_file, f"Couldn't delete temporary log file {log_file}. ", True)
if delete_keys:
# This is only true if keys were generated, so they must be in a temp folder.
temp_dir = os.path.dirname(cert_file)
file_utils.delete_folder(temp_dir, f"Couldn't delete temporary folder {temp_dir}", True)


def _issue_config_cleanup_warning(delete_cert, delete_keys, is_arc, cert_file, relay_info_path, ssh_client_folder):
Expand Down Expand Up @@ -363,6 +378,8 @@ def _issue_config_cleanup_warning(delete_cert, delete_keys, is_arc, cert_file, r


def _get_connection_status(log_file, connection_status):
if connection_status:
connection_status = connection_status.returncode
Comment thread
vthiebaut10 marked this conversation as resolved.
Outdated
# pylint: disable=bare-except
if log_file and os.path.isfile(log_file):
try:
Expand Down