Skip to content

Commit

Permalink
SSH hook: added OCI annotations to manage daemon pidfiles
Browse files Browse the repository at this point in the history
  • Loading branch information
michele-brambilla committed Dec 5, 2023
1 parent 089b328 commit 7013621
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 27 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.

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.pidfile_container` OCI annotation, which allows to customize the path to the Dropbear daemon PIDfile inside the container.
- SSH Hook: added support for the `com.hooks.ssh.pidfile_host` OCI annotation, which optionally copies the PIDfile of the Dropbear server to the specified path on the host.

## [1.6.1]

### Added
Expand Down
21 changes: 19 additions & 2 deletions doc/config/ssh-hook.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,18 @@ 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 tipically set to a value different than 22 in order to avoid clashes with an SSH
* ``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.

By default, the hook attempts to join the mount and PID namespaces of the container.
This behavior can be modified using the following optional environment variable:

* ``JOIN_NAMESPACES``: When `FALSE` (case insensitive), the hook does not actively join the mount and PID
namespaces of the container. This is useful when the hook is executed already inside the appropriate
namespaces, or when the hook does not have the privileges to join said namespaces.


The following is an example of `OCI hook JSON configuration file
<https://github.com/containers/common/blob/main/pkg/hooks/docs/oci-hooks.5.md>`_
enabling the SSH hook:
Expand All @@ -59,9 +67,18 @@ used by containers.
The ``com.hooks.ssh.enabled=true`` annotation that enables the hook is automatically
generated by Sarus if the ``--ssh`` command line option is passed to :program:`sarus run`.

The ``com.hooks.ssh.pidfile_container`` annotation allows the user to customize the path to the
Dropbear daemon PIDfile inside the container (the default path is ``/opt/oci-hooks/ssh/dropbear/dropbear.pid``).

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.

.. 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.
Thus, the hook currently requires the use of a :ref:`private PID namespace <user-private-pid>`
for the container. Thus, the ``--ssh`` option of :program:`sarus run` implies
``--pid=private``, and is incompatible with the use of ``--pid=host``.
If the hook is executed without a separate PID namespace (i.e. in the PID namespace of the host),
when the container is stopped the Dropbear daemon will be still alive and the user is responsible
for terminating it.
15 changes: 11 additions & 4 deletions doc/user/user_guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1266,12 +1266,19 @@ Notice that the annotation value must be a public key file, not the public key
itself. The annotation allows remote access via SSH to the running container
through user-specified (and potentially ephemeral) keys.
The ``com.hooks.ssh.pidfile_container`` annotation allows the user to define the
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.
.. warning::
The SSH hook currently does not implement a poststop functionality and
requires the use of a private PID namespace to cleanup the Dropbear daemon.
Thus, the hook currently requires the use of a :ref:`private PID namespace <user-private-pid>`
for the container. Thus, the ``--ssh`` option of :program:`sarus run` implies
``--pid=private``, and is incompatible with the use of ``--pid=host``.
requires the use of a :ref:`private PID namespace <user-private-pid>` for
the container in order to cleanup the Dropbear daemon.
Thus, the ``--ssh`` option of :program:`sarus run` implies ``--pid=private``,
and is incompatible with the use of ``--pid=host``.
OpenMPI communication through SSH
---------------------------------
Expand Down
2 changes: 1 addition & 1 deletion src/common/Utility.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ int forkExecWait(const common::CLIArguments& args,
SARUS_THROW_ERROR(message.str());
}

logMessage( boost::format("%s exited with status %d") % args % WEXITSTATUS(status),
logMessage( boost::format("%s (pid %d) exited with status %d") % args % pid % WEXITSTATUS(status),
common::LogLevel::DEBUG);

return WEXITSTATUS(status);
Expand Down
45 changes: 39 additions & 6 deletions src/hooks/ssh/SshHook.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@

#include "SshHook.hpp"

#include <chrono>
#include <thread>
#include <fstream>
#include <cstdlib>
#include <tuple>
#include <sstream>
#include <boost/algorithm/string.hpp>
#include <boost/format.hpp>
#include <boost/regex.hpp>

Expand Down Expand Up @@ -78,12 +81,18 @@ void SshHook::checkUserHasSshKeys() {
void SshHook::startSshDaemon() {
log("Activating SSH in container", sarus::common::LogLevel::INFO);

dropbearRelativeDirInContainer = boost::filesystem::path("/opt/oci-hooks/dropbear");
dropbearRelativeDirInContainer = boost::filesystem::path("/opt/oci-hooks/ssh/dropbear");
dropbearDirInHost = sarus::common::getEnvironmentVariable("DROPBEAR_DIR");
serverPort = std::stoi(sarus::common::getEnvironmentVariable("SERVER_PORT"));
try {
auto envJoinNamespaces = sarus::common::getEnvironmentVariable("JOIN_NAMESPACES");
joinNamespaces = (boost::algorithm::to_upper_copy(envJoinNamespaces) == std::string("TRUE"));
} catch (sarus::common::Error&) {}
std::tie(bundleDir, pidOfContainer) = hooks::common::utility::parseStateOfContainerFromStdin();
hooks::common::utility::enterMountNamespaceOfProcess(pidOfContainer);
hooks::common::utility::enterPidNamespaceOfProcess(pidOfContainer);
if (joinNamespaces) {
hooks::common::utility::enterMountNamespaceOfProcess(pidOfContainer);
hooks::common::utility::enterPidNamespaceOfProcess(pidOfContainer);
}
parseConfigJSONOfBundle();
username = getUsername(uidOfUser);
sshKeysDirInHost = getSshKeysDirInHost(username);
Expand Down Expand Up @@ -126,6 +135,12 @@ void SshHook::parseConfigJSONOfBundle() {
if(json["annotations"].HasMember("com.hooks.ssh.authorize_ssh_key")) {
userPublicKeyFilename = boost::filesystem::path(json["annotations"]["com.hooks.ssh.authorize_ssh_key"].GetString());
}
if(json["annotations"].HasMember("com.hooks.ssh.pidfile_container")) {
pidfileContainer = boost::filesystem::path(json["annotations"]["com.hooks.ssh.pidfile_container"].GetString());
}
if(json["annotations"].HasMember("com.hooks.ssh.pidfile_host")) {
pidfileHost = boost::filesystem::path(json["annotations"]["com.hooks.ssh.pidfile_host"].GetString());
}
}

log("Successfully parsed bundle's config.json", sarus::common::LogLevel::INFO);
Expand Down Expand Up @@ -196,7 +211,7 @@ void SshHook::generateAuthorizedKeys(const boost::filesystem::path& userKeyFile,
// output user's public key
auto command = boost::format{"%s/bin/dropbearkey -y -f %s"}
% dropbearDirInHost.string()
% (sshKeysDirInHost / "id_dropbear").string();
% userKeyFile.string();
auto output = sarus::common::executeCommand(command.str());

// extract public key
Expand All @@ -208,7 +223,7 @@ void SshHook::generateAuthorizedKeys(const boost::filesystem::path& userKeyFile,
// write public key to "authorized_keys" file
while(std::getline(ss, line)) {
if(boost::regex_match(line, matches, re)) {
auto ofs = std::ofstream{ (sshKeysDirInHost / "authorized_keys").c_str() };
auto ofs = std::ofstream{ authorizedKeysFile.c_str() };
ofs << matches[1] << std::endl;
ofs.close();
log("Successfully generated \"authorized_keys\" file", sarus::common::LogLevel::INFO);
Expand Down Expand Up @@ -381,11 +396,16 @@ void SshHook::startSshDaemonInContainer() const {

auto sshKeysPathWithinContainer = "/" / boost::filesystem::relative(sshKeysDirInContainer, rootfsDir);

auto pidfileContainerReal = sarus::common::realpathWithinRootfs(rootfsDir, pidfileContainer);
auto pidfileContainerFull = rootfsDir / pidfileContainerReal;
sarus::common::createFoldersIfNecessary(pidfileContainerFull.parent_path(), uidOfUser, gidOfUser);

auto dropbearCommand = sarus::common::CLIArguments{
dropbearRelativeDirInContainer.string() + "/bin/dropbear",
"-E",
"-r", sshKeysPathWithinContainer.string() + "/dropbear_ecdsa_host_key",
"-p", std::to_string(serverPort)
"-p", std::to_string(serverPort),
"-P", pidfileContainerReal.string()
};
auto status = sarus::common::forkExecWait(dropbearCommand, preExecActions);
if(status != 0) {
Expand All @@ -394,6 +414,19 @@ void SshHook::startSshDaemonInContainer() const {
SARUS_THROW_ERROR(message.str());
}

if (!pidfileHost.empty()) {
// Wait a little to ensure Dropbear has created its pidfile
std::this_thread::sleep_for(std::chrono::milliseconds(50));
if (boost::filesystem::is_regular_file(pidfileContainerFull)) {
sarus::common::copyFile(pidfileContainerFull, pidfileHost, uidOfUser, gidOfUser);
}
else {
auto message = boost::format("Failed to copy Dropbear pidfile to host path (%s): container pidfile (%s) not found")
% pidfileHost % pidfileContainerReal;
log(message, sarus::common::LogLevel::WARN);
}
}

log("Successfully started SSH daemon in container", sarus::common::LogLevel::INFO);
}

Expand Down
3 changes: 3 additions & 0 deletions src/hooks/ssh/SshHook.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,10 @@ class SshHook {

private:
bool isHookEnabled = false;
bool joinNamespaces = true;
std::string username;
boost::filesystem::path pidfileHost;
boost::filesystem::path pidfileContainer = "/var/run/dropbear/dropbear.pid";
boost::filesystem::path sshKeysDirInHost;
boost::filesystem::path sshKeysDirInContainer;
boost::filesystem::path dropbearDirInHost;
Expand Down
Loading

0 comments on commit 7013621

Please sign in to comment.