Repository: hydroshare/hydroshare

Issue:
Consider a public key login for the iRODS userzone management scripts
**Describe the feature you'd like and what it will do**
A clear and concise description of what you want to happen.

Currently, HS uses the following two key pairs in hydroshare/localsettings.py:

```
LINUX_ADMIN_USER_FOR_HS_USER_ZONE
LINUX_ADMIN_USER_PWD_FOR_HS_USER_ZONE
```

HS shells into the userzone ICAT with this user/pwd to run the` hydroshare/irods/create_user.sh` and `hydroshare/irods/delete_user.sh` scripts to create and delete iRODS users in the userzone.

Consider changing this to a public key login.

**Why is this feature important?**
A short description of the importance of this feature and what it will help you achieve.

In GCP, password authentication is disabled by default. This was changed in the GCP userzone to allow a password login so HS would continue to work. However, this is not really desirable.

**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...].

It is not ideal to sed `/etc/ssh/sshd_config` in the userzone to allow password logins.

**Additional context**
Add any other context or screenshots about the feature request here.



Discussion:
I need to move this up in the queue as the number of failed login attempts on GCP due to this are very very high.
https://docs.paramiko.org/en/3.4/api/keys.html

Resolving patch:
diff --git a/hs_core/views/utils.py b/hs_core/views/utils.py
--- a/hs_core/views/utils.py
+++ b/hs_core/views/utils.py
@@ -284,7 +284,7 @@ def edit_reference_url_in_resource(user, res, new_ref_url, curr_path, url_filena
     return status.HTTP_200_OK, 'success'
 
 
-def run_ssh_command(host, uname, pwd, exec_cmd):
+def run_ssh_command(host, uname, exec_cmd, pwd=None, private_key_file=None):
     """
     run ssh client to ssh to a remote host and run a command on the remote host
     Args:
@@ -297,9 +297,16 @@ def run_ssh_command(host, uname, pwd, exec_cmd):
         None, but raises SSHException from paramiko if there is any error during ssh
         connection and command execution
     """
+    if not private_key_file and not pwd:
+        raise ValueError("Either Password or .pem is required for ssh connection")
     ssh = paramiko.SSHClient()
     ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
-    ssh.connect(host, username=uname, password=pwd)
+    if private_key_file:
+        key = paramiko.Ed25519Key.from_private_key_file(private_key_file)
+        ssh.connect(host, username=uname, pkey=key)
+    else:
+        ssh.connect(host, username=uname, password=pwd)
+
     transport = ssh.get_transport()
     session = transport.open_session()
     session.set_combine_stderr(True)
@@ -319,8 +326,15 @@ def run_ssh_command(host, uname, pwd, exec_cmd):
 # when private netCDF resources are made public so that all links of data services
 # provided by Hyrax service are instantaneously available on demand
 def run_script_to_update_hyrax_input_files(shortkey):
+    pwd = None
+    pk = None
+    if hasattr(settings, 'LINUX_ADMIN_USER_PWD_FOR_HS_USER_ZONE'):
+        pwd = settings.LINUX_ADMIN_USER_PWD_FOR_HS_USER_ZONE
+    if hasattr(settings, 'PRIVATE_KEY_FILE_FOR_HS_USER_ZONE'):
+        pk = settings.PRIVATE_KEY_FILE_FOR_HS_USER_ZONE
     run_ssh_command(host=settings.HYRAX_SSH_HOST, uname=settings.HYRAX_SSH_PROXY_USER,
-                    pwd=settings.HYRAX_SSH_PROXY_USER_PWD,
+                    pwd=pwd,
+                    private_key_file=pk,
                     exec_cmd=settings.HYRAX_SCRIPT_RUN_COMMAND + ' ' + shortkey)
 
 
diff --git a/theme/views.py b/theme/views.py
--- a/theme/views.py
+++ b/theme/views.py
@@ -729,10 +729,17 @@ def delete_irods_account(request):
             exec_cmd = "{0} {1}".format(
                 settings.LINUX_ADMIN_USER_DELETE_USER_IN_USER_ZONE_CMD, user.username
             )
+            pwd = None
+            pk = None
+            if hasattr(settings, 'LINUX_ADMIN_USER_PWD_FOR_HS_USER_ZONE'):
+                pwd = settings.LINUX_ADMIN_USER_PWD_FOR_HS_USER_ZONE
+            if hasattr(settings, 'PRIVATE_KEY_FILE_FOR_HS_USER_ZONE'):
+                pk = settings.PRIVATE_KEY_FILE_FOR_HS_USER_ZONE
             output = run_ssh_command(
                 host=settings.HS_USER_ZONE_HOST,
                 uname=settings.LINUX_ADMIN_USER_FOR_HS_USER_ZONE,
-                pwd=settings.LINUX_ADMIN_USER_PWD_FOR_HS_USER_ZONE,
+                pwd=pwd,
+                private_key_file=pk,
                 exec_cmd=exec_cmd,
             )
             for out_str in output:
@@ -781,10 +788,17 @@ def create_irods_account(request):
                 user.username,
                 pwd,
             )
+            pwd = None
+            pk = None
+            if hasattr(settings, 'LINUX_ADMIN_USER_PWD_FOR_HS_USER_ZONE'):
+                pwd = settings.LINUX_ADMIN_USER_PWD_FOR_HS_USER_ZONE
+            if hasattr(settings, 'PRIVATE_KEY_FILE_FOR_HS_USER_ZONE'):
+                pk = settings.PRIVATE_KEY_FILE_FOR_HS_USER_ZONE
             output = run_ssh_command(
                 host=settings.HS_USER_ZONE_HOST,
                 uname=settings.LINUX_ADMIN_USER_FOR_HS_USER_ZONE,
-                pwd=settings.LINUX_ADMIN_USER_PWD_FOR_HS_USER_ZONE,
+                pwd=pwd,
+                private_key_file=pk,
                 exec_cmd=exec_cmd,
             )
             for out_str in output:
@@ -797,8 +811,8 @@ def create_irods_account(request):
                     return JsonResponse(
                         {
                             "error": "iRODS server failed to create this iRODS account {0}. "
-                            "If this issue persists, please notify help@cuahsi.org.".format(
-                                user.username
+                            "If this issue persists, please notify help@cuahsi.org. More details: {1}".format(
+                                user.username, out_str
                             )
                         },
                         status=status.HTTP_500_INTERNAL_SERVER_ERROR,


Test patch:
diff --git a/hs_core/testing.py b/hs_core/testing.py
--- a/hs_core/testing.py
+++ b/hs_core/testing.py
@@ -63,9 +63,16 @@ def create_irods_user_in_user_zone(self):
         try:
             exec_cmd = "{0} {1} {2}".format(settings.LINUX_ADMIN_USER_CREATE_USER_IN_USER_ZONE_CMD,
                                             self.user.username, self.user.username)
+            pwd = None
+            pk = None
+            if hasattr(settings, 'LINUX_ADMIN_USER_PWD_FOR_HS_USER_ZONE'):
+                pwd = settings.LINUX_ADMIN_USER_PWD_FOR_HS_USER_ZONE
+            if hasattr(settings, 'PRIVATE_KEY_FILE_FOR_HS_USER_ZONE'):
+                pk = settings.PRIVATE_KEY_FILE_FOR_HS_USER_ZONE
             output = run_ssh_command(host=settings.HS_USER_ZONE_HOST,
                                      uname=settings.LINUX_ADMIN_USER_FOR_HS_USER_ZONE,
-                                     pwd=settings.LINUX_ADMIN_USER_PWD_FOR_HS_USER_ZONE,
+                                     pwd=pwd,
+                                     private_key_file=pk,
                                      exec_cmd=exec_cmd)
             for out_str in output:
                 if 'ERROR:' in out_str.upper():
@@ -83,9 +90,16 @@ def delete_irods_user_in_user_zone(self):
         try:
             exec_cmd = "{0} {1}".format(settings.LINUX_ADMIN_USER_DELETE_USER_IN_USER_ZONE_CMD,
                                         self.user.username)
+            pwd = None
+            pk = None
+            if hasattr(settings, 'LINUX_ADMIN_USER_PWD_FOR_HS_USER_ZONE'):
+                pwd = settings.LINUX_ADMIN_USER_PWD_FOR_HS_USER_ZONE
+            if hasattr(settings, 'PRIVATE_KEY_FILE_FOR_HS_USER_ZONE'):
+                pk = settings.PRIVATE_KEY_FILE_FOR_HS_USER_ZONE
             output = run_ssh_command(host=settings.HS_USER_ZONE_HOST,
                                      uname=settings.LINUX_ADMIN_USER_FOR_HS_USER_ZONE,
-                                     pwd=settings.LINUX_ADMIN_USER_PWD_FOR_HS_USER_ZONE,
+                                     pwd=pwd,
+                                     private_key_file=pk,
                                      exec_cmd=exec_cmd)
             if output:
                 for out_str in output: