@@ -80,19 +80,19 @@ def __new__(cls: type[Self]) -> Self:
8080 ce ,
8181 )
8282
83- k8s_config .host = (
84- configuration .authentication_configuration .k8s_cluster_api
85- or k8s_config .host
86- )
83+ k8s_api_url = configuration .authentication_configuration .k8s_cluster_api
84+ if k8s_api_url :
85+ k8s_config .host = str (k8s_api_url )
8786 k8s_config .verify_ssl = (
8887 not configuration .authentication_configuration .skip_tls_verification
8988 )
90- k8s_config . ssl_ca_cert = (
89+ ca_cert_path = (
9190 configuration .authentication_configuration .k8s_ca_cert_path
92- if configuration .authentication_configuration .k8s_ca_cert_path
93- not in {None , Path ()}
94- else k8s_config .ssl_ca_cert
9591 )
92+ if ca_cert_path and ca_cert_path != Path ():
93+ # Kubernetes client library has incomplete type stubs for ssl_ca_cert
94+ k8s_config .ssl_ca_cert = str (ca_cert_path ) # type: ignore[assignment]
95+ # else keep the default k8s_config.ssl_ca_cert
9696 api_client = kubernetes .client .ApiClient (k8s_config )
9797 cls ._api_client = api_client
9898 cls ._custom_objects_api = kubernetes .client .CustomObjectsApi (api_client )
@@ -101,7 +101,10 @@ def __new__(cls: type[Self]) -> Self:
101101 except Exception as e :
102102 logger .info ("Failed to initialize Kubernetes client: %s" , e )
103103 raise
104- return cls ._instance
104+ # At this point _instance is guaranteed to be initialized
105+ # but we need this anyway for type checker
106+ assert cls ._instance is not None
107+ return cls ._instance # type: ignore[return-value]
105108
106109 @classmethod
107110 def get_authn_api (cls ) -> kubernetes .client .AuthenticationV1Api :
@@ -159,20 +162,33 @@ def _get_cluster_id(cls) -> str:
159162 ClusterIDUnavailableError: If the cluster ID cannot be obtained due
160163 to missing keys, an API error, or any unexpected error.
161164 """
165+ version_data = None
162166 try :
163167 custom_objects_api = cls .get_custom_objects_api ()
164168 version_data = custom_objects_api .get_cluster_custom_object (
165169 "config.openshift.io" , "v1" , "clusterversions" , "version"
166170 )
167- cluster_id = version_data ["spec" ]["clusterID" ]
171+ # Type assertion: we know this returns a dict-like object
172+ assert isinstance (version_data , dict ), "version_data must be a dict"
173+ # Kubernetes client library returns untyped dict-like objects
174+ spec = version_data .get ("spec" ) # type: ignore[union-attr]
175+ if not isinstance (spec , dict ):
176+ raise ClusterIDUnavailableError (
177+ "Missing or invalid 'spec' in ClusterVersion"
178+ )
179+ cluster_id = spec .get ("clusterID" )
180+ if not isinstance (cluster_id , str ) or not cluster_id .strip ():
181+ raise ClusterIDUnavailableError (
182+ "Missing or invalid 'clusterID' in ClusterVersion"
183+ )
168184 cls ._cluster_id = cluster_id
169185 return cluster_id
170186 except KeyError as e :
171187 logger .error (
172188 "Failed to get cluster_id from cluster, missing keys in version object"
173189 )
174190 raise ClusterIDUnavailableError ("Failed to get cluster ID" ) from e
175- except TypeError as e :
191+ except ( TypeError , AssertionError ) as e :
176192 logger .error (
177193 "Failed to get cluster_id, version object is: %s" , version_data
178194 )
@@ -212,14 +228,14 @@ def get_cluster_id(cls) -> str:
212228 return cls ._cluster_id
213229
214230
215- def get_user_info (token : str ) -> Optional [kubernetes .client .V1TokenReview ]:
231+ def get_user_info (token : str ) -> Optional [kubernetes .client .V1TokenReviewStatus ]:
216232 """Perform a Kubernetes TokenReview to validate a given token.
217233
218234 Parameters:
219235 token: The bearer token to be validated.
220236
221237 Returns:
222- The user information if the token is valid, None otherwise.
238+ The V1TokenReviewStatus if the token is valid, None otherwise.
223239
224240 Raises:
225241 HTTPException: If unable to connect to Kubernetes API or unexpected error occurs.
@@ -239,8 +255,10 @@ def get_user_info(token: str) -> Optional[kubernetes.client.V1TokenReview]:
239255 )
240256 try :
241257 response = auth_api .create_token_review (token_review )
242- if response .status .authenticated :
243- return response .status
258+ # Kubernetes client library has incomplete type stubs
259+ status = response .status # type: ignore[union-attr]
260+ if status is not None and status .authenticated :
261+ return status
244262 return None
245263 except Exception as e : # pylint: disable=broad-exception-caught
246264 logger .error ("API exception during TokenReview: %s" , e )
@@ -307,9 +325,10 @@ async def __call__(self, request: Request) -> tuple[str, str, bool, str]:
307325 response = UnauthorizedResponse (cause = "Invalid or expired Kubernetes token" )
308326 raise HTTPException (** response .model_dump ())
309327
310- if user_info .user .username == "kube:admin" :
328+ # Kubernetes client library has incomplete type stubs
329+ if user_info .user .username == "kube:admin" : # type: ignore[union-attr]
311330 try :
312- user_info .user .uid = K8sClientSingleton .get_cluster_id ()
331+ user_info .user .uid = K8sClientSingleton .get_cluster_id () # type: ignore[union-attr]
313332 except ClusterIDUnavailableError as e :
314333 logger .error ("Failed to get cluster ID: %s" , e )
315334 response = InternalServerErrorResponse (
@@ -318,12 +337,22 @@ async def __call__(self, request: Request) -> tuple[str, str, bool, str]:
318337 )
319338 raise HTTPException (** response .model_dump ()) from e
320339
340+ # Validate that uid is present and is a string
341+ user_uid = user_info .user .uid # type: ignore[union-attr]
342+ if not isinstance (user_uid , str ) or not user_uid :
343+ logger .error ("Authenticated Kubernetes user is missing a UID" )
344+ response = InternalServerErrorResponse (
345+ response = "Internal server error" ,
346+ cause = "Authenticated Kubernetes user is missing a UID" ,
347+ )
348+ raise HTTPException (** response .model_dump ())
349+
321350 try :
322351 authorization_api = K8sClientSingleton .get_authz_api ()
323352 sar = kubernetes .client .V1SubjectAccessReview (
324353 spec = kubernetes .client .V1SubjectAccessReviewSpec (
325- user = user_info .user .username ,
326- groups = user_info .user .groups ,
354+ user = user_info .user .username , # type: ignore[union-attr]
355+ groups = user_info .user .groups , # type: ignore[union-attr]
327356 non_resource_attributes = kubernetes .client .V1NonResourceAttributes (
328357 path = self .virtual_path , verb = "get"
329358 ),
@@ -339,13 +368,14 @@ async def __call__(self, request: Request) -> tuple[str, str, bool, str]:
339368 )
340369 raise HTTPException (** response .model_dump ()) from e
341370
342- if not response .status .allowed :
343- response = ForbiddenResponse .endpoint (user_id = user_info .user .uid )
371+ # Kubernetes client library has incomplete type stubs
372+ if not response .status .allowed : # type: ignore[union-attr]
373+ response = ForbiddenResponse .endpoint (user_id = user_uid )
344374 raise HTTPException (** response .model_dump ())
345375
346376 return (
347- user_info . user . uid ,
348- user_info .user .username ,
377+ user_uid ,
378+ user_info .user .username , # type: ignore[union-attr]
349379 self .skip_userid_check ,
350380 token ,
351381 )
0 commit comments