Skip to content

Commit 96e344c

Browse files
authored
Add randkey for krb api (#924)
1 parent b12f7fa commit 96e344c

18 files changed

Lines changed: 375 additions & 230 deletions

File tree

.kerberos/config_server.py

Lines changed: 180 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
)
3232
from fastapi.middleware.cors import CORSMiddleware
3333
from fastapi.responses import FileResponse
34-
from pydantic import BaseModel
34+
from pydantic import BaseModel, Field
3535
from starlette.background import BackgroundTask
3636

3737
KRB5_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+
82106
class 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

168209
class 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
337445
async 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)
495603
async 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("")
510620
async 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")
592702
async 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

Comments
 (0)