Repository: rucio/rucio

Issue:
do not skip CA check on retry
Motivation
----------
If the server can't be connected the retry will ignore the CA.

https://github.com/rucio/rucio/blob/master/lib/rucio/client/baseclient.py#L353


Discussion:

Resolving patch:
diff --git a/lib/rucio/client/baseclient.py b/lib/rucio/client/baseclient.py
--- a/lib/rucio/client/baseclient.py
+++ b/lib/rucio/client/baseclient.py
@@ -126,17 +126,17 @@ class BaseClient(object):
     def __init__(self, rucio_host=None, auth_host=None, account=None, ca_cert=None, auth_type=None, creds=None, timeout=600, user_agent='rucio-clients', vo=None):
         """
         Constructor of the BaseClient.
-        :param rucio_host: the address of the rucio server, if None it is read from the config file.
-        :param rucio_port: the port of the rucio server, if None it is read from the config file.
-        :param auth_host: the address of the rucio authentication server, if None it is read from the config file.
-        :param auth_port: the port of the rucio authentication server, if None it is read from the config file.
-        :param account: the account to authenticate to rucio.
-        :param use_ssl: enable or disable ssl for commucation. Default is enabled.
-        :param ca_cert: the path to the rucio server certificate.
-        :param auth_type: the type of authentication (e.g.: 'userpass', 'kerberos' ...)
-        :param creds: a dictionary with credentials needed for authentication.
-        :param user_agent: indicates the client
-        :param vo: the vo to authenticate into.
+        :param rucio_host: The address of the rucio server, if None it is read from the config file.
+        :param rucio_port: The port of the rucio server, if None it is read from the config file.
+        :param auth_host: The address of the rucio authentication server, if None it is read from the config file.
+        :param auth_port: The port of the rucio authentication server, if None it is read from the config file.
+        :param account: The account to authenticate to rucio.
+        :param use_ssl: Enable or disable ssl for commucation. Default is enabled.
+        :param ca_cert: The path to the rucio server certificate.
+        :param auth_type: The type of authentication (e.g.: 'userpass', 'kerberos' ...)
+        :param creds: Dictionary with credentials needed for authentication.
+        :param user_agent: Indicates the client.
+        :param vo: The VO to authenticate into.
         """
 
         self.host = rucio_host
@@ -173,7 +173,7 @@ def __init__(self, rucio_host=None, auth_host=None, account=None, ca_cert=None,
         self.auth_oidc_refresh_before_exp = config_get_int('client', 'auth_oidc_refresh_before_exp', False, 20)
 
         if auth_type is None:
-            LOG.debug('no auth_type passed. Trying to get it from the environment variable RUCIO_AUTH_TYPE and config file.')
+            LOG.debug('No auth_type passed. Trying to get it from the environment variable RUCIO_AUTH_TYPE and config file.')
             if 'RUCIO_AUTH_TYPE' in environ:
                 if environ['RUCIO_AUTH_TYPE'] not in ['userpass', 'x509', 'x509_proxy', 'gss', 'ssh', 'saml', 'oidc']:
                     raise MissingClientParameter('Possible RUCIO_AUTH_TYPE values: userpass, x509, x509_proxy, gss, ssh, saml, oidc, vs. ' + environ['RUCIO_AUTH_TYPE'])
@@ -207,7 +207,7 @@ def __init__(self, rucio_host=None, auth_host=None, account=None, ca_cert=None,
                 self.creds['oidc_polling'] = config_get_bool('client', 'oidc_polling', False, False)
 
         if not self.creds:
-            LOG.debug('no creds passed. Trying to get it from the config file.')
+            LOG.debug('No creds passed. Trying to get it from the config file.')
             self.creds = {}
             try:
                 if self.auth_type in ['userpass', 'saml']:
@@ -249,16 +249,17 @@ def __init__(self, rucio_host=None, auth_host=None, account=None, ca_cert=None,
             raise ClientProtocolNotSupported('\'%s\' not supported' % auth_scheme)
 
         if (rucio_scheme == 'https' or auth_scheme == 'https') and ca_cert is None:
-            LOG.debug('no ca_cert passed. Trying to get it from the config file.')
+            LOG.debug('HTTPS is required, but no ca_cert was passed. Trying to get it from the config file.')
             try:
                 self.ca_cert = path.expandvars(config_get('client', 'ca_cert'))
             except (NoOptionError, NoSectionError):
+                LOG.debug('No ca_cert found in configuration. Falling back to Mozilla default CA bundle (certifi).')
                 self.ca_cert = True
 
         self.list_hosts = [self.host]
 
         if account is None:
-            LOG.debug('no account passed. Trying to get it from the config file.')
+            LOG.debug('No account passed. Trying to get it from the config file.')
             try:
                 self.account = environ['RUCIO_ACCOUNT']
             except KeyError:
@@ -268,13 +269,15 @@ def __init__(self, rucio_host=None, auth_host=None, account=None, ca_cert=None,
                     raise MissingClientParameter('Option \'account\' cannot be found in config file and RUCIO_ACCOUNT is not set.')
 
         if vo is None:
-            LOG.debug('no vo passed. Trying to get it from the config file.')
+            LOG.debug('No VO passed. Trying to get it from environment variable RUCIO_VO.')
             try:
                 self.vo = environ['RUCIO_VO']
             except KeyError:
+                LOG.debug('No VO found. Trying to get it from the config file.')
                 try:
                     self.vo = config_get('client', 'vo')
                 except (NoOptionError, NoSectionError):
+                    LOG.debug('No VO found. Using default VO.')
                     self.vo = 'def'
 
         # if token file path is defined in the rucio.cfg file, use that file. Currently this prevents authenticating as another user or VO.
@@ -410,12 +413,11 @@ def __get_token_userpass(self):
                 break
             except ConnectionError as error:
                 LOG.warning('ConnectionError: ' + str(error))
-                self.ca_cert = False
                 if retry > self.request_retries:
                     raise
 
         if not result or 'result' not in locals():
-            LOG.error('cannot get auth_token')
+            LOG.error('Cannot retrieve authentication token!')
             return False
 
         if result.status_code != codes.ok:  # pylint: disable-msg=E1101
@@ -425,7 +427,6 @@ def __get_token_userpass(self):
             raise exc_cls(exc_msg)
 
         self.auth_token = result.headers['x-rucio-auth-token']
-        LOG.debug('got new token')
         return True
 
     def __refresh_token_OIDC(self):
@@ -492,7 +493,6 @@ def __refresh_token_OIDC(self):
                 break
             except RequestException:
                 LOG.warning('RequestException: %s', str(traceback.format_exc()))
-                self.ca_cert = False
                 if retry > self.request_retries:
                     raise
 
@@ -608,12 +608,11 @@ def __get_token_OIDC(self):
                 break
             except RequestException:
                 LOG.warning('RequestException: %s', str(traceback.format_exc()))
-                self.ca_cert = False
                 if retry > self.request_retries:
                     raise
 
         if not result or 'result' not in locals():
-            LOG.error('cannot get auth_token')
+            LOG.error('Cannot retrieve authentication token!')
             return False
 
         if result.status_code != codes.ok:  # pylint: disable-msg=E1101
@@ -669,14 +668,12 @@ def __get_token_x509(self):
         result = None
         for retry in range(self.AUTH_RETRIES + 1):
             try:
-                result = self.session.get(url, headers=headers, cert=cert,
-                                          verify=self.ca_cert)
+                result = self.session.get(url, headers=headers, cert=cert, verify=self.ca_cert)
                 break
             except ConnectionError as error:
                 if 'alert certificate expired' in str(error):
                     raise CannotAuthenticate(str(error))
                 LOG.warning('ConnectionError: ' + str(error))
-                self.ca_cert = False
                 if retry > self.request_retries:
                     raise
 
@@ -693,7 +690,6 @@ def __get_token_x509(self):
             raise exc_cls(exc_msg)
 
         self.auth_token = result.headers['x-rucio-auth-token']
-        LOG.debug('got new token')
         return True
 
     def __get_token_ssh(self):
@@ -723,7 +719,6 @@ def __get_token_ssh(self):
                 if 'alert certificate expired' in str(error):
                     raise CannotAuthenticate(str(error))
                 LOG.warning('ConnectionError: ' + str(error))
-                self.ca_cert = False
                 if retry > self.request_retries:
                     raise
 
@@ -757,12 +752,11 @@ def __get_token_ssh(self):
                 if 'alert certificate expired' in str(error):
                     raise CannotAuthenticate(str(error))
                 LOG.warning('ConnectionError: ' + str(error))
-                self.ca_cert = False
                 if retry > self.request_retries:
                     raise
 
         if not result:
-            LOG.error('cannot get auth_token')
+            LOG.error('Cannot retrieve authentication token!')
             return False
 
         if result.status_code != codes.ok:   # pylint: disable-msg=E1101
@@ -772,7 +766,6 @@ def __get_token_ssh(self):
             raise exc_cls(exc_msg)
 
         self.auth_token = result.headers['x-rucio-auth-token']
-        LOG.debug('got new token')
         return True
 
     def __get_token_gss(self):
@@ -795,12 +788,11 @@ def __get_token_gss(self):
                 break
             except ConnectionError as error:
                 LOG.warning('ConnectionError: ' + str(error))
-                self.ca_cert = False
                 if retry > self.request_retries:
                     raise
 
         if not result:
-            LOG.error('cannot get auth_token')
+            LOG.error('Cannot retrieve authentication token!')
             return False
 
         if result.status_code != codes.ok:   # pylint: disable-msg=E1101
@@ -810,7 +802,6 @@ def __get_token_gss(self):
             raise exc_cls(exc_msg)
 
         self.auth_token = result.headers['x-rucio-auth-token']
-        LOG.debug('got new token')
         return True
 
     def __get_token_saml(self):
@@ -838,12 +829,11 @@ def __get_token_saml(self):
                 break
             except ConnectionError as error:
                 LOG.warning('ConnectionError: ' + str(error))
-                self.ca_cert = False
                 if retry > self.request_retries:
                     raise
 
         if not result or 'result' not in locals():
-            LOG.error('cannot get auth_token')
+            LOG.error('Cannot retrieve authentication token!')
             return False
 
         if result.status_code != codes.ok:  # pylint: disable-msg=E1101
@@ -853,7 +843,6 @@ def __get_token_saml(self):
             raise exc_cls(exc_msg)
 
         self.auth_token = result.headers['X-Rucio-Auth-Token']
-        LOG.debug('got new token')
         return True
 
     def __get_token(self):


Test patch:
diff --git a/lib/rucio/tests/test_multi_vo.py b/lib/rucio/tests/test_multi_vo.py
--- a/lib/rucio/tests/test_multi_vo.py
+++ b/lib/rucio/tests/test_multi_vo.py
@@ -16,6 +16,7 @@
 # - Patrick Austin <patrick.austin@stfc.ac.uk>, 2020
 # - Eli Chadwick <eli.chadwick@stfc.ac.uk>, 2020
 # - Benedikt Ziemons <benedikt.ziemons@cern.ch>, 2020
+# - Mario Lassnig <mario.lassnig@cern.ch>, 2020
 
 import sys
 import unittest
@@ -1107,7 +1108,7 @@ def test_vo_option_admin_cli(self):
         exitcode, out, err = execute(cmd)
         print(out, err)
         assert len(out) == 0
-        assert 'cannot get auth_token' in err
+        assert 'Cannot retrieve authentication token!' in err
 
         cmd = 'rucio-admin rse list'
         print(self.marker + cmd)
@@ -1137,7 +1138,7 @@ def test_vo_option_cli(self):
         exitcode, out, err = execute(cmd)
         print(out, err)
         assert len(out) == 0
-        assert 'cannot get auth_token' in err
+        assert 'Cannot retrieve authentication token!' in err
 
         cmd = 'rucio list-rses'
         print(self.marker + cmd)