Example #1
0
    def locked_out() -> None:
        """
        Validate if this user is locked out from accessing the API
        :return:
        """

        if tacacs_auth_lockout(username=request.authorization.username):
            current_app.logger.error(
                f"{request.authorization.username} is currently locked out.")
            raise LockedOut
Example #2
0
    def test_tacacs_auth_lockout(self):
        """
        Tests for the tacacs_auth_lockout function
        :return:
        """

        # Test if no existing failures in cache, and we're not reporting one
        with self.subTest(
                msg="Checking failures, none yet reported for user."):
            self.assertEqual(tacacs_auth_lockout(username=self.username),
                             False)

        # Test if no existing failures in cache, and we _ARE_ reporting one
        with self.subTest(msg="Reporting first failure for user."):
            self.assertEqual(
                tacacs_auth_lockout(username=self.username,
                                    report_failure=True), False)

        # Iterate up to 9 failures:
        for _ in range(8):
            tacacs_auth_lockout(username=self.username, report_failure=True)

        # Test if 9 existing failures and checking (But not adding new failure)
        with self.subTest(
                msg="Checking failures, 9 reported so far for user."):
            self.assertEqual(tacacs_auth_lockout(username=self.username),
                             False)

        # Test if 9 existing failures and we report the tenth
        with self.subTest(
                msg="Checking failures, 9 reported, reporting 1 more."):
            self.assertEqual(
                tacacs_auth_lockout(username=self.username,
                                    report_failure=True), True)

        # Test if 10 failures and we are simply checking
        with self.subTest(
                msg="Checking failures, 10 reported so far for user."):
            self.assertEqual(tacacs_auth_lockout(username=self.username), True)

        # Test if 10 failures and we try to report another
        with self.subTest(
                msg=
                "Checking failures, 10 reported so far for user, trying to report another failure."
        ):
            self.assertEqual(
                tacacs_auth_lockout(username=self.username,
                                    report_failure=True), True)

        # Test "old" failures by stashing a 9 failures from _before_ ten minutes ago.
        self.stash_failures(failure_count=9, old=True)

        # Test if 9 existing failures from greater than 10 minutes ago:
        with self.subTest(
                msg="Checking failures, 9 reported > 10 minutes ago."):
            self.assertEqual(tacacs_auth_lockout(username=self.username),
                             False)

        # Test if 9 existing failures from greater than 10 minutes ago, and we're reporting a new failure:
        with self.subTest(
                msg=
                "Checking failures, 9 reported > 10 minutes ago, reporting 1 new failure."
        ):
            self.assertEqual(
                tacacs_auth_lockout(username=self.username,
                                    report_failure=True), False)

        # Now add 9 new failures
        for _ in range(9):
            tacacs_auth_lockout(username=self.username, report_failure=True)

        # Finally test that these failures "count" and we're locked out:
        with self.subTest(
                msg=
                "Testing lockout after removing old faiulres, but new came in."
        ):
            self.assertEqual(
                tacacs_auth_lockout(username=self.username,
                                    report_failure=True), True)
Example #3
0
    def post(self):
        """
        Will enqueue an attempt to use netmiko's send_config_set() method to run commands/put configuration on a device.

        Requires you submit the following in the payload:
            ip: str
            commands: Sequence[str]
        Optional:
            port: int - Default 22
            device_type: str - Default cisco_ios
            enable: Optional[str] - Default the password provided for basic auth
            save_config: bool
            commit: bool

        Secured by Basic Auth, which is then passed to the network device.
        :return: A dict of the job ID, a 202 response code, and the job_id as the X-Request-ID header
        """

        # Grab creds off the basic_auth header
        auth = request.authorization
        if auth.username is None:
            raise Unauthorized

        # Check if this user is locked out or not
        if tacacs_auth_lockout(username=auth.username):
            raise Forbidden

        # Create a credentials object
        creds = Credentials(username=auth.username,
                            password=auth.password,
                            enable=request.json.get("enable", None))

        # Grab x-request-id
        request_id = g.request_id

        # Validate there isn't already a job by this ID
        q = current_app.config["q"]
        if q.fetch_job(request_id) is not None:
            raise DuplicateRequestID

        # Enqueue your job, and return the job ID
        current_app.logger.debug("%s: Enqueueing job for %s@%s:%s", request_id,
                                 creds.username, request.json["ip"],
                                 request.json["port"])
        job = q.enqueue(
            netmiko_send_config,
            ip=request.json["ip"],
            port=request.json["port"],
            device_type=request.json["device_type"],
            credentials=creds,
            commands=request.json["commands"],
            save_config=request.json["save_config"],
            commit=request.json["commit"],
            job_id=request_id,
            result_ttl=86460,
            failure_ttl=86460,
        )
        job_id = job.get_id()
        current_app.logger.info("%s: Enqueued job for %s@%s:%s", job_id,
                                creds.username, request.json["ip"],
                                request.json["port"])

        # Generate the un/pw hash:
        user_hash = creds.salted_hash()

        # Stash the job_id in redis, with the user/pass hash so that only that user can retrieve results
        job_locker(salted_creds=user_hash, job_id=job_id)

        # Return our payload containing job_id, a 202 Accepted, and the X-Request-ID header
        return {
            "job_id": job_id,
            "app": "naas",
            "version": __version__
        }, 202, {
            "X-Request-ID": job_id
        }
Example #4
0
def netmiko_send_config(
    ip: str,
    credentials: "Credentials",
    device_type: str,
    commands: "Sequence[str]",
    port: int = 22,
    save_config: bool = False,
    commit: bool = False,
    delay_factor: int = 2,
    verbose: bool = False,
) -> "Tuple[Optional[dict], Optional[str]]":
    """
    Instantiate a netmiko wrapper instance, feed me an IP, Platform Type, Username, Password, any commands to run.

    :param ip: What IP are we connecting to?
    :param credentials: A naas.library.auth.Credentials object with the username/password/enable in it
    :param commands: List of the commands to issue to the device
    :param device_type: What Netmiko device type are we connecting to?
    :param port: What TCP Port are we connecting to?
    :param save_config: Do you want to save this configuration upon insertion?  Default: False, don't save the config
    :param commit: Do you want to commit this candidate configuration to the running config?  Default: False
    :param delay_factor: Netmiko delay factor, default of 2, higher is slower but more reliable on laggy links
    :param verbose: Turn on Netmiko verbose logging
    :return: A Tuple of a dict of the results (if any) and a string describing the error (if any)
    """

    # Create device dict to pass netmiko
    netmiko_device = {
        "device_type": device_type,
        "ip": ip,
        "username": credentials.username,
        "password": credentials.password,
        "secret": credentials.enable,
        "port": port,
        "ssh_config_file": "/app/naas/ssh_config",
        "allow_agent": False,
        "use_keys": False,
        "verbose": verbose,
    }

    try:
        logger.debug("%s:Establishing connection...", ip)
        net_connect = netmiko.ConnectHandler(**netmiko_device)

        net_output = {}
        logger.debug("%s:Sending config_set: %s", ip, commands)
        net_output["config_set_output"] = net_connect.send_config_set(
            commands, delay_factor=delay_factor)

        if save_config:
            try:
                logger.debug("%s: Saving configuration", ip)
                net_connect.save_config()
            except NotImplementedError:
                logger.debug(
                    "%s: This device_type (%s) does not support the save_config operation.",
                    ip, device_type)

        if commit:
            try:
                logger.debug("%s: Committing configuration", ip)
                net_connect.commit()
            except AttributeError:
                logger.debug(
                    "%s: This device_type (%s) does not support the commit operation",
                    ip, device_type)

        # Perform graceful disconnection of this SSH session
        net_connect.disconnect()

    except (netmiko.NetMikoTimeoutException, timeout) as e:
        logger.debug("%s:Netmiko timed out connecting to device: %s", ip, e)
        return None, str(e)
    except netmiko.NetMikoAuthenticationException as e:
        logger.debug(
            "%s:Netmiko authentication failure connecting to device: %s", ip,
            e)
        tacacs_auth_lockout(username=credentials.username, report_failure=True)
        return None, str(e)
    except (ssh_exception.SSHException, ValueError) as e:
        logger.debug("%s:Netmiko cannot connect to device: %s", ip, e)
        return None, ("Unknown SSH error connecting to device {0}: {1}".format(
            ip, str(e)))

    logger.debug("%s:Netmiko executed successfully.", ip)
    return net_output, None