예제 #1
0
    def _start_bundled_app(cls, labbook: LabBook, router: ProxyRouter, username: str, bundled_app: dict,
                           container_override_id: str = None):
        tool_port = bundled_app['port']
        labbook_ip = ContainerOperations.get_labbook_ip(labbook, username)
        endpoint = f'http://{labbook_ip}:{tool_port}'

        route_prefix = quote_plus(bundled_app['name'])

        matched_routes = router.get_matching_routes(endpoint, route_prefix)

        run_command = True
        suffix = None
        if len(matched_routes) == 1:
            logger.info(f"Found existing {bundled_app['name']} in route table for {str(labbook)}.")
            suffix = matched_routes[0]
            run_command = False

        elif len(matched_routes) > 1:
            raise ValueError(f"Multiple {bundled_app['name']} instances found in route table "
                             f"for {str(labbook)}! Restart container.")

        if run_command:
            logger.info(f"Adding {bundled_app['name']} to route table for {str(labbook)}.")
            suffix, _ = router.add(endpoint, route_prefix)
            suffix = "/" + suffix

            # Start app
            logger.info(f"Starting {bundled_app['name']} in {str(labbook)}.")
            start_bundled_app(labbook, username, bundled_app['command'], tag=container_override_id)

        return suffix
예제 #2
0
    def _start_rstudio(cls,
                       lb: LabBook,
                       pr: ProxyRouter,
                       username: str,
                       container_override_id: str = None):
        lb_ip = ContainerOperations.get_labbook_ip(lb, username)
        lb_endpoint = f'http://{lb_ip}:8787'

        mitm_endpoint = MITMProxyOperations.get_mitmendpoint(lb_endpoint)
        # start mitm proxy if it doesn't exist
        if mitm_endpoint is None:

            # get a proxy prefix
            unique_key = unique_id()

            # start proxy
            mitm_endpoint = MITMProxyOperations.start_mitm_proxy(
                lb_endpoint, unique_key)

            # Ensure we start monitor when starting MITM proxy
            start_labbook_monitor(
                lb,
                username,
                "rstudio",
                # This is the endpoint for the proxy and not the rserver?
                url=f'{lb_endpoint}/{unique_key}',
                author=get_logged_in_author())

            # All messages will come through MITM, so we don't need to monitor rserver directly
            start_rserver(lb, username, tag=container_override_id)

            # add route
            rt_prefix, _ = pr.add(mitm_endpoint, f'rserver/{unique_key}/')
            # Warning: RStudio will break if there is a trailing slash!
            suffix = f'/{rt_prefix}'

        else:
            # existing route to MITM or not?
            matched_routes = pr.get_matching_routes(mitm_endpoint, 'rserver')

            if len(matched_routes) == 1:
                suffix = matched_routes[0]
            elif len(matched_routes) == 0:
                logger.warning(
                    'Creating missing route for existing RStudio mitmproxy_proxy'
                )
                # TODO DC: This feels redundant with already getting the mitm_endpoint above
                # Can we refactor this into a more coherent single operation? Maybe an MITMProxy instance?
                unique_key = MITMProxyOperations.get_mitmkey(lb_endpoint)
                # add route
                rt_prefix, _ = pr.add(mitm_endpoint, f'rserver/{unique_key}/')
                # Warning: RStudio will break if there is a trailing slash!
                suffix = f'/{rt_prefix}'
            else:
                raise ValueError(
                    f"Multiple RStudio proxy instances for {str(lb)}. Please restart the Project "
                    "or manually delete stale containers.")

        return suffix
예제 #3
0
    def _start_jupyter_tool(cls,
                            lb: LabBook,
                            pr: ProxyRouter,
                            username: str,
                            container_override_id: str = None):
        tool_port = 8888
        lb_ip = ContainerOperations.get_labbook_ip(lb, username)
        lb_endpoint = f'http://{lb_ip}:{tool_port}'

        matched_routes = pr.get_matching_routes(lb_endpoint, 'jupyter')

        run_start_jupyter = True
        suffix = None
        if len(matched_routes) == 1:
            logger.info(
                f'Found existing Jupyter instance in route table for {str(lb)}.'
            )
            suffix = matched_routes[0]

            # wait for jupyter to be up
            try:
                check_jupyter_reachable(lb_ip, tool_port, suffix)
                run_start_jupyter = False
            except GigantumException:
                logger.warning(
                    f'Detected stale route. Attempting to restart Jupyter and clean up route table.'
                )
                pr.remove(suffix[1:])

        elif len(matched_routes) > 1:
            raise ValueError(
                f"Multiple Jupyter instances found in route table for {str(lb)}! Restart container."
            )

        if run_start_jupyter:
            rt_prefix = unique_id()
            rt_prefix, _ = pr.add(lb_endpoint, f'jupyter/{rt_prefix}')

            # Start jupyterlab
            suffix = start_jupyter(lb,
                                   username,
                                   tag=container_override_id,
                                   proxy_prefix=rt_prefix)

            # Ensure we start monitor IFF jupyter isn't already running.
            start_labbook_monitor(lb,
                                  username,
                                  'jupyterlab',
                                  url=f'{lb_endpoint}/{rt_prefix}',
                                  author=get_logged_in_author())

        return suffix
예제 #4
0
    def _start_dev_tool(cls,
                        lb: LabBook,
                        username: str,
                        dev_tool: str,
                        container_override_id: str = None):
        pr = ProxyRouter.get_proxy(lb.client_config.config['proxy'])

        if dev_tool == "rstudio":
            suffix = cls._start_rstudio(lb, pr, username)
        elif dev_tool in ["jupyterlab", "notebook"]:
            # Note that starting the dev tool is identical whether we're targeting jupyterlab or notebook
            suffix = cls._start_jupyter_tool(lb, pr, username,
                                             container_override_id)
        else:
            raise GigantumException(
                f"'{dev_tool}' not currently supported as a Dev Tool")

        # Don't include the port in the path if running on 80
        apparent_proxy_port = lb.client_config.config['proxy'][
            "apparent_proxy_port"]
        if apparent_proxy_port == 80:
            path = suffix
        else:
            path = f':{apparent_proxy_port}{suffix}'

        return path
예제 #5
0
def test_connect_to_internal_process_via_proxy_1(config_fixture, start_proxy):
    """ Create a route to proxy but specifify the route prefix. """
    pr = ProxyRouter.get_proxy(config_fixture)
    pfx, host = pr.add("http://localhost:5555", 'test/server/1')
    assert host == 'http://localhost:5555'
    assert pfx in [p[1:] for p in pr.routes.keys()]
    assert 'spool' in requests.get(f'http://localhost/{pfx}').text
예제 #6
0
    def _start_dev_tool(cls, labbook: LabBook, username: str, dev_tool: str, container_override_id: str = None):
        router = ProxyRouter.get_proxy(labbook.client_config.config['proxy'])
        bam = BundledAppManager(labbook)
        bundled_apps = bam.get_bundled_apps()
        bundled_app_names = [x for x in bundled_apps]

        if dev_tool == "rstudio":
            suffix = cls._start_rstudio(labbook, router, username)
        elif dev_tool in ["jupyterlab", "notebook"]:
            # Note that starting the dev tool is identical whether we're targeting jupyterlab or notebook
            suffix = cls._start_jupyter_tool(labbook, router, username, container_override_id)
        elif dev_tool in bundled_app_names:
            app_data = bundled_apps[dev_tool]
            app_data['name'] = dev_tool
            suffix = cls._start_bundled_app(labbook, router, username, app_data, container_override_id)
        else:
            raise GigantumException(f"'{dev_tool}' not currently supported as a Dev Tool")

        # Don't include the port in the path if running on 80
        apparent_proxy_port = labbook.client_config.config['proxy']["apparent_proxy_port"]
        if apparent_proxy_port == 80:
            path = suffix
        else:
            path = f':{apparent_proxy_port}{suffix}'

        return path
예제 #7
0
def test_make_and_delete_routes(config_fixture, start_proxy):
    pr = ProxyRouter.get_proxy(config_fixture)
    pfx1, host1 = pr.add("http://localhost:5555")
    pfx2, host2 = pr.add("http://localhost:6666")
    assert pr.routes
    pr.remove(pfx1)
    pr.remove(pfx2)
    assert len(pr.routes.keys()) == 0
예제 #8
0
def test_search(config_fixture, start_proxy):
    rando = str(uuid.uuid4()).replace('-', '')[:6]
    pr = ProxyRouter.get_proxy(config_fixture)
    pf1, host1 = pr.add("http://localhost:6666")
    pf2, host2 = pr.add("http://localhost:5555", f'constant/{rando}')

    assert pr.search('cat') is None
    assert pr.search('http://localhost:6666')[1:].isalnum()
    assert pr.search('http://localhost:5555') == f'/constant/{rando}'
    assert pr.search(pf1) is None
    assert pr.search(pf2) is None
예제 #9
0
def test_check(config_fixture, start_proxy):
    pr = ProxyRouter.get_proxy(config_fixture)
    pf1, host1 = pr.add("http://localhost:6666")
    pf2, host2 = pr.add("http://localhost:5555", f'constant/x')
    pf3, host3 = pr.add("http://localhost:9911", f'not/real')
    pf4, host4 = pr.add("http://12.24.111.21:22", f'should/timeout')

    r1 = pr.search("http://localhost:6666")
    r2 = pr.search("http://localhost:5555")

    t0 = time.time()
    assert pr.check(pf1) is True
    assert pr.check(pf2) is True
    assert pr.check(r1) is True
    assert pr.check(r2) is True
    assert pr.check('constant/x') is True
    assert pr.check(pf3) is False
    assert pr.check(pf4) is False
    t1 = time.time()
    # Check that timeouts timed-out super quickly
    assert t1 - t0 < 2.0
예제 #10
0
    # Allow CORS
    CORS(app, max_age=7200)

# Set Debug mode
app.config['DEBUG'] = config.config["flask"]["DEBUG"]

# Register LabBook service
app.register_blueprint(blueprint.complete_labbook_service)

# Configure CHP
try:
    api_prefix = app.config["LABMGR_CONFIG"].config['proxy']["labmanager_api_prefix"]
    apparent_proxy_port = app.config["LABMGR_CONFIG"].config['proxy']["apparent_proxy_port"]
    api_port = app.config["LABMGR_CONFIG"].config['proxy']['api_port']

    proxy_router = ProxyRouter.get_proxy(app.config["LABMGR_CONFIG"].config['proxy'])
    proxy_router.add("http://localhost:10001", "api")
    logger.info(f"Proxy routes ({type(proxy_router)}): {proxy_router.routes}")
except Exception as e:
    logger.exception(e)


# Set auth error handler
@app.errorhandler(AuthenticationError)
def handle_auth_error(ex):
    response = jsonify(ex.error)
    response.status_code = ex.status_code
    return response


# Set Unauth'd route for API health-check
예제 #11
0
def test_connect_to_internal_process_via_proxy_2(config_fixture, start_proxy):
    """ Create route to proxy but have it auto-generate a route prefix. """
    pr = ProxyRouter.get_proxy(config_fixture)
    pfx, host = pr.add("http://localhost:6666")
    assert pfx in [p[1:] for p in pr.routes.keys()]
    assert 'ldconfig' in requests.get(f'http://localhost/{pfx}').text
예제 #12
0
def test_no_routes(config_fixture, start_proxy):
    pr = ProxyRouter.get_proxy(config_fixture)
    assert type(pr) == ProxyRouter
    assert pr.routes == {}
예제 #13
0
def test_null1():
    pr = ProxyRouter.get_proxy()
    assert type(pr) == NullRouter
    assert pr.is_null_proxy is True
예제 #14
0
    def configure_mitmroute(cls,
                            labbook: LabBook,
                            router: ProxyRouter,
                            username: str,
                            _retry: Optional[bool] = False) -> Tuple[str, str]:
        """Ensure mitm is configured and proxied for labbook

        Args:
            labbook: the specific target running a dev tool
            router: The link to the configurable-proxy-router wrapper
            username: username of the logged in user
            _retry: (internal use only) is this a recursive call after clean-up?

        Returns:
            str that contains the mitm proxy endpoint as http://{ip}:{port}
        """
        labbook_container_name = ContainerOperations.labbook_image_name(
            labbook, username)
        # Note that the use of rserver is not intrinsically meaningful - we could make this more generic
        # if mitmproxy supports multiple dev tools
        proxy_target = f'rserver/{labbook_container_name}/'
        mitm_key = cls.get_mitm_redis_key(labbook_container_name)

        redis_conn = redis.Redis(db=1)

        devtool_ip = ContainerOperations.get_labbook_ip(labbook, username)
        devtool_endpoint = f"http://{devtool_ip}:8787"

        for monitored_labbook_name in cls.get_running_proxies():
            if (monitored_labbook_name != labbook_container_name) and \
                    (cls.get_devtool_endpoint(monitored_labbook_name) == devtool_endpoint):
                # Some other MITM proxy is pointing at our dev tool
                cls.stop_mitm_proxy(monitored_labbook_name)

        if not redis_conn.exists(mitm_key):
            # start mitm proxy if it's not configured yet
            mitm_endpoint = cls.start_mitm_proxy(devtool_endpoint,
                                                 labbook_container_name)

            # add route
            rt_prefix, _ = router.add(mitm_endpoint, proxy_target)
            # Warning: RStudio will break if there is a trailing slash!
            suffix = f'/{rt_prefix}'
        elif _retry:
            # This should never happen - but it's better than the chance of infinite recursion
            raise ValueError(
                f'Unable to cleanly stop mitmproxy_proxy for {labbook_container_name}. Please try manually.'
            )
        else:
            # Are we pointing to the correct dev tool endpoint?
            configured_devtool_endpoint = cls.get_devtool_endpoint(
                labbook_container_name)
            if devtool_endpoint != configured_devtool_endpoint:
                logger.warning(
                    f'Existing RStudio mitmproxy_proxy for stale endpoint {configured_devtool_endpoint}, removing'
                )
                cls.stop_mitm_proxy(labbook_container_name)
                return cls.configure_mitmroute(labbook,
                                               router,
                                               username,
                                               _retry=True)

            # Is the MITM endpoint configured in Redis?
            retval = cls.get_mitmendpoint(labbook_container_name)
            if retval is None:
                logger.warning(
                    'Existing RStudio mitmproxy_proxy is partially configured, removing'
                )
                cls.stop_mitm_proxy(labbook_container_name)
                return cls.configure_mitmroute(labbook,
                                               router,
                                               username,
                                               _retry=True)
            else:
                mitm_endpoint = retval

            # existing route to MITM or not?
            matched_routes = router.get_matching_routes(
                mitm_endpoint, proxy_target)

            if len(matched_routes) == 1:
                suffix = matched_routes[0]
            elif len(matched_routes) == 0:
                logger.warning(
                    'Creating missing route for existing RStudio mitmproxy_proxy'
                )
                rt_prefix, _ = router.add(mitm_endpoint, proxy_target)
                # Warning: RStudio will break if there is a trailing slash!
                suffix = f'/{rt_prefix}'
            else:
                raise ValueError(
                    f"Multiple RStudio proxy instances for {str(labbook)}. Please restart the Project "
                    "or manually delete stale containers.")

        # The endpoint plus proxy target constitute the full URL for an MITM-reverse-proxied RStudio
        return f"{mitm_endpoint}/{proxy_target}", suffix