Skip to content

Commit

Permalink
SSH Hook: Added support for setting custom server port through OCI an…
Browse files Browse the repository at this point in the history
…notation
  • Loading branch information
Madeeks committed Feb 26, 2024
1 parent 69da301 commit 3dd22d3
Show file tree
Hide file tree
Showing 7 changed files with 153 additions and 45 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## [Unreleased]

### Added

- SSH hook: added support for the `com.hooks.ssh.port` OCI annotation, which allows to customize the port used by the Dropbear server.

### Changed

- MPI hook: verbosity levels for log messages about ABI compatibility and library replacements have been slightly adjusted.
Expand All @@ -15,6 +19,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Fixed

- Glibc hook: fixed detection of the container's glibc version, which was causing a shell-init error on some systems
- SSH hook: the `SERVER_PORT` environment variable in the JSON configuration file has been renamed to `SERVER_PORT_DEFAULT`


## [1.6.3]
Expand Down
2 changes: 1 addition & 1 deletion CI/src/integration_tests/test_ssh_hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def generate_hook_config():
"HOOK_BASE_DIR=/home",
"PASSWD_FILE=" + os.environ["CMAKE_INSTALL_PREFIX"] + "/etc/passwd",
"DROPBEAR_DIR=" + os.environ["CMAKE_INSTALL_PREFIX"] + "/dropbear",
"SERVER_PORT=15263"
"SERVER_PORT_DEFAULT=15263"
],
"args": [
"ssh_hook",
Expand Down
48 changes: 30 additions & 18 deletions CI/src/integration_tests_for_virtual_cluster/test_ssh_hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def _generate_ssh_hook_config(cls):
f"HOOK_BASE_DIR={cls.OCI_HOOKS_BASE_DIR}",
f"PASSWD_FILE={cls.SARUS_INSTALL_PATH}/etc/passwd",
f"DROPBEAR_DIR={cls.SARUS_INSTALL_PATH}/dropbear",
"SERVER_PORT=15263"
"SERVER_PORT_DEFAULT=15263"
],
"args": [
"ssh_hook",
Expand Down Expand Up @@ -101,26 +101,18 @@ def test_ssh_hook(self):

# check SSH from node0 to node1
hostname_node1 = get_hostname_of_node1(self.image_with_musl)
hostname_node1_through_ssh = get_hostname_of_node1_though_ssh(self.image_with_musl, hostname_node1)
self.assertEqual(hostname_node1, hostname_node1_through_ssh)
hostname_node1_through_ssh = get_hostname_of_node1_though_ssh(self.image_with_glibc, hostname_node1)
self.assertEqual(hostname_node1, hostname_node1_through_ssh)
self._check_ssh_hook(hostname_node1)

# check SSH from node0 to node1 with non-standard $HOME
# in some systems the home from the passwd file (copied from the host)
# might not be present in the container. The hook has to deduce it from
# the passwd file and create it.
with custom_home_in_sarus_passwd_file("/users/test"):
hostname_node1_through_ssh = get_hostname_of_node1_though_ssh(self.image_with_musl, hostname_node1)
self.assertEqual(hostname_node1, hostname_node1_through_ssh)
hostname_node1_through_ssh = get_hostname_of_node1_though_ssh(self.image_with_glibc, hostname_node1)
self.assertEqual(hostname_node1, hostname_node1_through_ssh)
self._check_ssh_hook(hostname_node1)

# check SSH from node0 to node1 with server port set from Sarus CLI
self._check_ssh_hook(hostname_node1, ["--annotation=com.hooks.ssh.port=33333"])

# check SSH goes into container (not host)
prettyname = get_prettyname_of_node1_through_ssh(self.image_with_musl, hostname_node1)
self.assertEqual(prettyname, "Alpine Linux v3.14")
prettyname = get_prettyname_of_node1_through_ssh(self.image_with_glibc, hostname_node1)
self.assertEqual(prettyname, "Debian GNU/Linux 10 (buster)")

def test_ssh_keys_generation(self):
ssh_dir = os.environ['HOME'] + "/.oci-hooks/ssh"
Expand All @@ -143,6 +135,26 @@ def test_ssh_keys_generation(self):
fingerprint_changed = subprocess.check_output(fingerprint_command).decode()
self.assertNotEqual(fingerprint, fingerprint_changed)

def _check_ssh_hook(self, hostname_node1, sarus_run_extra_opts=[]):
hostname_node1_through_ssh = get_hostname_of_node1_though_ssh(self.image_with_musl,
hostname_node1,
sarus_run_extra_opts)
self.assertEqual(hostname_node1, hostname_node1_through_ssh)
hostname_node1_through_ssh = get_hostname_of_node1_though_ssh(self.image_with_glibc,
hostname_node1,
sarus_run_extra_opts)
self.assertEqual(hostname_node1, hostname_node1_through_ssh)

# check SSH goes into container (not host)
prettyname = get_prettyname_of_node1_through_ssh(self.image_with_musl,
hostname_node1,
sarus_run_extra_opts)
self.assertEqual(prettyname, "Alpine Linux v3.14")
prettyname = get_prettyname_of_node1_through_ssh(self.image_with_glibc,
hostname_node1,
sarus_run_extra_opts)
self.assertEqual(prettyname, "Debian GNU/Linux 10 (buster)")


def get_hostname_of_node1(image):
command = [
Expand All @@ -158,7 +170,7 @@ def get_hostname_of_node1(image):
return out[0]


def get_hostname_of_node1_though_ssh(image, hostname_node1):
def get_hostname_of_node1_though_ssh(image, hostname_node1, sarus_run_extra_opts=[]):
command = [
"sh",
"-c",
Expand All @@ -174,12 +186,12 @@ def get_hostname_of_node1_though_ssh(image, hostname_node1):
out = util.run_command_in_container_with_slurm(image=image,
command=command,
options_of_srun_command=["-N2"],
options_of_run_command=["--ssh"])
options_of_run_command=["--ssh"]+sarus_run_extra_opts)
assert len(out) == 1 # expect one single line of output
return out[0]


def get_prettyname_of_node1_through_ssh(image, hostname_node1):
def get_prettyname_of_node1_through_ssh(image, hostname_node1, sarus_run_extra_opts=[]):
command = [
"sh",
"-c",
Expand All @@ -195,7 +207,7 @@ def get_prettyname_of_node1_through_ssh(image, hostname_node1):
out = util.run_command_in_container_with_slurm(image=image,
command=command,
options_of_srun_command=["-N2"],
options_of_run_command=["--ssh"])
options_of_run_command=["--ssh"]+sarus_run_extra_opts)
assert len(out) == 1 # expect one single line of output
return re.match(r".*PRETTY_NAME=\"([^\"]+)\" .*", out[0]).group(1)

Expand Down
11 changes: 8 additions & 3 deletions doc/config/ssh-hook.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,10 @@ environment variables must be defined:

* ``DROPBEAR_DIR``: Absolute path to the location of the custom SSH software.

* ``SERVER_PORT``: TCP port on which the SSH daemon will listen. This must be an unused
port and is typically set to a value different than 22 in order to avoid clashes with an SSH
daemon that could be running on the host.
* ``SERVER_PORT_DEFAULT``: Default TCP port on which the SSH daemon will listen. This must be an unused
port and is typically set to a value different than 22 in order to avoid clashes with an OpenSSH
daemon that could be running on the host. This value can be overridden by setting the
``com.hooks.ssh.port`` annotation for the container.

The following optional environment variables can also be defined:

Expand Down Expand Up @@ -100,6 +101,10 @@ Dropbear daemon PIDfile inside the container (the default path is ``/opt/oci-hoo
The ``com.hooks.ssh.pidfile_host`` annotation can be used to copy the PIDfile of the
Dropbear daemon to the specified path on the host.

The ``com.hooks.ssh.port`` annotation can be used to set an arbitrary port for the Dropbear server
and client, overriding the value from the ``SERVER_PORT_DEFAULT`` environment variable set in the hook
configuration file.

.. important::
The SSH hook currently does not implement a poststop functionality and
requires the use of a private PID namespace to cleanup the Dropbear daemon.
Expand Down
22 changes: 16 additions & 6 deletions doc/user/user_guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1226,12 +1226,13 @@ SSH connection within containers
--------------------------------
Sarus also comes with a hook which enables support for SSH connections within
containers.
containers, leveraging the `Dropbear SSH software <https://matt.ucc.asn.au/dropbear/dropbear.html>`_.
When Sarus is configured to use this hook, you must first run the command
``sarus ssh-keygen`` to generate the SSH keys that will be used by the SSH
daemons and the SSH clients in the containers. It is sufficient to generate
the keys just once, as they are persistent between sessions.
When Sarus is configured to use this hook, before attempting SSH connections
to/from containers, the ``sarus ssh-keygen`` command must be run in order to
generate the keys that will be used by the SSH daemons and the SSH clients
inside containers.
It is sufficient to generate the keys just once, as they are persistent between sessions.
It is then possible to execute a container passing the ``--ssh`` option to
:program:`sarus run`, e.g. ``sarus run --ssh <image> <command>``. Using the
Expand Down Expand Up @@ -1272,6 +1273,9 @@ Dropbear daemon PIDfile inside the container.
The ``com.hooks.ssh.pidfile_host`` annotation can be used to copy the PIDfile of the
Dropbear daemon in the host.
The ``com.hooks.ssh.port`` annotation can be used to set an arbitrary port for the Dropbear server
and client, overriding the value from the default hook configuration.
.. warning::
The SSH hook currently does not implement a poststop functionality and
requires the use of a :ref:`private PID namespace <user-private-pid>` for
Expand Down Expand Up @@ -1514,7 +1518,13 @@ Configure Visual Studio Code to access the remote Sarus container:
11. Select "Connect" to connect the IDE to the remote container environment.
.. important::
In order to establish connections through "Remote - SSH" extension,
the ``scp`` program must be available within the container.
This is required by Visual Studio Code to send and establish the VS Code
Server into the remote container.
For more details about "Remote - SSH" Visual Studio Code extension, you can
refer to `this tutorial <https://code.visualstudio.com/docs/remote/ssh-tutorial>`_
from the official Visual Studio Code documentation.
5 changes: 4 additions & 1 deletion src/hooks/ssh/SshHook.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ void SshHook::startSshDaemon() {

dropbearRelativeDirInContainer = boost::filesystem::path("/opt/oci-hooks/ssh/dropbear");
dropbearDirInHost = sarus::common::getEnvironmentVariable("DROPBEAR_DIR");
serverPort = std::stoi(sarus::common::getEnvironmentVariable("SERVER_PORT"));
serverPort = std::stoi(sarus::common::getEnvironmentVariable("SERVER_PORT_DEFAULT"));
try {
auto envJoinNamespaces = sarus::common::getEnvironmentVariable("JOIN_NAMESPACES");
joinNamespaces = (boost::algorithm::to_upper_copy(envJoinNamespaces) == std::string("TRUE"));
Expand Down Expand Up @@ -139,6 +139,9 @@ void SshHook::parseConfigJSONOfBundle() {
if(json["annotations"].HasMember("com.hooks.ssh.pidfile_host")) {
pidfileHost = boost::filesystem::path(json["annotations"]["com.hooks.ssh.pidfile_host"].GetString());
}
if(json["annotations"].HasMember("com.hooks.ssh.port")) {
serverPort = std::stoi(json["annotations"]["com.hooks.ssh.port"].GetString());
}
}

try {
Expand Down
Loading

0 comments on commit 3dd22d3

Please sign in to comment.