コード例 #1
0
    def __get_token_userpass(self):
        """
        Sends a request to get an auth token from the server and stores it as a class attribute. Uses username/password.

        :returns: True if the token was successfully received. False otherwise.
        """

        headers = {'X-Rucio-Username': self.creds['username'],
                   'X-Rucio-Password': self.creds['password']}

        url = build_url(self.auth_host, path='auth/userpass')

        result = self._send_request(url, headers=headers, get_token=True)

        if not result:
            # result is either None or not OK.
            if isinstance(result, Response):
                if 'ExceptionClass' in result.headers and result.headers['ExceptionClass']:
                    if 'ExceptionMessage' in result.headers and result.headers['ExceptionMessage']:
                        raise CannotAuthenticate('%s: %s' % (result.headers['ExceptionClass'], result.headers['ExceptionMessage']))
                    else:
                        raise CannotAuthenticate(result.headers["ExceptionClass"])
                elif result.text:
                    raise CannotAuthenticate(result.text)
            self.logger.error('Cannot retrieve authentication token!')
            return False

        if result.status_code != codes.ok:  # pylint: disable-msg=E1101
            exc_cls, exc_msg = self._get_exception(headers=result.headers,
                                                   status_code=result.status_code,
                                                   data=result.content)
            raise exc_cls(exc_msg)

        self.auth_token = result.headers['x-rucio-auth-token']
        return True
コード例 #2
0
ファイル: baseclient.py プロジェクト: zzaiin/Rucio
    def __get_token(self):
        """
        Calls the corresponding method to receive an auth token depending on the auth type. To be used if a 401 - Unauthorized error is received.
        """

        retry = 0
        LOG.debug('get a new token')
        while retry <= self.AUTH_RETRIES:
            if self.auth_type == 'userpass':
                if not self.__get_token_userpass():
                    raise CannotAuthenticate('userpass authentication failed')
            elif self.auth_type == 'x509' or self.auth_type == 'x509_proxy':
                if not self.__get_token_x509():
                    raise CannotAuthenticate('x509 authentication failed')
            elif self.auth_type == 'gss':
                if not self.__get_token_gss():
                    raise CannotAuthenticate('kerberos authentication failed')
            else:
                raise CannotAuthenticate('auth type \'%s\' not supported' %
                                         self.auth_type)

            if self.auth_token is not None:
                self.__write_token()
                self.headers['X-Rucio-Auth-Token'] = self.auth_token
                break

            retry += 1

        if self.auth_token is None:
            raise CannotAuthenticate('cannot get an auth token from server')
コード例 #3
0
ファイル: baseclient.py プロジェクト: klabattermann/rucio
    def __authenticate(self):
        """
        Main method for authentication. It first tries to read a locally saved token. If not available it requests a new one.
        """

        if self.auth_type == 'userpass':
            if self.creds['username'] is None or self.creds['password'] is None:
                raise NoAuthInformation('No username or password passed')
        elif self.auth_type == 'x509':
            if self.creds['client_cert'] is None:
                raise NoAuthInformation(
                    'The path to the client certificate is required')
        elif self.auth_type == 'x509_proxy':
            if self.creds['client_proxy'] is None:
                raise NoAuthInformation('The client proxy has to be defined')
        elif self.auth_type == 'ssh':
            if self.creds['ssh_private_key'] is None:
                raise NoAuthInformation(
                    'The SSH private key has to be defined')
        elif self.auth_type == 'gss':
            pass
        else:
            raise CannotAuthenticate('auth type \'%s\' not supported' %
                                     self.auth_type)

        if not self.__read_token():
            self.__get_token()
コード例 #4
0
def redirect_auth_oidc(auth_code, fetchtoken=False, session=None):
    """
    Finds the Authentication URL in the Rucio DB oauth_requests table
    and redirects user's browser to this URL.

    :param auth_code: Rucio assigned code to redirect
                      authorization securely to IdP via Rucio Auth server through a browser.
    :param fetchtoken: If True, valid token temporarily saved in the oauth_requests table
                       will be returned. If False, redirection URL is returned.
    :param session: The database session in use.

    :returns: result of the query (authorization URL or a
              token if a user asks with the correct code) or None.
              Exception thrown in case of an unexpected crash.

    """
    try:
        redirect_result = session.query(models.OAuthRequest.redirect_msg).filter_by(access_msg=auth_code).first()

        if isinstance(redirect_result, tuple):
            if 'http' not in redirect_result[0] and fetchtoken:
                # in this case the function check if the value is a valid token
                vdict = validate_auth_token(redirect_result[0], session=session)
                if vdict:
                    return redirect_result[0]
                return None
            elif 'http' in redirect_result[0] and not fetchtoken:
                # return redirection URL
                return redirect_result[0]
            return None
        return None
    except:
        raise CannotAuthenticate(traceback.format_exc())
コード例 #5
0
ファイル: baseclient.py プロジェクト: sartiran/rucio
    def __get_token_x509(self):
        """
        Sends a request to get an auth token from the server and stores it as a class attribute. Uses x509 authentication.

        :returns: True if the token was successfully received. False otherwise.
        """

        headers = {'X-Rucio-Account': self.account}

        client_cert = None
        client_key = None
        if self.auth_type == 'x509':
            url = build_url(self.auth_host, path='auth/x509')
            client_cert = self.creds['client_cert']
            if 'client_key' in self.creds:
                client_key = self.creds['client_key']
        elif self.auth_type == 'x509_proxy':
            url = build_url(self.auth_host, path='auth/x509_proxy')
            client_cert = self.creds['client_proxy']

        if not path.exists(client_cert):
            LOG.error('given client cert (%s) doesn\'t exist' % client_cert)
            return False
        if client_key is not None and not path.exists(client_key):
            LOG.error('given client key (%s) doesn\'t exist' % client_key)

        if client_key is None:
            cert = client_cert
        else:
            cert = (client_cert, client_key)

        result = None
        for retry in range(self.AUTH_RETRIES + 1):
            try:
                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

        # Note a response object for a failed request evaluates to false, so we cannot
        # use "not result" here
        if result is None:
            LOG.error('Internal error: Request for authentication token returned no result!')
            return False

        if result.status_code != codes.ok:   # pylint: disable-msg=E1101
            exc_cls, exc_msg = self._get_exception(headers=result.headers,
                                                   status_code=result.status_code,
                                                   data=result.content)
            raise exc_cls(exc_msg)

        self.auth_token = result.headers['x-rucio-auth-token']
        LOG.debug('got new token \'%s\'' % self.auth_token)
        return True
コード例 #6
0
ファイル: baseclient.py プロジェクト: ic-hep/rucio
    def __get_token(self):
        """
        Calls the corresponding method to receive an auth token depending on the auth type. To be used if a 401 - Unauthorized error is received.
        """

        LOG.debug('get a new token')
        for retry in range(self.AUTH_RETRIES + 1):
            if self.auth_type == 'userpass':
                if not self.__get_token_userpass():
                    raise CannotAuthenticate('userpass authentication failed for account=%s with identity=%s' % (self.account,
                                                                                                                 self.creds['username']))
            elif self.auth_type == 'x509' or self.auth_type == 'x509_proxy':
                if not self.__get_token_x509():
                    raise CannotAuthenticate('x509 authentication failed for account=%s with identity=%s' % (self.account,
                                                                                                             self.creds))
            elif self.auth_type == 'gss':
                if not self.__get_token_gss():
                    raise CannotAuthenticate('kerberos authentication failed for account=%s with identity=%s' % (self.account,
                                                                                                                 self.creds))
            elif self.auth_type == 'ssh':
                if not self.__get_token_ssh():
                    raise CannotAuthenticate('ssh authentication failed for account=%s with identity=%s' % (self.account,
                                                                                                            self.creds))
            else:
                raise CannotAuthenticate('auth type \'%s\' not supported' % self.auth_type)

            if self.auth_token is not None:
                self.__write_token()
                self.headers['X-Rucio-Auth-Token'] = self.auth_token
                break

        if self.auth_token is None:
            raise CannotAuthenticate('cannot get an auth token from server')
コード例 #7
0
ファイル: baseclient.py プロジェクト: nsmith-/rucio
    def __authenticate(self):
        """
        Main method for authentication. It first tries to read a locally saved token. If not available it requests a new one.
        """

        if self.auth_type == 'userpass':
            if self.creds['username'] is None or self.creds['password'] is None:
                raise NoAuthInformation('No username or password passed')
        elif self.auth_type == 'oidc':
            if self.creds['oidc_auto'] and (
                    self.creds['oidc_username'] is None
                    or self.creds['oidc_password'] is None):
                raise NoAuthInformation(
                    'For automatic OIDC log-in with your Identity Provider username and password are required.'
                )
            if not self.creds['oidc_scope']:
                raise NoAuthInformation(
                    'For OIDC log-in you need to provide a scope parameter. The minimal expected by Rucio server is usually "openid profile"'
                )
        elif self.auth_type == 'x509':
            if self.creds['client_cert'] is None:
                raise NoAuthInformation(
                    'The path to the client certificate is required')
        elif self.auth_type == 'x509_proxy':
            if self.creds['client_proxy'] is None:
                raise NoAuthInformation('The client proxy has to be defined')
        elif self.auth_type == 'ssh':
            if self.creds['ssh_private_key'] is None:
                raise NoAuthInformation(
                    'The SSH private key has to be defined')
        elif self.auth_type == 'gss':
            pass
        elif self.auth_type == 'saml':
            if self.creds['username'] is None or self.creds['password'] is None:
                raise NoAuthInformation('No SAML username or password passed')
        else:
            raise CannotAuthenticate('auth type \'%s\' not supported' %
                                     self.auth_type)

        if not self.__read_token():
            self.__get_token()
コード例 #8
0
ファイル: baseclient.py プロジェクト: klabattermann/rucio
    def __get_token_ssh(self):
        """
        Sends a request to get an auth token from the server and stores it as a class attribute. Uses SSH key exchange authentication.

        :returns: True if the token was successfully received. False otherwise.
        """
        headers = {'X-Rucio-Account': self.account}

        private_key_path = self.creds['ssh_private_key']
        if not path.exists(private_key_path):
            LOG.error('given private key (%s) doesn\'t exist' %
                      private_key_path)
            return False
        if private_key_path is not None and not path.exists(private_key_path):
            LOG.error('given private key (%s) doesn\'t exist' %
                      private_key_path)
            return False

        url = build_url(self.auth_host, path='auth/ssh_challenge_token')

        result = None
        for retry in range(self.AUTH_RETRIES + 1):
            try:
                result = self.session.get(url,
                                          headers=headers,
                                          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

        if not result:
            LOG.error('cannot get ssh_challenge_token')
            return False

        if result.status_code != codes.ok:  # pylint: disable-msg=E1101
            exc_cls, exc_msg = self._get_exception(
                headers=result.headers,
                status_code=result.status_code,
                data=result.content)
            raise exc_cls(exc_msg)

        self.ssh_challenge_token = result.headers[
            'x-rucio-ssh-challenge-token']
        LOG.debug('got new ssh challenge token \'%s\'' %
                  self.ssh_challenge_token)

        # sign the challenge token with the private key
        with open(private_key_path, 'r') as fd_private_key_path:
            private_key = fd_private_key_path.read()
            signature = ssh_sign(private_key, self.ssh_challenge_token)
            headers['X-Rucio-SSH-Signature'] = signature

        url = build_url(self.auth_host, path='auth/ssh')

        result = None
        for retry in range(self.AUTH_RETRIES + 1):
            try:
                result = self.session.get(url,
                                          headers=headers,
                                          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

        if not result:
            LOG.error('cannot get auth_token')
            return False

        if result.status_code != codes.ok:  # pylint: disable-msg=E1101
            exc_cls, exc_msg = self._get_exception(
                headers=result.headers,
                status_code=result.status_code,
                data=result.content)
            raise exc_cls(exc_msg)

        self.auth_token = result.headers['x-rucio-auth-token']
        LOG.debug('got new token')
        return True
コード例 #9
0
    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, logger=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: Dictionary with credentials needed for authentication.
        :param user_agent: Indicates the client.
        :param vo: The VO to authenticate into.
        :param logger: Logger object to use. If None, use the default LOG created by the module
        """

        self.host = rucio_host
        self.list_hosts = []
        self.auth_host = auth_host
        self.logger = logger or LOG
        self.session = Session()
        self.user_agent = "%s/%s" % (user_agent, version.version_string())  # e.g. "rucio-clients/0.2.13"
        sys.argv[0] = sys.argv[0].split('/')[-1]
        self.script_id = '::'.join(sys.argv[0:2])
        if self.script_id == '':  # Python interpreter used
            self.script_id = 'python'
        try:
            if self.host is None:
                self.host = config_get('client', 'rucio_host')
            if self.auth_host is None:
                self.auth_host = config_get('client', 'auth_host')
        except (NoOptionError, NoSectionError) as error:
            raise MissingClientParameter('Section client and Option \'%s\' cannot be found in config file' % error.args[0])

        try:
            self.trace_host = config_get('trace', 'trace_host')
        except (NoOptionError, NoSectionError):
            self.trace_host = self.host
            self.logger.debug('No trace_host passed. Using rucio_host instead')

        self.account = account
        self.vo = vo
        self.ca_cert = ca_cert
        self.auth_type = auth_type
        self.creds = creds
        self.auth_token = None
        self.auth_token_file_path = config_get('client', 'auth_token_file_path', False, None)
        self.headers = {}
        self.timeout = timeout
        self.request_retries = self.REQUEST_RETRIES
        self.token_exp_epoch = None
        self.token_exp_epoch_file = None
        self.auth_oidc_refresh_active = config_get_bool('client', 'auth_oidc_refresh_active', False, False)
        # defining how many minutes before token expires, oidc refresh (if active) should start
        self.auth_oidc_refresh_before_exp = config_get_int('client', 'auth_oidc_refresh_before_exp', False, 20)

        if auth_type is None:
            self.logger.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'])
                self.auth_type = environ['RUCIO_AUTH_TYPE']
            else:
                try:
                    self.auth_type = config_get('client', 'auth_type')
                except (NoOptionError, NoSectionError) as error:
                    raise MissingClientParameter('Option \'%s\' cannot be found in config file' % error.args[0])

        if self.auth_type == 'oidc':
            if not self.creds:
                self.creds = {}
            # if there are defautl values, check if rucio.cfg does not specify them, otherwise put default
            if 'oidc_refresh_lifetime' not in self.creds or self.creds['oidc_refresh_lifetime'] is None:
                self.creds['oidc_refresh_lifetime'] = config_get('client', 'oidc_refresh_lifetime', False, None)
            if 'oidc_issuer' not in self.creds or self.creds['oidc_issuer'] is None:
                self.creds['oidc_issuer'] = config_get('client', 'oidc_issuer', False, None)
            if 'oidc_audience' not in self.creds or self.creds['oidc_audience'] is None:
                self.creds['oidc_audience'] = config_get('client', 'oidc_audience', False, None)
            if 'oidc_auto' not in self.creds or self.creds['oidc_auto'] is False:
                self.creds['oidc_auto'] = config_get_bool('client', 'oidc_auto', False, False)
            if self.creds['oidc_auto']:
                if 'oidc_username' not in self.creds or self.creds['oidc_username'] is None:
                    self.creds['oidc_username'] = config_get('client', 'oidc_username', False, None)
                if 'oidc_password' not in self.creds or self.creds['oidc_password'] is None:
                    self.creds['oidc_password'] = config_get('client', 'oidc_password', False, None)
            if 'oidc_scope' not in self.creds or self.creds['oidc_scope'] == 'openid profile':
                self.creds['oidc_scope'] = config_get('client', 'oidc_scope', False, 'openid profile')
            if 'oidc_polling' not in self.creds or self.creds['oidc_polling'] is False:
                self.creds['oidc_polling'] = config_get_bool('client', 'oidc_polling', False, False)

        if not self.creds:
            self.logger.debug('No creds passed. Trying to get it from the config file.')
            self.creds = {}
            try:
                if self.auth_type in ['userpass', 'saml']:
                    self.creds['username'] = config_get('client', 'username')
                    self.creds['password'] = config_get('client', 'password')
                elif self.auth_type == 'x509':
                    self.creds['client_cert'] = path.abspath(path.expanduser(path.expandvars(config_get('client', 'client_cert'))))
                    if not path.exists(self.creds['client_cert']):
                        raise MissingClientParameter('X.509 client certificate not found: %s' % self.creds['client_cert'])
                    self.creds['client_key'] = path.abspath(path.expanduser(path.expandvars(config_get('client', 'client_key'))))
                    if not path.exists(self.creds['client_key']):
                        raise MissingClientParameter('X.509 client key not found: %s' % self.creds['client_key'])
                    else:
                        perms = oct(os.stat(self.creds['client_key']).st_mode)[-3:]
                        if perms not in ['400', '600']:
                            raise CannotAuthenticate('X.509 authentication selected, but private key (%s) permissions are liberal (required: 400 or 600, found: %s)' % (self.creds['client_key'], perms))
                elif self.auth_type == 'x509_proxy':
                    try:
                        self.creds['client_proxy'] = path.abspath(path.expanduser(path.expandvars(config_get('client', 'client_x509_proxy'))))
                    except NoOptionError:
                        # Recreate the classic GSI logic for locating the proxy:
                        # - $X509_USER_PROXY, if it is set.
                        # - /tmp/x509up_u`id -u` otherwise.
                        # If neither exists (at this point, we don't care if it exists but is invalid), then rethrow
                        if 'X509_USER_PROXY' in environ:
                            self.creds['client_proxy'] = environ['X509_USER_PROXY']
                        else:
                            fname = '/tmp/x509up_u%d' % geteuid()
                            if path.exists(fname):
                                self.creds['client_proxy'] = fname
                            else:
                                raise MissingClientParameter('Cannot find a valid X509 proxy; not in %s, $X509_USER_PROXY not set, and '
                                                             '\'x509_proxy\' not set in the configuration file.' % fname)
                elif self.auth_type == 'ssh':
                    self.creds['ssh_private_key'] = path.abspath(path.expanduser(path.expandvars(config_get('client', 'ssh_private_key'))))
            except (NoOptionError, NoSectionError) as error:
                if error.args[0] != 'client_key':
                    raise MissingClientParameter('Option \'%s\' cannot be found in config file' % error.args[0])

        rucio_scheme = urlparse(self.host).scheme
        auth_scheme = urlparse(self.auth_host).scheme

        if rucio_scheme != 'http' and rucio_scheme != 'https':
            raise ClientProtocolNotSupported('\'%s\' not supported' % rucio_scheme)

        if auth_scheme != 'http' and auth_scheme != 'https':
            raise ClientProtocolNotSupported('\'%s\' not supported' % auth_scheme)

        if (rucio_scheme == 'https' or auth_scheme == 'https') and ca_cert is None:
            self.logger.debug('HTTPS is required, but no ca_cert was passed. Trying to get it from X509_CERT_DIR.')
            self.ca_cert = os.environ.get('X509_CERT_DIR', None)
            if self.ca_cert is None:
                self.logger.debug('HTTPS is required, but no ca_cert was passed and X509_CERT_DIR is not defined. Trying to get it from the config file.')
                try:
                    self.ca_cert = path.expandvars(config_get('client', 'ca_cert'))
                except (NoOptionError, NoSectionError):
                    self.logger.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:
            self.logger.debug('No account passed. Trying to get it from the RUCIO_ACCOUNT environment variable or the config file.')
            try:
                self.account = environ['RUCIO_ACCOUNT']
            except KeyError:
                try:
                    self.account = config_get('client', 'account')
                except (NoOptionError, NoSectionError):
                    pass

        if vo is None:
            self.logger.debug('No VO passed. Trying to get it from environment variable RUCIO_VO.')
            try:
                self.vo = environ['RUCIO_VO']
            except KeyError:
                self.logger.debug('No VO found. Trying to get it from the config file.')
                try:
                    self.vo = config_get('client', 'vo')
                except (NoOptionError, NoSectionError):
                    self.logger.debug('No VO found. Using default VO.')
                    self.vo = 'def'

        token_filename_suffix = "for_default_account" if self.account is None else "for_account_" + self.account

        # if token file path is defined in the rucio.cfg file, use that file. Currently this prevents authenticating as another user or VO.
        if self.auth_token_file_path:
            self.token_file = self.auth_token_file_path
            self.token_path = '/'.join(self.token_file.split('/')[:-1])
        else:
            self.token_path = self.TOKEN_PATH_PREFIX + getpass.getuser()
            if self.vo != 'def':
                self.token_path += '@%s' % self.vo
            self.token_file = self.token_path + '/' + self.TOKEN_PREFIX + token_filename_suffix

        self.token_exp_epoch_file = self.token_path + '/' + self.TOKEN_EXP_PREFIX + token_filename_suffix

        self.__authenticate()

        try:
            self.request_retries = int(config_get('client', 'request_retries'))
        except (NoOptionError, RuntimeError):
            LOG.debug('request_retries not specified in config file. Taking default.')
        except ValueError:
            self.logger.debug('request_retries must be an integer. Taking default.')