Skip to content

Commit 3f9e956

Browse files
Fix private key handling logic (#690)
This PR removes the feature allowing users to upload a private key in the process of SSH setup due to security concerns.
1 parent 9764e37 commit 3f9e956

2 files changed

Lines changed: 4 additions & 88 deletions

File tree

aiidalab_widgets_base/computational_resources.py

Lines changed: 4 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
import ipywidgets as ipw
1313
import jinja2
1414
import pexpect
15-
import shortuuid
1615
import traitlets as tl
1716
from aiida import common, orm, plugins
1817
from aiida.orm.utils.builders.computer import ComputerBuilder
@@ -337,17 +336,10 @@ def __init__(self, ssh_folder: Path | None = None, **kwargs):
337336
style=STYLE,
338337
)
339338

340-
self._inp_private_key = ipw.FileUpload(
341-
accept="",
342-
layout=LAYOUT,
343-
description="Private key",
344-
multiple=False,
345-
)
346339
self._verification_mode = ipw.Dropdown(
347340
options=[
348341
("Provide password to remote machine", "password"),
349342
("Manually deploy a public key", "public_key"),
350-
("Upload a custom private key", "private_key"),
351343
("Use multiple factor authentication", "mfa"),
352344
],
353345
layout=LAYOUT,
@@ -379,7 +371,6 @@ def __init__(self, ssh_folder: Path | None = None, **kwargs):
379371
<ul>
380372
<li><b>Provide password to remote machine</b>: We use your password to deploy a generated public key to the remote machine (<b>recommended</b>)</li>
381373
<li><b>Manually deploy a public key</b>: Manually copy a generated public key over to the remote machine</li>
382-
<li><b>Upload a custom private key</b>: Upload a custom private key file</li>
383374
<li><b>Use multiple factor authentication</b>: To be used in tandem with the MFA app (beta)</li>
384375
</ul>
385376
""",
@@ -454,7 +445,7 @@ def _is_in_config(self):
454445
return False
455446
return True
456447

457-
def _write_ssh_config(self, private_key_abs_fname=None):
448+
def _write_ssh_config(self):
458449
"""Put host information into the config file."""
459450
config_path = self._ssh_folder / "config"
460451

@@ -474,8 +465,6 @@ def _write_ssh_config(self, private_key_abs_fname=None):
474465
file.write(
475466
f" ProxyCommand {self.proxy_command.value.format(username=self.username.value)}\n"
476467
)
477-
if private_key_abs_fname:
478-
file.write(f" IdentityFile {private_key_abs_fname}\n")
479468
file.write(" ServerAliveInterval 5\n")
480469

481470
def key_pair_prepare(self):
@@ -511,39 +500,18 @@ def _on_setup_ssh_button_pressed(self, _=None):
511500

512501
self.thread_ssh_copy_id()
513502

514-
# For not password ssh auth (such as using private_key or 2FA), key pair is not needed (2FA)
503+
# For not password ssh auth (e.g., 2FA), key pair is not needed (2FA)
515504
# or the key pair is ready.
516505
# There are other mechanism to set up the ssh connection.
517506
# But we still need to write the ssh config to the ssh config file for such as
518507
# proxy jump.
519508

520-
private_key_fname = None
521-
if self._verification_mode.value == "private_key":
522-
# Write private key in ~/.ssh/ and use the name of upload file,
523-
# if exist, generate random string and append to filename then override current name.
524-
525-
# unwrap private key file and setting temporary private_key content
526-
private_key_fname, private_key_content = self._private_key
527-
if private_key_fname is None: # check private key file
528-
message = "Please upload your private key file."
529-
self.message = wrap_message(message, MessageLevel.ERROR)
530-
return
531-
532-
filename = Path(private_key_fname).name
533-
534-
# if the private key filename is exist, generate random string and append to filename subfix
535-
# then override current name.
536-
if filename in [str(p.name) for p in Path(self._ssh_folder).iterdir()]:
537-
private_key_fpath = self._ssh_folder / f"{filename}-{shortuuid.uuid()}"
538-
539-
self._add_private_key(private_key_fpath, private_key_content)
540-
541509
# TODO(danielhollas): I am not sure this is correct. What if the user wants
542-
# to overwrite the private key? Or any other config? The configuration would never be written.
510+
# to overwrite a config entry? The configuration would never be written.
543511
# And the user is not notified that we did not write anything.
544512
# https://github.com/aiidalab/aiidalab-widgets-base/issues/516
545513
if not self._is_in_config():
546-
self._write_ssh_config(private_key_abs_fname=private_key_fname)
514+
self._write_ssh_config()
547515

548516
def _ssh_copy_id(self):
549517
"""Run the ssh-copy-id command and follow it until it is completed."""
@@ -647,20 +615,6 @@ def _on_verification_mode_change(self, change):
647615
clear_output()
648616
if self._verification_mode.value == "password":
649617
self.password_box.layout.display = "block"
650-
elif self._verification_mode.value == "private_key":
651-
display(self._inp_private_key)
652-
self.password_box.layout.display = "none"
653-
display(
654-
ipw.HTML(
655-
value="""
656-
<div class="alert alert-warning">
657-
<strong>Warning!</strong> The use of private keys is generally <b>not recommended</b>.
658-
However, some HPC centers do use this method.
659-
Please make sure you know what you are doing.
660-
</div>
661-
"""
662-
)
663-
)
664618
elif self._verification_mode.value == "public_key":
665619
self.password_box.layout.display = "none"
666620
public_key = self._ssh_folder / "id_rsa.pub"
@@ -674,23 +628,6 @@ def _on_verification_mode_change(self, change):
674628
else:
675629
self.password_box.layout.display = "none"
676630

677-
@property
678-
def _private_key(self) -> tuple[str | None, bytes | None]:
679-
"""Unwrap private key file and setting filename and file content."""
680-
if self._inp_private_key.value:
681-
(fname, _value), *_ = self._inp_private_key.value.items()
682-
content = copy.copy(_value["content"])
683-
self._inp_private_key.value.clear()
684-
self._inp_private_key._counter = 0 # pylint: disable=protected-access
685-
return fname, content
686-
return None, None
687-
688-
@staticmethod
689-
def _add_private_key(private_key_fpath: Path, private_key_content: bytes):
690-
"""Write private key to the private key file in the ssh folder."""
691-
private_key_fpath.write_bytes(private_key_content)
692-
private_key_fpath.chmod(0o600)
693-
694631
def _reset(self):
695632
self.hostname.value = ""
696633
self.port.value = 22

tests/test_computational_resources.py

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -53,29 +53,8 @@ def test_ssh_computer_setup_widget(monkeypatch, tmp_path):
5353
# Check that ssh-keygen operation is successful.
5454
widget._ssh_keygen()
5555

56-
# Create non-default private key file.
57-
private_key_path = tmp_path / ".ssh" / "my_key_name"
58-
widget._add_private_key(private_key_path, b"my_key_content")
59-
assert private_key_path.exists()
60-
with open(private_key_path) as f:
61-
assert f.read() == "my_key_content"
62-
63-
# set private key with same name to trigger the rename operation
64-
widget._verification_mode.value = "private_key"
65-
# mock _private_key to mimic the upload of the private key
66-
monkeypatch.setattr(
67-
"aiidalab_widgets_base.computational_resources.SshComputerSetup._private_key",
68-
property(lambda _: ("my_key_name", b"my_key_content_new")),
69-
)
70-
# check the private key is renamed, monkeypatch the shortuuid to make the test deterministic
71-
monkeypatch.setattr("shortuuid.uuid", lambda: "00001111")
72-
7356
widget._on_setup_ssh_button_pressed()
7457

75-
assert "my_key_name-00001111" in [
76-
str(p.name) for p in Path(tmp_path / ".ssh").iterdir()
77-
]
78-
7958
# Setting the ssh_config to an empty dictionary should reset the widget.
8059
widget.ssh_config = {}
8160
assert widget.hostname.value == ""

0 commit comments

Comments
 (0)