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

Draft: Gl 1455 user mapping #2009

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
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
36 changes: 31 additions & 5 deletions autosubmit/platforms/paramiko_platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,35 @@ def interactive_auth_handler(self, title, instructions, prompt_list):
# pass
return tuple(answers)

def map_user_config_file(self, as_conf) -> None:
"""
Maps the shared account user ssh config file to the current user config file.
Defaults to ~/.ssh/config if the mapped file does not exist.
Defaults to ~/.ssh/config_%AS_ENV_CURRENT_USER% if %AS_ENV_SSH_CONFIG_PATH% is not defined.
param as_conf: Autosubmit configuration
return: None
"""
self._user_config_file = os.path.expanduser("~/.ssh/config")
if not as_conf.is_current_real_user_owner: # Using shared account
if 'AS_ENV_SSH_CONFIG_PATH' not in self.config: # if not defined in the ENV variables, use the default + current user
mapped_config_file = os.path.expanduser(f"~/.ssh/config_{self.config['AS_ENV_CURRENT_USER']}")
else:
mapped_config_file = self.config['AS_ENV_SSH_CONFIG_PATH']
if mapped_config_file.startswith("~"):
mapped_config_file = os.path.expanduser(mapped_config_file)
if not Path(mapped_config_file).exists():
Log.debug(f"{mapped_config_file} not found")
else:
Log.info(f"Using {mapped_config_file} as ssh config file")
self._user_config_file = mapped_config_file

if Path(self._user_config_file).exists():
Log.info(f"Using {self._user_config_file} as ssh config file")
with open(self._user_config_file) as f:
self._ssh_config.parse(f)
else:
Log.warning(f"SSH config file {self._user_config_file} not found")

def connect(self, as_conf, reconnect=False):
"""
Creates ssh connection to host
Expand All @@ -242,10 +271,7 @@ def connect(self, as_conf, reconnect=False):
self._ssh = paramiko.SSHClient()
self._ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
self._ssh_config = paramiko.SSHConfig()
self._user_config_file = os.path.expanduser("~/.ssh/config")
if os.path.exists(self._user_config_file):
with open(self._user_config_file) as f:
self._ssh_config.parse(f)
self.map_user_config_file(as_conf)
self._host_config = self._ssh_config.lookup(self.host)
if "," in self._host_config['hostname']:
if reconnect:
Expand All @@ -255,7 +281,7 @@ def connect(self, as_conf, reconnect=False):
self._host_config['hostname'] = self._host_config['hostname'].split(',')[0]
if 'identityfile' in self._host_config:
self._host_config_id = self._host_config['identityfile']
port = int(self._host_config.get('port',22))
port = int(self._host_config.get('port', 22) )
if not self.two_factor_auth:
# Agent Auth
if not self.agent_auth(port):
Expand Down
4 changes: 1 addition & 3 deletions docs/source/userguide/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ User Guide
/userguide/expids
/userguide/provenance
/userguide/traceability
/userguide/user_mapping

Command list
============
Expand Down Expand Up @@ -68,6 +69,3 @@ TODO add ``workflow_validation``.
* :ref:`archive`

* :ref:`advanced_features`



123 changes: 123 additions & 0 deletions docs/source/userguide/user_mapping.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
############
User Mapping
############

About
-----

For Autosubmit, user mapping means associating selected personal user accounts with a shared account.

The personal user account is used to access each remote platform, while the shared account is used to run the experiments on the machine where Autosubmit is deployed.

When to use
-------------

When to use: When you want to run a set of shared experiments using different HPC users.

More specifically, this can be useful for launching something like an experiment testing suite on a shared machine without having to create redundant experiments for each user who wants to run the tests.

Prerequisites
--------------

* The sysadmin of the machine where Autosubmit is deployed must have created a shared user account that will be used to run the experiments.

* The sysadmin is responsible for securing the remote keys used so that the personal user accounts are not compromised.

* The user is responsible for keeping their personal user account details (e.g., SSH keys) secure, including not sharing them with others.

* Someone has to create the ``platform_${SUDO_USER}.yml`` file for each user with access to the shared account.

* Someone has to create the ``ssh_config_${SUDO_USER}`` file for each user with access to the shared account.

How it works
--------------

The idea is to map two different things depending on the user logged in to the shared account to ensure the correct Autosubmit behavior.

* Platform.yml file that contains the personal user for each platform.

(Personal user action): The user must set the environment variable "AS_ENV_PLATFORMS_PATH" to point to the file that contains the personal platforms.yml file.

Defaults to: None

(One time, all shared experiments): Has to have this defined in the $autosubmit_data/$expid/conf

.. code-block:: yaml

...
DEFAULT:
...
CUSTOM_CONFIG:
...
POST: "%AS_ENV_PLATFORMS_PATH%"
...
...


* (OPTIONAL) ssh_config file that contains the ssh config for each platform

(Personal user action): The user must set the environment variable "AS_ENV_SSH_CONFIG_PATH" to point to a file that contains the personal ~/.ssh/config file.

Defaults to: "~/.ssh/config" or "~/.ssh/config_${SUDO_USER}" if the env variable: "AS_ENV_PLATFORMS_PATH" is set.


How to activate it with examples
----------------------------------

* (once) Generate the platform_${SUDO_USER}.yml

.. code-block:: yaml

Platforms:
Platform:
User: bscXXXXX

* (once) Generate the ssh_config_${SUDO_USER}.yml

.. code-block:: ini

Host marenostrum5
Hostname glogin1.bsc.es
User bscXXXXX
Host marenostrum5.2
Hostname glogin2.bsc.es
User bscXXXXX

1) Set the environment variable "AS_ENV_PLATFORMS_PATH".

.. code-block:: bash

export AS_ENV_PLATFORMS_PATH="~/platforms/platform_${SUDO_USER}.yml"

Tip: Add it to the shared account .bashrc file.

2) Set the environment variable "AS_ENV_SSH_CONFIG_PATH" (OPTIONAL).

.. code-block:: bash

export AS_ENV_SSH_CONFIG_PATH="~/ssh/config_${SUDO_USER}.yml"

Tip: Add it to the shared account .bashrc file.

3) Ensure that the experiments have set the %CUSTOM_CONFIG.POST% to the "AS_ENV_PLATFORMS_PATH" variable.

.. code-block:: bash

cat $autosubmit_data/$expid/conf/minimal.yml

.. code-block:: yaml

...
DEFAULT:
...
CUSTOM_CONFIG:
...
POST: "%AS_ENV_PLATFORMS_PATH%"
...
...

4) Run the experiments.

.. code-block:: bash

autosubmit run $expid
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
'py3dotplus==1.1.0',
'numpy<2',
'rocrate==0.*',
'autosubmitconfigparser==1.0.73',
'autosubmitconfigparser==1.0.75',
'configparser',
'setproctitle',
'invoke>=2.0',
Expand Down
60 changes: 60 additions & 0 deletions test/unit/test_paramiko_platform_pytest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import pytest
from autosubmit.platforms.paramiko_platform import ParamikoPlatform
import os
import autosubmitconfigparser.config.configcommon

def add_ssh_config_file(tmpdir, user, content):
if not tmpdir.join(".ssh").exists():
tmpdir.mkdir(".ssh")
if user:
ssh_config_file = tmpdir.join(f".ssh/config_{user}")
else:
ssh_config_file = tmpdir.join(".ssh/config")
ssh_config_file.write(content)


@pytest.fixture(scope="function")
def generate_all_files(tmpdir):
ssh_content = """
Host mn5-gpp
User %change%
HostName glogin1.bsc.es
ForwardAgent yes
"""
for user in [os.environ["USER"], "dummy-one"]:
ssh_content_user = ssh_content.replace("%change%", user)
add_ssh_config_file(tmpdir, user, ssh_content_user)
return tmpdir


@pytest.mark.parametrize("user, env_ssh_config_defined",
[(os.environ["USER"], False),
("dummy-one", True),
("dummy-one", False),
("not-exists", True),
("not_exists", False)],
ids=["OWNER",
"SUDO USER(exists) + AS_ENV_CONFIG_SSH_PATH(defined)",
"SUDO USER(exists) + AS_ENV_CONFIG_SSH_PATH(not defined)",
"SUDO USER(not exists) + AS_ENV_CONFIG_SSH_PATH(defined)",
"SUDO USER(not exists) + AS_ENV_CONFIG_SSH_PATH(not defined)"])
def test_map_user_config_file(tmpdir, autosubmit_config, mocker, generate_all_files, user, env_ssh_config_defined):
experiment_data = {
"ROOTDIR": str(tmpdir),
"PROJDIR": str(tmpdir),
"LOCAL_TMP_DIR": str(tmpdir),
"LOCAL_ROOT_DIR": str(tmpdir),
"AS_ENV_CURRENT_USER": user,
}
if env_ssh_config_defined:
experiment_data["AS_ENV_SSH_CONFIG_PATH"] = str(tmpdir.join(f".ssh/config_{user}"))
as_conf = autosubmit_config(expid='a000', experiment_data=experiment_data)
mocker.patch('autosubmitconfigparser.config.configcommon.AutosubmitConfig.is_current_real_user_owner', os.environ["USER"] == user)
platform = ParamikoPlatform(expid='a000', name='ps', config=experiment_data)
platform._ssh_config = mocker.MagicMock()
mocker.patch('os.path.expanduser', side_effect=lambda x: x) # Easier to test, and also not mess with the real user's config
platform.map_user_config_file(as_conf)
if not env_ssh_config_defined or not tmpdir.join(f".ssh/config_{user}").exists():
assert platform._user_config_file == "~/.ssh/config"
else:
assert platform._user_config_file == str(tmpdir.join(f".ssh/config_{user}"))