Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ssharc public preview fix #28

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
4 changes: 4 additions & 0 deletions src/ssh/HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
Release History
===============
1.1.1
-----
* Undo changes to rely on PATH variable to find SSH executables on Windows. Look for executables under "C:\Windows\System32\OpenSSH"

1.1.0
-----
* SSHArc Public Preview
Expand Down
13 changes: 8 additions & 5 deletions src/ssh/azext_ssh/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
text: |
az ssh vm --local-user username --resource-group myResourceGroup --name myArcServer

- name: Give a SSH Client Folder to use the ssh executables in that folder, like ssh-keygen.exe and ssh.exe. If not provided, the extension attempt to use pre-installed OpenSSH client (in that case, ensure OpenSSH client is installed and the Path environment variable is set correctly).
- name: Give a SSH Client Folder to use the ssh executables in that folder, like ssh-keygen.exe and ssh.exe. If not provided, the extension attempts to use pre-installed OpenSSH client (on Windows, extension looks for pre-installed executables under C:\\Windows\\System32\\OpenSSH).
text: |
az ssh vm --resource-group myResourceGroup --name myVM --ssh-client-folder "C:\\Program Files\\OpenSSH"
"""
Expand Down Expand Up @@ -91,9 +91,9 @@
rsync -e 'ssh -F ./sshconfig' -avP directory/ myvm:~/directory
GIT_SSH_COMMAND="ssh -F ./sshconfig" git clone myvm:~/gitrepo

- name: Give SSH Client Folder to use the ssh executables in that folder. If not provided, the extension attempt to use pre-installed OpenSSH client (in that case, ensure OpenSSH client is installed and the Path environment variable is set correctly).
- name: Give a SSH Client Folder to use the ssh executables in that folder, like ssh-keygen.exe. If not provided, the extension attempts to use pre-installed OpenSSH client (on Windows, extension looks for pre-installed executables under C:\\Windows\\System32\\OpenSSH).
text: |
az ssh config --file ./sshconfig --resource-group myResourceGroup --name myMachine --ssh-client-folder "C:\\Program Files\\OpenSSH"
az ssh config --file ./myconfig --resource-group myResourceGroup --name myVM --ssh-client-folder "C:\\Program Files\\OpenSSH"

- 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: |
Expand All @@ -107,12 +107,15 @@
- name: Create a short lived ssh certificate signed by AAD
text: |
az ssh cert --public-key-file ./id_rsa.pub --file ./id_rsa-aadcert.pub
- name: Give a SSH Client Folder to use the ssh executables in that folder, like ssh-keygen.exe. If not provided, the extension attempts to use pre-installed OpenSSH client (on Windows, extension looks for pre-installed executables under C:\\Windows\\System32\\OpenSSH).
text: |
az ssh cert --file ./id_rsa-aadcert.pub --ssh-client-folder "C:\\Program Files\\OpenSSH"
"""

helps['ssh arc'] = """
type: command
short-summary: SSH into Azure Arc Servers
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.
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 name and machine name to SSH using AAD issued certificates
text: |
Expand All @@ -138,7 +141,7 @@
text: |
az ssh arc --local-user username --resource-group myResourceGroup --name myMachine

- name: Give a SSH Client Folder to use the ssh executables in that folder, like ssh-keygen.exe and ssh.exe. If not provided, the extension attempt to use pre-installed OpenSSH client (ensure OpenSSH client is installed and the Path environment variable is set correctly).
- name: Give a SSH Client Folder to use the ssh executables in that folder, like ssh-keygen.exe and ssh.exe. If not provided, the extension attempts to use pre-installed OpenSSH client (on Windows, extension looks for pre-installed executables under C:\\Windows\\System32\\OpenSSH).
text: |
az ssh arc --resource-group myResourceGroup --name myMachine --ssh-client-folder "C:\\Program Files\\OpenSSH"
"""
8 changes: 4 additions & 4 deletions src/ssh/azext_ssh/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ def load_arguments(self, _):
help='Folder path that contains ssh executables (ssh.exe, ssh-keygen.exe, etc). '
'Default to ssh pre-installed if not provided.')
c.argument('delete_credentials', options_list=['--force-delete-credentials', '--delete-private-key'],
help=('This is an internal argument. This argument is used by Azure Portal to provide a one click '
'SSH login experience in Cloud shell.'),
help=('This is an internal argument. This argument is used by Azure Portal to '
'provide a one click SSH login experience in Cloud shell.'),
deprecate_info=c.deprecate(hide=True), action='store_true')
c.argument('ssh_proxy_folder', options_list=['--ssh-proxy-folder'],
help=('Path to the folder where the ssh proxy should be saved. '
Expand Down Expand Up @@ -81,8 +81,8 @@ def load_arguments(self, _):
help='Folder path that contains ssh executables (ssh.exe, ssh-keygen.exe, etc). '
'Default to ssh pre-installed if not provided.')
c.argument('delete_credentials', options_list=['--force-delete-credentials', '--delete-private-key'],
help=('This is an internal argument. This argument is used by Azure Portal to provide a one click '
'SSH login experience in Cloud shell.'),
help=('This is an internal argument. This argument is used by Azure Portal to '
'provide a one click SSH login experience in Cloud shell.'),
deprecate_info=c.deprecate(hide=True), action='store_true')
c.argument('ssh_proxy_folder', options_list=['--ssh-proxy-folder'],
help=('Path to the folder where the ssh proxy should be saved. '
Expand Down
3 changes: 1 addition & 2 deletions src/ssh/azext_ssh/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@
CLEANUP_AWAIT_TERMINATION_IN_SECONDS = 30
RELAY_INFO_MAXIMUM_DURATION_IN_SECONDS = 3600
WINDOWS_INVALID_FOLDERNAME_CHARS = "\\/*:<>?\"|"
RECOMMENDATION_SSH_CLIENT_NOT_FOUND = (Fore.YELLOW + "Ensure OpenSSH is installed and the PATH Environment "
"Variable is set correctly.\nAlternatively, use "
RECOMMENDATION_SSH_CLIENT_NOT_FOUND = (Fore.YELLOW + "Ensure OpenSSH is installed correctly.\nAlternatively, use "
"--ssh-client-folder to provide OpenSSH folder path." + Style.RESET_ALL)
RECOMMENDATION_RESOURCE_NOT_FOUND = (Fore.YELLOW + "Please ensure the active subscription is set properly "
"and resource exists." + Style.RESET_ALL)
6 changes: 3 additions & 3 deletions src/ssh/azext_ssh/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -408,16 +408,16 @@ def _decide_resource_type(cmd, op_info):
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.",
Fore.YELLOW + "Please provide --local-user." + Style.RESET_ALL)

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

target_resource_type = "Microsoft.Compute"
if is_arc_server:
target_resource_type = "Microsoft.HybridCompute"
Expand Down
52 changes: 46 additions & 6 deletions src/ssh/azext_ssh/ssh_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,12 @@ def start_ssh_connection(op_info, delete_keys, delete_cert):
# pylint: disable=subprocess-run-check
try:
if set(['-v', '-vv', '-vvv']).isdisjoint(ssh_arg_list) or log_file:
connection_status = subprocess.run(command, env=env, stderr=subprocess.PIPE, text=True)
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, env=env, text=True)
except Exception as e:
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)
Expand Down Expand Up @@ -101,7 +102,7 @@ def create_ssh_keyfile(private_key_file, ssh_client_folder=None):
logger.debug("Running ssh-keygen command %s", ' '.join(command))
try:
subprocess.call(command)
except Exception as e:
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)
Expand All @@ -113,7 +114,7 @@ def get_ssh_cert_info(cert_file, ssh_client_folder=None):
logger.debug("Running ssh-keygen command %s", ' '.join(command))
try:
return subprocess.check_output(command).decode().splitlines()
except Exception as e:
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)
Expand Down Expand Up @@ -219,9 +220,48 @@ def _get_ssh_client_path(ssh_command="ssh", ssh_client_folder=None):
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 use pre-installed OpenSSH.", ssh_path, ssh_client_folder)
"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 + ". ",
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. " + Style.RESET_ALL)

return ssh_path


Expand Down
Loading