Added initial container definition for uffd-ldapd
Signed-off-by: Nis Wechselberg <enbewe@enbewe.de>
This commit is contained in:
parent
f36cc07b65
commit
27d78f4719
4 changed files with 402 additions and 0 deletions
21
Containerfile
Normal file
21
Containerfile
Normal file
|
@ -0,0 +1,21 @@
|
|||
FROM docker.io/library/debian:bookworm-20250520-slim
|
||||
|
||||
RUN apt-get -qq update && \
|
||||
apt-get -qq dist-upgrade && \
|
||||
apt-get -qq install ca-certificates
|
||||
|
||||
# Add key and config for cccv repository
|
||||
COPY cccv-archive-key.asc /etc/apt/trusted.gpg.d/
|
||||
COPY cccv-archive.list /etc/apt/sources.list.d/
|
||||
|
||||
# Install uffd-ldapd from (new) package sources
|
||||
RUN apt-get -qq update && \
|
||||
apt-get -qq install uffd-ldapd
|
||||
|
||||
# Copy the patched version of uffd-ldapd (for now) to allow SIGTERM handling
|
||||
COPY uffd-ldapd /usr/bin/uffd-ldapd
|
||||
|
||||
EXPOSE 389/tcp
|
||||
|
||||
ENTRYPOINT ["/usr/bin/uffd-ldapd"]
|
||||
CMD ["--socket-address", "0.0.0.0"]
|
41
cccv-archive-key.asc
Normal file
41
cccv-archive-key.asc
Normal file
|
@ -0,0 +1,41 @@
|
|||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
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
|
||||
JRWMzY9EoBOHB1PubZuzUNIUQeui1vyX
|
||||
=uRc5
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
1
cccv-archive.list
Normal file
1
cccv-archive.list
Normal file
|
@ -0,0 +1 @@
|
|||
deb [signed-by=/etc/apt/trusted.gpg.d/cccv-archive-key.asc] https://packages.cccv.de/uffd bookworm main
|
339
uffd-ldapd
Executable file
339
uffd-ldapd
Executable file
|
@ -0,0 +1,339 @@
|
|||
#!/usr/bin/python3
|
||||
import os
|
||||
import sys
|
||||
import socketserver
|
||||
import logging
|
||||
import socket
|
||||
import re
|
||||
import signal
|
||||
|
||||
import click
|
||||
import requests
|
||||
from cachecontrol import CacheControl
|
||||
from cachecontrol.heuristics import ExpiresAfter
|
||||
|
||||
import ldapserver
|
||||
from ldapserver.exceptions import LDAPInvalidCredentials, LDAPInsufficientAccessRights, LDAPUnwillingToPerform
|
||||
from ldapserver.schema import RFC2307BIS_SCHEMA, RFC2798_SCHEMA
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
CUSTOM_SCHEMA = (RFC2307BIS_SCHEMA|RFC2798_SCHEMA).extend(attribute_type_definitions=[
|
||||
# pylint: disable=line-too-long
|
||||
"( 1.2.840.113556.1.2.102 NAME 'memberOf' DESC 'Group that the entry belongs to' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 USAGE dSAOperation )"
|
||||
])
|
||||
|
||||
class UffdAPI:
|
||||
def __init__(self, baseurl, client_id, client_secret, cache_ttl=60):
|
||||
self.baseurl = baseurl
|
||||
self.client_id = client_id
|
||||
self.client_secret = client_secret
|
||||
self.session = requests.Session()
|
||||
self.session.auth = (client_id, client_secret)
|
||||
if cache_ttl:
|
||||
self.session = CacheControl(self.session, heuristic=ExpiresAfter(seconds=cache_ttl))
|
||||
|
||||
def get(self, endpoint, **kwargs):
|
||||
resp = self.session.get(self.baseurl + endpoint, params=kwargs)
|
||||
resp.raise_for_status()
|
||||
return resp.json()
|
||||
|
||||
def post(self, endpoint, **kwargs):
|
||||
resp = self.session.post(self.baseurl + endpoint, data=kwargs)
|
||||
resp.raise_for_status()
|
||||
return resp.json()
|
||||
|
||||
# pylint: disable=invalid-name,redefined-builtin
|
||||
def get_users(self, id=None, loginname=None, group=None):
|
||||
return self.get('/api/v1/getusers', id=id, loginname=loginname, group=group)
|
||||
|
||||
def get_groups(self, id=None, name=None, member=None):
|
||||
return self.get('/api/v1/getgroups', id=id, name=name, member=member)
|
||||
|
||||
def check_password(self, loginname, password):
|
||||
return self.post('/api/v1/checkpassword', loginname=loginname, password=password)
|
||||
|
||||
def normalize_user_loginname(loginname):
|
||||
# The equality matching rule for uid is caseIgnoreMatch. It prepares
|
||||
# attribute and assertion value according to LDAP stringprep with
|
||||
# case-folding.
|
||||
#
|
||||
# Uffd restricts loginnames to lower-case ASCII letters, digits,
|
||||
# underscores and dashes. None of these characters are changed or
|
||||
# rejetced by stringprep with case-folding. The effect stringprep has
|
||||
# on loginnames is that it adds a leading and a final SPACE character.
|
||||
#
|
||||
# The assertion value (the argument to this function) could however contain
|
||||
# characters that are mapped to SPACE or nothing for example. So we apply
|
||||
# stringprep to the assertion value and then strip the added leading and
|
||||
# final SPACE characters. Stringprep case-folds the input string to
|
||||
# lower-case.
|
||||
#
|
||||
# The resulting string can be compared to an actual loginname with simple
|
||||
# byte-for-byte or codepoint-for-codepoint comparison with or without
|
||||
# case-folding. It matches if and only if the loginname matches the input
|
||||
# value according to caseIgnoreMatch.
|
||||
try:
|
||||
return ldapserver.rfc4518_stringprep.prepare(loginname, ldapserver.rfc4518_stringprep.MatchingType.CASE_IGNORE_STRING).strip(' ')
|
||||
except ValueError: # Input value contains prohibited characters
|
||||
return None
|
||||
|
||||
def normalize_group_name(name):
|
||||
# Currently uffd has no restrictions for group names, but it is planned
|
||||
# to add restrictions similar to loginname restrictions.
|
||||
# See https://git.cccv.de/uffd/uffd/-/issues/127
|
||||
return normalize_user_loginname(name)
|
||||
|
||||
class UffdLDAPRequestHandler(ldapserver.LDAPRequestHandler):
|
||||
subschema = ldapserver.SubschemaSubentry(CUSTOM_SCHEMA, 'cn=Subschema')
|
||||
|
||||
# Overwritten before use
|
||||
api = None
|
||||
dn_base = None
|
||||
bind_password = None # if None anonymous reads are allowed
|
||||
group_filter_regex = None
|
||||
|
||||
def do_bind_simple_authenticated(self, dn, password):
|
||||
dn = self.subschema.DN.from_str(dn)
|
||||
if dn == self.subschema.DN('cn=service,ou=system') + self.dn_base and password == self.bind_password:
|
||||
return True
|
||||
if not dn.is_direct_child_of(self.subschema.DN('ou=users') + self.dn_base) or len(dn[0]) != 1 or dn[0][0].attribute != 'uid':
|
||||
raise LDAPInvalidCredentials()
|
||||
try:
|
||||
if self.api.check_password(loginname=dn[0][0].value, password=password):
|
||||
return True
|
||||
except requests.exceptions.HTTPError as exc:
|
||||
if exc.response.status_code == 403: # We don't have "checkpassword" scope
|
||||
raise LDAPInsufficientAccessRights() from exc
|
||||
if exc.response.status_code == 429: # Ratelimited
|
||||
raise LDAPUnwillingToPerform('Too Many Requests') from exc
|
||||
raise exc
|
||||
raise LDAPInvalidCredentials()
|
||||
|
||||
supports_sasl_plain = True
|
||||
|
||||
def do_bind_sasl_plain(self, identity, password, authzid=None):
|
||||
if authzid is not None and identity != authzid:
|
||||
raise LDAPInvalidCredentials()
|
||||
try:
|
||||
if self.api.check_password(loginname=identity, password=password):
|
||||
return True
|
||||
except requests.exceptions.HTTPError as exc:
|
||||
if exc.response.status_code == 403: # We don't have "checkpassword" scope
|
||||
raise LDAPInsufficientAccessRights() from exc
|
||||
if exc.response.status_code == 429: # Ratelimited
|
||||
raise LDAPUnwillingToPerform('Too Many Requests') from exc
|
||||
raise exc
|
||||
raise LDAPInvalidCredentials()
|
||||
|
||||
def do_search(self, baseobj, scope, filterobj):
|
||||
yield from super().do_search(baseobj, scope, filterobj)
|
||||
if self.bind_object or self.bind_password is None:
|
||||
yield from self.do_search_static()
|
||||
yield from self.do_search_users(baseobj, scope, filterobj)
|
||||
yield from self.do_search_groups(baseobj, scope, filterobj)
|
||||
|
||||
def do_search_static(self):
|
||||
base_attrs = {
|
||||
'objectClass': ['top', 'dcObject', 'organization'],
|
||||
'structuralObjectClass': ['organization'],
|
||||
}
|
||||
for rdnassertion in self.dn_base[0]: # pylint: disable=unsubscriptable-object
|
||||
base_attrs[rdnassertion.attribute] = [rdnassertion.value]
|
||||
yield self.subschema.ObjectEntry(self.dn_base, **base_attrs)
|
||||
yield self.subschema.ObjectEntry(self.subschema.DN('ou=users') + self.dn_base,
|
||||
ou=['users'],
|
||||
objectClass=['top', 'organizationalUnit'],
|
||||
structuralObjectClass=['organizationalUnit'],
|
||||
)
|
||||
yield self.subschema.ObjectEntry(self.subschema.DN('ou=groups') + self.dn_base,
|
||||
ou=['groups'],
|
||||
objectClass=['top', 'organizationalUnit'],
|
||||
structuralObjectClass=['organizationalUnit'],
|
||||
)
|
||||
yield self.subschema.ObjectEntry(self.subschema.DN('ou=system') + self.dn_base,
|
||||
ou=['system'],
|
||||
objectClass=['top', 'organizationalUnit'],
|
||||
structuralObjectClass=['organizationalUnit'],
|
||||
)
|
||||
yield self.subschema.ObjectEntry(self.subschema.DN('cn=service,ou=system') + self.dn_base,
|
||||
cn=['service'],
|
||||
objectClass=['top', 'organizationalRole', 'simpleSecurityObject'],
|
||||
structuralObjectClass=['organizationalRole'],
|
||||
)
|
||||
|
||||
def do_search_users(self, baseobj, scope, filterobj):
|
||||
template = self.subschema.EntryTemplate(self.subschema.DN(self.dn_base, ou='users'), 'uid',
|
||||
structuralObjectClass=['inetorgperson'],
|
||||
objectClass=['top', 'inetorgperson', 'organizationalperson', 'person', 'posixaccount'],
|
||||
cn=ldapserver.WILDCARD,
|
||||
displayname=ldapserver.WILDCARD,
|
||||
givenname=ldapserver.WILDCARD,
|
||||
homeDirectory=ldapserver.WILDCARD,
|
||||
mail=ldapserver.WILDCARD,
|
||||
sn=[' '],
|
||||
uid=ldapserver.WILDCARD,
|
||||
uidNumber=ldapserver.WILDCARD,
|
||||
memberOf=ldapserver.WILDCARD,
|
||||
)
|
||||
if not template.match_search(baseobj, scope, filterobj):
|
||||
return
|
||||
constraints = template.extract_search_constraints(baseobj, scope, filterobj)
|
||||
request_params = {}
|
||||
if 'uid' in constraints:
|
||||
request_params = {'loginname': normalize_user_loginname(constraints['uid'][0])}
|
||||
elif 'uidnumber' in constraints:
|
||||
request_params = {'id': constraints['uidnumber'][0]}
|
||||
elif 'memberof' in constraints:
|
||||
for value in constraints['memberof']:
|
||||
if value.is_direct_child_of(self.subschema.DN(self.dn_base, ou='groups')) and value.object_attribute == 'cn':
|
||||
request_params = {'group': normalize_group_name(value.object_value)}
|
||||
break
|
||||
if 'group' in request_params and not self.group_filter_regex.match(request_params['group']):
|
||||
return
|
||||
for user in self.api.get_users(**request_params):
|
||||
yield template.create_entry(user['loginname'],
|
||||
cn=[user['displayname']],
|
||||
displayname=[user['displayname']],
|
||||
givenname=[user['displayname']],
|
||||
homeDirectory=['/home/'+user['loginname']],
|
||||
mail=[user['email']],
|
||||
uid=[user['loginname']],
|
||||
uidNumber=[user['id']],
|
||||
memberOf=[self.subschema.DN(self.subschema.DN(self.dn_base, ou='groups'), cn=group)
|
||||
for group in user['groups']
|
||||
if self.group_filter_regex.match(group)],
|
||||
)
|
||||
|
||||
def do_search_groups(self, baseobj, scope, filterobj):
|
||||
template = self.subschema.EntryTemplate(self.subschema.DN(self.dn_base, ou='groups'), 'cn',
|
||||
structuralObjectClass=['groupOfUniqueNames'],
|
||||
objectClass=['top', 'groupOfUniqueNames', 'posixGroup'],
|
||||
cn=ldapserver.WILDCARD,
|
||||
description=[' '],
|
||||
gidNumber=ldapserver.WILDCARD,
|
||||
uniqueMember=ldapserver.WILDCARD,
|
||||
)
|
||||
if not template.match_search(baseobj, scope, filterobj):
|
||||
return
|
||||
constraints = template.extract_search_constraints(baseobj, scope, filterobj)
|
||||
request_params = {}
|
||||
if 'cn' in constraints:
|
||||
request_params = {'name': normalize_group_name(constraints['cn'][0])}
|
||||
elif 'gidnumber' in constraints:
|
||||
request_params = {'id': constraints['gidnumber'][0]}
|
||||
elif 'uniquemember' in constraints:
|
||||
for value in constraints['uniquemember']:
|
||||
if value.is_direct_child_of(self.subschema.DN(self.dn_base, ou='users')) and value.object_attribute == 'uid':
|
||||
request_params = {'member': normalize_user_loginname(value.object_value)}
|
||||
break
|
||||
if 'name' in request_params and not self.group_filter_regex.match(request_params['name']):
|
||||
return
|
||||
for group in self.api.get_groups(**request_params):
|
||||
if not self.group_filter_regex.match(group['name']):
|
||||
continue
|
||||
yield template.create_entry(group['name'],
|
||||
cn=[group['name']],
|
||||
gidNumber=[group['id']],
|
||||
uniqueMember=[self.subschema.DN(self.subschema.DN(self.dn_base, ou='users'), uid=user) for user in group['members']],
|
||||
)
|
||||
|
||||
def make_requesthandler(api, dn_base, bind_password=None, group_filter_regex=None):
|
||||
class RequestHandler(UffdLDAPRequestHandler):
|
||||
pass
|
||||
dn_base = RequestHandler.subschema.DN.from_str(dn_base)
|
||||
RequestHandler.api = api
|
||||
RequestHandler.dn_base = dn_base
|
||||
RequestHandler.bind_password = bind_password.encode() if bind_password else None
|
||||
RequestHandler.group_filter_regex = re.compile(group_filter_regex) if group_filter_regex else re.compile('')
|
||||
return RequestHandler
|
||||
|
||||
class FilenoUnixStreamServer(socketserver.UnixStreamServer):
|
||||
def __init__(self, fd, RequestHandlerClass, bind_and_activate=True):
|
||||
self.server_fd = fd
|
||||
super().__init__(None, RequestHandlerClass, bind_and_activate=bind_and_activate)
|
||||
|
||||
def server_bind(self):
|
||||
self.socket.close() # UnixStreamServer.__init__ creates an unbound socket
|
||||
self.socket = socket.fromfd(self.server_fd, socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
self.server_address = self.socket.getsockname()
|
||||
|
||||
class ThreadingFilenoUnixStreamServer(socketserver.ThreadingMixIn, FilenoUnixStreamServer):
|
||||
pass
|
||||
|
||||
def cleanup_unix_socket(path):
|
||||
if not os.path.exists(path):
|
||||
return
|
||||
conn = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
try:
|
||||
conn.connect(path)
|
||||
except ConnectionRefusedError:
|
||||
os.remove(path)
|
||||
conn.close()
|
||||
|
||||
def parse_network_address(addr):
|
||||
port = '389'
|
||||
if addr.startswith('['):
|
||||
addr, remainder = addr[1:].split(']', 1)
|
||||
if remainder.startswith(':'):
|
||||
port = remainder[1:]
|
||||
elif ':' in addr:
|
||||
addr, port = addr.split(':')
|
||||
return addr, port
|
||||
|
||||
class StdoutFilter(logging.Filter):
|
||||
def filter(self, record):
|
||||
return record.levelno <= logging.INFO
|
||||
|
||||
def sigterm_handler(signum, frame):
|
||||
logger.info("Received SIGTERM, shutting down gracefully ...")
|
||||
sys.exit(0)
|
||||
|
||||
# pylint: disable=line-too-long
|
||||
@click.command(help='LDAP proxy for integrating LDAP service with uffd SSO. Supports user and group searches and as well as binds with user passwords.')
|
||||
@click.option('--socket-address', help='Host and port "ip:port" to listen on')
|
||||
@click.option('--socket-path', type=click.Path(), help='Path for UNIX domain socket')
|
||||
@click.option('--socket-fd', type=int, help='Use fd number as server socket (alternative to --socket-path)')
|
||||
@click.option('--api-url', required=True, help='Uffd base URL without API prefix or trailing slash (e.g. https://example.com)')
|
||||
@click.option('--api-user', required=True, help='API user/client id')
|
||||
@click.option('--api-secret', required=True, help='API secret, do not set this on the command-line, use environment variable SERVER_API_SECRET instead')
|
||||
@click.option('--cache-ttl', default=60, help='Time-to-live for API response caching in seconds')
|
||||
@click.option('--base-dn', required=True, help='Base DN for user, group and system objects. E.g. "dc=example,dc=com"')
|
||||
@click.option('--bind-password', help='Authentication password for the service connection to LDAP. Bind DN is always "cn=service,ou=system,BASEDN". If set, anonymous access is disabled.')
|
||||
@click.option('--group-filter-regex', help='Python regular expression that group names must match for the group to be visible to LDAP clients')
|
||||
def main(socket_address, socket_path, socket_fd, api_url, api_user, api_secret, cache_ttl, base_dn, bind_password, group_filter_regex):
|
||||
# Register signal handler for proper SIGTERM handling
|
||||
signal.signal(signal.SIGTERM, sigterm_handler)
|
||||
|
||||
# pylint: disable=too-many-locals
|
||||
if (socket_address is not None) \
|
||||
+ (socket_path is not None) \
|
||||
+ (socket_fd is not None) != 1:
|
||||
raise click.ClickException('Either --socket-address, --socket-path or --socket-fd must be specified')
|
||||
|
||||
stdout_handler = logging.StreamHandler(sys.stdout)
|
||||
stdout_handler.setLevel(logging.INFO)
|
||||
stdout_handler.addFilter(StdoutFilter())
|
||||
stderr_handler = logging.StreamHandler(sys.stderr)
|
||||
stderr_handler.setLevel(logging.WARNING)
|
||||
root_logger = logging.getLogger()
|
||||
root_logger.setLevel(logging.INFO)
|
||||
root_logger.addHandler(stdout_handler)
|
||||
root_logger.addHandler(stderr_handler)
|
||||
|
||||
api = UffdAPI(api_url, api_user, api_secret, cache_ttl)
|
||||
RequestHandler = make_requesthandler(api, base_dn, bind_password, group_filter_regex)
|
||||
if socket_address is not None:
|
||||
host, port = parse_network_address(socket_address)
|
||||
server = socketserver.ThreadingTCPServer((host, int(port)), RequestHandler)
|
||||
elif socket_path is not None:
|
||||
cleanup_unix_socket(socket_path)
|
||||
server = socketserver.ThreadingUnixStreamServer(socket_path, RequestHandler)
|
||||
else:
|
||||
server = ThreadingFilenoUnixStreamServer(socket_fd, RequestHandler)
|
||||
server.serve_forever()
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Pylint does not seem to understand click's decorators
|
||||
# pylint: disable=unexpected-keyword-arg,no-value-for-parameter
|
||||
main(auto_envvar_prefix='SERVER')
|
Loading…
Add table
Add a link
Reference in a new issue