From 5fb18ad275c701971e447c32b1a60f053edf2b35 Mon Sep 17 00:00:00 2001 From: Charles d'Hondt Date: Fri, 28 Apr 2017 16:19:27 +0200 Subject: [PATCH 1/9] Added LDAP Auth --- mitmproxy/addons/proxyauth.py | 30 ++++++++++++++++++++++++- mitmproxy/options.py | 8 +++++-- test/mitmproxy/addons/test_proxyauth.py | 26 +++++++++++++++++++++ 3 files changed, 61 insertions(+), 3 deletions(-) diff --git a/mitmproxy/addons/proxyauth.py b/mitmproxy/addons/proxyauth.py index fecdcb843..4ca69bc90 100644 --- a/mitmproxy/addons/proxyauth.py +++ b/mitmproxy/addons/proxyauth.py @@ -1,5 +1,6 @@ import binascii import weakref +import ldap3 from typing import Optional from typing import MutableMapping # noqa from typing import Tuple @@ -46,11 +47,12 @@ class ProxyAuth: self.nonanonymous = False self.htpasswd = None self.singleuser = None + self.ldapserver = None self.authenticated = weakref.WeakKeyDictionary() # type: MutableMapping[connections.ClientConnection, Tuple[str, str]] """Contains all connections that are permanently authenticated after an HTTP CONNECT""" def enabled(self) -> bool: - return any([self.nonanonymous, self.htpasswd, self.singleuser]) + return any([self.nonanonymous, self.htpasswd, self.singleuser, self.ldapserver]) def is_proxy_auth(self) -> bool: """ @@ -99,6 +101,21 @@ class ProxyAuth: elif self.htpasswd: if self.htpasswd.check_password(username, password): return username, password + elif self.ldapserver: + if not username or not password: + return None + dn = ctx.options.proxyauth.split(":")[2] + parts = dn.split("?") + conn = ldap3.Connection( + self.ldapserver, + parts[0] + username + parts[1], + password, + auto_bind=True) + if conn: + conn.search(parts[1][1:], '('+parts[0]+username+')', attributes=['objectclass']) + if ctx.options.proxyauth.split(":")[3] in conn.entries[0]['objectclass']: + conn.unbind() + return username, password return None @@ -129,6 +146,17 @@ class ProxyAuth: raise exceptions.OptionsError( "Could not open htpasswd file: %s" % p ) + elif ctx.options.proxyauth.startswith("ldap"): + parts = ctx.options.proxyauth.split(":") + if len(parts) != 4: + raise exceptions.OptionsError( + "Invalid ldap specification" + ) + if parts[0] == "ldaps": + server = ldap3.Server(parts[1], use_ssl=True) + elif parts[0] == "ldap": + server = ldap3.Server(parts[1]) + self.ldapserver = server else: parts = ctx.options.proxyauth.split(':') if len(parts) != 2: diff --git a/mitmproxy/options.py b/mitmproxy/options.py index e477bed52..289360f17 100644 --- a/mitmproxy/options.py +++ b/mitmproxy/options.py @@ -199,8 +199,12 @@ class Options(optmanager.OptManager): """ Require proxy authentication. Value may be "any" to require authenticaiton but accept any credentials, start with "@" to specify - a path to an Apache htpasswd file, or be of the form - "username:password". + a path to an Apache htpasswd file, be of the form + "username:password", or be of the form + "ldap[s]:url_server_ldap:dn:group", the dn must include "?", which will be + the username prompted, and the group is the group the user must belong to + an example would be + "ldap:ldap.forumsys.com:uid=?,dc=example,dc=com:person". """ ) self.add_option( diff --git a/test/mitmproxy/addons/test_proxyauth.py b/test/mitmproxy/addons/test_proxyauth.py index 866217096..6c36b7e84 100644 --- a/test/mitmproxy/addons/test_proxyauth.py +++ b/test/mitmproxy/addons/test_proxyauth.py @@ -1,4 +1,5 @@ import binascii +import ldap3 import pytest @@ -41,6 +42,13 @@ def test_configure(): ctx.configure(up, proxyauth=None) assert not up.nonanonymous + ctx.configure(up, proxyauth="ldap:ldap.forumsys.com:uid=?,dc=example,dc=com:person") + assert up.ldapserver + ctx.configure(up, proxyauth="ldaps:ldap.forumsys.com:uid=?,dc=example,dc=com:person") + assert up.ldapserver + with pytest.raises(exceptions.OptionsError): + ctx.configure(up, proxyauth="ldapldap.forumsys.com:uid=?dc=example,dc=com:person") + with pytest.raises(exceptions.OptionsError): ctx.configure( up, @@ -109,6 +117,24 @@ def test_check(): ) assert not up.check(f) + ctx.configure( + up, + proxyauth="ldap:ldap.forumsys.com:uid=?,dc=example,dc=com:person" + ) + f.request.headers["Proxy-Authorization"] = proxyauth.mkauth( + "einstein", "password" + ) + assert up.check(f) + f.request.headers["Proxy-Authorization"] = proxyauth.mkauth( + "", "" + ) + assert not up.check(f) + with pytest.raises(ldap3.core.exceptions.LDAPBindError): + f.request.headers["Proxy-Authorization"] = proxyauth.mkauth( + "einstein", "foo" + ) + assert not up.check(f) + def test_authenticate(): up = proxyauth.ProxyAuth() From 29c1f303d6c7ba8a0318cf0694189af9da0b4308 Mon Sep 17 00:00:00 2001 From: Charles d'Hondt Date: Fri, 28 Apr 2017 16:23:32 +0200 Subject: [PATCH 2/9] Fixed typo --- test/mitmproxy/addons/test_proxyauth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/mitmproxy/addons/test_proxyauth.py b/test/mitmproxy/addons/test_proxyauth.py index 6c36b7e84..766729566 100644 --- a/test/mitmproxy/addons/test_proxyauth.py +++ b/test/mitmproxy/addons/test_proxyauth.py @@ -133,7 +133,7 @@ def test_check(): f.request.headers["Proxy-Authorization"] = proxyauth.mkauth( "einstein", "foo" ) - assert not up.check(f) + assert not up.check(f) def test_authenticate(): From f91ed91bf1408c9d998b27c478edb83b0e026572 Mon Sep 17 00:00:00 2001 From: Charles d'Hondt Date: Fri, 28 Apr 2017 16:40:34 +0200 Subject: [PATCH 3/9] fix --- mitmproxy/addons/proxyauth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mitmproxy/addons/proxyauth.py b/mitmproxy/addons/proxyauth.py index 4ca69bc90..eac216c35 100644 --- a/mitmproxy/addons/proxyauth.py +++ b/mitmproxy/addons/proxyauth.py @@ -114,7 +114,6 @@ class ProxyAuth: if conn: conn.search(parts[1][1:], '('+parts[0]+username+')', attributes=['objectclass']) if ctx.options.proxyauth.split(":")[3] in conn.entries[0]['objectclass']: - conn.unbind() return username, password return None @@ -135,6 +134,7 @@ class ProxyAuth: self.nonanonymous = False self.singleuser = None self.htpasswd = None + self.ldapserver = None if ctx.options.proxyauth: if ctx.options.proxyauth == "any": self.nonanonymous = True From 6d3837fd54a30db7b2f5d34eefd581156831c027 Mon Sep 17 00:00:00 2001 From: Charles d'Hondt Date: Fri, 28 Apr 2017 16:44:50 +0200 Subject: [PATCH 4/9] fix --- test/mitmproxy/addons/test_proxyauth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/mitmproxy/addons/test_proxyauth.py b/test/mitmproxy/addons/test_proxyauth.py index 766729566..6c36b7e84 100644 --- a/test/mitmproxy/addons/test_proxyauth.py +++ b/test/mitmproxy/addons/test_proxyauth.py @@ -133,7 +133,7 @@ def test_check(): f.request.headers["Proxy-Authorization"] = proxyauth.mkauth( "einstein", "foo" ) - assert not up.check(f) + assert not up.check(f) def test_authenticate(): From 65202f5f1c2d9f55e8d72e72656a6d43e7947f88 Mon Sep 17 00:00:00 2001 From: Charles d'Hondt Date: Fri, 28 Apr 2017 17:00:21 +0200 Subject: [PATCH 5/9] Added ldapsss vef --- mitmproxy/addons/proxyauth.py | 6 +++++- test/mitmproxy/addons/test_proxyauth.py | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/mitmproxy/addons/proxyauth.py b/mitmproxy/addons/proxyauth.py index eac216c35..281a1a8fd 100644 --- a/mitmproxy/addons/proxyauth.py +++ b/mitmproxy/addons/proxyauth.py @@ -112,7 +112,7 @@ class ProxyAuth: password, auto_bind=True) if conn: - conn.search(parts[1][1:], '('+parts[0]+username+')', attributes=['objectclass']) + conn.search(parts[1][1:], '(' + parts[0] + username + ')', attributes=['objectclass']) if ctx.options.proxyauth.split(":")[3] in conn.entries[0]['objectclass']: return username, password @@ -156,6 +156,10 @@ class ProxyAuth: server = ldap3.Server(parts[1], use_ssl=True) elif parts[0] == "ldap": server = ldap3.Server(parts[1]) + else: + raise exceptions.OptionsError( + "Invalid ldap specfication on the first part" + ) self.ldapserver = server else: parts = ctx.options.proxyauth.split(':') diff --git a/test/mitmproxy/addons/test_proxyauth.py b/test/mitmproxy/addons/test_proxyauth.py index 6c36b7e84..6311e97e9 100644 --- a/test/mitmproxy/addons/test_proxyauth.py +++ b/test/mitmproxy/addons/test_proxyauth.py @@ -46,8 +46,12 @@ def test_configure(): assert up.ldapserver ctx.configure(up, proxyauth="ldaps:ldap.forumsys.com:uid=?,dc=example,dc=com:person") assert up.ldapserver + with pytest.raises(exceptions.OptionsError): - ctx.configure(up, proxyauth="ldapldap.forumsys.com:uid=?dc=example,dc=com:person") + ctx.configure(up, proxyauth="ldap:ldap.forumsys.comuid=?dc=example,dc=com:person") + + with pytest.raises(exceptions.OptionsError): + ctx.configure(up, proxyauth="ldapssssssss:ldap.forumsys.com:uid=?,dc=example,dc=com:person") with pytest.raises(exceptions.OptionsError): ctx.configure( From 3f094a54ce282c65c7d955aff7750c0cfeb979d5 Mon Sep 17 00:00:00 2001 From: Charles d'Hondt Date: Tue, 2 May 2017 10:18:49 +0200 Subject: [PATCH 6/9] added ldap3 to setup.py --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 68f82a1d8..8f032b37a 100644 --- a/setup.py +++ b/setup.py @@ -71,6 +71,7 @@ setup( "hyperframe>=5.0, <6", "jsbeautifier>=1.6.3, <1.7", "kaitaistruct>=0.7, <0.8", + "ldap3>=2.2.0, <2.2.1", "passlib>=1.6.5, <1.8", "pyasn1>=0.1.9, <0.3", "pyOpenSSL>=16.0, <17.1", From a68808294dd3d0dee698b5355c1a6e786f4f9475 Mon Sep 17 00:00:00 2001 From: Charles d'Hondt Date: Tue, 2 May 2017 10:35:56 +0200 Subject: [PATCH 7/9] lint checks --- mitmproxy/addons/proxyauth.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mitmproxy/addons/proxyauth.py b/mitmproxy/addons/proxyauth.py index 281a1a8fd..8e90dd088 100644 --- a/mitmproxy/addons/proxyauth.py +++ b/mitmproxy/addons/proxyauth.py @@ -107,10 +107,10 @@ class ProxyAuth: dn = ctx.options.proxyauth.split(":")[2] parts = dn.split("?") conn = ldap3.Connection( - self.ldapserver, - parts[0] + username + parts[1], - password, - auto_bind=True) + self.ldapserver, + parts[0] + username + parts[1], + password, + auto_bind=True) if conn: conn.search(parts[1][1:], '(' + parts[0] + username + ')', attributes=['objectclass']) if ctx.options.proxyauth.split(":")[3] in conn.entries[0]['objectclass']: From f67d9adc314c7723c1d3be3733dbe1ae2495b86b Mon Sep 17 00:00:00 2001 From: Charles d'Hondt Date: Thu, 4 May 2017 13:25:15 +0200 Subject: [PATCH 8/9] Added ldap mock test --- mitmproxy/addons/proxyauth.py | 1 - setup.py | 2 +- test/mitmproxy/addons/test_proxyauth.py | 31 +++++++++++++++---------- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/mitmproxy/addons/proxyauth.py b/mitmproxy/addons/proxyauth.py index 8e90dd088..e656421e1 100644 --- a/mitmproxy/addons/proxyauth.py +++ b/mitmproxy/addons/proxyauth.py @@ -115,7 +115,6 @@ class ProxyAuth: conn.search(parts[1][1:], '(' + parts[0] + username + ')', attributes=['objectclass']) if ctx.options.proxyauth.split(":")[3] in conn.entries[0]['objectclass']: return username, password - return None def authenticate(self, f: http.HTTPFlow) -> bool: diff --git a/setup.py b/setup.py index 8f032b37a..c8a1fe206 100644 --- a/setup.py +++ b/setup.py @@ -71,7 +71,7 @@ setup( "hyperframe>=5.0, <6", "jsbeautifier>=1.6.3, <1.7", "kaitaistruct>=0.7, <0.8", - "ldap3>=2.2.0, <2.2.1", + "ldap3>=2.2.0, <2.2.3", "passlib>=1.6.5, <1.8", "pyasn1>=0.1.9, <0.3", "pyOpenSSL>=16.0, <17.1", diff --git a/test/mitmproxy/addons/test_proxyauth.py b/test/mitmproxy/addons/test_proxyauth.py index 6311e97e9..fa02d119d 100644 --- a/test/mitmproxy/addons/test_proxyauth.py +++ b/test/mitmproxy/addons/test_proxyauth.py @@ -2,6 +2,7 @@ import binascii import ldap3 import pytest +from _pytest.monkeypatch import MonkeyPatch from mitmproxy import exceptions from mitmproxy.addons import proxyauth @@ -42,16 +43,20 @@ def test_configure(): ctx.configure(up, proxyauth=None) assert not up.nonanonymous - ctx.configure(up, proxyauth="ldap:ldap.forumsys.com:uid=?,dc=example,dc=com:person") + ctx.configure(up, proxyauth="ldap:fake_server:fake_dn:fake_group") assert up.ldapserver - ctx.configure(up, proxyauth="ldaps:ldap.forumsys.com:uid=?,dc=example,dc=com:person") + + + ctx.configure(up, proxyauth="ldap:fake_server:uid=?,dc=example,dc=com:person") + assert up.ldapserver + ctx.configure(up, proxyauth="ldaps:fake_server.com:uid=?,dc=example,dc=com:person") assert up.ldapserver with pytest.raises(exceptions.OptionsError): - ctx.configure(up, proxyauth="ldap:ldap.forumsys.comuid=?dc=example,dc=com:person") + ctx.configure(up, proxyauth="ldap:fake_serveruid=?dc=example,dc=com:person") with pytest.raises(exceptions.OptionsError): - ctx.configure(up, proxyauth="ldapssssssss:ldap.forumsys.com:uid=?,dc=example,dc=com:person") + ctx.configure(up, proxyauth="ldapssssssss:fake_server.com:uid=?,dc=example,dc=com:person") with pytest.raises(exceptions.OptionsError): ctx.configure( @@ -79,7 +84,7 @@ def test_configure(): ctx.configure(up, proxyauth="any", mode="socks5") -def test_check(): +def test_check(monkeypatch): up = proxyauth.ProxyAuth() with taddons.context() as ctx: ctx.configure(up, proxyauth="any", mode="regular") @@ -121,23 +126,25 @@ def test_check(): ) assert not up.check(f) + ctx.configure( up, - proxyauth="ldap:ldap.forumsys.com:uid=?,dc=example,dc=com:person" + proxyauth="ldap:fake-server:cn=?,ou=test,o=lab:test" ) + conn = ldap3.Connection("fake-server", user="cn=user0,ou=test,o=lab", password="password", client_strategy=ldap3.MOCK_SYNC) + conn.bind() + conn.strategy.add_entry('cn=user0,ou=test,o=lab', {'userPassword': 'test0', 'sn': 'user0_sn', 'revision': 0, 'objectClass': 'test'}) + def conn_mp(ldap, user, password, **kwargs): + return conn + monkeypatch.setattr(ldap3, "Connection", conn_mp) f.request.headers["Proxy-Authorization"] = proxyauth.mkauth( - "einstein", "password" + "user0", "test0" ) assert up.check(f) f.request.headers["Proxy-Authorization"] = proxyauth.mkauth( "", "" ) assert not up.check(f) - with pytest.raises(ldap3.core.exceptions.LDAPBindError): - f.request.headers["Proxy-Authorization"] = proxyauth.mkauth( - "einstein", "foo" - ) - assert not up.check(f) def test_authenticate(): From 154e8ac0fc1b1553beaba2a73de1130e681a61c0 Mon Sep 17 00:00:00 2001 From: Charles d'Hondt Date: Thu, 4 May 2017 13:39:48 +0200 Subject: [PATCH 9/9] fixed lint --- setup.py | 2 +- test/mitmproxy/addons/test_proxyauth.py | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index c8a1fe206..b75466fbc 100644 --- a/setup.py +++ b/setup.py @@ -71,7 +71,7 @@ setup( "hyperframe>=5.0, <6", "jsbeautifier>=1.6.3, <1.7", "kaitaistruct>=0.7, <0.8", - "ldap3>=2.2.0, <2.2.3", + "ldap3>=2.2.0, <=2.2.3", "passlib>=1.6.5, <1.8", "pyasn1>=0.1.9, <0.3", "pyOpenSSL>=16.0, <17.1", diff --git a/test/mitmproxy/addons/test_proxyauth.py b/test/mitmproxy/addons/test_proxyauth.py index fa02d119d..58e059add 100644 --- a/test/mitmproxy/addons/test_proxyauth.py +++ b/test/mitmproxy/addons/test_proxyauth.py @@ -2,7 +2,6 @@ import binascii import ldap3 import pytest -from _pytest.monkeypatch import MonkeyPatch from mitmproxy import exceptions from mitmproxy.addons import proxyauth @@ -46,7 +45,6 @@ def test_configure(): ctx.configure(up, proxyauth="ldap:fake_server:fake_dn:fake_group") assert up.ldapserver - ctx.configure(up, proxyauth="ldap:fake_server:uid=?,dc=example,dc=com:person") assert up.ldapserver ctx.configure(up, proxyauth="ldaps:fake_server.com:uid=?,dc=example,dc=com:person") @@ -126,7 +124,6 @@ def test_check(monkeypatch): ) assert not up.check(f) - ctx.configure( up, proxyauth="ldap:fake-server:cn=?,ou=test,o=lab:test" @@ -134,8 +131,10 @@ def test_check(monkeypatch): conn = ldap3.Connection("fake-server", user="cn=user0,ou=test,o=lab", password="password", client_strategy=ldap3.MOCK_SYNC) conn.bind() conn.strategy.add_entry('cn=user0,ou=test,o=lab', {'userPassword': 'test0', 'sn': 'user0_sn', 'revision': 0, 'objectClass': 'test'}) + def conn_mp(ldap, user, password, **kwargs): return conn + monkeypatch.setattr(ldap3, "Connection", conn_mp) f.request.headers["Proxy-Authorization"] = proxyauth.mkauth( "user0", "test0"