From 182f82b2c87e9a11f8affe8ba54cc96e1e104d37 Mon Sep 17 00:00:00 2001 From: "m.shvets" Date: Tue, 3 Feb 2026 13:05:11 +0300 Subject: [PATCH 1/3] Add: introduce new schemas for principal and ktadd requests, refactor related API endpoints to utilize these schemas for better data handling and validation. --- .kerberos/config_server.py | 41 +++++++++++++++++++------ app/api/main/adapters/kerberos.py | 40 ++++++++++++++---------- app/api/main/krb5_router.py | 38 +++++++++-------------- app/api/main/schema.py | 24 +++++++++++++++ app/ldap_protocol/kerberos/base.py | 19 +++++++++--- app/ldap_protocol/kerberos/client.py | 30 +++++++++++++++--- app/ldap_protocol/kerberos/service.py | 29 ++++++++++++++--- app/ldap_protocol/kerberos/stub.py | 15 ++++++--- app/ldap_protocol/ldap_requests/bind.py | 2 +- interface | 2 +- 10 files changed, 170 insertions(+), 70 deletions(-) diff --git a/.kerberos/config_server.py b/.kerberos/config_server.py index 2806c9b86..6657ac6cd 100644 --- a/.kerberos/config_server.py +++ b/.kerberos/config_server.py @@ -61,6 +61,21 @@ class ConfigSchema(BaseModel): stash_password: str +class PrincipalCreateSchema(BaseModel): + """Schema for principal creation.""" + + name: str + password: str | None = None + algorithms: list[str] | None = None + + +class KtaddSchema(BaseModel): + """Schema for ktadd request.""" + + names: list[str] + is_rand_key: bool = False + + class Principal(BaseModel): """Principal kadmin object.""" @@ -300,7 +315,12 @@ async def rename_princ(self, name: str, new_name: str) -> None: new_name, ) - async def ktadd(self, names: list[str], fn: str) -> None: + async def ktadd( + self, + names: list[str], + fn: str, + is_rand_key: bool = False, # noqa: ARG002 + ) -> None: """Create or write to keytab. :param str name: principal @@ -494,16 +514,18 @@ async def reset_setup() -> None: @principal_router.post("", response_class=Response, status_code=201) async def add_princ( kadmin: Annotated[AbstractKRBManager, Depends(get_kadmin)], - name: Annotated[str, Body()], - password: Annotated[str | None, Body(embed=True)] = None, + schema: PrincipalCreateSchema, ) -> None: """Add principal. :param Annotated[AbstractKRBManager, Depends kadmin: kadmin abstract - :param Annotated[str, Body name: principal name - :param Annotated[str, Body password: principal password + :param PrincipalCreateSchema schema: principal data """ - await kadmin.add_princ(name, password) + await kadmin.add_princ( + schema.name, + schema.password, + algorithms=schema.algorithms or {}, + ) @principal_router.get("") @@ -591,16 +613,15 @@ async def rename_princ( @principal_router.post("/ktadd") async def ktadd( kadmin: Annotated[AbstractKRBManager, Depends(get_kadmin)], - names: Annotated[list[str], Body()], + schema: KtaddSchema, ) -> FileResponse: """Ktadd principal. :param Annotated[AbstractKRBManager, Depends kadmin: kadmin abstract - :param Annotated[str, Body name: principal name - :param Annotated[str, Body password: principal password + :param KtaddSchema schema: ktadd request data """ filename = os.path.join(gettempdir(), str(uuid.uuid1())) - await kadmin.ktadd(names, filename) + await kadmin.ktadd(schema.names, filename, is_rand_key=schema.is_rand_key) return FileResponse( filename, diff --git a/app/api/main/adapters/kerberos.py b/app/api/main/adapters/kerberos.py index 1bbe252e2..6124636c4 100644 --- a/app/api/main/adapters/kerberos.py +++ b/app/api/main/adapters/kerberos.py @@ -12,7 +12,12 @@ from starlette.background import BackgroundTask from api.base_adapter import BaseAdapter -from api.main.schema import KerberosSetupRequest +from api.main.schema import ( + KerberosSetupRequest, + KtaddRequest, + PrincipalAddRequest, + PrincipalPutRequest, +) from ldap_protocol.dialogue import LDAPSession, UserSchema from ldap_protocol.kerberos import KerberosState from ldap_protocol.kerberos.service import KerberosService @@ -66,31 +71,29 @@ async def setup_kdc( ) return Response(background=task) - async def add_principal( - self, - primary: str, - instance: str, - ) -> None: + async def add_principal(self, request: PrincipalAddRequest) -> None: """Create principal in Kerberos with given name. :raises HTTPException: on Kerberos errors :return: None """ - return await self._service.add_principal(primary, instance) + return await self._service.add_principal( + request.principal_name, + password=request.password, + algorithms=request.algorithms, + ) - async def rename_principal( - self, - principal_name: str, - principal_new_name: str, - ) -> None: - """Rename principal in Kerberos. + async def rename_principal(self, request: PrincipalPutRequest) -> None: + """Modify principal (rename, password, algorithms). :raises HTTPException: on Kerberos errors :return: None """ return await self._service.rename_principal( - principal_name, - principal_new_name, + principal_name=request.principal_name, + principal_new_name=request.new_principal_name, + algorithms=request.algorithms, + password=request.password, ) async def reset_principal_pw( @@ -121,14 +124,17 @@ async def delete_principal( async def ktadd( self, - names: list[str], + data: KtaddRequest, ) -> StreamingResponse: """Generate keytab and return as streaming response. :raises HTTPException: on Kerberos errors :return: StreamingResponse """ - aiter_bytes, task_struct = await self._service.ktadd(names) + aiter_bytes, task_struct = await self._service.ktadd( + data.names, + is_rand_key=data.is_rand_key, + ) task = BackgroundTask( task_struct.func, *task_struct.args, diff --git a/app/api/main/krb5_router.py b/app/api/main/krb5_router.py index 91f64a5b6..56959aa4d 100644 --- a/app/api/main/krb5_router.py +++ b/app/api/main/krb5_router.py @@ -23,7 +23,12 @@ DomainErrorTranslator, ) from api.main.adapters.kerberos import KerberosFastAPIAdapter -from api.main.schema import KerberosSetupRequest +from api.main.schema import ( + KerberosSetupRequest, + KtaddRequest, + PrincipalAddRequest, + PrincipalPutRequest, +) from enums import DomainCodes from ldap_protocol.dialogue import LDAPSession from ldap_protocol.kerberos import KerberosState @@ -143,15 +148,15 @@ async def setup_kdc( error_map=error_map, ) async def ktadd( - names: Annotated[LIMITED_LIST, Body()], kerberos_adapter: FromDishka[KerberosFastAPIAdapter], + request: KtaddRequest, ) -> StreamingResponse: """Create keytab from kadmin server. :param Annotated[LDAPSession, Depends ldap_session: ldap :return bytes: file """ - return await kerberos_adapter.ktadd(names) + return await kerberos_adapter.ktadd(request) @krb5_router.get( @@ -172,13 +177,12 @@ async def get_krb_status( @krb5_router.post( - "/principal/add", + "/principal", dependencies=[Depends(verify_auth)], error_map=error_map, ) async def add_principal( - primary: Annotated[LIMITED_STR, Body()], - instance: Annotated[LIMITED_STR, Body()], + request: PrincipalAddRequest, kerberos_adapter: FromDishka[KerberosFastAPIAdapter], ) -> None: """Create principal in kerberos with given name. @@ -188,31 +192,19 @@ async def add_principal( :param Annotated[LDAPSession, Depends ldap_session: ldap :raises HTTPException: on failed kamin request. """ - await kerberos_adapter.add_principal(primary, instance) + await kerberos_adapter.add_principal(request) -@krb5_router.patch( - "/principal/rename", +@krb5_router.put( + "/principal", dependencies=[Depends(verify_auth)], error_map=error_map, ) async def rename_principal( - principal_name: Annotated[LIMITED_STR, Body()], - principal_new_name: Annotated[LIMITED_STR, Body()], + request: PrincipalPutRequest, kerberos_adapter: FromDishka[KerberosFastAPIAdapter], ) -> None: - """Rename principal in kerberos with given name. - - \f - :param Annotated[str, Body principal_name: upn - :param Annotated[LIMITED_STR, Body principal_new_name: _description_ - :param Annotated[LDAPSession, Depends ldap_session: ldap - :raises HTTPException: on failed kamin request. - """ - await kerberos_adapter.rename_principal( - principal_name, - principal_new_name, - ) + await kerberos_adapter.rename_principal(request) @krb5_router.patch( diff --git a/app/api/main/schema.py b/app/api/main/schema.py index 537b0af7c..96f90d254 100644 --- a/app/api/main/schema.py +++ b/app/api/main/schema.py @@ -70,6 +70,30 @@ class KerberosSetupRequest(BaseModel): stash_password: SecretStr +class PrincipalAddRequest(BaseModel): + """Request schema for POST /principal/add.""" + + principal_name: str + algorithms: list[str] | None = None + password: str | None = None + + +class KtaddRequest(BaseModel): + """Request schema for POST /ktadd.""" + + names: list[str] + is_rand_key: bool = False + + +class PrincipalPutRequest(BaseModel): + """Request schema for PUT /principal (full modify).""" + + principal_name: str + new_principal_name: str + algorithms: list[str] | None = None + password: str | None = None + + class DNSServiceSetupRequest(BaseModel): """DNS setup request schema.""" diff --git a/app/ldap_protocol/kerberos/base.py b/app/ldap_protocol/kerberos/base.py index d70960738..7faf30565 100644 --- a/app/ldap_protocol/kerberos/base.py +++ b/app/ldap_protocol/kerberos/base.py @@ -153,8 +153,9 @@ async def setup( @abstractmethod async def add_principal( self, - name: str, - password: str | None, + principal_name: str, + password: str | None = None, + algorithms: list[str] | None = None, timeout: int | float = 1, ) -> None: ... @@ -179,7 +180,13 @@ async def create_or_update_principal_pw( ) -> None: ... @abstractmethod - async def rename_princ(self, name: str, new_name: str) -> None: ... + async def rename_princ( + self, + name: str, + new_name: str, + algorithms: list[str] | None = None, + password: str | None = None, + ) -> None: ... @backoff.on_exception( backoff.constant, @@ -202,7 +209,11 @@ async def get_status(self, wait_for_positive: bool = False) -> bool: return status @abstractmethod - async def ktadd(self, names: list[str]) -> httpx.Response: ... + async def ktadd( + self, + names: list[str], + is_rand_key: bool, + ) -> httpx.Response: ... @abstractmethod async def lock_principal(self, name: str) -> None: ... diff --git a/app/ldap_protocol/kerberos/client.py b/app/ldap_protocol/kerberos/client.py index 8dfcb8a23..4d35d17c2 100644 --- a/app/ldap_protocol/kerberos/client.py +++ b/app/ldap_protocol/kerberos/client.py @@ -20,12 +20,17 @@ async def add_principal( self, name: str, password: str | None, + algorithms: list[str] | None, timeout: int = 1, ) -> None: """Add request.""" response = await self.client.post( "principal", - json={"name": name, "password": password}, + json={ + "name": name, + "password": password, + "algorithms": algorithms, + }, timeout=timeout, ) @@ -89,17 +94,32 @@ async def create_or_update_principal_pw( raise krb_exc.KRBAPIChangePasswordError(response.text) @logger_wraps() - async def rename_princ(self, name: str, new_name: str) -> None: + async def rename_princ( + self, + name: str, + new_name: str, + algorithms: list[str] | None, + password: str | None, + ) -> None: """Rename request.""" response = await self.client.put( "principal", - json={"name": name, "new_name": new_name}, + json={ + "name": name, + "new_name": new_name, + "algorithms": algorithms, + "password": password, + }, ) if response.status_code != 202: raise krb_exc.KRBAPIRenamePrincipalError(response.text) @logger_wraps() - async def ktadd(self, names: list[str]) -> httpx.Response: + async def ktadd( + self, + names: list[str], + is_rand_key: bool, + ) -> httpx.Response: """Ktadd build request for stream and return response. :param list[str] names: principals @@ -108,7 +128,7 @@ async def ktadd(self, names: list[str]) -> httpx.Response: request = self.client.build_request( "POST", "/principal/ktadd", - json=names, + json={"names": names, "is_rand_key": is_rand_key}, ) response = await self.client.send(request, stream=True) diff --git a/app/ldap_protocol/kerberos/service.py b/app/ldap_protocol/kerberos/service.py index 985d8cdc1..8d3ad1fc8 100644 --- a/app/ldap_protocol/kerberos/service.py +++ b/app/ldap_protocol/kerberos/service.py @@ -358,7 +358,12 @@ async def _schedule_principal_task( ) return TaskStruct(func=func, args=args) - async def add_principal(self, primary: str, instance: str) -> None: + async def add_principal( + self, + principal_name: str, + password: str | None, + algorithms: list[str] | None, + ) -> None: """Create principal in Kerberos with given name. :param str primary: Principal primary name. @@ -367,8 +372,11 @@ async def add_principal(self, primary: str, instance: str) -> None: :return None: None. """ try: - principal_name = f"{primary}/{instance}" - await self._kadmin.add_principal(principal_name, None) + await self._kadmin.add_principal( + principal_name, + password, + algorithms, + ) except KRBAPIAddPrincipalError as exc: raise KerberosDependencyError( f"Error adding principal: {exc}", @@ -378,16 +386,25 @@ async def rename_principal( self, principal_name: str, principal_new_name: str, + algorithms: list[str] | None, + password: str | None, ) -> None: """Rename principal in Kerberos with given name. :param str principal_name: Current principal name. :param str principal_new_name: New principal name. + :param list[str] | None algorithms: Algorithms. + :param str | None password: Password. :raises KerberosDependencyError: On failed kadmin request. :return None: None. """ try: - await self._kadmin.rename_princ(principal_name, principal_new_name) + await self._kadmin.rename_princ( + principal_name, + principal_new_name, + algorithms, + password, + ) except KRBAPIRenamePrincipalError as exc: raise KerberosDependencyError( f"Error renaming principal: {exc}", @@ -432,15 +449,17 @@ async def delete_principal(self, principal_name: str) -> None: async def ktadd( self, names: list[str], + is_rand_key: bool, ) -> tuple[AsyncIterator[bytes], TaskStruct]: """Generate keytab and return (aiter_bytes, TaskStruct). :param list[str] names: List of principal names. + :param bool is_rand_key: If True, generate random key. :raises KerberosNotFoundError: If principal not found. :return tuple: (aiter_bytes, (func, args, kwargs)). """ try: - response = await self._kadmin.ktadd(names) + response = await self._kadmin.ktadd(names, is_rand_key) except KRBAPIPrincipalNotFoundError: raise KerberosNotFoundError("Principal not found") aiter_bytes = response.aiter_bytes() diff --git a/app/ldap_protocol/kerberos/stub.py b/app/ldap_protocol/kerberos/stub.py index 889583c16..b118b09e8 100644 --- a/app/ldap_protocol/kerberos/stub.py +++ b/app/ldap_protocol/kerberos/stub.py @@ -19,8 +19,9 @@ async def setup(self, *args, **kwargs) -> None: # type: ignore @logger_wraps(is_stub=True) async def add_principal( self, - name: str, - password: str | None, + principal_name: str, + password: str | None = None, + algorithms: list[str] | None = None, timeout: int = 1, ) -> None: ... @@ -45,10 +46,16 @@ async def create_or_update_principal_pw( ) -> None: ... @logger_wraps(is_stub=True) - async def rename_princ(self, name: str, new_name: str) -> None: ... + async def rename_princ( + self, + name: str, + new_name: str, + algorithms: list[str] | None = None, + password: str | None = None, + ) -> None: ... @logger_wraps(is_stub=True) - async def ktadd(self, names: list[str]) -> NoReturn: # noqa: ARG002 + async def ktadd(self, names: list[str], is_rand_key: bool) -> NoReturn: # noqa: ARG002 raise KRBAPIPrincipalNotFoundError @logger_wraps(is_stub=True) diff --git a/app/ldap_protocol/ldap_requests/bind.py b/app/ldap_protocol/ldap_requests/bind.py index ad764649e..5d81f4003 100644 --- a/app/ldap_protocol/ldap_requests/bind.py +++ b/app/ldap_protocol/ldap_requests/bind.py @@ -211,7 +211,7 @@ async def handle( await ctx.kadmin.add_principal( user.sam_account_name, self.authentication_choice.password.get_secret_value(), - 0.1, + timeout=0.1, ) await ctx.ldap_session.set_user(user) diff --git a/interface b/interface index e1ca5656a..f31962020 160000 --- a/interface +++ b/interface @@ -1 +1 @@ -Subproject commit e1ca5656aeabc20a1862aeaf11ded72feaa97403 +Subproject commit f31962020a6689e6a4c61fb3349db5b5c7895f92 From 6449d0433cb59e04e282d26d72338245488f25ac Mon Sep 17 00:00:00 2001 From: "m.shvets" Date: Tue, 3 Feb 2026 14:10:00 +0300 Subject: [PATCH 2/3] Refactor: remove unused schemas for principal creation and ktadd requests, update API endpoints to accept parameters directly for improved clarity and simplicity. --- .kerberos/config_server.py | 30 +++++++--------------------- app/ldap_protocol/kerberos/client.py | 4 ++-- 2 files changed, 9 insertions(+), 25 deletions(-) diff --git a/.kerberos/config_server.py b/.kerberos/config_server.py index 6657ac6cd..abc0d9a51 100644 --- a/.kerberos/config_server.py +++ b/.kerberos/config_server.py @@ -61,21 +61,6 @@ class ConfigSchema(BaseModel): stash_password: str -class PrincipalCreateSchema(BaseModel): - """Schema for principal creation.""" - - name: str - password: str | None = None - algorithms: list[str] | None = None - - -class KtaddSchema(BaseModel): - """Schema for ktadd request.""" - - names: list[str] - is_rand_key: bool = False - - class Principal(BaseModel): """Principal kadmin object.""" @@ -319,7 +304,6 @@ async def ktadd( self, names: list[str], fn: str, - is_rand_key: bool = False, # noqa: ARG002 ) -> None: """Create or write to keytab. @@ -514,17 +498,17 @@ async def reset_setup() -> None: @principal_router.post("", response_class=Response, status_code=201) async def add_princ( kadmin: Annotated[AbstractKRBManager, Depends(get_kadmin)], - schema: PrincipalCreateSchema, + name: Annotated[str, Body()], + password: Annotated[str | None, Body(embed=True)] = None, ) -> None: """Add principal. :param Annotated[AbstractKRBManager, Depends kadmin: kadmin abstract - :param PrincipalCreateSchema schema: principal data + """ await kadmin.add_princ( - schema.name, - schema.password, - algorithms=schema.algorithms or {}, + name, + password, ) @@ -613,7 +597,7 @@ async def rename_princ( @principal_router.post("/ktadd") async def ktadd( kadmin: Annotated[AbstractKRBManager, Depends(get_kadmin)], - schema: KtaddSchema, + names: Annotated[list[str], Body()], ) -> FileResponse: """Ktadd principal. @@ -621,7 +605,7 @@ async def ktadd( :param KtaddSchema schema: ktadd request data """ filename = os.path.join(gettempdir(), str(uuid.uuid1())) - await kadmin.ktadd(schema.names, filename, is_rand_key=schema.is_rand_key) + await kadmin.ktadd(names, filename) return FileResponse( filename, diff --git a/app/ldap_protocol/kerberos/client.py b/app/ldap_protocol/kerberos/client.py index 4d35d17c2..b7bacfdb8 100644 --- a/app/ldap_protocol/kerberos/client.py +++ b/app/ldap_protocol/kerberos/client.py @@ -20,8 +20,8 @@ async def add_principal( self, name: str, password: str | None, - algorithms: list[str] | None, - timeout: int = 1, + algorithms: list[str] | None = None, + timeout: int | float = 1, ) -> None: """Add request.""" response = await self.client.post( From 6a75be528a490acf3fd1f3d895545f1253c6ecfa Mon Sep 17 00:00:00 2001 From: "m.shvets" Date: Tue, 3 Feb 2026 14:12:00 +0300 Subject: [PATCH 3/3] Refactor: streamline ktadd and add_princ methods by removing unnecessary line breaks for improved readability. --- .kerberos/config_server.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/.kerberos/config_server.py b/.kerberos/config_server.py index abc0d9a51..1c397105a 100644 --- a/.kerberos/config_server.py +++ b/.kerberos/config_server.py @@ -300,11 +300,7 @@ async def rename_princ(self, name: str, new_name: str) -> None: new_name, ) - async def ktadd( - self, - names: list[str], - fn: str, - ) -> None: + async def ktadd(self, names: list[str], fn: str) -> None: """Create or write to keytab. :param str name: principal @@ -506,10 +502,7 @@ async def add_princ( :param Annotated[AbstractKRBManager, Depends kadmin: kadmin abstract """ - await kadmin.add_princ( - name, - password, - ) + await kadmin.add_princ(name, password) @principal_router.get("")