Beispiel #1
0
    def _authenticate(self, timeout: int, retries: int, retry_wait: int):
        """ Authenticates with the device and retrieves a session token to be
            used in actual requests

        Args:
            timeout: The timeout to use when establishing the connection
            retries: How many times to retry to connect to the device if it fails
            retry_wait: Time in seconds to wait between retries
        """
        # URL to authenticate and receive the token
        url = f"{self.base_url}/mgmt/shared/authn/login"

        payload = {
            'username': self.username,
            'password': self.password,
            'loginProviderName': self._auth_provider
        }

        iCRS = iControlRESTSession(self.username,
                                   self.password,
                                   timeout=timeout,
                                   verify=self.verify)

        log.info("Connecting to '%s'", self.device.name)

        response = iCRS.post(
            url,
            json=payload,
        )

        log.debug(response.json())

        if response.status_code != 200:
            if b'Configuration Utility restarting...' in response.content:
                if retries > 0:
                    time.sleep(retry_wait)
                    return self._authenticate(timeout, retries - 1, retry_wait)
                else:
                    raise iControlUnexpectedHTTPError(
                        f"Failed to connect to {self.device.name}: "
                        f"{response.content}")
            else:
                raise iControlUnexpectedHTTPError(
                    f"Failed to authenticate with {self.device.name}")

        self.token = response.json()['token']['token']

        log.debug("The following token is used to connect: '%s'", self.token)
 def wrapper(self, RIC_base_uri, **kwargs):
     partition = kwargs.pop('partition', '')
     name = kwargs.pop('name', '')
     suffix = kwargs.pop('suffix', '')
     uri_as_parts = kwargs.pop('uri_as_parts', False)
     if uri_as_parts:
         REST_uri = generate_bigip_uri(RIC_base_uri, partition, name,
                                       suffix, **kwargs)
     else:
         REST_uri = RIC_base_uri
     pre_message = "%s WITH uri: %s AND suffix: %s AND kwargs: %s" %\
         (method.__name__, REST_uri, suffix, kwargs)
     print("About to log pre_message.")
     logging.info(pre_message)
     response = method(self, REST_uri, **kwargs)
     post_message =\
         "RESPONSE::STATUS: %s Content-Type: %s Content-Encoding:"\
         " %s\nText: %r" % (response.status_code,
                            response.headers.get('Content-Type', None),
                            response.headers.get('Content-Encoding', None),
                            response.text)
     logging.debug(post_message)
     if response.status_code not in range(200, 207):
         error_message = '%s Unexpected Error: %s for uri: %s\nText: %r' %\
                         (response.status_code,
                          response.reason,
                          response.url,
                          response.text)
         raise iControlUnexpectedHTTPError(error_message, response=response)
     return response
Beispiel #3
0
    def delete(self, api_url, timeout=30, verbose=False):
        """DELETE REST Command to delete information from the device"""

        if not self.connected:
            raise Exception(f"No active connection for '{self.device.name}'")

        full_url = "{b}{a}".format(b=self.base_url, a=api_url)

        log.info("Sending Post to '{d}': "
                 "{u}"
                 " with the header '{h}'".format(d=self.device.name,
                                                 u=full_url,
                                                 h=self.header))

        response = self.icr_session.delete(full_url, timeout=timeout)

        output = response.text

        log.debug("Response: {c}, headers: {h}".format(c=response.status_code,
                                                       h=self.header))
        if verbose:
            log.info("Output received:\n{output}".format(output=output))

        # Make sure it returned ok
        if not response.ok:
            raise iControlUnexpectedHTTPError(
                "Connection to '{d}' has returned the "
                "following code '{c}', instead of the "
                "expected status code 'ok'".format(d=self.device.name,
                                                   c=response.status_code))

        log.info(
            "Successfully fetched data from '{d}'".format(d=self.device.name))

        return output
Beispiel #4
0
    def get(self, api_url, timeout=30, verbose=False):
        """GET REST Command to retrieve information from the device"""

        full_url = "{b}{a}".format(b=self.base_url, a=api_url)

        log.info("Sending GET to '{d}': "
                 "{u}".format(d=self.device.name, u=full_url))

        response = self.icr_session.get(full_url, timeout=timeout)

        output = response

        log.debug("Response: {c}, headers: {h}".format(c=response.status_code,
                                                       h=self.header))
        if verbose:
            log.info("Output received:\n{output}".format(output=output))

        # Make sure it returned ok
        if not response.ok:
            raise iControlUnexpectedHTTPError(
                "Connection to '{d}' has returned the "
                "following code '{c}', instead of the "
                "expected status code 'ok'".format(d=self.device.name,
                                                   c=response.status_code))

        log.info(
            "Successfully fetched data from '{d}'".format(d=self.device.name))

        log.info("Successfully fetched data using token: '{t}'".format(
            t=self.token))

        return output
Beispiel #5
0
    def delete(self, **kwargs):
        """Custom deletion logic to handle edge cases

        This shouldn't be needed, but ASM has a tendency to raise various errors that
        are painful to handle from a customer point-of-view. These errors are especially
        pronounced when doing things concurrently with asm.

        The error itself are described in their exception handler

        To address these failure, we try a number of exception handling cases to catch
        and reliably deal with the error.

        :param kwargs:
        :return:
        """
        ex = iControlUnexpectedHTTPError("Failed to delete the signature")
        for _ in range(0, 30):
            try:
                return self._delete(**kwargs)
            except iControlUnexpectedHTTPError as ex:
                if self._check_exception(ex):
                    continue
                else:
                    raise
        raise ex
Beispiel #6
0
 def _get_last_update_micros(self, token):
     try:
         last_updated = token['lastUpdateMicros']
         created_bigip = int(last_updated) / 1000000.0
     except (KeyError, ValueError):
         raise iControlUnexpectedHTTPError(
             "lastUpdateMicros field was not found in the response")
     return created_bigip
Beispiel #7
0
 def _get_token_from_response(self, respJson):
     try:
         token = respJson['token']
         self.token = token['token']
     except KeyError:
         raise iControlUnexpectedHTTPError(
             "Token field not found in the response")
     return token
Beispiel #8
0
 def update(self, **kwargs):
     ex = iControlUnexpectedHTTPError("Failed to delete the signature")
     for _ in range(0, 30):
         try:
             return self._update(**kwargs)
         except iControlUnexpectedHTTPError as ex:
             if self._check_exception(ex):
                 continue
             else:
                 raise
     raise ex
Beispiel #9
0
 def store_create(self, **items):
     try:
         key = self.derive_key(items)
         self.__store_create[key] = items
     except Exception:
         response = Mock()
         response.status_code = 409
         error = iControlUnexpectedHTTPError(
             "Key Word 'name' or 'partition' is missing!",
             response=response)
         raise error
Beispiel #10
0
 def store_create(self, **items):
     try:
         key = self.derive_key(items)
         self.__store_create[key] = items
     except Exception:
         response = Mock()
         response.status_code = 409
         error = iControlUnexpectedHTTPError(
             "Key Word 'name' or 'partition' is missing!",
             response=response)
         raise error
Beispiel #11
0
def test_create_subresource_icontrol_500_exception(bigip, response):
    """Test create can handle HTTP server exception."""
    data = resource_data()
    subres = SubResource(name=data['name'], partition=data['partition'])

    response.status_code = 500
    bigip.tm.ltm.subresources.subresource.create.side_effect = ([
        iControlUnexpectedHTTPError(response=response), None
    ])

    with pytest.raises(cccl_exc.F5CcclError):
        obj = subres.create(bigip)

        assert not obj
Beispiel #12
0
def test_create_subresource_icontrol_500_exception(bigip, response):
    u"""Test create can handle HTTP server exception."""
    data = resource_data()
    subres = SubResource(name=data['name'], partition=data['partition'])

    response.status_code = 500
    bigip.tm.ltm.subresources.subresource.create.side_effect = (
        [iControlUnexpectedHTTPError(response=response), None]
    )

    with pytest.raises(cccl_exc.F5CcclError):
        obj = subres.create(bigip)

        assert not obj
Beispiel #13
0
 def read_from_create(self, *args, **items):
     key = self.derive_key(items)
     retval = MagicMock(spec=f5.bigip.resource.Resource)
     retval.delete = self.nuke_from_create
     retval.modify = self.update_create
     retval.update = self.update_create
     self.last_key = key
     try:
         retval.raw = self.__store_create[key].copy()
     except KeyError:
         response = Mock()
         response.status_code = 404
         raise iControlUnexpectedHTTPError("NOT FOUND HERE!",
                                           response=response)
     return retval
Beispiel #14
0
 def read_from_create(self, *args, **items):
     key = self.derive_key(items)
     retval = MagicMock(spec=f5.bigip.resource.Resource)
     retval.delete = self.nuke_from_create
     retval.modify = self.update_create
     retval.update = self.update_create
     self.last_key = key
     try:
         retval.raw = self.__store_create[key].copy()
     except KeyError:
         response = Mock()
         response.status_code = 404
         raise iControlUnexpectedHTTPError("NOT FOUND HERE!",
                                           response=response)
     return retval
Beispiel #15
0
    def patch(self, api_url, payload, timeout=30, verbose=False):

        """PATCH REST Command to update information on the device"""

        if not self.connected:
            raise Exception(
                "'{d}' is not connected for "
                "alias '{a}'".format(d=self.device.name, a=self.alias)
            )

        full_url = "{b}{a}".format(b=self.base_url, a=api_url)

        log.info(
            "Sending Post to '{d}': "
            "{u}"
            " with the header '{h}'"
            " and payload '{p}'".format(
                d=self.device.name, u=full_url, h=self.header, p=payload
            )
        )

        response = self.icr_session.patch(full_url, json=payload, timeout=timeout)

        output = response

        log.debug(
            "Response: {c}, headers: {h}".format(
                c=response.status_code, h=self.header
            )
        )
        if verbose:
            log.info("Output received:\n{output}".format(output=output))

        # Make sure it returned ok
        if not response.ok:
            raise iControlUnexpectedHTTPError(
                "Connection to '{d}' has returned the "
                "following code '{c}', instead of the "
                "expected status code 'ok'".format(
                    d=self.device.name, c=response.status_code
                )
            )

        log.info(
            "Successfully fetched data from '{d}'".format(d=self.device.name)
        )

        return output
Beispiel #16
0
def test_delete_subresource_icontrol_4XX_exception(bigip, response):
    """Test delete can handle gener HTTP client request exception."""
    data = resource_data()
    subres = SubResource(name=data['name'], partition=data['partition'])

    response.status_code = 400
    bigip.tm.ltm.subresources.subresource.load.return_value = (
        bigip.tm.ltm.subresources.subresource)
    bigip.tm.ltm.subresources.subresource.delete.side_effect = ([
        iControlUnexpectedHTTPError(response=response), None
    ])

    with pytest.raises(cccl_exc.F5CcclResourceRequestError):
        obj = subres.delete(bigip)

        assert not obj
Beispiel #17
0
def test_delete_subresource_icontrol_4XX_exception(bigip, response):
    u"""Test delete can handle gener HTTP client request exception."""
    data = resource_data()
    subres = SubResource(name=data['name'], partition=data['partition'])

    response.status_code = 400
    bigip.tm.ltm.subresources.subresource.load.return_value = (
        bigip.tm.ltm.subresources.subresource
    )
    bigip.tm.ltm.subresources.subresource.delete.side_effect = (
        [iControlUnexpectedHTTPError(response=response), None]
    )

    with pytest.raises(cccl_exc.F5CcclResourceRequestError):
        obj = subres.delete(bigip)

        assert not obj
Beispiel #18
0
def test_delete_subresource_icontrol_404_exception(bigip, response):
    u"""Test delete can handle HTTP 404 not found exception.

    A NotFound error should occur when the resource load is performed.
    """
    data = resource_data()
    subres = SubResource(name=data['name'], partition=data['partition'])

    response.status_code = 404

    bigip.tm.ltm.subresources.subresource.load.side_effect = (
        [iControlUnexpectedHTTPError(response=response), None]
    )

    with pytest.raises(cccl_exc.F5CcclResourceNotFoundError):
        subres.delete(bigip)

    bigip.tm.ltm.subresources.subresource.delete.assert_not_called()
Beispiel #19
0
def test_delete_subresource_icontrol_404_exception(bigip, response):
    """Test delete can handle HTTP 404 not found exception.

    A NotFound error should occur when the resource load is performed.
    """
    data = resource_data()
    subres = SubResource(name=data['name'], partition=data['partition'])

    response.status_code = 404

    bigip.tm.ltm.subresources.subresource.load.side_effect = ([
        iControlUnexpectedHTTPError(response=response), None
    ])

    with pytest.raises(cccl_exc.F5CcclResourceNotFoundError):
        subres.delete(bigip)

    bigip.tm.ltm.subresources.subresource.delete.assert_not_called()
Beispiel #20
0
    def _extend_session_ttl(self, ttl: int) -> None:
        """ Sets the TTL for the active session

        Args:
            ttl: The TTL to be set for the session
        """
        # Self-link of the token
        timeout_url = f"{self.base_url}/mgmt/shared/authz/tokens/{self.token}"
        timeout_payload = {"timeout": ttl}
        token_icr_session = iControlRESTSession(self.username,
                                                self.password,
                                                verify=self.verify,
                                                token_to_use=self.token)
        # Extending the timeout for the token received
        response = token_icr_session.patch(timeout_url, json=timeout_payload)
        if response.status_code != 200 or not response.ok:
            raise iControlUnexpectedHTTPError(
                "Failed to refresh session: "
                f"{response.reason} ({response.status_code})")
        log.debug("Token TTL extended to '%d' seconds", ttl)
Beispiel #21
0
    def wrapper(self, RIC_base_uri, **kwargs):
        partition = kwargs.pop('partition', '')
        sub_path = kwargs.pop('subPath', '')
        suffix = kwargs.pop('suffix', '')
        identifier, kwargs = _unique_resource_identifier_from_kwargs(**kwargs)
        uri_as_parts = kwargs.pop('uri_as_parts', False)
        transform_name = kwargs.pop('transform_name', False)
        transform_subpath = kwargs.pop('transform_subpath', False)
        if uri_as_parts:
            REST_uri = generate_bigip_uri(RIC_base_uri,
                                          partition,
                                          identifier,
                                          sub_path,
                                          suffix,
                                          transform_name=transform_name,
                                          transform_subpath=transform_subpath,
                                          **kwargs)
        else:
            REST_uri = RIC_base_uri
        pre_message = "%s WITH uri: %s AND suffix: %s AND kwargs: %s" %\
            (method.__name__, REST_uri, suffix, kwargs)

        logger = logging.getLogger(__name__)
        logger.debug(pre_message)
        response = method(self, REST_uri, **kwargs)
        post_message =\
            "RESPONSE::STATUS: %s Content-Type: %s Content-Encoding:"\
            " %s\nText: %r" % (response.status_code,
                               response.headers.get('Content-Type', None),
                               response.headers.get('Content-Encoding', None),
                               response.text)
        logger.debug(post_message)
        if response.status_code not in range(200, 207):
            error_message = '%s Unexpected Error: %s for uri: %s\nText: %r' %\
                            (response.status_code,
                             response.reason,
                             response.url,
                             response.text)
            raise iControlUnexpectedHTTPError(error_message, response=response)
        return response
    def load(self, **kwargs):
        # If delete() was called, then there is no reason to check if something
        # exists in the loop below.
        deleted = False
        if 'deleted' in self.__dict__ and self.__dict__['deleted'] is True:
            deleted = True
        if deleted:
            return self._load(**kwargs)

        # It is possible that even after creation, ASM will not correctly be
        # reporting creation of a Session Tracking Status. Therefore, try several
        # times before we finally fail.
        ex = iControlUnexpectedHTTPError(
            "Failed to load the session-tracking-status"
        )
        for _ in range(0, 30):
            try:
                return self._load(**kwargs)
            except iControlUnexpectedHTTPError as ex:
                if self._check_exception(ex):
                    continue
                else:
                    raise
        raise ex
Beispiel #23
0
    def get_auth_providers(self, netloc):
        """BIG-IQ specific query for auth providers

        BIG-IP doesn't really need this because BIG-IP's multiple auth providers
        seem to handle fallthrough just fine. BIG-IQ on the other hand, needs to
        have its auth provider specified if you're using one of the non-default
        ones.

        :param netloc:
        :return:
        """
        url = "https://%s/info/system?null" % (netloc)

        response = requests.get(url, verify=self.verify)
        if not response.ok or not hasattr(response, "json"):
            error_message = '%s Unexpected Error: %s for uri: %s\nText: %r' %\
                            (response.status_code,
                             response.reason,
                             response.url,
                             response.text)
            raise iControlUnexpectedHTTPError(error_message, response=response)
        respJson = response.json()
        result = respJson['providers']
        return result
    def get_new_token(self, netloc):
        """Get a new token from BIG-IP and store it internally.

        Throws relevant exception if it fails to get a new token.

        This method will be called automatically if a request is attempted
        but there is no authentication token, or the authentication token
        is expired.  It is usually not necessary for users to call it, but
        it can be called if it is known that the authentication token has
        been invalidated by other means.
        """
        login_body = {
            'username': self.username,
            'password': self.password,
            'loginProviderName': self.login_provider_name,
        }
        login_url = "https://%s/mgmt/shared/authn/login" % (netloc)
        req_start_time = time.time()
        response = requests.post(login_url,
                                 json=login_body,
                                 verify=False,
                                 auth=HTTPBasicAuth(self.username,
                                                    self.password))
        self.attempts += 1
        if not response.ok or not hasattr(response, "json"):
            error_message = '%s Unexpected Error: %s for uri: %s\nText: %r' %\
                            (response.status_code,
                             response.reason,
                             response.url,
                             response.text)
            raise iControlUnexpectedHTTPError(error_message, response=response)
        respJson = response.json()

        expiration_bigip, created_bigip = None, None
        try:
            self.token = respJson['token']['token']
            expiration_bigip = int(respJson['token']['expirationMicros']) / \
                1000000.0
            created_bigip = int(respJson['token']['lastUpdateMicros']) / \
                1000000.0
        except (KeyError, ValueError):
            error_message = \
                '%s Unparseable Response: %s for uri: %s\nText: %r' %\
                (response.status_code,
                 response.reason,
                 response.url,
                 response.text)
            raise iControlUnexpectedHTTPError(error_message, response=response)

        # Set our token expiration time.
        # The expirationMicros field is when BIG-IP will expire the token
        # relative to its local clock.  To avoid issues caused by incorrect
        # clocks or network latency, we'll compute an expiration time that is
        # referenced to our local clock, and expires slightly before the token
        # should actually expire on BIG-IP

        # Reference to our clock: compute for how long this token is valid as
        # the difference between when it expires and when it was created,
        # according to BIG-IP.
        if expiration_bigip < created_bigip:
            error_message = \
                '%s Token already expired: %s for uri: %s\nText: %r' % \
                (response.status_code,
                 time.ctime(expiration_bigip),
                 response.url,
                 response.text)
            raise iControlUnexpectedHTTPError(error_message, response=response)
        valid_duration = expiration_bigip - created_bigip

        # Assign new expiration time that is 1 minute earlier than BIG-IP's
        # expiration time, as long as that would still be at least a minute in
        # the future.  This should account for clock skew between us and
        # BIG-IP.  By default tokens last for 60 minutes so getting one every
        # 59 minutes instead of 60 is harmless.
        if valid_duration > 120.0:
            valid_duration -= 60.0
        self.expiration = req_start_time + valid_duration
    def get_new_token(self, netloc):
        """Get a new token from BIG-IP and store it internally.

        Throws relevant exception if it fails to get a new token.

        This method will be called automatically if a request is attempted
        but there is no authentication token, or the authentication token
        is expired.  It is usually not necessary for users to call it, but
        it can be called if it is known that the authentication token has
        been invalidated by other means.
        """
        login_body = {
            'username': self.username,
            'password': self.password,
            'loginProviderName': self.login_provider_name,
        }
        login_url = "https://%s/mgmt/shared/authn/login" % (netloc)
        req_start_time = time.time()
        response = requests.post(login_url,
                                 json=login_body,
                                 verify=False,
                                 auth=HTTPBasicAuth(self.username,
                                                    self.password))
        self.attempts += 1
        if not response.ok or not hasattr(response, "json"):
            error_message = '%s Unexpected Error: %s for uri: %s\nText: %r' %\
                            (response.status_code,
                             response.reason,
                             response.url,
                             response.text)
            raise iControlUnexpectedHTTPError(error_message,
                                              response=response)
        respJson = response.json()

        expiration_bigip, created_bigip = None, None
        try:
            self.token = respJson['token']['token']
            expiration_bigip = int(respJson['token']['expirationMicros']) / \
                1000000.0
            created_bigip = int(respJson['token']['lastUpdateMicros']) / \
                1000000.0
        except (KeyError, ValueError):
            error_message = \
                '%s Unparseable Response: %s for uri: %s\nText: %r' %\
                (response.status_code,
                 response.reason,
                 response.url,
                 response.text)
            raise iControlUnexpectedHTTPError(error_message,
                                              response=response)

        # Set our token expiration time.
        # The expirationMicros field is when BIG-IP will expire the token
        # relative to its local clock.  To avoid issues caused by incorrect
        # clocks or network latency, we'll compute an expiration time that is
        # referenced to our local clock, and expires slightly before the token
        # should actually expire on BIG-IP

        # Reference to our clock: compute for how long this token is valid as
        # the difference between when it expires and when it was created,
        # according to BIG-IP.
        if expiration_bigip < created_bigip:
            error_message = \
                '%s Token already expired: %s for uri: %s\nText: %r' % \
                (response.status_code,
                 time.ctime(expiration_bigip),
                 response.url,
                 response.text)
            raise iControlUnexpectedHTTPError(error_message,
                                              response=response)
        valid_duration = expiration_bigip - created_bigip

        # Assign new expiration time that is 1 minute earlier than BIG-IP's
        # expiration time, as long as that would still be at least a minute in
        # the future.  This should account for clock skew between us and
        # BIG-IP.  By default tokens last for 60 minutes so getting one every
        # 59 minutes instead of 60 is harmless.
        if valid_duration > 120.0:
            valid_duration -= 60.0
        self.expiration = req_start_time + valid_duration
Beispiel #26
0
    def get_new_token(self, netloc):
        """Get a new token from BIG-IP and store it internally.

        Throws relevant exception if it fails to get a new token.

        This method will be called automatically if a request is attempted
        but there is no authentication token, or the authentication token
        is expired.  It is usually not necessary for users to call it, but
        it can be called if it is known that the authentication token has
        been invalidated by other means.
        """
        login_body = {
            'username': self.username,
            'password': self.password,
        }

        if self.auth_provider:
            if self.auth_provider == 'local':
                login_body['loginProviderName'] = 'local'
            elif self.auth_provider == 'tmos':
                login_body['loginProviderName'] = 'tmos'
            elif self.auth_provider not in ['none', 'default']:
                providers = self.get_auth_providers(netloc)
                for provider in providers:
                    if self.auth_provider in provider['link']:
                        login_body['loginProviderName'] = provider['name']
                        break
                    elif self.auth_provider == provider['name']:
                        login_body['loginProviderName'] = provider['name']
                        break
        else:
            if self.login_provider_name == 'tmos':
                login_body['loginProviderName'] = self.login_provider_name

        login_url = "https://%s/mgmt/shared/authn/login" % (netloc)

        response = requests.post(login_url,
                                 json=login_body,
                                 verify=self.verify,
                                 auth=HTTPBasicAuth(self.username,
                                                    self.password))
        self.attempts += 1
        if not response.ok or not hasattr(response, "json"):
            error_message = '%s Unexpected Error: %s for uri: %s\nText: %r' %\
                            (response.status_code,
                             response.reason,
                             response.url,
                             response.text)
            raise iControlUnexpectedHTTPError(error_message, response=response)
        respJson = response.json()

        token = self._get_token_from_response(respJson)
        created_bigip = self._get_last_update_micros(token)

        try:
            expiration_bigip = self._get_expiration_micros(
                token, created_bigip)
        except (KeyError, ValueError):
            error_message = \
                '%s Unparseable Response: %s for uri: %s\nText: %r' %\
                (response.status_code,
                 response.reason,
                 response.url,
                 response.text)
            raise iControlUnexpectedHTTPError(error_message, response=response)

        try:
            self.expiration = self._get_token_expiration_time(
                created_bigip, expiration_bigip)
        except iControlUnexpectedHTTPError:
            error_message = \
                '%s Token already expired: %s for uri: %s\nText: %r' % \
                (response.status_code,
                 time.ctime(expiration_bigip),
                 response.url,
                 response.text)
            raise iControlUnexpectedHTTPError(error_message, response=response)