diff --git a/.flake8 b/.flake8 index 39cf6840..cfc0b078 100644 --- a/.flake8 +++ b/.flake8 @@ -82,6 +82,7 @@ extend-ignore = S101 # FIXME: assertions are thrown away in optimized mode, needs audit S104 # FIXME: bind-all interface listen S105 # FIXME: hardcoded password? + S113 # FIXME: Call to httpx without timeout (false positive) S303 # FIXME: insecure hash func S311 # FIXME: `random` needs auditing S404 # FIXME: `subprocess` use needs auditing diff --git a/proxy/common/constants.py b/proxy/common/constants.py index 90bcaf9f..f1385390 100644 --- a/proxy/common/constants.py +++ b/proxy/common/constants.py @@ -180,11 +180,12 @@ DEFAULT_METRICS_DIRECTORY_PATH = os.path.join(DEFAULT_DATA_DIRECTORY_PATH, "metr # Cor plugins enabled by default or via flags DEFAULT_ABC_PLUGINS = [ - 'HttpProtocolHandlerPlugin', - 'HttpProxyBasePlugin', - 'HttpWebServerBasePlugin', - 'WebSocketTransportBasePlugin', - 'ReverseProxyBasePlugin', + "HttpProtocolHandlerPlugin", + "HttpProxyBasePlugin", + "HttpWebServerBasePlugin", + "WebSocketTransportBasePlugin", + "ReverseProxyBasePlugin", + "GroutClientBasePlugin", ] PLUGIN_DASHBOARD = 'proxy.dashboard.ProxyDashboard' PLUGIN_HTTP_PROXY = 'proxy.http.proxy.HttpProxyPlugin' diff --git a/proxy/plugin/grout_client.py b/proxy/plugin/grout_client.py new file mode 100644 index 00000000..10939512 --- /dev/null +++ b/proxy/plugin/grout_client.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +""" + proxy.py + ~~~~~~~~ + ⚡⚡⚡ Fast, Lightweight, Pluggable, TLS interception capable proxy server focused on + Network monitoring, controls & Application development, testing, debugging. + + :copyright: (c) 2013-present by Abhinav Singh and contributors. + :license: BSD, see LICENSE for more details. +""" + + +from proxy.proxy import GroutClientBasePlugin +from proxy.common.types import HostPort +from proxy.http.parser.parser import HttpParser + + +class GroutClientPlugin(GroutClientBasePlugin): + + def resolve_route( + self, + route: str, + request: HttpParser, + origin: HostPort, + server: HostPort, + ) -> str: + print(request, origin, server, '->', route) + print(request.header(b'host'), request.path) + # Send to localhost:7001 irrespective of the + # original "route" value provided to the grout client + # OR any custom host:upstream mapping provided through the + # --tunnel-route flags. + return 'http://localhost:7001' diff --git a/proxy/proxy.py b/proxy/proxy.py index 3dbbc937..ce550969 100644 --- a/proxy/proxy.py +++ b/proxy/proxy.py @@ -20,6 +20,7 @@ import getpass import logging import argparse import threading +from abc import ABC, abstractmethod from typing import TYPE_CHECKING, Any, Dict, List, Type, Tuple, Optional, cast from .core.ssh import SshTunnelListener, SshHttpProtocolHandler @@ -28,6 +29,7 @@ from .core.event import EventManager from .http.codes import httpStatusCodes from .common.flag import FlagParser, flags from .http.client import client +from .common.types import HostPort from .common.utils import bytes_ from .core.work.fd import RemoteFdExecutor from .http.methods import httpMethods @@ -44,6 +46,7 @@ from .common.constants import ( DEFAULT_SSH_LISTENER_KLASS, ) from .core.event.metrics import MetricsEventSubscriber +from .http.parser.parser import HttpParser if TYPE_CHECKING: # pragma: no cover @@ -509,3 +512,28 @@ def grout() -> None: # noqa: C901 assert env is not None print('\r' + ' ' * 70 + '\r', end='', flush=True) Plugins.from_bytes(env['m'].encode(), name='client').grout(env=env['e']) # type: ignore[attr-defined] + + +class GroutClientBasePlugin(ABC): + """Base class for dynamic grout client rules. + + Implementation of this class must be stateless because a new instance is created + for every route decision making. + """ + + @abstractmethod + def resolve_route( + self, + route: str, + request: HttpParser, + origin: HostPort, + server: HostPort, + ) -> str: + """Returns a valid grout route string. + + You MUST override this method. For a simple pass through, + simply return the "route" argument value itself. You can also + return a dynamic value based upon "request" and "origin" information. + E.g. sending to different upstream services based upon request Host header. + """ + raise NotImplementedError()