22#
33# SPDX-License-Identifier: MIT
44
5+ import asyncio
56import logging
7+ import re
8+ import subprocess
69from functools import lru_cache
710from os import getenv
811
12+ import koji as koji_module
913from copr .v3 import Client
1014
1115
16+ class KerberosError (Exception ):
17+ """Exception raised for Kerberos-related errors."""
18+
19+
1220@lru_cache
1321def copr ():
1422 return Client ({"copr_url" : "https://copr.fedorainfracloud.org" })
1523
1624
25+ @lru_cache
26+ def koji ():
27+ """
28+ Create and return a Koji session for querying Fedora Koji builds.
29+ """
30+ koji_url = getenv ("KOJI_URL" , "https://koji.fedoraproject.org/kojihub" )
31+ return koji_module .ClientSession (koji_url )
32+
33+
1734@lru_cache
1835def sentry_sdk ():
1936 if sentry_secret := getenv ("SENTRY_SECRET" ):
@@ -32,3 +49,121 @@ def log_failure(message: str):
3249 return
3350
3451 logging .warning (message )
52+
53+
54+ async def extract_principal_from_keytab (keytab_file : str ) -> str :
55+ """
56+ Extract principal from the specified keytab file.
57+ Assumes there is a single principal in the keytab.
58+
59+ Args:
60+ keytab_file: Path to a keytab file.
61+
62+ Returns:
63+ Extracted principal name.
64+ """
65+ proc = await asyncio .create_subprocess_exec (
66+ "klist" ,
67+ "-k" ,
68+ "-K" ,
69+ "-e" ,
70+ keytab_file ,
71+ stdout = subprocess .PIPE ,
72+ stderr = subprocess .PIPE ,
73+ )
74+ stdout , stderr = await proc .communicate ()
75+ if proc .returncode :
76+ logging .error ("klist command failed: %s" , stderr .decode ())
77+ msg = "klist command failed"
78+ raise KerberosError (msg )
79+
80+ # Parse klist output to extract principal
81+ # Format: " 2 principal@REALM (aes256-cts-hmac-sha1-96) (0x...)"
82+ key_pattern = re .compile (r"^\s*(\d+)\s+(\S+)\s+\((\S+)\)\s+\((\S+)\)$" )
83+ for line in stdout .decode ().splitlines ():
84+ if match := key_pattern .match (line ):
85+ # Return the principal associated with the first key
86+ return match .group (2 )
87+
88+ msg = "No valid key found in the keytab file"
89+ raise KerberosError (msg )
90+
91+
92+ async def init_kerberos_ticket (keytab_file : str ) -> str :
93+ """
94+ Initialize Kerberos ticket from keytab file.
95+
96+ Args:
97+ keytab_file: Path to keytab file
98+
99+ Returns:
100+ Principal name for which ticket was initialized
101+ """
102+ # Extract principal from keytab
103+ principal = await extract_principal_from_keytab (keytab_file )
104+ logging .debug ("Extracted principal from keytab: %s" , principal )
105+
106+ # Check if ticket already exists
107+ proc = await asyncio .create_subprocess_exec (
108+ "klist" ,
109+ "-l" ,
110+ stdout = subprocess .PIPE ,
111+ stderr = subprocess .PIPE ,
112+ )
113+ stdout , stderr = await proc .communicate ()
114+
115+ if proc .returncode == 0 :
116+ # Parse existing principals
117+ principals = [
118+ parts [0 ]
119+ for line in stdout .decode ().splitlines ()
120+ if "Expired" not in line
121+ for parts in (line .split (),)
122+ if len (parts ) >= 1 and "@" in parts [0 ]
123+ ]
124+
125+ if principal in principals :
126+ logging .info ("Using existing Kerberos ticket for %s" , principal )
127+ return principal
128+
129+ # Initialize new ticket
130+ logging .info ("Initializing Kerberos ticket for %s" , principal )
131+ proc = await asyncio .create_subprocess_exec (
132+ "kinit" ,
133+ "-k" ,
134+ "-t" ,
135+ keytab_file ,
136+ principal ,
137+ stdout = subprocess .PIPE ,
138+ stderr = subprocess .PIPE ,
139+ )
140+ stdout , stderr = await proc .communicate ()
141+
142+ if proc .returncode :
143+ logging .error ("kinit failed: %s" , stderr .decode ())
144+ msg = "kinit command failed"
145+ raise KerberosError (msg )
146+
147+ logging .info ("Kerberos ticket initialized for %s" , principal )
148+ return principal
149+
150+
151+ async def destroy_kerberos_ticket (principal : str ):
152+ """
153+ Destroy Kerberos ticket for the specified principal.
154+
155+ Args:
156+ principal: Principal name whose ticket should be destroyed
157+ """
158+ logging .info ("Destroying Kerberos ticket for %s" , principal )
159+ proc = await asyncio .create_subprocess_exec (
160+ "kdestroy" ,
161+ "-p" ,
162+ principal ,
163+ stdout = subprocess .PIPE ,
164+ stderr = subprocess .PIPE ,
165+ )
166+ await proc .communicate ()
167+
168+ if proc .returncode :
169+ logging .warning ("Failed to destroy Kerberos ticket for %s" , principal )
0 commit comments