Skip to content

ADD: Support SSH to Windows Machine #477

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

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Changes from all 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
75 changes: 67 additions & 8 deletions datashuttle/utils/ssh.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from typing import TYPE_CHECKING

import paramiko.ssh_exception

if TYPE_CHECKING:
from datashuttle.configs.config_class import Configs

Expand Down Expand Up @@ -45,6 +47,39 @@ def connect_client_core(
)


def is_windows(client: paramiko.SSHClient) -> bool:
"""
Checks the target machine for a Windows OS using two approaches
1. execute "ver" : outputs "Microsoft Windows <specific_version>"
2. check for the presence of powershell
"""
try:
stdin, stdout, stderr = client.exec_command("ver")
output = stdout.read().decode().strip().lower()
if "windows" in output:
return True

# rechecking in case the previous check fails for some reason
stdin, stdout, stderr = client.exec_command(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks nice, could you add a small docstring to explain the approach taken (I guess "ver" will not work in powershell terminal, only cmd so that it checked after "ver")?

"powershell -Command echo 'powershell'"
)
output = stdout.read().decode().strip().lower()
if "powershell" in output:
return True

return False
except Exception:
return False


def is_windows_user_admin(client: paramiko.SSHClient) -> bool:
stdin, stdout, stderr = client.exec_command("whoami /groups")
output = stdout.read().decode()
if "BUILTIN\\Administrators" in output:
return True
return False


def add_public_key_to_central_authorized_keys(
cfg: Configs, password: str, log=True
) -> None:
Expand All @@ -61,15 +96,39 @@ def add_public_key_to_central_authorized_keys(
connect_client_with_logging(client, cfg, password=password)
else:
connect_client_core(client, cfg, password=password)
if is_windows(client):
if is_windows_user_admin(client):
client.exec_command("mkdir C:\\ProgramData\\ssh")
client.exec_command(
# double >> for concatenate
f"echo {key.get_name()} {key.get_base64()}"
f">> C:\\ProgramData\\ssh\\administrators_authorized_keys"
)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it suggested to add commands to change file permissions for admin users?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point @cs7-shrey, I think it makes sense to assume file permissions are already granted, as you say. If it turns out that its causing problems in testing, we can add this later if required.

else:
client.exec_command("mkdir %USERPROFILE%\\.ssh")
client.exec_command(
# double >> for concatenate
f"echo {key.get_name()} {key.get_base64()}"
f">> %USERPROFILE%\\.ssh\\authorized_keys"
)
# setup permissions for .ssh directory
client.exec_command(
"icacls %USERPROFILE%\\.ssh /inheritance:r /grant %USERNAME%:(OI)(CI)F"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a couple of comments here explaining what these steps do?

)
# setup permissions for authorized_keys file
client.exec_command(
"icacls %USERPROFILE%\\.ssh\\authorized_keys /inheritance:r /grant %USERNAME%:(F)"
)

client.exec_command("mkdir -p ~/.ssh/")
client.exec_command(
# double >> for concatenate
f'echo "{key.get_name()} {key.get_base64()}" '
f">> ~/.ssh/authorized_keys"
)
client.exec_command("chmod 644 ~/.ssh/authorized_keys")
client.exec_command("chmod 700 ~/.ssh/")
else:
client.exec_command("mkdir -p ~/.ssh")
client.exec_command(
# double >> for concatenate
f'echo "{key.get_name()} {key.get_base64()}" '
f">> ~/.ssh/authorized_keys"
)
client.exec_command("chmod 644 ~/.ssh/authorized_keys")
client.exec_command("chmod 700 ~/.ssh/")


def generate_and_write_ssh_key(ssh_key_path: Path) -> None:
Expand Down