-
Notifications
You must be signed in to change notification settings - Fork 0
Add Domain Controller #938
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
Open
Naksen
wants to merge
24
commits into
dev
Choose a base branch
from
feature/1121_add_instance_record
base: dev
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
4c154d3
add: Domain Controller OU in first setup
Naksen 4b255be
add: implement domain controller data creation in setup process
Naksen 0afaeba
fix: update type hints for AsyncTTLCache callable signature
Naksen 8385b8d
fix: adjust parameter order in AsyncTTLCache __call__ method
Naksen fa6ca54
add: implement creation and deletion of 'Domain Controllers' OU in da…
Naksen ea46d6e
add: include settings parameter in SetupUseCase and use it for ipHost…
Naksen 9175aea
fix: update sAMAccountType to use SAM_MACHINE_ACCOUNT in domain contr…
Naksen ef874fa
add: introduce DEFAULT_NAMESERVER setting in configuration
Naksen 931feff
add: implement add_domain_controller script and schedule task
Naksen 54b4a71
add: set DEFAULT_NAMESERVER in test environment configuration
Naksen d192473
add: set is_system attribute for organizational units and groups in s…
Naksen e54b726
test: set is_system attribute to False for test data and remove from …
Naksen 8117002
fix: ensure DEFAULT_NAMESERVER is correctly defined in local.env
Naksen 8495b65
add: implement test for add_domain_controller function
Naksen 3a29197
refactor: change critical logs to debug level in add_domain_controlle…
Naksen f82fc4a
refactor: streamline attribute creation in _add_domain_controller fun…
Naksen 00de376
add: define DEFAULT_NAMESERVER in md-test service environment
Naksen 4338452
refactor: update debug log message for existing domain controllers
Naksen 4abc8af
add: include 'cn' attribute in domain controller creation
Naksen 37fda7b
add: include HOSTNAME variable in configuration and setup scripts
Naksen 9c07220
add: use HOSTNAME variable for domain controller naming in setup
Naksen 2b0c9f3
refactor: rename HOSTNAME to HOST_MACHINE_NAME across configuration a…
Naksen a66d767
test: set is_system attribute to True for group and container definit…
Naksen afb0dde
fix: add missing newline at end of setup.sh
Naksen File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
102 changes: 102 additions & 0 deletions
102
app/alembic/versions/ebf19750805e_add_domain_controllers_ou.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| """Add OU 'Domain Controllers' if it does not exist. | ||
|
|
||
| Revision ID: ebf19750805e | ||
| Revises: 2dadf40c026a | ||
| Create Date: 2026-02-17 08:52:28.048004 | ||
|
|
||
| """ | ||
|
|
||
| from alembic import op | ||
| from dishka import AsyncContainer, Scope | ||
| from sqlalchemy import delete, exists, select | ||
| from sqlalchemy.ext.asyncio import AsyncConnection, AsyncSession | ||
|
|
||
| from constants import DOMAIN_CONTROLLERS_OU_NAME | ||
| from entities import Directory | ||
| from ldap_protocol.auth.setup_gateway import SetupGateway | ||
| from ldap_protocol.roles.role_use_case import RoleUseCase | ||
| from ldap_protocol.utils.queries import get_base_directories | ||
| from repo.pg.tables import queryable_attr as qa | ||
|
|
||
| # revision identifiers, used by Alembic. | ||
| revision: None | str = "ebf19750805e" | ||
| down_revision: None | str = "2dadf40c026a" | ||
| branch_labels: None | list[str] = None | ||
| depends_on: None | list[str] = None | ||
|
|
||
|
|
||
| _OU_DOMAIN_CONTROLLERS_DATA = { | ||
| "name": DOMAIN_CONTROLLERS_OU_NAME, | ||
| "object_class": "organizationalUnit", | ||
| "is_system": True, | ||
| "attributes": {"objectClass": ["top", "container"]}, | ||
| "children": [], | ||
| } | ||
|
|
||
|
|
||
| def upgrade(container: AsyncContainer) -> None: | ||
| """Upgrade.""" | ||
|
|
||
| async def _create_domain_controllers_ou( | ||
| connection: AsyncConnection, # noqa: ARG001 | ||
| ) -> None: | ||
| async with container(scope=Scope.REQUEST) as cnt: | ||
| session = await cnt.get(AsyncSession) | ||
| setup_gateway = await cnt.get(SetupGateway) | ||
| role_use_case = await cnt.get(RoleUseCase) | ||
|
|
||
| base_directories = await get_base_directories(session) | ||
| if not base_directories: | ||
| return | ||
| domain_dir = base_directories[0] | ||
|
|
||
| exists_dc_ou = await session.scalar( | ||
| select( | ||
| exists(Directory) | ||
| .where(qa(Directory.name) == DOMAIN_CONTROLLERS_OU_NAME), | ||
| ), | ||
| ) # fmt: skip | ||
| if exists_dc_ou: | ||
| return | ||
|
|
||
| await setup_gateway.create_dir( | ||
| _OU_DOMAIN_CONTROLLERS_DATA, | ||
| domain=domain_dir, | ||
| parent=domain_dir, | ||
| ) | ||
|
|
||
| dc_ou = await session.scalar( | ||
| select(Directory).where( | ||
| qa(Directory.name) == DOMAIN_CONTROLLERS_OU_NAME, | ||
| ), | ||
| ) | ||
| if not dc_ou: | ||
| raise Exception("Domain Controllers OU was not created") | ||
|
|
||
| await role_use_case.inherit_parent_aces( | ||
| parent_directory=domain_dir, | ||
| directory=dc_ou, | ||
| ) | ||
|
|
||
| await session.commit() | ||
|
|
||
| op.run_async(_create_domain_controllers_ou) | ||
|
|
||
|
|
||
| def downgrade(container: AsyncContainer) -> None: | ||
| """Downgrade.""" | ||
|
|
||
| async def _delete_domain_controllers_ou( | ||
| connection: AsyncConnection, # noqa: ARG001 | ||
| ) -> None: | ||
| async with container(scope=Scope.REQUEST) as cnt: | ||
| session = await cnt.get(AsyncSession) | ||
|
|
||
| await session.execute( | ||
| delete(Directory).where( | ||
| qa(Directory.name) == DOMAIN_CONTROLLERS_OU_NAME, | ||
| ), | ||
| ) | ||
| await session.commit() | ||
|
|
||
| op.run_async(_delete_domain_controllers_ou) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,147 @@ | ||
| """Add domain controller. | ||
|
|
||
| Copyright (c) 2026 MultiFactor | ||
| License: https://github.com/MultiDirectoryLab/MultiDirectory/blob/main/LICENSE | ||
| """ | ||
|
|
||
| from loguru import logger | ||
| from sqlalchemy import select | ||
| from sqlalchemy.ext.asyncio import AsyncSession | ||
|
|
||
| from config import Settings | ||
| from constants import DOMAIN_CONTROLLERS_OU_NAME | ||
| from entities import Attribute, Directory | ||
| from enums import SamAccountTypeCodes | ||
| from ldap_protocol.ldap_schema.entity_type_dao import EntityTypeDAO | ||
| from ldap_protocol.objects import UserAccountControlFlag | ||
| from ldap_protocol.roles.role_use_case import RoleUseCase | ||
| from ldap_protocol.utils.helpers import create_object_sid | ||
| from ldap_protocol.utils.queries import get_base_directories | ||
| from repo.pg.tables import queryable_attr as qa | ||
|
|
||
|
|
||
| async def _add_domain_controller( | ||
| session: AsyncSession, | ||
| role_use_case: RoleUseCase, | ||
| entity_type_dao: EntityTypeDAO, | ||
| settings: Settings, | ||
| domain: Directory, | ||
| dc_ou_dir: Directory, | ||
| ) -> None: | ||
| dc_directory = Directory( | ||
| object_class="", | ||
| name=settings.HOST_MACHINE_NAME, | ||
| is_system=False, | ||
| ) | ||
| dc_directory.create_path(dc_ou_dir) | ||
| session.add(dc_directory) | ||
| await session.flush() | ||
|
|
||
| dc_directory.parent_id = dc_ou_dir.id | ||
| dc_directory.object_sid = create_object_sid(domain, dc_directory.id) | ||
| await session.flush() | ||
|
|
||
| attributes = [ | ||
| Attribute( | ||
| name="objectClass", | ||
| value="top", | ||
| directory_id=dc_directory.id, | ||
| ), | ||
| Attribute( | ||
| name="objectClass", | ||
| value="computer", | ||
| directory_id=dc_directory.id, | ||
| ), | ||
| Attribute( | ||
| name="sAMAccountName", | ||
| value=settings.HOST_MACHINE_NAME, | ||
| directory_id=dc_directory.id, | ||
| ), | ||
| Attribute( | ||
| name="userAccountControl", | ||
| value=str( | ||
| UserAccountControlFlag.SERVER_TRUST_ACCOUNT, | ||
| ), | ||
| directory_id=dc_directory.id, | ||
| ), | ||
| Attribute( | ||
| name="sAMAccountType", | ||
| value=str(SamAccountTypeCodes.SAM_MACHINE_ACCOUNT), | ||
| directory_id=dc_directory.id, | ||
| ), | ||
| Attribute( | ||
| name="ipHostNumber", | ||
| value=settings.DEFAULT_NAMESERVER, | ||
| directory_id=dc_directory.id, | ||
| ), | ||
| Attribute( | ||
| name="cn", | ||
| value=settings.HOST_MACHINE_NAME, | ||
| directory_id=dc_directory.id, | ||
| ), | ||
| ] | ||
|
|
||
| session.add_all(attributes) | ||
| await session.flush() | ||
|
|
||
| await role_use_case.inherit_parent_aces( | ||
| parent_directory=dc_ou_dir, | ||
| directory=dc_directory, | ||
| ) | ||
| await entity_type_dao.attach_entity_type_to_directory( | ||
| directory=dc_directory, | ||
| is_system_entity_type=False, | ||
| object_class_names={"top", "computer"}, | ||
| ) | ||
| await session.flush() | ||
|
|
||
|
|
||
| async def add_domain_controller( | ||
| session: AsyncSession, | ||
| settings: Settings, | ||
| role_use_case: RoleUseCase, | ||
| entity_type_dao: EntityTypeDAO, | ||
| ) -> None: | ||
| logger.info("Adding domain controller.") | ||
|
|
||
| domains = await get_base_directories(session) | ||
| if not domains: | ||
| logger.debug("Cannot get base directory") | ||
| return | ||
|
|
||
| domain_controllers_ou = await session.scalar( | ||
| select(Directory).where( | ||
| qa(Directory.name) == DOMAIN_CONTROLLERS_OU_NAME, | ||
| ), | ||
| ) | ||
|
|
||
| if not domain_controllers_ou: | ||
| logger.debug("Domain controllers OU does not exist.") | ||
| return | ||
|
|
||
Naksen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| domain_controller = await session.scalar( | ||
| select(qa(Directory.id).distinct()) | ||
| .join(qa(Directory.attributes)) | ||
| .where( | ||
| qa(Directory.parent_id) == domain_controllers_ou.id, | ||
| qa(Attribute.name) == "ipHostNumber", | ||
| qa(Attribute.value) == settings.DEFAULT_NAMESERVER, | ||
| ), | ||
| ) | ||
|
|
||
| if domain_controller: | ||
| logger.debug("Domain controllers already exists") | ||
| return | ||
|
|
||
| await _add_domain_controller( | ||
| session=session, | ||
| role_use_case=role_use_case, | ||
| entity_type_dao=entity_type_dao, | ||
Naksen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| settings=settings, | ||
| domain=domains[0], | ||
| dc_ou_dir=domain_controllers_ou, | ||
| ) | ||
|
|
||
| logger.debug("Domain controller added.") | ||
|
|
||
| await session.commit() | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.