diff options
author | Aldo Cortesi <aldo@nullcube.com> | 2016-07-18 18:10:21 +1200 |
---|---|---|
committer | Aldo Cortesi <aldo@nullcube.com> | 2016-07-19 16:25:09 +1200 |
commit | f9622074ccdfafda384fa3a1466d8363c2a65244 (patch) | |
tree | ac262a4019c5014405324ff2883b47a0e5f15426 | |
parent | bd733e12323b02491090e531b6743da0a34b56c6 (diff) | |
download | mitmproxy-f9622074ccdfafda384fa3a1466d8363c2a65244.tar.gz mitmproxy-f9622074ccdfafda384fa3a1466d8363c2a65244.tar.bz2 mitmproxy-f9622074ccdfafda384fa3a1466d8363c2a65244.zip |
ProxyConfig: mode, upstream_server and upstream_auth to Options
-rw-r--r-- | mitmproxy/cmdline.py | 74 | ||||
-rw-r--r-- | mitmproxy/console/statusbar.py | 2 | ||||
-rw-r--r-- | mitmproxy/flow/master.py | 2 | ||||
-rw-r--r-- | mitmproxy/flow/options.py | 6 | ||||
-rw-r--r-- | mitmproxy/main.py | 1 | ||||
-rw-r--r-- | mitmproxy/protocol/http_replay.py | 2 | ||||
-rw-r--r-- | mitmproxy/proxy/config.py | 78 | ||||
-rw-r--r-- | mitmproxy/proxy/server.py | 2 | ||||
-rw-r--r-- | mitmproxy/web/app.py | 2 | ||||
-rw-r--r-- | test/mitmproxy/test_cmdline.py | 29 | ||||
-rw-r--r-- | test/mitmproxy/test_flow.py | 8 | ||||
-rw-r--r-- | test/mitmproxy/test_proxy.py | 33 | ||||
-rw-r--r-- | test/mitmproxy/test_proxy_config.py | 48 | ||||
-rw-r--r-- | test/mitmproxy/test_server.py | 5 | ||||
-rw-r--r-- | test/mitmproxy/tservers.py | 20 |
15 files changed, 173 insertions, 139 deletions
diff --git a/mitmproxy/cmdline.py b/mitmproxy/cmdline.py index fcf1f89f..b68de635 100644 --- a/mitmproxy/cmdline.py +++ b/mitmproxy/cmdline.py @@ -1,6 +1,5 @@ from __future__ import absolute_import, print_function, division -import base64 import os import re @@ -9,11 +8,10 @@ import configargparse from mitmproxy import exceptions from mitmproxy import filt from mitmproxy.proxy import config +from mitmproxy import platform from netlib import human -from netlib import strutils from netlib import tcp from netlib import version -from netlib.http import url APP_HOST = "mitm.it" APP_PORT = 80 @@ -109,30 +107,6 @@ def parse_setheader(s): return _parse_hook(s) -def parse_server_spec(spec): - try: - p = url.parse(spec) - if p[0] not in (b"http", b"https"): - raise ValueError() - except ValueError: - raise configargparse.ArgumentTypeError( - "Invalid server specification: %s" % spec - ) - - address = tcp.Address(p[1:3]) - scheme = p[0].lower() - return config.ServerSpec(scheme, address) - - -def parse_upstream_auth(auth): - pattern = re.compile(".+:") - if pattern.search(auth) is None: - raise configargparse.ArgumentTypeError( - "Invalid upstream auth specification: %s" % auth - ) - return b"Basic" + b" " + base64.b64encode(strutils.always_bytes(auth)) - - def get_common_options(args): stickycookie, stickyauth = None, None if args.stickycookie_filt: @@ -197,11 +171,46 @@ def get_common_options(args): if body_size_limit: try: body_size_limit = human.parse_size(body_size_limit) - except ValueError, e: + except ValueError as e: raise exceptions.OptionsError( "Invalid body size limit specification: %s" % body_size_limit ) + # Establish proxy mode + c = 0 + mode, upstream_server = "regular", None + if args.transparent_proxy: + c += 1 + if not platform.resolver: + 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 args.no_upstream_cert: + raise exceptions.OptionsError( + "The no-upstream-cert and add-upstream-certs-to-client-chain " + "options are mutually exclusive. If no-upstream-cert is enabled " + "then the upstream certificate is not retrieved before generating " + "the client certificate chain." + ) + return dict( app=args.app, app_host=args.app_host, @@ -237,6 +246,9 @@ def get_common_options(args): clientcerts = args.clientcerts, listen_host = args.addr, listen_port = args.port, + mode = mode, + upstream_server = upstream_server, + upstream_auth = args.upstream_auth, ) @@ -353,7 +365,7 @@ def proxy_modes(parser): group.add_argument( "-R", "--reverse", action="store", - type=parse_server_spec, + type=str, dest="reverse_proxy", default=None, help=""" @@ -376,7 +388,7 @@ def proxy_modes(parser): group.add_argument( "-U", "--upstream", action="store", - type=parse_server_spec, + type=str, dest="upstream_proxy", default=None, help="Forward all requests to upstream proxy server: http://host[:port]" @@ -434,7 +446,7 @@ def proxy_options(parser): parser.add_argument( "--upstream-auth", action="store", dest="upstream_auth", default=None, - type=parse_upstream_auth, + type=str, help=""" Proxy Authentication: username:password diff --git a/mitmproxy/console/statusbar.py b/mitmproxy/console/statusbar.py index 8f039e48..e1c65714 100644 --- a/mitmproxy/console/statusbar.py +++ b/mitmproxy/console/statusbar.py @@ -214,7 +214,7 @@ class StatusBar(urwid.WidgetWrap): if opts: r.append("[%s]" % (":".join(opts))) - if self.master.server.config.mode in ["reverse", "upstream"]: + if self.master.options.mode in ["reverse", "upstream"]: dst = self.master.server.config.upstream_server r.append("[dest:%s]" % netlib.http.url.unparse( dst.scheme, diff --git a/mitmproxy/flow/master.py b/mitmproxy/flow/master.py index 64a242ba..a31840d9 100644 --- a/mitmproxy/flow/master.py +++ b/mitmproxy/flow/master.py @@ -191,7 +191,7 @@ class FlowMaster(controller.Master): Loads a flow """ if isinstance(f, models.HTTPFlow): - if self.server and self.server.config.mode == "reverse": + if self.server and self.options.mode == "reverse": f.request.host = self.server.config.upstream_server.address.host f.request.port = self.server.config.upstream_server.address.port f.request.scheme = self.server.config.upstream_server.scheme diff --git a/mitmproxy/flow/options.py b/mitmproxy/flow/options.py index c8839399..7875e9bf 100644 --- a/mitmproxy/flow/options.py +++ b/mitmproxy/flow/options.py @@ -45,6 +45,9 @@ class Options(options.Options): clientcerts = None, # type: Optional[str] listen_host = "", # type: str listen_port = 8080, # type: int + mode = "regular", # type: str + upstream_server = "", # type: str + upstream_auth = "", # type: str ): # We could replace all assignments with clever metaprogramming, # but type hints are a much more valueable asset. @@ -83,5 +86,8 @@ class Options(options.Options): self.clientcerts = clientcerts self.listen_host = listen_host self.listen_port = listen_port + self.mode = mode + self.upstream_server = upstream_server + self.upstream_auth = upstream_auth super(Options, self).__init__() diff --git a/mitmproxy/main.py b/mitmproxy/main.py index fee90d38..77cce161 100644 --- a/mitmproxy/main.py +++ b/mitmproxy/main.py @@ -98,7 +98,6 @@ def mitmdump(args=None): # pragma: no cover if args.quiet: args.flow_detail = 0 - try: dump_options = dump.Options(**cmdline.get_common_options(args)) dump_options.flow_detail = args.flow_detail diff --git a/mitmproxy/protocol/http_replay.py b/mitmproxy/protocol/http_replay.py index ba58075e..bfde06c5 100644 --- a/mitmproxy/protocol/http_replay.py +++ b/mitmproxy/protocol/http_replay.py @@ -44,7 +44,7 @@ class RequestReplayThread(basethread.BaseThread): if not self.flow.response: # In all modes, we directly connect to the server displayed - if self.config.mode == "upstream": + if self.config.options.mode == "upstream": server_address = self.config.upstream_server.address server = models.ServerConnection(server_address, (self.config.options.listen_host, 0)) server.connect() diff --git a/mitmproxy/proxy/config.py b/mitmproxy/proxy/config.py index e0994d34..403a4174 100644 --- a/mitmproxy/proxy/config.py +++ b/mitmproxy/proxy/config.py @@ -1,17 +1,19 @@ from __future__ import absolute_import, print_function, division +import base64 import collections import os import re +from netlib import strutils import six from OpenSSL import SSL, crypto -from mitmproxy import platform from mitmproxy import exceptions from netlib import certutils from netlib import tcp from netlib.http import authentication +from netlib.http import url CONF_BASENAME = "mitmproxy" @@ -54,13 +56,36 @@ class HostMatcher(object): ServerSpec = collections.namedtuple("ServerSpec", "scheme address") +def parse_server_spec(spec): + try: + p = url.parse(spec) + if p[0] not in (b"http", b"https"): + raise ValueError() + except ValueError: + raise exceptions.OptionsError( + "Invalid server specification: %s" % spec + ) + + address = tcp.Address(p[1:3]) + scheme = p[0].lower() + return ServerSpec(scheme, address) + + +def parse_upstream_auth(auth): + pattern = re.compile(".+:") + if pattern.search(auth) is None: + raise exceptions.OptionsError( + "Invalid upstream auth specification: %s" % auth + ) + return b"Basic" + b" " + base64.b64encode(strutils.always_bytes(auth)) + + class ProxyConfig: def __init__( self, options, no_upstream_cert=False, - mode="regular", upstream_server=None, upstream_auth=None, authenticator=None, @@ -82,13 +107,6 @@ class ProxyConfig: self.ciphers_client = ciphers_client self.ciphers_server = ciphers_server self.no_upstream_cert = no_upstream_cert - self.mode = mode - if upstream_server: - self.upstream_server = ServerSpec(upstream_server[0], tcp.Address.wrap(upstream_server[1])) - self.upstream_auth = upstream_auth - else: - self.upstream_server = None - self.upstream_auth = None self.check_ignore = HostMatcher(ignore_hosts) self.check_tcp = HostMatcher(tcp_hosts) @@ -125,6 +143,7 @@ class ProxyConfig: os.path.expanduser(options.cadir), CONF_BASENAME ) + if options.clientcerts: clientcerts = os.path.expanduser(options.clientcerts) if not os.path.exists(clientcerts): @@ -147,39 +166,15 @@ class ProxyConfig: "Invalid certificate format: %s" % cert ) + self.upstream_server = None + self.upstream_auth = None + if options.upstream_server: + self.upstream_server = parse_server_spec(options.upstream_server) + if options.upstream_auth: + self.upstream_auth = parse_upstream_auth(options.upstream_auth) + def process_proxy_options(parser, options, args): - c = 0 - mode, upstream_server, upstream_auth = "regular", None, None - if args.transparent_proxy: - c += 1 - if not platform.resolver: - return parser.error("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 - upstream_auth = args.upstream_auth - if c > 1: - return parser.error( - "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 args.no_upstream_cert: - return parser.error( - "The no-upstream-cert and add-upstream-certs-to-client-chain " - "options are mutually exclusive. If no-upstream-cert is enabled " - "then the upstream certificate is not retrieved before generating " - "the client certificate chain." - ) if args.add_upstream_certs_to_client_chain and args.ssl_verify_upstream_cert: return parser.error( "The verify-upstream-cert and add-upstream-certs-to-client-chain " @@ -220,9 +215,6 @@ def process_proxy_options(parser, options, args): return ProxyConfig( options, no_upstream_cert=args.no_upstream_cert, - mode=mode, - upstream_server=upstream_server, - upstream_auth=upstream_auth, ignore_hosts=args.ignore_hosts, tcp_hosts=args.tcp_hosts, http2=args.http2, diff --git a/mitmproxy/proxy/server.py b/mitmproxy/proxy/server.py index e5c4c3a1..26f2e294 100644 --- a/mitmproxy/proxy/server.py +++ b/mitmproxy/proxy/server.py @@ -85,7 +85,7 @@ class ConnectionHandler(object): self.channel ) - mode = self.config.mode + mode = self.config.options.mode if mode == "upstream": return modes.HttpUpstreamProxy( root_ctx, diff --git a/mitmproxy/web/app.py b/mitmproxy/web/app.py index 8c080e98..e6b95cdf 100644 --- a/mitmproxy/web/app.py +++ b/mitmproxy/web/app.py @@ -336,7 +336,7 @@ class Settings(RequestHandler): self.write(dict( data=dict( version=version.VERSION, - mode=str(self.master.server.config.mode), + mode=str(self.master.options.mode), intercept=self.state.intercept_txt, showhost=self.master.options.showhost, no_upstream_cert=self.master.server.config.no_upstream_cert, diff --git a/test/mitmproxy/test_cmdline.py b/test/mitmproxy/test_cmdline.py index 4fe2cf94..55627408 100644 --- a/test/mitmproxy/test_cmdline.py +++ b/test/mitmproxy/test_cmdline.py @@ -1,5 +1,4 @@ import argparse -import base64 from mitmproxy import cmdline from . import tutils @@ -36,34 +35,6 @@ def test_parse_replace_hook(): ) -def test_parse_server_spec(): - tutils.raises("Invalid server specification", cmdline.parse_server_spec, "") - assert cmdline.parse_server_spec( - "http://foo.com:88") == (b"http", (b"foo.com", 88)) - assert cmdline.parse_server_spec( - "http://foo.com") == (b"http", (b"foo.com", 80)) - assert cmdline.parse_server_spec( - "https://foo.com") == (b"https", (b"foo.com", 443)) - tutils.raises( - "Invalid server specification", - cmdline.parse_server_spec, - "foo.com") - tutils.raises( - "Invalid server specification", - cmdline.parse_server_spec, - "http://") - - -def test_parse_upstream_auth(): - tutils.raises("Invalid upstream auth specification", cmdline.parse_upstream_auth, "") - tutils.raises("Invalid upstream auth specification", cmdline.parse_upstream_auth, ":") - tutils.raises("Invalid upstream auth specification", cmdline.parse_upstream_auth, ":test") - assert cmdline.parse_upstream_auth( - "test:test") == b"Basic" + b" " + base64.b64encode(b"test:test") - assert cmdline.parse_upstream_auth( - "test:") == b"Basic" + b" " + base64.b64encode(b"test:") - - def test_parse_setheaders(): x = cmdline.parse_setheader("/foo/bar/voing") assert x == ("foo", "bar", "voing") diff --git a/test/mitmproxy/test_flow.py b/test/mitmproxy/test_flow.py index a44353e7..ee588a5c 100644 --- a/test/mitmproxy/test_flow.py +++ b/test/mitmproxy/test_flow.py @@ -640,12 +640,12 @@ class TestSerialize: def test_load_flows_reverse(self): r = self._treader() s = flow.State() - conf = ProxyConfig( - options.Options(), + opts = options.Options( mode="reverse", - upstream_server=("https", ("use-this-domain", 80)) + upstream_server="https://use-this-domain" ) - fm = flow.FlowMaster(None, DummyServer(conf), s) + conf = ProxyConfig(opts) + fm = flow.FlowMaster(opts, DummyServer(conf), s) fm.load_flows(r) assert s.flows[0].request.host == "use-this-domain" diff --git a/test/mitmproxy/test_proxy.py b/test/mitmproxy/test_proxy.py index 70ddfd40..16c4821c 100644 --- a/test/mitmproxy/test_proxy.py +++ b/test/mitmproxy/test_proxy.py @@ -85,21 +85,22 @@ class TestProcessProxyOptions: @mock.patch("mitmproxy.platform.resolver") def test_modes(self, _): - self.assert_noerr("-R", "http://localhost") - self.assert_err("expected one argument", "-R") - self.assert_err("Invalid server specification", "-R", "reverse") - - self.assert_noerr("-T") - - self.assert_noerr("-U", "http://localhost") - self.assert_err("expected one argument", "-U") - self.assert_err("Invalid server specification", "-U", "upstream") - - self.assert_noerr("--upstream-auth", "test:test") - self.assert_err("expected one argument", "--upstream-auth") - self.assert_err("Invalid upstream auth specification", "--upstream-auth", "test") - - self.assert_err("mutually exclusive", "-R", "http://localhost", "-T") + # self.assert_noerr("-R", "http://localhost") + # self.assert_err("expected one argument", "-R") + # self.assert_err("Invalid server specification", "-R", "reverse") + # + # self.assert_noerr("-T") + # + # self.assert_noerr("-U", "http://localhost") + # self.assert_err("expected one argument", "-U") + # self.assert_err("Invalid server specification", "-U", "upstream") + # + # self.assert_noerr("--upstream-auth", "test:test") + # self.assert_err("expected one argument", "--upstream-auth") + self.assert_err( + "Invalid upstream auth specification", "--upstream-auth", "test" + ) + # self.assert_err("mutually exclusive", "-R", "http://localhost", "-T") def test_socks_auth(self): self.assert_err("Proxy Authentication not supported in SOCKS mode.", "--socks", "--nonanonymous") @@ -187,7 +188,7 @@ class TestConnectionHandler: config = mock.Mock() root_layer = mock.Mock() root_layer.side_effect = RuntimeError - config.mode.return_value = root_layer + config.options.mode.return_value = root_layer channel = mock.Mock() def ask(_, x): diff --git a/test/mitmproxy/test_proxy_config.py b/test/mitmproxy/test_proxy_config.py new file mode 100644 index 00000000..2f31d502 --- /dev/null +++ b/test/mitmproxy/test_proxy_config.py @@ -0,0 +1,48 @@ +from . import tutils +import base64 +from mitmproxy.proxy import config + + +def test_parse_server_spec(): + tutils.raises( + "Invalid server specification", config.parse_server_spec, "" + ) + assert config.parse_server_spec("http://foo.com:88") == ( + b"http", (b"foo.com", 88) + ) + assert config.parse_server_spec("http://foo.com") == ( + b"http", (b"foo.com", 80) + ) + assert config.parse_server_spec("https://foo.com") == ( + b"https", (b"foo.com", 443) + ) + tutils.raises( + "Invalid server specification", + config.parse_server_spec, + "foo.com" + ) + tutils.raises( + "Invalid server specification", + config.parse_server_spec, + "http://" + ) + + +def test_parse_upstream_auth(): + tutils.raises( + "Invalid upstream auth specification", + config.parse_upstream_auth, + "" + ) + tutils.raises( + "Invalid upstream auth specification", + config.parse_upstream_auth, + ":" + ) + tutils.raises( + "Invalid upstream auth specification", + config.parse_upstream_auth, + ":test" + ) + assert config.parse_upstream_auth("test:test") == b"Basic" + b" " + base64.b64encode(b"test:test") + assert config.parse_upstream_auth("test:") == b"Basic" + b" " + base64.b64encode(b"test:") diff --git a/test/mitmproxy/test_server.py b/test/mitmproxy/test_server.py index 20372c92..ca3f8a97 100644 --- a/test/mitmproxy/test_server.py +++ b/test/mitmproxy/test_server.py @@ -15,7 +15,7 @@ from pathod import pathoc, pathod from mitmproxy.builtins import script from mitmproxy import controller -from mitmproxy.proxy.config import HostMatcher +from mitmproxy.proxy.config import HostMatcher, parse_server_spec from mitmproxy.models import Error, HTTPResponse, HTTPFlow from . import tutils, tservers @@ -485,7 +485,8 @@ class TestHttps2Http(tservers.ReverseProxyTest): @classmethod def get_proxy_config(cls): d, opts = super(TestHttps2Http, cls).get_proxy_config() - d["upstream_server"] = ("http", d["upstream_server"][1]) + s = parse_server_spec(opts.upstream_server) + opts.upstream_server = "http://%s" % s.address.decode("ascii") return d, opts def pathoc(self, ssl, sni=None): diff --git a/test/mitmproxy/tservers.py b/test/mitmproxy/tservers.py index ddb2922a..b7b1f001 100644 --- a/test/mitmproxy/tservers.py +++ b/test/mitmproxy/tservers.py @@ -200,7 +200,7 @@ class TransparentProxyTest(ProxyTestBase): @classmethod def get_proxy_config(cls): d, opts = ProxyTestBase.get_proxy_config() - d["mode"] = "transparent" + opts.mode = "transparent" return d, opts def pathod(self, spec, sni=None): @@ -232,11 +232,15 @@ class ReverseProxyTest(ProxyTestBase): @classmethod def get_proxy_config(cls): d, opts = ProxyTestBase.get_proxy_config() - d["upstream_server"] = ( - "https" if cls.ssl else "http", - ("127.0.0.1", cls.server.port) + opts.upstream_server = "".join( + [ + "https" if cls.ssl else "http", + "://", + "127.0.0.1:", + str(cls.server.port) + ] ) - d["mode"] = "reverse" + opts.mode = "reverse" return d, opts def pathoc(self, sni=None): @@ -267,7 +271,7 @@ class SocksModeTest(HTTPProxyTest): @classmethod def get_proxy_config(cls): d, opts = ProxyTestBase.get_proxy_config() - d["mode"] = "socks5" + opts.mode = "socks5" return d, opts @@ -314,9 +318,9 @@ class ChainProxyTest(ProxyTestBase): def get_proxy_config(cls): d, opts = super(ChainProxyTest, cls).get_proxy_config() if cls.chain: # First proxy is in normal mode. - d.update( + opts.update( mode="upstream", - upstream_server=("http", ("127.0.0.1", cls.chain[0].port)) + upstream_server="http://127.0.0.1:%s" % cls.chain[0].port ) return d, opts |