3131)
3232from fastapi .middleware .cors import CORSMiddleware
3333from fastapi .responses import FileResponse
34- from pydantic import BaseModel
34+ from pydantic import BaseModel , Field
3535from starlette .background import BackgroundTask
3636
3737KRB5_CONF_PATH = "/etc/krb5.conf"
@@ -79,6 +79,30 @@ class PrincipalNotFoundError(Exception):
7979 """Not found error."""
8080
8181
82+ class AddPrincipalRequest (BaseModel ):
83+ """Request model for adding principal."""
84+
85+ principal_name : str
86+ password : str | None = None
87+ algorithms : list [str ] | None = None
88+
89+
90+ class KtaddRequest (BaseModel ):
91+ """Request model for ktadd."""
92+
93+ names : list [str ]
94+ is_rand_key : bool = Field (default = False )
95+
96+
97+ class ModifyPrincipalRequest (BaseModel ):
98+ """Request model for modifying principal."""
99+
100+ principal_name : str
101+ new_name : str | None = None
102+ algorithms : list [str ] | None = None
103+ password : str | None = None
104+
105+
82106class AbstractKRBManager (ABC ):
83107 """Kadmin manager."""
84108
@@ -95,12 +119,14 @@ async def add_princ(
95119 self ,
96120 name : str ,
97121 password : str | None ,
122+ algorithms : list [str ] | None = None ,
98123 ** dbargs ,
99124 ) -> None :
100125 """Create principal.
101126
102127 :param str name: principal
103- :param str | None password: if empty - uses randkey.
128+ :param str | None password: if None - uses randkey.
129+ :param list[str] | None algorithms: encryption algorithms
104130 """
105131
106132 @abstractmethod
@@ -135,19 +161,17 @@ async def del_princ(self, name: str) -> None:
135161 """
136162
137163 @abstractmethod
138- async def rename_princ (self , name : str , new_name : str ) -> None :
139- """Rename principal.
140-
141- :param str name: original name
142- :param str new_name: new name
143- """
144-
145- @abstractmethod
146- async def ktadd (self , names : list [str ], fn : str ) -> None :
164+ async def ktadd (
165+ self ,
166+ names : list [str ],
167+ fn : str ,
168+ is_rand_key : bool = False ,
169+ ) -> None :
147170 """Create or write to keytab.
148171
149- :param str name: principal
172+ :param list[ str] names: principals
150173 :param str fn: filename
174+ :param bool is_rand_key: generate random key
151175 """
152176
153177 @abstractmethod
@@ -164,6 +188,23 @@ async def force_pw_principal(self, name: str, **dbargs) -> None:
164188 :param str name: principal
165189 """
166190
191+ @abstractmethod
192+ async def modify_principal (
193+ self ,
194+ principal_name : str ,
195+ new_name : str | None = None ,
196+ algorithms : list [str ] | None = None ,
197+ password : str | None = None ,
198+ ** dbargs ,
199+ ) -> None :
200+ """Modify principal (rename, change algorithms, password).
201+
202+ :param str principal_name: current principal name
203+ :param str | None new_name: new name if rename needed
204+ :param list[str] | None algorithms: new encryption algorithms
205+ :param str | None password: new password
206+ """
207+
167208
168209class KAdminLocalManager (AbstractKRBManager ):
169210 """Kadmin manager."""
@@ -206,19 +247,30 @@ async def add_princ(
206247 self ,
207248 name : str ,
208249 password : str | None ,
250+ algorithms : list [str ] | None = None ,
209251 ** dbargs ,
210252 ) -> None :
211253 """Create principal.
212254
213255 :param str name: principal
214- :param str | None password: if empty - uses randkey.
256+ :param str | None password: if None - uses randkey.
257+ :param list[str] | None algorithms: encryption algorithms
215258 """
216- await self .loop .run_in_executor (
217- self .pool ,
218- self .client .add_principal ,
219- name ,
220- password ,
221- )
259+ if algorithms :
260+ await self .loop .run_in_executor (
261+ self .pool ,
262+ self .client .add_principal ,
263+ name ,
264+ password ,
265+ algorithms ,
266+ )
267+ else :
268+ await self .loop .run_in_executor (
269+ self .pool ,
270+ self .client .add_principal ,
271+ name ,
272+ password ,
273+ )
222274
223275 if password :
224276 # NOTE: add preauth, attributes == krbticketflags
@@ -287,32 +339,58 @@ async def del_princ(self, name: str) -> None:
287339 except kadmv .UnknownPrincipalError :
288340 raise PrincipalNotFoundError
289341
290- async def rename_princ (self , name : str , new_name : str ) -> None :
291- """Rename principal.
292-
293- :param str name: original name
294- :param str new_name: new name
295- """
296- await self .loop .run_in_executor (
297- self .pool ,
298- self .client .rename_principal ,
299- name ,
300- new_name ,
301- )
302-
303- async def ktadd (self , names : list [str ], fn : str ) -> None :
342+ async def ktadd (
343+ self ,
344+ names : list [str ],
345+ fn : str ,
346+ is_rand_key : bool = False ,
347+ ) -> None :
304348 """Create or write to keytab.
305349
306- :param str name: principal
350+ :param list[ str] names: principals
307351 :param str fn: filename
308- :raises self.PrincipalNotFoundError: on not found princ
352+ :param bool is_rand_key: generate random key
353+ :raises PrincipalNotFoundError: on not found princ
309354 """
310355 principals = [await self ._get_raw_principal (name ) for name in names ]
311356 if not all (principals ):
312357 raise PrincipalNotFoundError ("Principal not found" )
313358
314- for princ in principals :
315- await self .loop .run_in_executor (self .pool , princ .ktadd , fn )
359+ if is_rand_key :
360+ for princ in principals :
361+ await self .loop .run_in_executor (
362+ self .pool ,
363+ princ .ktadd ,
364+ fn ,
365+ True ,
366+ )
367+
368+ else :
369+ for princ in principals :
370+ await self .loop .run_in_executor (self .pool , princ .ktadd , fn )
371+
372+ async def _ktadd_with_randkey_via_subprocess (
373+ self ,
374+ principal_name : str ,
375+ keytab_path : str ,
376+ ) -> None :
377+ """Execute ktadd with randkey via subprocess."""
378+ cmd = [
379+ "kadmin.local" ,
380+ "-q" ,
381+ f"ktadd -k { keytab_path } -randkey { principal_name } " ,
382+ ]
383+
384+ proc = await asyncio .create_subprocess_exec (
385+ * cmd ,
386+ stdout = asyncio .subprocess .PIPE ,
387+ stderr = asyncio .subprocess .PIPE ,
388+ )
389+
390+ stdout , stderr = await proc .communicate ()
391+
392+ if await proc .wait () != 0 :
393+ raise RuntimeError (f"ktadd failed: { stderr .decode ()} " )
316394
317395 async def lock_princ (self , name : str , ** dbargs ) -> None :
318396 """Lock princ.
@@ -332,6 +410,36 @@ async def force_pw_principal(self, name: str, **dbargs) -> None:
332410 princ .pwexpire = "Now"
333411 await self .loop .run_in_executor (self .pool , princ .commit )
334412
413+ async def modify_principal (
414+ self ,
415+ principal_name : str ,
416+ new_name : str | None = None ,
417+ algorithms : list [str ] | None = None ,
418+ password : str | None = None ,
419+ ** dbargs ,
420+ ) -> None :
421+ """Modify principal (rename, change algorithms, password).
422+
423+ :param str principal_name: current principal name
424+ :param str | None new_name: new name if rename needed
425+ :param list[str] | None algorithms: new encryption algorithms
426+ :param str | None password: new password
427+ """
428+ args = []
429+ if new_name :
430+ args .append (new_name )
431+ if password :
432+ args .append (password )
433+ if algorithms :
434+ args .append (algorithms )
435+
436+ await self .loop .run_in_executor (
437+ self .pool ,
438+ self .client .modify_principal ,
439+ principal_name ,
440+ * args ,
441+ )
442+
335443
336444@asynccontextmanager
337445async def kadmin_lifespan (app : FastAPI ) -> AsyncIterator [None ]:
@@ -494,28 +602,29 @@ async def reset_setup() -> None:
494602@principal_router .post ("" , response_class = Response , status_code = 201 )
495603async def add_princ (
496604 kadmin : Annotated [AbstractKRBManager , Depends (get_kadmin )],
497- name : Annotated [str , Body ()],
498- password : Annotated [str | None , Body (embed = True )] = None ,
605+ request : AddPrincipalRequest ,
499606) -> None :
500607 """Add principal.
501608
502609 :param Annotated[AbstractKRBManager, Depends kadmin: kadmin abstract
503- :param Annotated[str, Body name: principal name
504- :param Annotated[str, Body password: principal password
610+ :param AddPrincipalRequest request: request data
505611 """
506- await kadmin .add_princ (name , password )
612+ await kadmin .add_princ (
613+ request .principal_name ,
614+ request .password ,
615+ algorithms = request .algorithms ,
616+ )
507617
508618
509619@principal_router .get ("" )
510620async def get_princ (
511621 kadmin : Annotated [AbstractKRBManager , Depends (get_kadmin )],
512622 name : str ,
513623) -> Principal :
514- """Add principal.
624+ """Get principal.
515625
516626 :param Annotated[AbstractKRBManager, Depends kadmin: kadmin abstract
517- :param Annotated[str, Body name: principal name
518- :param Annotated[str, Body password: principal password
627+ :param str name: principal name
519628 """
520629 return await kadmin .get_princ (name )
521630
@@ -525,11 +634,10 @@ async def del_princ(
525634 kadmin : Annotated [AbstractKRBManager , Depends (get_kadmin )],
526635 name : str ,
527636) -> None :
528- """Add principal.
637+ """Delete principal.
529638
530639 :param Annotated[AbstractKRBManager, Depends kadmin: kadmin abstract
531- :param Annotated[str, Body name: principal name
532- :param Annotated[str, Body password: principal password
640+ :param str name: principal name
533641 """
534642 await kadmin .del_princ (name )
535643
@@ -569,39 +677,49 @@ async def create_or_update_princ_password(
569677
570678
571679@principal_router .put (
572- "" ,
680+ "/modify " ,
573681 status_code = status .HTTP_202_ACCEPTED ,
574682 response_class = Response ,
575683)
576- async def rename_princ (
684+ async def modify_princ (
577685 kadmin : Annotated [AbstractKRBManager , Depends (get_kadmin )],
578- name : Annotated [str , Body ()],
579- new_name : Annotated [str , Body ()],
686+ request : ModifyPrincipalRequest ,
580687) -> None :
581- """Rename principal.
688+ """Modify principal (rename, algorithms, password) .
582689
583690 :param Annotated[AbstractKRBManager, Depends kadmin: kadmin abstract
584- :param Annotated[str, Body name: principal name
585- :param Annotated[str, Body new_name: principal new name
691+ :param ModifyPrincipalRequest request: request data
586692 """
587- """"""
588- await kadmin .rename_princ (name , new_name )
693+ await kadmin .modify_principal (
694+ principal_name = request .principal_name ,
695+ new_name = request .new_name ,
696+ algorithms = request .algorithms ,
697+ password = request .password ,
698+ )
589699
590700
591701@principal_router .post ("/ktadd" )
592702async def ktadd (
593703 kadmin : Annotated [AbstractKRBManager , Depends (get_kadmin )],
594- names : Annotated [ list [ str ], Body ()] ,
704+ request : KtaddRequest ,
595705) -> FileResponse :
596706 """Ktadd principal.
597707
598708 :param Annotated[AbstractKRBManager, Depends kadmin: kadmin abstract
599- :param Annotated[str, Body name: principal name
600- :param Annotated[str, Body password: principal password
709+ :param KtaddRequest request: request data
601710 """
602711 filename = os .path .join (gettempdir (), str (uuid .uuid1 ()))
603- await kadmin .ktadd (names , filename )
604-
712+ if request .is_rand_key :
713+ await kadmin .ktadd (
714+ request .names ,
715+ filename ,
716+ is_rand_key = request .is_rand_key ,
717+ )
718+ else :
719+ await kadmin .ktadd (
720+ request .names ,
721+ filename ,
722+ )
605723 return FileResponse (
606724 filename ,
607725 background = BackgroundTask (os .unlink , filename ),
0 commit comments