From ddc4990b69d62c767ac983c2bca2303d01301841 Mon Sep 17 00:00:00 2001 From: Milov Dmitriy Date: Tue, 16 Dec 2025 18:34:55 +0300 Subject: [PATCH 01/15] add: global ldap service task_1046 --- .package/docker-compose.yml | 2 +- app/config.py | 23 +++++ app/ioc.py | 30 ++++++ app/multidirectory.py | 37 +++++++- docker-compose.test.yml | 24 ++++- docker-compose.yml | 60 +++++++++++- interface | 2 +- tests/conftest.py | 142 ++++++++++++++++++++++++++++ tests/test_global_ldap/__init__.py | 0 tests/test_global_ldap/test_bind.py | 50 ++++++++++ 10 files changed, 365 insertions(+), 5 deletions(-) create mode 100644 tests/test_global_ldap/__init__.py create mode 100644 tests/test_global_ldap/test_bind.py diff --git a/.package/docker-compose.yml b/.package/docker-compose.yml index 839a45e89..f408a5c98 100644 --- a/.package/docker-compose.yml +++ b/.package/docker-compose.yml @@ -127,7 +127,7 @@ services: - traefik.tcp.routers.ldaps.tls.certResolver=md-resolver - traefik.tcp.services.ldaps.loadbalancer.server.port=636 - traefik.tcp.services.ldaps.loadbalancer.proxyprotocol.version=2 - + cldap_server: image: ghcr.io/multidirectorylab/multidirectory:${VERSION:-latest} restart: unless-stopped diff --git a/app/config.py b/app/config.py index e5b82da31..905a4a1bd 100644 --- a/app/config.py +++ b/app/config.py @@ -41,6 +41,8 @@ class Settings(BaseModel): PORT: int = 389 TLS_PORT: int = 636 HTTP_PORT: int = 8000 + GLOBAL_LDAP_PORT: int = 3268 + GLOBAL_LDAP_TLS_PORT: int = 3269 USE_CORE_TLS: bool = False LDAP_LOAD_SSL_CERT: bool = False @@ -156,6 +158,10 @@ def TEST_POSTGRES_SCHEMA(self) -> str: # noqa: N802 return "public" return f"test_schema_{self.PYTEST_XDIST_WORKER}" + def set_test_global_port(self) -> None: + """Set test port.""" + self.GLOBAL_LDAP_PORT += self.TEST_WORKER_ID + def set_test_port(self) -> None: """Set test port.""" self.PORT += self.TEST_WORKER_ID @@ -197,6 +203,23 @@ def get_copy_4_tls(self) -> "Settings": tls_settings.PORT = tls_settings.TLS_PORT return tls_settings + def get_copy_4_global(self) -> "Settings": + """Create a copy for global LDAP server.""" + from copy import copy + + global_settings = copy(self) + global_settings.PORT = global_settings.GLOBAL_LDAP_PORT + return global_settings + + def get_copy_4_global_tls(self) -> "Settings": + """Create a copy for global LDAP server with TLS.""" + from copy import copy + + global_tls_settings = copy(self) + global_tls_settings.USE_CORE_TLS = True + global_tls_settings.PORT = global_tls_settings.GLOBAL_LDAP_TLS_PORT + return global_tls_settings + def check_certs_exist(self) -> bool: """Check if certs exist.""" return os.path.exists(self.SSL_CERT) and os.path.exists(self.SSL_KEY) diff --git a/app/ioc.py b/app/ioc.py index cd74d77f3..f77eef698 100644 --- a/app/ioc.py +++ b/app/ioc.py @@ -674,6 +674,36 @@ async def get_session( await session.disconnect() +class GlobalLDAPServerProvider(Provider): + """Provider with session scope.""" + + scope = Scope.SESSION + + @provide(scope=Scope.SESSION, provides=LDAPSession) + async def get_session( + self, + storage: SessionStorage, + ) -> AsyncIterator[LDAPSession]: + """Create ldap session.""" + session = LDAPSession(storage=storage) + await session.start() + yield session + await session.disconnect() + + bind_request_context = provide( + LDAPBindRequestContext, + scope=Scope.REQUEST, + ) + search_request_context = provide( + LDAPSearchRequestContext, + scope=Scope.REQUEST, + ) + unbind_request_context = provide( + LDAPUnbindRequestContext, + scope=Scope.REQUEST, + ) + + class MFACredsProvider(Provider): """Creds provider.""" diff --git a/app/multidirectory.py b/app/multidirectory.py index 613dce878..88ffa955f 100644 --- a/app/multidirectory.py +++ b/app/multidirectory.py @@ -41,6 +41,7 @@ from extra.dump_acme_certs import dump_acme_cert from ioc import ( EventSenderProvider, + GlobalLDAPServerProvider, HTTPProvider, LDAPServerProvider, MainProvider, @@ -214,6 +215,28 @@ async def cldap_factory(settings: Settings) -> None: await CLDAPUDPServer(settings, container).start() +async def global_ldap_server_factory(settings: Settings) -> None: + """Run global_ldap_server_factory.""" + servers = [] + + for setting in ( + settings.get_copy_4_global(), + settings.get_copy_4_global_tls(), + ): + container = make_async_container( + GlobalLDAPServerProvider(), + MainProvider(), + MFAProvider(), + MFACredsProvider(), + context={Settings: setting}, + ) + + settings = await container.get(Settings) + servers.append(PoolClientHandler(settings, container).start()) + + await asyncio.gather(*servers) + + async def event_handler_factory(settings: Settings) -> None: """Run event handler.""" main_container = make_async_container( @@ -244,6 +267,10 @@ async def event_sender_factory(settings: Settings) -> None: ldap = partial(run_entrypoint, factory=ldap_factory) cldap = partial(run_entrypoint, factory=cldap_factory) +global_ldap_server = partial( + run_entrypoint, + factory=global_ldap_server_factory, +) scheduler = partial(run_entrypoint, factory=scheduler_factory) create_shadow_app = partial(create_prod_app, factory=_create_shadow_app) event_handler = partial(run_entrypoint, factory=event_handler_factory) @@ -257,6 +284,11 @@ async def event_sender_factory(settings: Settings) -> None: group = parser.add_mutually_exclusive_group(required=True) group.add_argument("--ldap", action="store_true", help="Run ldap") group.add_argument("--cldap", action="store_true", help="Run cldap") + group.add_argument( + "--global_ldap_server", + action="store_true", + help="Run global_ldap_server", + ) group.add_argument("--http", action="store_true", help="Run http") group.add_argument("--shadow", action="store_true", help="Run http") group.add_argument("--scheduler", action="store_true", help="Run tasks") @@ -286,9 +318,12 @@ async def event_sender_factory(settings: Settings) -> None: if args.ldap: ldap(settings=settings) - if args.cldap: + elif args.cldap: cldap(settings=settings) + elif args.global_ldap_server: + global_ldap_server(settings=settings) + elif args.event_sender: event_sender(settings=settings) diff --git a/docker-compose.test.yml b/docker-compose.test.yml index 221a4e4bf..deebba4e8 100644 --- a/docker-compose.test.yml +++ b/docker-compose.test.yml @@ -20,9 +20,31 @@ services: POSTGRES_HOST: postgres # PYTHONTRACEMALLOC: 1 PYTHONDONTWRITEBYTECODE: 1 - command: sh -c "python -B -m pytest -n auto -x -W ignore::DeprecationWarning -vv" + command: sh -c "python -B -m pytest -n auto -x tests/test_global_ldap -W ignore::DeprecationWarning -vv" tty: true + # global_ldap_server: + # build: + # context: . + # dockerfile: ./.docker/dev.Dockerfile + # args: + # DOCKER_BUILDKIT: 1 + # target: runtime + # image: multidirectory + # container_name: global_ldap_server + # restart: unless-stopped + # environment: + # - SERVICE_NAME=global_ldap_server + # volumes: + # - ./app:/app + # - ./certs:/certs + # - ldap_keytab:/LDAP_keytab/ + # env_file: local.env + # command: python multidirectory.py --global_ldap_server + # ports: + # - "3268:3268" + # - "3269:3269" + postgres: container_name: MD-test-postgres image: postgres:16 diff --git a/docker-compose.yml b/docker-compose.yml index 891095345..fcd39e12a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,6 +12,8 @@ services: - "8080:8080" - "389:389" - "389:389/udp" + - "3268:3268" + - "3269:3269" - "636:636" - "749:749" - "464:464" @@ -113,6 +115,60 @@ services: - traefik.udp.routers.cldap.service=cldap - traefik.udp.services.cldap.loadbalancer.server.port=389 + global_ldap_server: + build: + context: . + dockerfile: ./.docker/dev.Dockerfile + args: + DOCKER_BUILDKIT: 1 + target: runtime + image: multidirectory + # container_name: global_ldap_server + restart: unless-stopped + deploy: + mode: replicated + replicas: 2 + endpoint_mode: dnsrr + resources: + reservations: + cpus: "0.25" + memory: 100M + environment: + - SERVICE_NAME=global_ldap_server + volumes: + - ./app:/app + - ./certs:/certs + - ldap_keytab:/LDAP_keytab/ + env_file: local.env + command: python -OO multidirectory.py --global_ldap_server + tty: true + depends_on: + migrations: + condition: service_completed_successfully + cert_local_check: + condition: service_completed_successfully + healthcheck: + test: ["CMD-SHELL", "nc -zv 127.0.0.1 3268 3269"] + interval: 30s + timeout: 10s + retries: 10 + start_period: 3s + labels: + - traefik.enable=true + + - traefik.tcp.routers.global_ldap.rule=HostSNI(`*`) + - traefik.tcp.routers.global_ldap.entrypoints=global_ldap + - traefik.tcp.routers.global_ldap.service=global_ldap + - traefik.tcp.services.global_ldap.loadbalancer.server.port=3268 + - traefik.tcp.services.global_ldap.loadbalancer.proxyprotocol.version=2 + + - traefik.tcp.routers.global_ldap_tls.rule=HostSNI(`*`) + - traefik.tcp.routers.global_ldap_tls.entrypoints=global_ldap_tls + - traefik.tcp.routers.global_ldap_tls.service=global_ldap_tls + - traefik.tcp.routers.global_ldap_tls.tls=true + - traefik.tcp.services.global_ldap_tls.loadbalancer.server.port=3269 + - traefik.tcp.services.global_ldap_tls.loadbalancer.proxyprotocol.version=2 + api: image: multidirectory container_name: multidirectory_api @@ -159,7 +215,7 @@ services: postgres: condition: service_healthy restart: true - + cert_check: image: multidirectory container_name: multidirectory_certs_check @@ -449,6 +505,8 @@ services: migrations: condition: service_completed_successfully + + event_sender: image: multidirectory container_name: event_sender diff --git a/interface b/interface index bef67d7cb..242c01f0f 160000 --- a/interface +++ b/interface @@ -1 +1 @@ -Subproject commit bef67d7cbfcb16648a4c4ebc5a870f97bdda0856 +Subproject commit 242c01f0f26a5080beef14523f9dd9dfab3c89ec diff --git a/tests/conftest.py b/tests/conftest.py index 4906ccc1d..d8ffed8a4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -875,6 +875,148 @@ async def raw_audit_manager( yield await container.get(RawAuditManager) +@pytest.fixture(scope="session") +def global_settings() -> Settings: + """Get settings.""" + settings = Settings.from_os() + return settings.get_copy_4_global() + + +@pytest_asyncio.fixture(scope="session") +async def global_container( + global_settings: Settings, +) -> AsyncIterator[AsyncContainer]: + """Create test container.""" + ctnr = make_async_container( + TestProvider(), + MFACredsProvider(), + context={Settings: global_settings}, + start_scope=Scope.RUNTIME, + ) + yield ctnr + await ctnr.close() + + +@pytest_asyncio.fixture(scope="session") +async def global_handler( + global_settings: Settings, + global_container: AsyncContainer, +) -> AsyncIterator[PoolClientHandler]: + """Create test handler.""" + global_settings.set_test_global_port() + async with global_container(scope=Scope.APP) as app_scope: + yield PoolClientHandler(global_settings, app_scope) + + +@pytest_asyncio.fixture(scope="function") +async def global_session( + global_container: AsyncContainer, + global_handler: PoolClientHandler, +) -> AsyncIterator[AsyncSession]: + """Get session and acquire after completion.""" + async with global_container(scope=Scope.APP) as global_container: + session = await global_container.get(AsyncSession) + global_handler.container = global_container + yield session + + +@pytest_asyncio.fixture(scope="function") +async def setup_global_session( + global_session: AsyncSession, + raw_audit_manager: RawAuditManager, + password_utils: PasswordUtils, +) -> None: + """Get session and acquire after completion.""" + object_class_dao = ObjectClassDAO(global_session) + attribute_value_validator = AttributeValueValidator() + entity_type_dao = EntityTypeDAO( + global_session, + object_class_dao=object_class_dao, + attribute_value_validator=attribute_value_validator, + ) + for entity_type_data in ENTITY_TYPE_DATAS: + await entity_type_dao.create( + dto=EntityTypeDTO( + id=None, + name=entity_type_data["name"], + object_class_names=entity_type_data["object_class_names"], + is_system=True, + ), + ) + + await global_session.flush() + + audit_policy_dao = AuditPoliciesDAO(global_session) + audit_destination_dao = AuditDestinationDAO(global_session) + audit_use_case = AuditUseCase( + audit_policy_dao, + audit_destination_dao, + raw_audit_manager, + ) + password_policy_dao = PasswordPolicyDAO( + global_session, + attribute_value_validator=attribute_value_validator, + ) + password_policy_validator = PasswordPolicyValidator( + PasswordValidatorSettings(), + password_utils, + ) + password_ban_word_repository = PasswordBanWordRepository(global_session) + password_use_cases = PasswordPolicyUseCases( + password_policy_dao, + password_policy_validator, + password_ban_word_repository, + ) + setup_gateway = SetupGateway( + global_session, + password_utils, + entity_type_dao, + attribute_value_validator=attribute_value_validator, + ) + await audit_use_case.create_policies() + await setup_gateway.setup_enviroment(dn="md.test", data=TEST_DATA) + + # NOTE: after setup environment we need base DN to be created + await password_use_cases.create_default_domain_policy() + + role_dao = RoleDAO(global_session) + ace_dao = AccessControlEntryDAO(global_session) + role_use_case = RoleUseCase(role_dao, ace_dao) + await role_use_case.create_domain_admins_role() + + await role_use_case._role_dao.create( # noqa: SLF001 + dto=RoleDTO( + name="TEST ONLY LOGIN ROLE", + creator_upn=None, + is_system=True, + groups=["cn=admin login only,cn=groups,dc=md,dc=test"], + permissions=AuthorizationRules.AUTH_LOGIN, + ), + ) + + global_session.add( + AttributeType( + oid="1.2.3.4.5.6.7.8", + name="attr_with_bvalue", + syntax="1.3.6.1.4.1.1466.115.121.1.40", # Octet String + single_value=True, + no_user_modification=False, + is_system=True, + ), + ) + global_session.add( + AttributeType( + oid="1.2.3.4.5.6.7.8.9", + name="testing_attr", + syntax="1.3.6.1.4.1.1466.115.121.1.15", + single_value=True, + no_user_modification=False, + is_system=True, + ), + ) + await global_session.commit() + + @pytest_asyncio.fixture(scope="function") async def setup_session( session: AsyncSession, diff --git a/tests/test_global_ldap/__init__.py b/tests/test_global_ldap/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/test_global_ldap/test_bind.py b/tests/test_global_ldap/test_bind.py new file mode 100644 index 000000000..72d5cfa1e --- /dev/null +++ b/tests/test_global_ldap/test_bind.py @@ -0,0 +1,50 @@ +"""Test search with ldaputil. + +Copyright (c) 2024 MultiFactor +License: https://github.com/MultiDirectoryLab/MultiDirectory/blob/main/LICENSE +""" + +import asyncio + +import pytest + +from config import Settings +from tests.conftest import TestCreds + + +@pytest.mark.asyncio +@pytest.mark.usefixtures("setup_global_session") +@pytest.mark.usefixtures("global_session") +async def test_ldap_search( + global_settings: Settings, + creds: TestCreds, +) -> None: + """Test ldapsearch on server.""" + proc = await asyncio.create_subprocess_exec( + "ldapsearch", + "-vvv", + "-x", + "-H", + f"ldap://{global_settings.HOST}:{global_settings.GLOBAL_LDAP_PORT}", + "-D", + creds.un, + "-w", + creds.pw, + "-b", + "dc=md,dc=test", + "objectclass=*", + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + + raw_data, _ = await proc.communicate() + data = raw_data.decode().split("\n") + result = await proc.wait() + + print("SOSI") + print(data) + print(result) + assert result == 0 + assert "dn: cn=groups,dc=md,dc=test" in data + assert "dn: cn=users,dc=md,dc=test" in data + assert "dn: cn=user0,cn=users,dc=md,dc=test" in data From 5fc9d2d4ebfe4c5f46dee7b1bdb5be3b12408edf Mon Sep 17 00:00:00 2001 From: Milov Dmitriy Date: Wed, 17 Dec 2025 16:11:25 +0300 Subject: [PATCH 02/15] fix --- docker-compose.test.yml | 22 ------- tests/conftest.py | 97 ----------------------------- tests/test_global_ldap/test_bind.py | 3 +- 3 files changed, 1 insertion(+), 121 deletions(-) diff --git a/docker-compose.test.yml b/docker-compose.test.yml index deebba4e8..cb2915325 100644 --- a/docker-compose.test.yml +++ b/docker-compose.test.yml @@ -23,28 +23,6 @@ services: command: sh -c "python -B -m pytest -n auto -x tests/test_global_ldap -W ignore::DeprecationWarning -vv" tty: true - # global_ldap_server: - # build: - # context: . - # dockerfile: ./.docker/dev.Dockerfile - # args: - # DOCKER_BUILDKIT: 1 - # target: runtime - # image: multidirectory - # container_name: global_ldap_server - # restart: unless-stopped - # environment: - # - SERVICE_NAME=global_ldap_server - # volumes: - # - ./app:/app - # - ./certs:/certs - # - ldap_keytab:/LDAP_keytab/ - # env_file: local.env - # command: python multidirectory.py --global_ldap_server - # ports: - # - "3268:3268" - # - "3269:3269" - postgres: container_name: MD-test-postgres image: postgres:16 diff --git a/tests/conftest.py b/tests/conftest.py index d8ffed8a4..d2c8833a8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -920,103 +920,6 @@ async def global_session( yield session -@pytest_asyncio.fixture(scope="function") -async def setup_global_session( - global_session: AsyncSession, - raw_audit_manager: RawAuditManager, - password_utils: PasswordUtils, -) -> None: - """Get session and acquire after completion.""" - object_class_dao = ObjectClassDAO(global_session) - attribute_value_validator = AttributeValueValidator() - entity_type_dao = EntityTypeDAO( - global_session, - object_class_dao=object_class_dao, - attribute_value_validator=attribute_value_validator, - ) - for entity_type_data in ENTITY_TYPE_DATAS: - await entity_type_dao.create( - dto=EntityTypeDTO( - id=None, - name=entity_type_data["name"], - object_class_names=entity_type_data["object_class_names"], - is_system=True, - ), - ) - - await global_session.flush() - - audit_policy_dao = AuditPoliciesDAO(global_session) - audit_destination_dao = AuditDestinationDAO(global_session) - audit_use_case = AuditUseCase( - audit_policy_dao, - audit_destination_dao, - raw_audit_manager, - ) - password_policy_dao = PasswordPolicyDAO( - global_session, - attribute_value_validator=attribute_value_validator, - ) - password_policy_validator = PasswordPolicyValidator( - PasswordValidatorSettings(), - password_utils, - ) - password_ban_word_repository = PasswordBanWordRepository(global_session) - password_use_cases = PasswordPolicyUseCases( - password_policy_dao, - password_policy_validator, - password_ban_word_repository, - ) - setup_gateway = SetupGateway( - global_session, - password_utils, - entity_type_dao, - attribute_value_validator=attribute_value_validator, - ) - await audit_use_case.create_policies() - await setup_gateway.setup_enviroment(dn="md.test", data=TEST_DATA) - - # NOTE: after setup environment we need base DN to be created - await password_use_cases.create_default_domain_policy() - - role_dao = RoleDAO(global_session) - ace_dao = AccessControlEntryDAO(global_session) - role_use_case = RoleUseCase(role_dao, ace_dao) - await role_use_case.create_domain_admins_role() - - await role_use_case._role_dao.create( # noqa: SLF001 - dto=RoleDTO( - name="TEST ONLY LOGIN ROLE", - creator_upn=None, - is_system=True, - groups=["cn=admin login only,cn=groups,dc=md,dc=test"], - permissions=AuthorizationRules.AUTH_LOGIN, - ), - ) - - global_session.add( - AttributeType( - oid="1.2.3.4.5.6.7.8", - name="attr_with_bvalue", - syntax="1.3.6.1.4.1.1466.115.121.1.40", # Octet String - single_value=True, - no_user_modification=False, - is_system=True, - ), - ) - global_session.add( - AttributeType( - oid="1.2.3.4.5.6.7.8.9", - name="testing_attr", - syntax="1.3.6.1.4.1.1466.115.121.1.15", - single_value=True, - no_user_modification=False, - is_system=True, - ), - ) - await global_session.commit() - - @pytest_asyncio.fixture(scope="function") async def setup_session( session: AsyncSession, diff --git a/tests/test_global_ldap/test_bind.py b/tests/test_global_ldap/test_bind.py index 72d5cfa1e..dc1487f97 100644 --- a/tests/test_global_ldap/test_bind.py +++ b/tests/test_global_ldap/test_bind.py @@ -13,7 +13,6 @@ @pytest.mark.asyncio -@pytest.mark.usefixtures("setup_global_session") @pytest.mark.usefixtures("global_session") async def test_ldap_search( global_settings: Settings, @@ -41,7 +40,7 @@ async def test_ldap_search( data = raw_data.decode().split("\n") result = await proc.wait() - print("SOSI") + print("OLOLO") print(data) print(result) assert result == 0 From 52cbd4f9bcecf95781828bd243e0b619e2ec51ca Mon Sep 17 00:00:00 2001 From: Milov Dmitriy Date: Wed, 17 Dec 2025 16:26:46 +0300 Subject: [PATCH 03/15] fix --- app/multidirectory.py | 2 +- docker-compose.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/multidirectory.py b/app/multidirectory.py index 88ffa955f..ec98c971b 100644 --- a/app/multidirectory.py +++ b/app/multidirectory.py @@ -221,7 +221,7 @@ async def global_ldap_server_factory(settings: Settings) -> None: for setting in ( settings.get_copy_4_global(), - settings.get_copy_4_global_tls(), + # settings.get_copy_4_global_tls(), ): container = make_async_container( GlobalLDAPServerProvider(), diff --git a/docker-compose.yml b/docker-compose.yml index fcd39e12a..658153946 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -123,11 +123,11 @@ services: DOCKER_BUILDKIT: 1 target: runtime image: multidirectory - # container_name: global_ldap_server + container_name: global_ldap_server restart: unless-stopped deploy: mode: replicated - replicas: 2 + # replicas: 1 endpoint_mode: dnsrr resources: reservations: From 099bbd96d9510c70247e0d153546bf901f4259f2 Mon Sep 17 00:00:00 2001 From: Milov Dmitriy Date: Wed, 17 Dec 2025 18:27:56 +0300 Subject: [PATCH 04/15] fix: it works --- app/multidirectory.py | 2 +- docker-compose.yml | 3 +-- traefik.yml | 4 ++++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/multidirectory.py b/app/multidirectory.py index ec98c971b..88ffa955f 100644 --- a/app/multidirectory.py +++ b/app/multidirectory.py @@ -221,7 +221,7 @@ async def global_ldap_server_factory(settings: Settings) -> None: for setting in ( settings.get_copy_4_global(), - # settings.get_copy_4_global_tls(), + settings.get_copy_4_global_tls(), ): container = make_async_container( GlobalLDAPServerProvider(), diff --git a/docker-compose.yml b/docker-compose.yml index 658153946..35a0f8572 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -123,11 +123,10 @@ services: DOCKER_BUILDKIT: 1 target: runtime image: multidirectory - container_name: global_ldap_server restart: unless-stopped deploy: mode: replicated - # replicas: 1 + replicas: 2 endpoint_mode: dnsrr resources: reservations: diff --git a/traefik.yml b/traefik.yml index 7b2384086..3fca1e1d0 100644 --- a/traefik.yml +++ b/traefik.yml @@ -22,6 +22,10 @@ entryPoints: address: ":636" proxyProtocol: insecure: true + global_ldap: + address: ":3268" + global_ldap_tls: + address: ":3269" kadmind: address: ":749" kpasswd: From a4cb277dc61f4ad45423d5f359513113b729c783 Mon Sep 17 00:00:00 2001 From: Milov Dmitriy Date: Fri, 19 Dec 2025 11:11:13 +0300 Subject: [PATCH 05/15] add: draft --- app/ldap_protocol/server.py | 2 +- docker-compose.test.yml | 4 +- interface | 2 +- tests/conftest.py | 30 +++-- tests/test_global_ldap/test_bind.py | 49 -------- tests/test_global_ldap/test_search.py | 172 ++++++++++++++++++++++++++ 6 files changed, 195 insertions(+), 64 deletions(-) delete mode 100644 tests/test_global_ldap/test_bind.py create mode 100644 tests/test_global_ldap/test_search.py diff --git a/app/ldap_protocol/server.py b/app/ldap_protocol/server.py index 597b107a6..2b8340199 100644 --- a/app/ldap_protocol/server.py +++ b/app/ldap_protocol/server.py @@ -444,7 +444,7 @@ async def start(self) -> None: server = await self._get_server() log.info( f"started {'DEBUG' if self.settings.DEBUG else 'PROD'} " - f"{'LDAPS' if self.settings.USE_CORE_TLS else 'LDAP'} server", + f"{'LDAPS' if self.settings.USE_CORE_TLS else 'LDAP'} server {self.settings.HOST}:{self.settings.PORT}", ) try: diff --git a/docker-compose.test.yml b/docker-compose.test.yml index cb2915325..e58e47bac 100644 --- a/docker-compose.test.yml +++ b/docker-compose.test.yml @@ -20,7 +20,9 @@ services: POSTGRES_HOST: postgres # PYTHONTRACEMALLOC: 1 PYTHONDONTWRITEBYTECODE: 1 - command: sh -c "python -B -m pytest -n auto -x tests/test_global_ldap -W ignore::DeprecationWarning -vv" + # command: sh -c "python -B -m pytest -n auto -x tests/test_ldap/test_util/test_add.py -W ignore::DeprecationWarning -vv" + command: sh -c "python -B -m pytest -x -s tests/test_global_ldap -W ignore::DeprecationWarning -vv" + # command: sh -c "python -B -m pytest -x -s tests/test_ldap/test_util/test_search.py -W ignore::DeprecationWarning -vv" tty: true postgres: diff --git a/interface b/interface index 242c01f0f..094bf0367 160000 --- a/interface +++ b/interface @@ -1 +1 @@ -Subproject commit 242c01f0f26a5080beef14523f9dd9dfab3c89ec +Subproject commit 094bf0367c26c574310e43714a1adfcf61ca57a1 diff --git a/tests/conftest.py b/tests/conftest.py index d2c8833a8..8ef7a476f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -908,18 +908,6 @@ async def global_handler( yield PoolClientHandler(global_settings, app_scope) -@pytest_asyncio.fixture(scope="function") -async def global_session( - global_container: AsyncContainer, - global_handler: PoolClientHandler, -) -> AsyncIterator[AsyncSession]: - """Get session and acquire after completion.""" - async with global_container(scope=Scope.APP) as global_container: - session = await global_container.get(AsyncSession) - global_handler.container = global_container - yield session - - @pytest_asyncio.fixture(scope="function") async def setup_session( session: AsyncSession, @@ -1194,6 +1182,7 @@ async def role_use_case( def _server( event_loop: asyncio.BaseEventLoop, handler: PoolClientHandler, + _migrations: None, ) -> Generator: """Run server in background.""" task = asyncio.ensure_future(handler.start(), loop=event_loop) @@ -1203,6 +1192,23 @@ def _server( task.cancel() +@pytest.fixture(scope="session") +def _global_server( + event_loop: asyncio.BaseEventLoop, + global_handler: PoolClientHandler, + _migrations: None, +) -> Generator: + """Run global LDAP server in background.""" + task = asyncio.ensure_future( + global_handler.start(), + loop=event_loop, + ) + event_loop.run_until_complete(asyncio.sleep(1)) + yield + with suppress(asyncio.CancelledError): + task.cancel() + + @pytest.fixture async def ldap_client( settings: Settings, diff --git a/tests/test_global_ldap/test_bind.py b/tests/test_global_ldap/test_bind.py deleted file mode 100644 index dc1487f97..000000000 --- a/tests/test_global_ldap/test_bind.py +++ /dev/null @@ -1,49 +0,0 @@ -"""Test search with ldaputil. - -Copyright (c) 2024 MultiFactor -License: https://github.com/MultiDirectoryLab/MultiDirectory/blob/main/LICENSE -""" - -import asyncio - -import pytest - -from config import Settings -from tests.conftest import TestCreds - - -@pytest.mark.asyncio -@pytest.mark.usefixtures("global_session") -async def test_ldap_search( - global_settings: Settings, - creds: TestCreds, -) -> None: - """Test ldapsearch on server.""" - proc = await asyncio.create_subprocess_exec( - "ldapsearch", - "-vvv", - "-x", - "-H", - f"ldap://{global_settings.HOST}:{global_settings.GLOBAL_LDAP_PORT}", - "-D", - creds.un, - "-w", - creds.pw, - "-b", - "dc=md,dc=test", - "objectclass=*", - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, - ) - - raw_data, _ = await proc.communicate() - data = raw_data.decode().split("\n") - result = await proc.wait() - - print("OLOLO") - print(data) - print(result) - assert result == 0 - assert "dn: cn=groups,dc=md,dc=test" in data - assert "dn: cn=users,dc=md,dc=test" in data - assert "dn: cn=user0,cn=users,dc=md,dc=test" in data diff --git a/tests/test_global_ldap/test_search.py b/tests/test_global_ldap/test_search.py new file mode 100644 index 000000000..c06b60f03 --- /dev/null +++ b/tests/test_global_ldap/test_search.py @@ -0,0 +1,172 @@ +"""Test search with ldaputil. + +Copyright (c) 2024 MultiFactor +License: https://github.com/MultiDirectoryLab/MultiDirectory/blob/main/LICENSE +""" + +import asyncio + +import pytest + +from config import Settings +from tests.conftest import TestCreds + + +@pytest.mark.asyncio +@pytest.mark.usefixtures("setup_session") +@pytest.mark.usefixtures("session") +async def test_ldap_search(settings: Settings, creds: TestCreds) -> None: + """Test ldapsearch on server.""" + proc = await asyncio.create_subprocess_exec( + "ldapsearch", + "-vvv", + "-x", + "-H", + f"ldap://{settings.HOST}:{settings.PORT}", + "-D", + creds.un, + "-w", + creds.pw, + "-b", + "dc=md,dc=test", + "objectclass=*", + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + + raw_data, _ = await proc.communicate() + data = raw_data.decode().split("\n") + result = await proc.wait() + + assert result == 0 + assert "dn: cn=groups,dc=md,dc=test" in data + assert "dn: cn=users,dc=md,dc=test" in data + assert "dn: cn=user0,cn=users,dc=md,dc=test" in data + + +@pytest.mark.asyncio +@pytest.mark.usefixtures("setup_session") +@pytest.mark.usefixtures("_global_server") +async def test_global_ldap_search( + global_settings: Settings, + creds: TestCreds, +) -> None: + """Test ldapsearch on server.""" + print("OLOLO 00000") + print( + global_settings.HOST, + global_settings.PORT, + "\n\n", + creds.un, + creds.pw, + ) + + # Проверка что порты слушаются + netstat_proc = await asyncio.create_subprocess_exec( + "netstat", + "-tuln", + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + netstat_out, _ = await netstat_proc.communicate() + print("NETSTAT OUTPUT:") + print(netstat_out.decode()) + print("=" * 50) + + # Используем 127.0.0.1 для подключения (0.0.0.0 это bind адрес, не connect) + host = ( + "127.0.0.1" + if str(global_settings.HOST) == "0.0.0.0" + else str(global_settings.HOST) + ) + + # Сначала проверим что порт доступен через telnet + print(f"Testing TCP connection to {host}:{global_settings.PORT}...") + try: + reader, writer = await asyncio.wait_for( + asyncio.open_connection(host, global_settings.PORT), + timeout=2.0, + ) + print("✓ TCP connection successful!") + writer.close() + await writer.wait_closed() + except TimeoutError: + print("✗ TCP connection timeout!") + except Exception as e: + print(f"✗ TCP connection failed: {e}") + + print(f"Running ldapsearch to {host}:{global_settings.PORT}...") + + # Подождем еще чтобы сервер точно готов + await asyncio.sleep(2) + + # Сначала проверим что обычный LDAP на 389 работает + print("Testing regular LDAP on port 389 first...") + proc_389 = await asyncio.create_subprocess_exec( + "ldapsearch", + "-x", + "-H", + f"ldap://{host}:389", + "-D", + creds.un, + "-w", + creds.pw, + "-b", + "dc=md,dc=test", + "-s", + "base", + "objectclass=*", + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + try: + data_389, err_389 = await asyncio.wait_for( + proc_389.communicate(), + timeout=5.0, + ) + result_389 = await proc_389.wait() + print(f"✓ Port 389 works! Result: {result_389}") + except TimeoutError: + print("✗ Port 389 also timeout!") + proc_389.kill() + + print("Now testing global LDAP on port 3268...") + proc = await asyncio.create_subprocess_exec( + "ldapsearch", + "-vvv", + "-x", + "-H", + f"ldap://{host}:{global_settings.PORT}", + "-D", + creds.un, + "-w", + creds.pw, + "-b", + "dc=md,dc=test", + "objectclass=*", + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + print("ldapsearch process started, waiting for response...") + + try: + raw_data, raw_err = await asyncio.wait_for( + proc.communicate(), + timeout=10.0, + ) + data = raw_data.decode().split("\n") + err = raw_err.decode().split("\n") + result = await proc.wait() + except TimeoutError: + print("✗ ldapsearch timeout after 10 seconds!") + proc.kill() + raise + + print("OLOLO") + print("STDOUT:", data) + print("STDERR:", err) + print("RESULT:", result) + assert result == 0 + assert "dn: cn=groups,dc=md,dc=test" in data + assert "dn: cn=users,dc=md,dc=test" in data + assert "dn: cn=user0,cn=users,dc=md,dc=test" in data From 06ad1e0e6056063e8c9a88907c97e4d288d4d43c Mon Sep 17 00:00:00 2001 From: Milov Dmitriy Date: Fri, 19 Dec 2025 15:52:14 +0300 Subject: [PATCH 06/15] dont work --- app/config.py | 1 + app/ldap_protocol/server.py | 8 + docker-compose.test.yml | 2 +- tests/conftest.py | 42 ++-- tests/test_global_ldap/test_search.py | 293 ++++++++++++++------------ 5 files changed, 200 insertions(+), 146 deletions(-) diff --git a/app/config.py b/app/config.py index 905a4a1bd..4a9cca12e 100644 --- a/app/config.py +++ b/app/config.py @@ -161,6 +161,7 @@ def TEST_POSTGRES_SCHEMA(self) -> str: # noqa: N802 def set_test_global_port(self) -> None: """Set test port.""" self.GLOBAL_LDAP_PORT += self.TEST_WORKER_ID + self.PORT = self.GLOBAL_LDAP_PORT def set_test_port(self) -> None: """Set test port.""" diff --git a/app/ldap_protocol/server.py b/app/ldap_protocol/server.py index 2b8340199..7030079ba 100644 --- a/app/ldap_protocol/server.py +++ b/app/ldap_protocol/server.py @@ -69,6 +69,7 @@ async def __call__( writer: asyncio.StreamWriter, ) -> None: """Create session, queue and start message handlers concurrently.""" + log.info("SOSI4") async with self.container(scope=Scope.SESSION) as session_scope: ldap_session = await session_scope.get(LDAPSession) addr, first_chunk = await self.recieve( @@ -421,6 +422,9 @@ async def _handle_responses( async def _get_server(self) -> asyncio.base_events.Server: """Get async server.""" + log.info( + f"SOSAI _get_server {self.settings.HOST}, {self.settings.PORT}", + ) return await asyncio.start_server( self, str(self.settings.HOST), @@ -431,8 +435,11 @@ async def _get_server(self) -> asyncio.base_events.Server: @staticmethod async def _run_server(server: asyncio.base_events.Server) -> None: """Run server.""" + log.info("SOSI1") async with server: + log.info("SOSI2") await server.serve_forever() + log.info("SOSI3") @staticmethod def log_addrs(server: asyncio.base_events.Server) -> None: @@ -448,6 +455,7 @@ async def start(self) -> None: ) try: + log.info("SOSI") await self._run_server(server) finally: server.close() diff --git a/docker-compose.test.yml b/docker-compose.test.yml index e58e47bac..07908c61a 100644 --- a/docker-compose.test.yml +++ b/docker-compose.test.yml @@ -21,7 +21,7 @@ services: # PYTHONTRACEMALLOC: 1 PYTHONDONTWRITEBYTECODE: 1 # command: sh -c "python -B -m pytest -n auto -x tests/test_ldap/test_util/test_add.py -W ignore::DeprecationWarning -vv" - command: sh -c "python -B -m pytest -x -s tests/test_global_ldap -W ignore::DeprecationWarning -vv" + command: sh -c "python -B -m pytest -n 0 -x -s tests/test_global_ldap -W ignore::DeprecationWarning -vv" # command: sh -c "python -B -m pytest -x -s tests/test_ldap/test_util/test_search.py -W ignore::DeprecationWarning -vv" tty: true diff --git a/tests/conftest.py b/tests/conftest.py index 8ef7a476f..4d33fb436 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -770,6 +770,16 @@ def event_loop() -> Generator: loop.close() +@pytest.fixture(scope="session") +def global_event_loop() -> Generator: + """Create uvloop event loop.""" + loop = uvloop.new_event_loop() + yield loop + with suppress(asyncio.CancelledError, RuntimeError): + asyncio.gather(*asyncio.tasks.all_tasks(loop)).cancel() + loop.close() + + @pytest.fixture(scope="session") def settings() -> Settings: """Get settings.""" @@ -1178,32 +1188,32 @@ async def role_use_case( yield RoleUseCase(role_dao, ace_dao) -@pytest.fixture(scope="session", autouse=True) -def _server( - event_loop: asyncio.BaseEventLoop, - handler: PoolClientHandler, - _migrations: None, -) -> Generator: - """Run server in background.""" - task = asyncio.ensure_future(handler.start(), loop=event_loop) - event_loop.run_until_complete(asyncio.sleep(0.1)) - yield - with suppress(asyncio.CancelledError): - task.cancel() +# @pytest.fixture(scope="session", autouse=True) +# def _server( +# event_loop: asyncio.BaseEventLoop, +# handler: PoolClientHandler, +# _migrations: None, +# ) -> Generator: +# """Run server in background.""" +# task = asyncio.ensure_future(handler.start(), loop=event_loop) +# event_loop.run_until_complete(asyncio.sleep(0.1)) +# yield +# with suppress(asyncio.CancelledError): +# task.cancel() -@pytest.fixture(scope="session") +@pytest.fixture(scope="session", autouse=True) def _global_server( - event_loop: asyncio.BaseEventLoop, + global_event_loop: asyncio.BaseEventLoop, global_handler: PoolClientHandler, _migrations: None, ) -> Generator: """Run global LDAP server in background.""" task = asyncio.ensure_future( global_handler.start(), - loop=event_loop, + loop=global_event_loop, ) - event_loop.run_until_complete(asyncio.sleep(1)) + global_event_loop.run_until_complete(asyncio.sleep(0.1)) yield with suppress(asyncio.CancelledError): task.cancel() diff --git a/tests/test_global_ldap/test_search.py b/tests/test_global_ldap/test_search.py index c06b60f03..01140f999 100644 --- a/tests/test_global_ldap/test_search.py +++ b/tests/test_global_ldap/test_search.py @@ -12,36 +12,36 @@ from tests.conftest import TestCreds -@pytest.mark.asyncio -@pytest.mark.usefixtures("setup_session") -@pytest.mark.usefixtures("session") -async def test_ldap_search(settings: Settings, creds: TestCreds) -> None: - """Test ldapsearch on server.""" - proc = await asyncio.create_subprocess_exec( - "ldapsearch", - "-vvv", - "-x", - "-H", - f"ldap://{settings.HOST}:{settings.PORT}", - "-D", - creds.un, - "-w", - creds.pw, - "-b", - "dc=md,dc=test", - "objectclass=*", - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, - ) - - raw_data, _ = await proc.communicate() - data = raw_data.decode().split("\n") - result = await proc.wait() - - assert result == 0 - assert "dn: cn=groups,dc=md,dc=test" in data - assert "dn: cn=users,dc=md,dc=test" in data - assert "dn: cn=user0,cn=users,dc=md,dc=test" in data +# @pytest.mark.asyncio +# @pytest.mark.usefixtures("setup_session") +# @pytest.mark.usefixtures("session") +# async def test_ldap_search(settings: Settings, creds: TestCreds) -> None: +# """Test ldapsearch on server.""" +# proc = await asyncio.create_subprocess_exec( +# "ldapsearch", +# "-vvv", +# "-x", +# "-H", +# f"ldap://{settings.HOST}:{settings.PORT}", +# "-D", +# creds.un, +# "-w", +# creds.pw, +# "-b", +# "dc=md,dc=test", +# "objectclass=*", +# stdout=asyncio.subprocess.PIPE, +# stderr=asyncio.subprocess.PIPE, +# ) + +# raw_data, _ = await proc.communicate() +# data = raw_data.decode().split("\n") +# result = await proc.wait() + +# assert result == 0 +# assert "dn: cn=groups,dc=md,dc=test" in data +# assert "dn: cn=users,dc=md,dc=test" in data +# assert "dn: cn=user0,cn=users,dc=md,dc=test" in data @pytest.mark.asyncio @@ -52,91 +52,12 @@ async def test_global_ldap_search( creds: TestCreds, ) -> None: """Test ldapsearch on server.""" - print("OLOLO 00000") - print( - global_settings.HOST, - global_settings.PORT, - "\n\n", - creds.un, - creds.pw, - ) - - # Проверка что порты слушаются - netstat_proc = await asyncio.create_subprocess_exec( - "netstat", - "-tuln", - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, - ) - netstat_out, _ = await netstat_proc.communicate() - print("NETSTAT OUTPUT:") - print(netstat_out.decode()) - print("=" * 50) - - # Используем 127.0.0.1 для подключения (0.0.0.0 это bind адрес, не connect) - host = ( - "127.0.0.1" - if str(global_settings.HOST) == "0.0.0.0" - else str(global_settings.HOST) - ) - - # Сначала проверим что порт доступен через telnet - print(f"Testing TCP connection to {host}:{global_settings.PORT}...") - try: - reader, writer = await asyncio.wait_for( - asyncio.open_connection(host, global_settings.PORT), - timeout=2.0, - ) - print("✓ TCP connection successful!") - writer.close() - await writer.wait_closed() - except TimeoutError: - print("✗ TCP connection timeout!") - except Exception as e: - print(f"✗ TCP connection failed: {e}") - - print(f"Running ldapsearch to {host}:{global_settings.PORT}...") - - # Подождем еще чтобы сервер точно готов - await asyncio.sleep(2) - - # Сначала проверим что обычный LDAP на 389 работает - print("Testing regular LDAP on port 389 first...") - proc_389 = await asyncio.create_subprocess_exec( - "ldapsearch", - "-x", - "-H", - f"ldap://{host}:389", - "-D", - creds.un, - "-w", - creds.pw, - "-b", - "dc=md,dc=test", - "-s", - "base", - "objectclass=*", - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, - ) - try: - data_389, err_389 = await asyncio.wait_for( - proc_389.communicate(), - timeout=5.0, - ) - result_389 = await proc_389.wait() - print(f"✓ Port 389 works! Result: {result_389}") - except TimeoutError: - print("✗ Port 389 also timeout!") - proc_389.kill() - - print("Now testing global LDAP on port 3268...") proc = await asyncio.create_subprocess_exec( "ldapsearch", "-vvv", "-x", "-H", - f"ldap://{host}:{global_settings.PORT}", + f"ldap://{global_settings.HOST}:{global_settings.PORT}", "-D", creds.un, "-w", @@ -147,26 +68,140 @@ async def test_global_ldap_search( stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) - print("ldapsearch process started, waiting for response...") - - try: - raw_data, raw_err = await asyncio.wait_for( - proc.communicate(), - timeout=10.0, - ) - data = raw_data.decode().split("\n") - err = raw_err.decode().split("\n") - result = await proc.wait() - except TimeoutError: - print("✗ ldapsearch timeout after 10 seconds!") - proc.kill() - raise - - print("OLOLO") - print("STDOUT:", data) - print("STDERR:", err) - print("RESULT:", result) + + raw_data, _ = await proc.communicate() + data = raw_data.decode().split("\n") + result = await proc.wait() + assert result == 0 assert "dn: cn=groups,dc=md,dc=test" in data assert "dn: cn=users,dc=md,dc=test" in data assert "dn: cn=user0,cn=users,dc=md,dc=test" in data + + +# @pytest.mark.asyncio +# @pytest.mark.usefixtures("setup_session") +# @pytest.mark.usefixtures("_global_server") +# async def test_global_ldap_search( +# global_settings: Settings, +# creds: TestCreds, +# ) -> None: +# """Test ldapsearch on server.""" +# print("OLOLO 00000") +# print( +# global_settings.HOST, +# global_settings.PORT, +# "\n\n", +# creds.un, +# creds.pw, +# ) + +# # Проверка что порты слушаются +# netstat_proc = await asyncio.create_subprocess_exec( +# "netstat", +# "-tuln", +# stdout=asyncio.subprocess.PIPE, +# stderr=asyncio.subprocess.PIPE, +# ) +# netstat_out, _ = await netstat_proc.communicate() +# print("NETSTAT OUTPUT:") +# print(netstat_out.decode()) +# print("=" * 50) + +# # Используем 127.0.0.1 для подключения (0.0.0.0 это bind адрес, не connect) +# host = ( +# "127.0.0.1" +# if str(global_settings.HOST) == "0.0.0.0" +# else str(global_settings.HOST) +# ) + +# # Сначала проверим что порт доступен через telnet +# print(f"Testing TCP connection to {host}:{global_settings.PORT}...") +# try: +# reader, writer = await asyncio.wait_for( +# asyncio.open_connection(host, global_settings.PORT), +# timeout=2.0, +# ) +# print("✓ TCP connection successful!") +# writer.close() +# await writer.wait_closed() +# except TimeoutError: +# print("✗ TCP connection timeout!") +# except Exception as e: +# print(f"✗ TCP connection failed: {e}") + +# print(f"Running ldapsearch to {host}:{global_settings.PORT}...") + +# # Подождем еще чтобы сервер точно готов +# await asyncio.sleep(2) + +# # Сначала проверим что обычный LDAP на 389 работает +# print("Testing regular LDAP on port 389 first...") +# proc_389 = await asyncio.create_subprocess_exec( +# "ldapsearch", +# "-x", +# "-H", +# f"ldap://{host}:389", +# "-D", +# creds.un, +# "-w", +# creds.pw, +# "-b", +# "dc=md,dc=test", +# "-s", +# "base", +# "objectclass=*", +# stdout=asyncio.subprocess.PIPE, +# stderr=asyncio.subprocess.PIPE, +# ) +# try: +# data_389, err_389 = await asyncio.wait_for( +# proc_389.communicate(), +# timeout=5.0, +# ) +# result_389 = await proc_389.wait() +# print(f"✓ Port 389 works! Result: {result_389}") +# except TimeoutError: +# print("✗ Port 389 also timeout!") +# proc_389.kill() + +# print("Now testing global LDAP on port 3268...") +# proc = await asyncio.create_subprocess_exec( +# "ldapsearch", +# "-vvv", +# "-x", +# "-H", +# f"ldap://{host}:{global_settings.PORT}", +# "-D", +# creds.un, +# "-w", +# creds.pw, +# "-b", +# "dc=md,dc=test", +# "objectclass=*", +# stdout=asyncio.subprocess.PIPE, +# stderr=asyncio.subprocess.PIPE, +# ) +# print("ldapsearch process started, waiting for response...") + +# try: +# raw_data, raw_err = await asyncio.wait_for( +# proc.communicate(), +# timeout=10.0, +# ) +# data = raw_data.decode().split("\n") +# err = raw_err.decode().split("\n") +# result = await proc.wait() +# except TimeoutError: +# print("✗ ldapsearch timeout after 10 seconds!") +# proc.kill() +# raise + +# print("OLOLO") +# print("STDOUT:", data) +# print("STDERR:", err) +# print("RESULT:", result) +# assert result == 0 +# assert "dn: cn=groups,dc=md,dc=test" in data +# assert "dn: cn=users,dc=md,dc=test" in data +# assert "dn: cn=user0,cn=users,dc=md,dc=test" in data From f2a94c850af660b29860cecf2bba9fd78de178f0 Mon Sep 17 00:00:00 2001 From: Milov Dmitriy Date: Tue, 23 Dec 2025 15:59:54 +0300 Subject: [PATCH 07/15] tests: delete tests task_1046 --- app/ldap_protocol/server.py | 5 - docker-compose.test.yml | 4 +- interface | 2 +- tests/test_global_ldap/__init__.py | 0 tests/test_global_ldap/test_search.py | 207 -------------------------- 5 files changed, 2 insertions(+), 216 deletions(-) delete mode 100644 tests/test_global_ldap/__init__.py delete mode 100644 tests/test_global_ldap/test_search.py diff --git a/app/ldap_protocol/server.py b/app/ldap_protocol/server.py index 7030079ba..661fc51fc 100644 --- a/app/ldap_protocol/server.py +++ b/app/ldap_protocol/server.py @@ -69,7 +69,6 @@ async def __call__( writer: asyncio.StreamWriter, ) -> None: """Create session, queue and start message handlers concurrently.""" - log.info("SOSI4") async with self.container(scope=Scope.SESSION) as session_scope: ldap_session = await session_scope.get(LDAPSession) addr, first_chunk = await self.recieve( @@ -435,11 +434,8 @@ async def _get_server(self) -> asyncio.base_events.Server: @staticmethod async def _run_server(server: asyncio.base_events.Server) -> None: """Run server.""" - log.info("SOSI1") async with server: - log.info("SOSI2") await server.serve_forever() - log.info("SOSI3") @staticmethod def log_addrs(server: asyncio.base_events.Server) -> None: @@ -455,7 +451,6 @@ async def start(self) -> None: ) try: - log.info("SOSI") await self._run_server(server) finally: server.close() diff --git a/docker-compose.test.yml b/docker-compose.test.yml index 07908c61a..221a4e4bf 100644 --- a/docker-compose.test.yml +++ b/docker-compose.test.yml @@ -20,9 +20,7 @@ services: POSTGRES_HOST: postgres # PYTHONTRACEMALLOC: 1 PYTHONDONTWRITEBYTECODE: 1 - # command: sh -c "python -B -m pytest -n auto -x tests/test_ldap/test_util/test_add.py -W ignore::DeprecationWarning -vv" - command: sh -c "python -B -m pytest -n 0 -x -s tests/test_global_ldap -W ignore::DeprecationWarning -vv" - # command: sh -c "python -B -m pytest -x -s tests/test_ldap/test_util/test_search.py -W ignore::DeprecationWarning -vv" + command: sh -c "python -B -m pytest -n auto -x -W ignore::DeprecationWarning -vv" tty: true postgres: diff --git a/interface b/interface index 094bf0367..017b7f344 160000 --- a/interface +++ b/interface @@ -1 +1 @@ -Subproject commit 094bf0367c26c574310e43714a1adfcf61ca57a1 +Subproject commit 017b7f344e290814e3af5ca0f210a592afaf08ed diff --git a/tests/test_global_ldap/__init__.py b/tests/test_global_ldap/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/test_global_ldap/test_search.py b/tests/test_global_ldap/test_search.py deleted file mode 100644 index 01140f999..000000000 --- a/tests/test_global_ldap/test_search.py +++ /dev/null @@ -1,207 +0,0 @@ -"""Test search with ldaputil. - -Copyright (c) 2024 MultiFactor -License: https://github.com/MultiDirectoryLab/MultiDirectory/blob/main/LICENSE -""" - -import asyncio - -import pytest - -from config import Settings -from tests.conftest import TestCreds - - -# @pytest.mark.asyncio -# @pytest.mark.usefixtures("setup_session") -# @pytest.mark.usefixtures("session") -# async def test_ldap_search(settings: Settings, creds: TestCreds) -> None: -# """Test ldapsearch on server.""" -# proc = await asyncio.create_subprocess_exec( -# "ldapsearch", -# "-vvv", -# "-x", -# "-H", -# f"ldap://{settings.HOST}:{settings.PORT}", -# "-D", -# creds.un, -# "-w", -# creds.pw, -# "-b", -# "dc=md,dc=test", -# "objectclass=*", -# stdout=asyncio.subprocess.PIPE, -# stderr=asyncio.subprocess.PIPE, -# ) - -# raw_data, _ = await proc.communicate() -# data = raw_data.decode().split("\n") -# result = await proc.wait() - -# assert result == 0 -# assert "dn: cn=groups,dc=md,dc=test" in data -# assert "dn: cn=users,dc=md,dc=test" in data -# assert "dn: cn=user0,cn=users,dc=md,dc=test" in data - - -@pytest.mark.asyncio -@pytest.mark.usefixtures("setup_session") -@pytest.mark.usefixtures("_global_server") -async def test_global_ldap_search( - global_settings: Settings, - creds: TestCreds, -) -> None: - """Test ldapsearch on server.""" - proc = await asyncio.create_subprocess_exec( - "ldapsearch", - "-vvv", - "-x", - "-H", - f"ldap://{global_settings.HOST}:{global_settings.PORT}", - "-D", - creds.un, - "-w", - creds.pw, - "-b", - "dc=md,dc=test", - "objectclass=*", - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, - ) - - raw_data, _ = await proc.communicate() - data = raw_data.decode().split("\n") - result = await proc.wait() - - assert result == 0 - assert "dn: cn=groups,dc=md,dc=test" in data - assert "dn: cn=users,dc=md,dc=test" in data - assert "dn: cn=user0,cn=users,dc=md,dc=test" in data - - -# @pytest.mark.asyncio -# @pytest.mark.usefixtures("setup_session") -# @pytest.mark.usefixtures("_global_server") -# async def test_global_ldap_search( -# global_settings: Settings, -# creds: TestCreds, -# ) -> None: -# """Test ldapsearch on server.""" -# print("OLOLO 00000") -# print( -# global_settings.HOST, -# global_settings.PORT, -# "\n\n", -# creds.un, -# creds.pw, -# ) - -# # Проверка что порты слушаются -# netstat_proc = await asyncio.create_subprocess_exec( -# "netstat", -# "-tuln", -# stdout=asyncio.subprocess.PIPE, -# stderr=asyncio.subprocess.PIPE, -# ) -# netstat_out, _ = await netstat_proc.communicate() -# print("NETSTAT OUTPUT:") -# print(netstat_out.decode()) -# print("=" * 50) - -# # Используем 127.0.0.1 для подключения (0.0.0.0 это bind адрес, не connect) -# host = ( -# "127.0.0.1" -# if str(global_settings.HOST) == "0.0.0.0" -# else str(global_settings.HOST) -# ) - -# # Сначала проверим что порт доступен через telnet -# print(f"Testing TCP connection to {host}:{global_settings.PORT}...") -# try: -# reader, writer = await asyncio.wait_for( -# asyncio.open_connection(host, global_settings.PORT), -# timeout=2.0, -# ) -# print("✓ TCP connection successful!") -# writer.close() -# await writer.wait_closed() -# except TimeoutError: -# print("✗ TCP connection timeout!") -# except Exception as e: -# print(f"✗ TCP connection failed: {e}") - -# print(f"Running ldapsearch to {host}:{global_settings.PORT}...") - -# # Подождем еще чтобы сервер точно готов -# await asyncio.sleep(2) - -# # Сначала проверим что обычный LDAP на 389 работает -# print("Testing regular LDAP on port 389 first...") -# proc_389 = await asyncio.create_subprocess_exec( -# "ldapsearch", -# "-x", -# "-H", -# f"ldap://{host}:389", -# "-D", -# creds.un, -# "-w", -# creds.pw, -# "-b", -# "dc=md,dc=test", -# "-s", -# "base", -# "objectclass=*", -# stdout=asyncio.subprocess.PIPE, -# stderr=asyncio.subprocess.PIPE, -# ) -# try: -# data_389, err_389 = await asyncio.wait_for( -# proc_389.communicate(), -# timeout=5.0, -# ) -# result_389 = await proc_389.wait() -# print(f"✓ Port 389 works! Result: {result_389}") -# except TimeoutError: -# print("✗ Port 389 also timeout!") -# proc_389.kill() - -# print("Now testing global LDAP on port 3268...") -# proc = await asyncio.create_subprocess_exec( -# "ldapsearch", -# "-vvv", -# "-x", -# "-H", -# f"ldap://{host}:{global_settings.PORT}", -# "-D", -# creds.un, -# "-w", -# creds.pw, -# "-b", -# "dc=md,dc=test", -# "objectclass=*", -# stdout=asyncio.subprocess.PIPE, -# stderr=asyncio.subprocess.PIPE, -# ) -# print("ldapsearch process started, waiting for response...") - -# try: -# raw_data, raw_err = await asyncio.wait_for( -# proc.communicate(), -# timeout=10.0, -# ) -# data = raw_data.decode().split("\n") -# err = raw_err.decode().split("\n") -# result = await proc.wait() -# except TimeoutError: -# print("✗ ldapsearch timeout after 10 seconds!") -# proc.kill() -# raise - -# print("OLOLO") -# print("STDOUT:", data) -# print("STDERR:", err) -# print("RESULT:", result) -# assert result == 0 -# assert "dn: cn=groups,dc=md,dc=test" in data -# assert "dn: cn=users,dc=md,dc=test" in data -# assert "dn: cn=user0,cn=users,dc=md,dc=test" in data From 30bf13a2697b2fb82c3a420ea548185e1eb1f44f Mon Sep 17 00:00:00 2001 From: Milov Dmitriy Date: Tue, 23 Dec 2025 16:00:53 +0300 Subject: [PATCH 08/15] refactor: fix lines task_1046 --- docker-compose.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 35a0f8572..903d59845 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -504,8 +504,6 @@ services: migrations: condition: service_completed_successfully - - event_sender: image: multidirectory container_name: event_sender From c26eeacb606ef64896e47b2bad20c4a48268302b Mon Sep 17 00:00:00 2001 From: Milov Dmitriy Date: Tue, 23 Dec 2025 16:07:02 +0300 Subject: [PATCH 09/15] fix: linter task_1046 --- app/ldap_protocol/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/ldap_protocol/server.py b/app/ldap_protocol/server.py index c023e27b9..9e55e23d7 100644 --- a/app/ldap_protocol/server.py +++ b/app/ldap_protocol/server.py @@ -452,7 +452,7 @@ async def start(self) -> None: server = await self._get_server() log.info( f"started {'DEBUG' if self.settings.DEBUG else 'PROD'} " - f"{'LDAPS' if self.settings.USE_CORE_TLS else 'LDAP'} server {self.settings.HOST}:{self.settings.PORT}", + f"{'LDAPS' if self.settings.USE_CORE_TLS else 'LDAP'} server", ) try: From 27392ea31175673aaed2dd9bcc7f5ba67fa6f382 Mon Sep 17 00:00:00 2001 From: Milov Dmitriy Date: Tue, 23 Dec 2025 16:08:10 +0300 Subject: [PATCH 10/15] tests: fix delete global catalog ldap server task_1046 --- tests/conftest.py | 74 ----------------------------------------------- 1 file changed, 74 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 42f64f9d7..dd04858f9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -784,16 +784,6 @@ def event_loop() -> Generator: loop.close() -@pytest.fixture(scope="session") -def global_event_loop() -> Generator: - """Create uvloop event loop.""" - loop = uvloop.new_event_loop() - yield loop - with suppress(asyncio.CancelledError, RuntimeError): - asyncio.gather(*asyncio.tasks.all_tasks(loop)).cancel() - loop.close() - - @pytest.fixture(scope="session") def settings() -> Settings: """Get settings.""" @@ -926,39 +916,6 @@ async def raw_audit_manager( yield await container.get(RawAuditManager) -@pytest.fixture(scope="session") -def global_settings() -> Settings: - """Get settings.""" - settings = Settings.from_os() - return settings.get_copy_4_global() - - -@pytest_asyncio.fixture(scope="session") -async def global_container( - global_settings: Settings, -) -> AsyncIterator[AsyncContainer]: - """Create test container.""" - ctnr = make_async_container( - TestProvider(), - MFACredsProvider(), - context={Settings: global_settings}, - start_scope=Scope.RUNTIME, - ) - yield ctnr - await ctnr.close() - - -@pytest_asyncio.fixture(scope="session") -async def global_handler( - global_settings: Settings, - global_container: AsyncContainer, -) -> AsyncIterator[PoolClientHandler]: - """Create test handler.""" - global_settings.set_test_global_port() - async with global_container(scope=Scope.APP) as app_scope: - yield PoolClientHandler(global_settings, app_scope) - - @pytest_asyncio.fixture(scope="function") async def setup_session( session: AsyncSession, @@ -1247,37 +1204,6 @@ async def role_use_case( yield RoleUseCase(role_dao, ace_dao) -# @pytest.fixture(scope="session", autouse=True) -# def _server( -# event_loop: asyncio.BaseEventLoop, -# handler: PoolClientHandler, -# _migrations: None, -# ) -> Generator: -# """Run server in background.""" -# task = asyncio.ensure_future(handler.start(), loop=event_loop) -# event_loop.run_until_complete(asyncio.sleep(0.1)) -# yield -# with suppress(asyncio.CancelledError): -# task.cancel() - - -@pytest.fixture(scope="session", autouse=True) -def _global_server( - global_event_loop: asyncio.BaseEventLoop, - global_handler: PoolClientHandler, - _migrations: None, -) -> Generator: - """Run global LDAP server in background.""" - task = asyncio.ensure_future( - global_handler.start(), - loop=global_event_loop, - ) - global_event_loop.run_until_complete(asyncio.sleep(0.1)) - yield - with suppress(asyncio.CancelledError): - task.cancel() - - @pytest.fixture async def ldap_client( settings: Settings, From b553cc397b9c44732826ec6f35c41fe51197dac4 Mon Sep 17 00:00:00 2001 From: Milov Dmitriy Date: Tue, 23 Dec 2025 16:17:28 +0300 Subject: [PATCH 11/15] fix: tests --- tests/conftest.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index dd04858f9..b97a8ce4a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1204,6 +1204,19 @@ async def role_use_case( yield RoleUseCase(role_dao, ace_dao) +@pytest.fixture(scope="session", autouse=True) +def _server( + event_loop: asyncio.BaseEventLoop, + handler: PoolClientHandler, +) -> Generator: + """Run server in background.""" + task = asyncio.ensure_future(handler.start(), loop=event_loop) + event_loop.run_until_complete(asyncio.sleep(0.1)) + yield + with suppress(asyncio.CancelledError): + task.cancel() + + @pytest.fixture async def ldap_client( settings: Settings, From 1c03ee69e3f37940eabbf94dc683abfcf198386a Mon Sep 17 00:00:00 2001 From: Milov Dmitriy Date: Tue, 23 Dec 2025 16:42:12 +0300 Subject: [PATCH 12/15] fix: merge task_1046 --- app/config.py | 5 ----- app/ioc.py | 10 ++++++++++ app/ldap_protocol/server.py | 3 --- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/app/config.py b/app/config.py index 4a9cca12e..423eb2bf8 100644 --- a/app/config.py +++ b/app/config.py @@ -158,11 +158,6 @@ def TEST_POSTGRES_SCHEMA(self) -> str: # noqa: N802 return "public" return f"test_schema_{self.PYTEST_XDIST_WORKER}" - def set_test_global_port(self) -> None: - """Set test port.""" - self.GLOBAL_LDAP_PORT += self.TEST_WORKER_ID - self.PORT = self.GLOBAL_LDAP_PORT - def set_test_port(self) -> None: """Set test port.""" self.PORT += self.TEST_WORKER_ID diff --git a/app/ioc.py b/app/ioc.py index 7b245fef9..c111396dd 100644 --- a/app/ioc.py +++ b/app/ioc.py @@ -733,6 +733,16 @@ async def get_session( scope=Scope.REQUEST, ) + network_policy_validator = provide( + NetworkPolicyValidatorGateway, + provides=NetworkPolicyValidatorProtocol, + scope=Scope.REQUEST, + ) + network_policy_validator_use_case = provide( + NetworkPolicyValidatorUseCase, + scope=Scope.REQUEST, + ) + class MFACredsProvider(Provider): """Creds provider.""" diff --git a/app/ldap_protocol/server.py b/app/ldap_protocol/server.py index 9e55e23d7..6f24cd312 100644 --- a/app/ldap_protocol/server.py +++ b/app/ldap_protocol/server.py @@ -426,9 +426,6 @@ async def _handle_responses( async def _get_server(self) -> asyncio.base_events.Server: """Get async server.""" - log.info( - f"SOSAI _get_server {self.settings.HOST}, {self.settings.PORT}", - ) return await asyncio.start_server( self, str(self.settings.HOST), From 1b716104239a13ea645346e96be1fe40cd228b47 Mon Sep 17 00:00:00 2001 From: Milov Dmitriy Date: Wed, 24 Dec 2025 13:32:29 +0300 Subject: [PATCH 13/15] fix: extend composes task_1046 --- .package/docker-compose.yml | 53 +++++++++++++++++++++++++++++++++-- .package/traefik.yml | 4 +++ docker-compose.dev.yml | 56 +++++++++++++++++++++++++++++++++++-- docker-compose.yml | 4 +-- 4 files changed, 111 insertions(+), 6 deletions(-) diff --git a/.package/docker-compose.yml b/.package/docker-compose.yml index f408a5c98..b01eeb724 100644 --- a/.package/docker-compose.yml +++ b/.package/docker-compose.yml @@ -67,8 +67,6 @@ services: depends_on: - api_server - - migrations: image: ghcr.io/multidirectorylab/multidirectory:${VERSION:-latest} container_name: multidirectory_migrations @@ -155,6 +153,57 @@ services: - traefik.udp.routers.cldap.service=cldap - traefik.udp.services.cldap.loadbalancer.server.port=389 + global_ldap_server: + image: ghcr.io/multidirectorylab/multidirectory:${VERSION:-latest} + restart: unless-stopped + build: + context: . + dockerfile: ./.docker/dev.Dockerfile + args: + DOCKER_BUILDKIT: 1 + target: runtime + deploy: + mode: replicated + replicas: 2 + endpoint_mode: dnsrr + resources: + reservations: + cpus: "0.25" + memory: 100M + environment: + - SERVICE_NAME=global_ldap_server + volumes: + - ./app:/app + - ./certs:/certs + - ldap_keytab:/LDAP_keytab/ + env_file: local.env + command: python -OO multidirectory.py --global_ldap_server + tty: true + depends_on: + migrations: + condition: service_completed_successfully + healthcheck: + test: ["CMD-SHELL", "nc -zv 127.0.0.1 3268 3269"] + interval: 30s + timeout: 10s + retries: 10 + start_period: 3s + labels: + - traefik.enable=true + + - traefik.tcp.routers.global_ldap.rule=HostSNI(`*`) + - traefik.tcp.routers.global_ldap.entrypoints=global_ldap + - traefik.tcp.routers.global_ldap.service=global_ldap + - traefik.tcp.services.global_ldap.loadbalancer.server.port=3268 + - traefik.tcp.services.global_ldap.loadbalancer.proxyprotocol.version=2 + + - traefik.tcp.routers.global_ldap_tls.rule=HostSNI(`*`) + - traefik.tcp.routers.global_ldap_tls.entrypoints=global_ldap_tls + - traefik.tcp.routers.global_ldap_tls.service=global_ldap_tls + - traefik.tcp.routers.global_ldap_tls.tls=true + - traefik.tcp.services.global_ldap_tls.loadbalancer.server.port=3269 + - traefik.tcp.services.global_ldap_tls.loadbalancer.proxyprotocol.version=2 + api_server: image: ghcr.io/multidirectorylab/multidirectory:${VERSION:-latest} container_name: multidirectory_api diff --git a/.package/traefik.yml b/.package/traefik.yml index 77c0f9594..b33d35ed6 100644 --- a/.package/traefik.yml +++ b/.package/traefik.yml @@ -24,6 +24,10 @@ entryPoints: address: ":389" proxyProtocol: insecure: true + global_ldap: + address: ":3268" + global_ldap_tls: + address: ":3269" ldaps: address: ":636" proxyProtocol: diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index a562245b7..d14df0bc2 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -121,6 +121,58 @@ services: - traefik.udp.routers.cldap.service=cldap - traefik.udp.services.cldap.loadbalancer.server.port=389 + global_ldap_server: + image: multidirectory + restart: unless-stopped + build: + context: . + dockerfile: ./.docker/dev.Dockerfile + args: + DOCKER_BUILDKIT: 1 + target: runtime + deploy: + mode: replicated + replicas: 2 + endpoint_mode: dnsrr + resources: + reservations: + cpus: "0.25" + memory: 100M + environment: + - SERVICE_NAME=global_ldap_server + volumes: + - ./app:/app + - ./certs:/certs + - ldap_keytab:/LDAP_keytab/ + env_file: local.env + command: python -OO multidirectory.py --global_ldap_server + tty: true + depends_on: + migrations: + condition: service_completed_successfully + cert_local_check: + condition: service_completed_successfully + healthcheck: + test: ["CMD-SHELL", "nc -zv 127.0.0.1 3268 3269"] + interval: 30s + timeout: 10s + retries: 10 + start_period: 3s + labels: + - traefik.enable=true + + - traefik.tcp.routers.global_ldap.rule=HostSNI(`*`) + - traefik.tcp.routers.global_ldap.entrypoints=global_ldap + - traefik.tcp.routers.global_ldap.service=global_ldap + - traefik.tcp.services.global_ldap.loadbalancer.server.port=3268 + - traefik.tcp.services.global_ldap.loadbalancer.proxyprotocol.version=2 + + - traefik.tcp.routers.global_ldap_tls.rule=HostSNI(`*`) + - traefik.tcp.routers.global_ldap_tls.entrypoints=global_ldap_tls + - traefik.tcp.routers.global_ldap_tls.service=global_ldap_tls + - traefik.tcp.routers.global_ldap_tls.tls=true + - traefik.tcp.services.global_ldap_tls.loadbalancer.server.port=3269 + - traefik.tcp.services.global_ldap_tls.loadbalancer.proxyprotocol.version=2 cert_local_check: image: multidirectory @@ -313,7 +365,7 @@ services: condition: service_healthy restart: true command: krb5kdc -n - + ports: - "88:88" - "88:88/udp" @@ -447,4 +499,4 @@ volumes: dns_server_file: dns_server_config: ldap_keytab: - dragonflydata: \ No newline at end of file + dragonflydata: diff --git a/docker-compose.yml b/docker-compose.yml index 903d59845..2bc4dce87 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -116,14 +116,14 @@ services: - traefik.udp.services.cldap.loadbalancer.server.port=389 global_ldap_server: + image: multidirectory + restart: unless-stopped build: context: . dockerfile: ./.docker/dev.Dockerfile args: DOCKER_BUILDKIT: 1 target: runtime - image: multidirectory - restart: unless-stopped deploy: mode: replicated replicas: 2 From 0adcc5d07e457194a66aa28b3364ffbe1e777e77 Mon Sep 17 00:00:00 2001 From: Milov Dmitriy Date: Thu, 25 Dec 2025 14:28:08 +0300 Subject: [PATCH 14/15] fixes --- .package/docker-compose.yml | 9 +++------ .package/traefik.yml | 4 ++++ docker-compose.dev.yml | 1 + traefik.yml | 4 ++++ 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.package/docker-compose.yml b/.package/docker-compose.yml index b01eeb724..778749dd5 100644 --- a/.package/docker-compose.yml +++ b/.package/docker-compose.yml @@ -13,6 +13,8 @@ services: - "53:53/udp" - "80:80" - "389:389" + - "3268:3268" + - "3269:3269" - "443:443" - "464:464" - "636:636" @@ -156,12 +158,6 @@ services: global_ldap_server: image: ghcr.io/multidirectorylab/multidirectory:${VERSION:-latest} restart: unless-stopped - build: - context: . - dockerfile: ./.docker/dev.Dockerfile - args: - DOCKER_BUILDKIT: 1 - target: runtime deploy: mode: replicated replicas: 2 @@ -201,6 +197,7 @@ services: - traefik.tcp.routers.global_ldap_tls.entrypoints=global_ldap_tls - traefik.tcp.routers.global_ldap_tls.service=global_ldap_tls - traefik.tcp.routers.global_ldap_tls.tls=true + - traefik.tcp.routers.global_ldap_tls.tls.certresolver=md-resolver - traefik.tcp.services.global_ldap_tls.loadbalancer.server.port=3269 - traefik.tcp.services.global_ldap_tls.loadbalancer.proxyprotocol.version=2 diff --git a/.package/traefik.yml b/.package/traefik.yml index b33d35ed6..bb8d711de 100644 --- a/.package/traefik.yml +++ b/.package/traefik.yml @@ -26,8 +26,12 @@ entryPoints: insecure: true global_ldap: address: ":3268" + proxyProtocol: + insecure: true global_ldap_tls: address: ":3269" + proxyProtocol: + insecure: true ldaps: address: ":636" proxyProtocol: diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index d14df0bc2..1373e1744 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -171,6 +171,7 @@ services: - traefik.tcp.routers.global_ldap_tls.entrypoints=global_ldap_tls - traefik.tcp.routers.global_ldap_tls.service=global_ldap_tls - traefik.tcp.routers.global_ldap_tls.tls=true + - traefik.tcp.routers.global_ldap_tls.tls.certresolver=md-resolver - traefik.tcp.services.global_ldap_tls.loadbalancer.server.port=3269 - traefik.tcp.services.global_ldap_tls.loadbalancer.proxyprotocol.version=2 diff --git a/traefik.yml b/traefik.yml index 3fca1e1d0..f95bf72f3 100644 --- a/traefik.yml +++ b/traefik.yml @@ -24,8 +24,12 @@ entryPoints: insecure: true global_ldap: address: ":3268" + proxyProtocol: + insecure: true global_ldap_tls: address: ":3269" + proxyProtocol: + insecure: true kadmind: address: ":749" kpasswd: From b115e0732c26827551dc12d02d1b48f260c570be Mon Sep 17 00:00:00 2001 From: Milov Dmitriy Date: Thu, 25 Dec 2025 15:21:08 +0300 Subject: [PATCH 15/15] fix --- .package/docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.package/docker-compose.yml b/.package/docker-compose.yml index 778749dd5..d865ee0c1 100644 --- a/.package/docker-compose.yml +++ b/.package/docker-compose.yml @@ -169,10 +169,10 @@ services: environment: - SERVICE_NAME=global_ldap_server volumes: - - ./app:/app - ./certs:/certs - ldap_keytab:/LDAP_keytab/ - env_file: local.env + env_file: + - .env command: python -OO multidirectory.py --global_ldap_server tty: true depends_on: