Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added authorization via LDAP #1329

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ extension-pkg-allow-list=
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code. (This is an alternative name to extension-pkg-allow-list
# for backward compatibility.)
extension-pkg-whitelist=
extension-pkg-whitelist=_ldap

# Return non-zero exit code if any of these messages/categories are detected,
# even if score is above --fail-under value. Syntax same as enable. Messages
Expand Down
38 changes: 28 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2355,8 +2355,12 @@ usage: -m [-h] [--tunnel-hostname TUNNEL_HOSTNAME] [--tunnel-port TUNNEL_PORT]
[--ca-signing-key-file CA_SIGNING_KEY_FILE]
[--auth-plugin AUTH_PLUGIN] [--cache-requests]
[--cache-by-content-type] [--cache-dir CACHE_DIR]
[--proxy-pool PROXY_POOL] [--enable-web-server]
[--enable-static-server] [--static-server-dir STATIC_SERVER_DIR]
[--ldap-server LDAP_SERVER] [--ldap-root-dn LDAP_ROOT_DN]
[--ldap-root-pw LDAP_ROOT_PW] [--ldap-base-dn LDAP_BASE_DN]
[--ldap-user-search LDAP_USER_SEARCH]
[--ldap-auth-timeout LDAP_AUTH_TIMEOUT] [--proxy-pool PROXY_POOL]
[--enable-web-server] [--enable-static-server]
[--static-server-dir STATIC_SERVER_DIR]
[--min-compression-length MIN_COMPRESSION_LENGTH]
[--enable-reverse-proxy] [--pac-file PAC_FILE]
[--pac-file-url-path PAC_FILE_URL_PATH]
Expand All @@ -2366,7 +2370,7 @@ usage: -m [-h] [--tunnel-hostname TUNNEL_HOSTNAME] [--tunnel-port TUNNEL_PORT]
[--filtered-client-ips FILTERED_CLIENT_IPS]
[--filtered-url-regex-config FILTERED_URL_REGEX_CONFIG]

proxy.py v2.4.4rc4.dev6+g4ee982a.d20221022
proxy.py v0.1.dev886+g9ba2ea7.d20230425

options:
-h, --help show this help message and exit
Expand Down Expand Up @@ -2489,10 +2493,10 @@ options:
Default: None. Signing certificate to use for signing
dynamically generated HTTPS certificates. If used,
must also pass --ca-key-file and --ca-signing-key-file
--ca-file CA_FILE Default: /Users/abhinavsingh/Dev/proxy.py/.venv/lib/py
thon3.10/site-packages/certifi/cacert.pem. Provide
path to custom CA bundle for peer certificate
verification
--ca-file CA_FILE Default:
/home/sv/MyProjects/proxy.py/venv/lib/python3.10/site-
packages/certifi/cacert.pem. Provide path to custom CA
bundle for peer certificate verification
--ca-signing-key-file CA_SIGNING_KEY_FILE
Default: None. CA signing key to use for dynamic
generation of HTTPS certificates. If used, must also
Expand All @@ -2507,9 +2511,23 @@ options:
from responses. Extracted content type is written to
the cache directory e.g. video.mp4.
--cache-dir CACHE_DIR
Default: /Users/abhinavsingh/.proxy/cache. Flag only
applicable when cache plugin is used with on-disk
storage.
Default: /home/sv/.proxy/cache. Flag only applicable
when cache plugin is used with on-disk storage.
--ldap-server LDAP_SERVER
Default: ldap://ldap.example.org. LDAP server address.
--ldap-root-dn LDAP_ROOT_DN
Default: uid=Manager,ou=People,dc=example,dc=com. LDAP
root dn.
--ldap-root-pw LDAP_ROOT_PW
Default: SecretPassword. LDAP root password.
--ldap-base-dn LDAP_BASE_DN
Default: ou=People,dc=example,dc=com. LDAP users base
DN.
--ldap-user-search LDAP_USER_SEARCH
Default: (&(uid={user})(accountStatus=active)). LDAP
user search filter.
--ldap-auth-timeout LDAP_AUTH_TIMEOUT
Default: 3600. LDAP user auth timeout.
--proxy-pool PROXY_POOL
List of upstream proxies to use in the pool
--enable-web-server Default: False. Whether to enable
Expand Down
2 changes: 2 additions & 0 deletions proxy/plugin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
Lua
"""
from .cache import CacheResponsesPlugin, BaseCacheResponsesPlugin
from .auth_ldap import LDAPAuthPlugin
from .shortlink import ShortLinkPlugin
from .proxy_pool import ProxyPoolPlugin
from .program_name import ProgramNamePlugin
Expand All @@ -36,6 +37,7 @@


__all__ = [
'LDAPAuthPlugin',
'CacheResponsesPlugin',
'BaseCacheResponsesPlugin',
'FilterByUpstreamHostPlugin',
Expand Down
121 changes: 121 additions & 0 deletions proxy/plugin/auth_ldap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# -*- 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.

.. spelling::

auth
http
ldap
"""
import base64
from time import time
from typing import Dict, Optional

import ldap

from ..http import httpHeaders
from ..http.proxy import HttpProxyBasePlugin
from ..common.flag import flags
from ..http.parser import HttpParser
from ..http.exception import ProxyAuthenticationFailed


DEFAULT_LDAP_SERVER = 'ldap://ldap.example.org'
DEFAULT_LDAP_ROOT_DN = 'uid=Manager,ou=People,dc=example,dc=com'
DEFAULT_LDAP_ROOT_PW = 'SecretPassword'
DEFAULT_LDAP_BASE_DN = 'ou=People,dc=example,dc=com'
DEFAULT_LDAP_USER_SEARCH = '(&(uid={user})(accountStatus=active))'
DEFAULT_LDAP_AUTH_TIMEOUT = 3600

flags.add_argument(
'--ldap-server',
type=str,
default=DEFAULT_LDAP_SERVER,
help='Default: ' + DEFAULT_LDAP_SERVER +
'. LDAP server address.',
)

flags.add_argument(
'--ldap-root-dn',
type=str,
default=DEFAULT_LDAP_ROOT_DN,
help='Default: ' + DEFAULT_LDAP_ROOT_DN +
'. LDAP root dn.',
)

flags.add_argument(
'--ldap-root-pw',
type=str,
default=DEFAULT_LDAP_ROOT_PW,
help='Default: ' + DEFAULT_LDAP_ROOT_PW +
'. LDAP root password.',
)

flags.add_argument(
'--ldap-base-dn',
type=str,
default=DEFAULT_LDAP_BASE_DN,
help='Default: ' + DEFAULT_LDAP_BASE_DN +
'. LDAP users base DN.',
)

flags.add_argument(
'--ldap-user-search',
type=str,
default=DEFAULT_LDAP_USER_SEARCH,
help='Default: ' + DEFAULT_LDAP_USER_SEARCH +
'. LDAP user search filter.',
)

flags.add_argument(
'--ldap-auth-timeout',
type=int,
default=DEFAULT_LDAP_AUTH_TIMEOUT,
help='Default: ' + str(DEFAULT_LDAP_AUTH_TIMEOUT) +
'. LDAP user auth timeout.',
)


class LDAPAuthPlugin(HttpProxyBasePlugin):
"""Performs proxy authentication through LDAP."""

__auth_pass__: Dict[bytes, float] = {}

def auth_user(self, user: str, password: str) -> bool:
ldap_connection = ldap.initialize(self.flags.ldap_server)
ldap_connection.bind_s(self.flags.ldap_root_dn, self.flags.ldap_root_pw)
search_filter = self.flags.ldap_user_search.format(user=user)
search_result = ldap_connection.search_s(self.flags.ldap_base_dn, ldap.SCOPE_SUBTREE, search_filter, ['uid'])
if len(search_result) != 1 and len(search_result[0]) != 2:
return False
try:
ldap_connection.bind_s(search_result[0][0], password)
except ldap.LDAPError:
return False
return True

def before_upstream_connection(
self, request: HttpParser,
) -> Optional[HttpParser]:
if not request.headers or httpHeaders.PROXY_AUTHORIZATION not in request.headers:
raise ProxyAuthenticationFailed()
parts = request.headers[httpHeaders.PROXY_AUTHORIZATION][1].split()
if len(parts) != 2 or parts[0].lower() != b'basic':
raise ProxyAuthenticationFailed()
elif self.__auth_pass__.get(parts[1], 0) > time():
return request
elif self.__auth_pass__.get(parts[1], 0) < time():
userpass = base64.b64decode(parts[1]).decode().split(':')
user = userpass[0]
password = userpass[-1]
if self.auth_user(user, password):
self.__auth_pass__[parts[1]] = time() + self.flags.ldap_auth_timeout
return request
raise ProxyAuthenticationFailed()
1 change: 1 addition & 0 deletions requirements-release.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
setuptools-scm == 6.3.2
twine==3.8.0
python-ldap==3.4.3
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. We should not add this as a release requirement
  2. Since auth_ldap is just an example plugin, it's upto user to install the dependency if they choose to use this plugin
  3. We kind of do similar thing for other plugins where a dependency might be required.

Otherwise, now to install proxy.py, users will also need to install python-ldap even when not using the plugin.

1 change: 1 addition & 0 deletions requirements-testing.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ tox==3.25.1
mccabe==0.6.1
pylint==2.13.7
rope==1.1.1
python-ldap==3.4.3
# Required by test_http2.py
httpx==0.22.0
h2==4.1.0
Expand Down
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ zip_safe = False

# These are required in actual runtime:
install_requires =
python-ldap==3.4.3

[options.entry_points]
console_scripts =
Expand Down