Unify mode specification

We now have:

    --mode regular (the default)
    --mode transparent
    --mode socks5
    --mode reverse:SPEC
    --mode upstream:SPEC

Where SPEC is a host specification.
This commit is contained in:
Aldo Cortesi 2017-03-06 20:30:49 +13:00
parent f5fb6972aa
commit 82163a1e68
14 changed files with 80 additions and 129 deletions

View File

@ -3,18 +3,37 @@
checked by other addons.
"""
from mitmproxy import exceptions
from mitmproxy import options
from mitmproxy import platform
from mitmproxy.utils import human
class Core:
def configure(self, options, updated):
if "body_size_limit" in updated and options.body_size_limit:
def configure(self, opts, updated):
if "body_size_limit" in updated and opts.body_size_limit:
try:
options._processed["body_size_limit"] = human.parse_size(
options.body_size_limit
opts._processed["body_size_limit"] = human.parse_size(
opts.body_size_limit
)
except ValueError as e:
raise exceptions.OptionsError(
"Invalid body size limit specification: %s" %
options.body_size_limit
opts.body_size_limit
)
if "mode" in updated:
mode = opts.mode
if mode.startswith("reverse:") or mode.startswith("upstream:"):
spec = options.get_mode_spec(mode)
if not spec:
raise exceptions.OptionsError(
"Invalid mode specification: %s" % mode
)
elif mode == "transparent":
if not platform.original_addr:
raise exceptions.OptionsError(
"Transparent mode not supported on this platform."
)
elif mode not in ["regular", "socks5"]:
raise exceptions.OptionsError(
"Invalid mode specification: %s" % mode
)

View File

@ -148,7 +148,7 @@ class Master:
Loads a flow
"""
if isinstance(f, http.HTTPFlow):
if self.server and self.options.mode == "reverse":
if self.server and self.options.mode.startswith("reverse:"):
f.request.host = self.server.config.upstream_server.address[0]
f.request.port = self.server.config.upstream_server.address[1]
f.request.scheme = self.server.config.upstream_server.scheme

View File

@ -22,6 +22,11 @@ view_orders = [
"size",
]
def get_mode_spec(m):
return m.split(":", maxsplit=1)[1]
APP_HOST = "mitm.it"
APP_PORT = 80
CA_DIR = "~/.mitmproxy"
@ -247,7 +252,14 @@ class Options(optmanager.OptManager):
"upstream_bind_address", "", str,
"Address to bind upstream requests to (defaults to none)"
)
self.add_option("mode", "regular", str)
self.add_option(
"mode", "regular", str,
"""
Mode can be "regular", "transparent", "socks5", "reverse:SPEC",
or "upstream:SPEC". For reverse and upstream proxy modes, SPEC
is proxy specification in the form of "http[s]://host[:port]".
"""
)
self.add_option(
"upstream_cert", True, bool,
"Connect to upstream server to look up certificate details."
@ -285,7 +297,6 @@ class Options(optmanager.OptManager):
"Use the client's IP for server-side connections. "
"Combine with --upstream-bind-address to spoof a fixed source address."
)
self.add_option("upstream_server", None, Optional[str])
self.add_option(
"upstream_auth", None, Optional[str],
"""

View File

@ -121,7 +121,7 @@ class ProxyConfig:
raise exceptions.OptionsError(
"Invalid certificate format: %s" % cert
)
self.upstream_server = None
if options.upstream_server:
self.upstream_server = parse_server_spec(options.upstream_server)
m = options.mode
if m.startswith("upstream:") or m.startswith("reverse:"):
spec = moptions.get_mode_spec(options.mode)
self.upstream_server = parse_server_spec(spec)

View File

@ -290,7 +290,7 @@ class HttpLayer(base.Layer):
request.first_line_format = "relative"
# update host header in reverse proxy mode
if self.config.options.mode == "reverse" and not self.config.options.keep_host_header:
if self.config.options.mode.startswith("reverse:") and not self.config.options.keep_host_header:
f.request.host_header = self.config.upstream_server.address[0]
# Determine .scheme, .host and .port attributes for inline scripts. For

View File

@ -45,7 +45,7 @@ class RequestReplayThread(basethread.BaseThread):
if not self.f.response:
# In all modes, we directly connect to the server displayed
if self.config.options.mode == "upstream":
if self.config.options.mode.startswith("upstream:"):
server_address = self.config.upstream_server.address
server = connections.ServerConnection(server_address, (self.config.options.listen_host, 0))
server.connect()

View File

@ -85,14 +85,14 @@ class ConnectionHandler:
)
mode = self.config.options.mode
if mode == "upstream":
if mode.startswith("upstream:"):
return modes.HttpUpstreamProxy(
root_ctx,
self.config.upstream_server.address
)
elif mode == "transparent":
return modes.TransparentProxy(root_ctx)
elif mode == "reverse":
elif mode.startswith("reverse:"):
server_tls = self.config.upstream_server.scheme == "https"
return modes.ReverseProxy(
root_ctx,

View File

@ -3,7 +3,6 @@ import os
from mitmproxy import exceptions
from mitmproxy import options
from mitmproxy import platform
from mitmproxy import version
@ -15,34 +14,6 @@ class ParseException(Exception):
def get_common_options(args):
# Establish proxy mode
c = 0
mode, upstream_server = "regular", None
if args.transparent_proxy:
c += 1
if not platform.original_addr:
raise exceptions.OptionsError(
"Transparent mode not supported on this platform."
)
mode = "transparent"
if args.socks_proxy:
c += 1
mode = "socks5"
if args.reverse_proxy:
c += 1
mode = "reverse"
upstream_server = args.reverse_proxy
if args.upstream_proxy:
c += 1
mode = "upstream"
upstream_server = args.upstream_proxy
if c > 1:
raise exceptions.OptionsError(
"Transparent, SOCKS5, reverse and upstream proxy mode "
"are mutually exclusive. Read the docs on proxy modes "
"to understand why."
)
if args.add_upstream_certs_to_client_chain and not args.upstream_cert:
raise exceptions.OptionsError(
"The no-upstream-cert and add-upstream-certs-to-client-chain "
@ -99,7 +70,7 @@ def get_common_options(args):
listen_host = args.listen_host,
listen_port = args.listen_port,
upstream_bind_address = args.upstream_bind_address,
mode = mode,
mode = args.mode,
upstream_cert = args.upstream_cert,
spoof_source_address = args.spoof_source_address,
@ -108,7 +79,6 @@ def get_common_options(args):
websocket = args.websocket,
rawtcp = args.rawtcp,
upstream_server = upstream_server,
upstream_auth = args.upstream_auth,
ssl_version_client = args.ssl_version_client,
ssl_version_server = args.ssl_version_server,
@ -154,39 +124,6 @@ def basic_options(parser, opts):
opts.make_parser(parser, "stream_large_bodies")
def proxy_modes(parser, opts):
group = parser.add_argument_group("Proxy Modes")
group.add_argument(
"-R", "--reverse",
action="store",
type=str,
dest="reverse_proxy",
help="""
Forward all requests to upstream HTTP server:
http[s]://host[:port]. Clients can always connect both
via HTTPS and HTTP, the connection to the server is
determined by the specified scheme.
"""
)
group.add_argument(
"--socks",
action="store_true", dest="socks_proxy",
help="Set SOCKS5 proxy mode."
)
group.add_argument(
"-T", "--transparent",
action="store_true", dest="transparent_proxy",
help="Set transparent proxy mode."
)
group.add_argument(
"-U", "--upstream",
action="store",
type=str,
dest="upstream_proxy",
help="Forward all requests to upstream proxy server: http://host[:port]"
)
def proxy_options(parser, opts):
group = parser.add_argument_group("Proxy Options")
opts.make_parser(group, "listen_host")
@ -315,8 +252,8 @@ def common_options(parser, opts):
metavar="PATH",
help="Configuration file"
)
opts.make_parser(parser, "mode")
basic_options(parser, opts)
proxy_modes(parser, opts)
proxy_options(parser, opts)
proxy_ssl_options(parser, opts)
onboarding_app(parser, opts)

View File

@ -2,7 +2,6 @@ import os.path
import urwid
import mitmproxy.net.http.url
from mitmproxy.tools.console import common
from mitmproxy.tools.console import pathedit
from mitmproxy.tools.console import signals
@ -234,13 +233,8 @@ class StatusBar(urwid.WidgetWrap):
if opts:
r.append("[%s]" % (":".join(opts)))
if self.master.options.mode in ["reverse", "upstream"]:
dst = self.master.server.config.upstream_server
r.append("[dest:%s]" % mitmproxy.net.http.url.unparse(
dst.scheme,
dst.address[0],
dst.address[1],
))
if self.master.options.mode != "regular":
r.append("[%s]" % self.master.options.mode)
if self.master.options.scripts:
r.append("[")
r.append(("heading_key", "s"))

View File

@ -2,6 +2,7 @@ from mitmproxy import exceptions
from mitmproxy.addons import core
from mitmproxy.test import taddons
import pytest
from unittest import mock
def test_simple():
@ -11,3 +12,20 @@ def test_simple():
tctx.configure(sa, body_size_limit = "invalid")
tctx.configure(sa, body_size_limit = "1m")
assert tctx.options._processed["body_size_limit"]
@mock.patch("mitmproxy.platform.original_addr", None)
def test_no_transparent():
sa = core.Core()
with taddons.context() as tctx:
with pytest.raises(Exception, match="Transparent mode not supported"):
tctx.configure(sa, mode = "transparent")
@mock.patch("mitmproxy.platform.original_addr")
def test_modes(m):
sa = core.Core()
with taddons.context() as tctx:
tctx.configure(sa, mode = "reverse:http://localhost")
with pytest.raises(Exception, match="Invalid mode"):
tctx.configure(sa, mode = "reverse:")

View File

@ -10,7 +10,7 @@ from mitmproxy import options
from mitmproxy.addons import script
from mitmproxy.addons import proxyauth
from mitmproxy import http
from mitmproxy.proxy.config import HostMatcher, parse_server_spec
from mitmproxy.proxy.config import HostMatcher
import mitmproxy.net.http
from mitmproxy.net import tcp
from mitmproxy.net import socks
@ -579,8 +579,6 @@ class TestHttps2Http(tservers.ReverseProxyTest):
@classmethod
def get_options(cls):
opts = super().get_options()
s = parse_server_spec(opts.upstream_server)
opts.upstream_server = "http://{}:{}".format(s.address[0], s.address[1])
return opts
def pathoc(self, ssl, sni=None):

View File

@ -63,8 +63,7 @@ class TestSerialize:
r = self._treader()
s = tservers.TestState()
opts = options.Options(
mode="reverse",
upstream_server="https://use-this-domain"
mode="reverse:https://use-this-domain"
)
conf = ProxyConfig(opts)
fm = master.Master(opts, DummyServer(conf))

View File

@ -49,31 +49,6 @@ class TestProcessProxyOptions:
with tutils.tmpdir() as cadir:
self.assert_noerr("--cadir", cadir)
@mock.patch("mitmproxy.platform.original_addr", None)
def test_no_transparent(self):
with pytest.raises(Exception, match="Transparent mode not supported"):
self.p("-T")
@mock.patch("mitmproxy.platform.original_addr")
def test_modes(self, _):
self.assert_noerr("-R", "http://localhost")
with pytest.raises(Exception, match="expected one argument"):
self.p("-R")
with pytest.raises(Exception, match="Invalid server specification"):
self.p("-R", "reverse")
self.assert_noerr("-T")
self.assert_noerr("-U", "http://localhost")
with pytest.raises(Exception, match="Invalid server specification"):
self.p("-U", "upstream")
self.assert_noerr("--upstream-auth", "test:test")
with pytest.raises(Exception, match="expected one argument"):
self.p("--upstream-auth")
with pytest.raises(Exception, match="mutually exclusive"):
self.p("-R", "http://localhost", "-T")
def test_client_certs(self):
with tutils.tmpdir() as cadir:
self.assert_noerr("--client-certs", cadir)
@ -131,19 +106,19 @@ class TestDummyServer:
class TestConnectionHandler:
def test_fatal_error(self, capsys):
config = mock.Mock()
root_layer = mock.Mock()
root_layer.side_effect = RuntimeError
config.options.mode.return_value = root_layer
opts = options.Options()
pconf = config.ProxyConfig(opts)
channel = mock.Mock()
def ask(_, x):
return x
raise RuntimeError
channel.ask = ask
c = ConnectionHandler(
mock.MagicMock(),
("127.0.0.1", 8080),
config,
pconf,
channel
)
c.handle()

View File

@ -288,7 +288,7 @@ class ReverseProxyTest(ProxyTestBase):
@classmethod
def get_options(cls):
opts = ProxyTestBase.get_options()
opts.upstream_server = "".join(
s = "".join(
[
"https" if cls.ssl else "http",
"://",
@ -296,7 +296,7 @@ class ReverseProxyTest(ProxyTestBase):
str(cls.server.port)
]
)
opts.mode = "reverse"
opts.mode = "reverse:" + s
return opts
def pathoc(self, sni=None):
@ -373,9 +373,9 @@ class ChainProxyTest(ProxyTestBase):
def get_options(cls):
opts = super().get_options()
if cls.chain: # First proxy is in normal mode.
s = "http://127.0.0.1:%s" % cls.chain[0].port
opts.update(
mode="upstream",
upstream_server="http://127.0.0.1:%s" % cls.chain[0].port
mode="upstream:" + s,
)
return opts