44import os
55from pathlib import Path
66from typing import Optional , Self
7- from fastapi import Request , HTTPException
87
98import kubernetes .client
9+ from fastapi import HTTPException , Request
1010from kubernetes .client .rest import ApiException
1111from kubernetes .config import ConfigException
1212
13- from configuration import configuration
1413from authentication .interface import AuthInterface
14+ from authentication .utils import extract_user_token
15+ from configuration import configuration
1516from constants import DEFAULT_VIRTUAL_PATH
17+ from models .responses import (
18+ ForbiddenResponse ,
19+ InternalServerErrorResponse ,
20+ ServiceUnavailableResponse ,
21+ UnauthorizedResponse ,
22+ )
1623
1724logger = logging .getLogger (__name__ )
1825
@@ -172,8 +179,20 @@ def get_user_info(token: str) -> Optional[kubernetes.client.V1TokenReview]:
172179
173180 Returns:
174181 The user information if the token is valid, None otherwise.
182+
183+ Raises:
184+ HTTPException: If unable to connect to Kubernetes API or unexpected error occurs.
175185 """
176- auth_api = K8sClientSingleton .get_authn_api ()
186+ try :
187+ auth_api = K8sClientSingleton .get_authn_api ()
188+ except Exception as e :
189+ logger .error ("Failed to get Kubernetes authentication API: %s" , e )
190+ response = ServiceUnavailableResponse (
191+ backend_name = "Kubernetes API" ,
192+ cause = "Unable to initialize Kubernetes client" ,
193+ )
194+ raise HTTPException (** response .model_dump ()) from e
195+
177196 token_review = kubernetes .client .V1TokenReview (
178197 spec = kubernetes .client .V1TokenReviewSpec (token = token )
179198 )
@@ -182,31 +201,9 @@ def get_user_info(token: str) -> Optional[kubernetes.client.V1TokenReview]:
182201 if response .status .authenticated :
183202 return response .status
184203 return None
185- except ApiException as e :
204+ except Exception as e : # pylint: disable=broad-exception-caught
186205 logger .error ("API exception during TokenReview: %s" , e )
187206 return None
188- except Exception as e :
189- logger .error ("Unexpected error during TokenReview - Unauthorized: %s" , e )
190- raise HTTPException (
191- status_code = 500 ,
192- detail = {"response" : "Forbidden: Unable to Review Token" , "cause" : str (e )},
193- ) from e
194-
195-
196- def _extract_bearer_token (header : str ) -> str :
197- """Extract the bearer token from an HTTP authorization header.
198-
199- Args:
200- header: The authorization header containing the token.
201-
202- Returns:
203- The extracted token if present, else an empty string.
204- """
205- try :
206- scheme , token = header .split (" " , 1 )
207- return token if scheme .lower () == "bearer" else ""
208- except ValueError :
209- return ""
210207
211208
212209class K8SAuthDependency (AuthInterface ): # pylint: disable=too-few-public-methods
@@ -239,47 +236,51 @@ async def __call__(self, request: Request) -> tuple[str, str, bool, str]:
239236 user_id check should never be skipped with K8s authentication
240237 If user_id check should be skipped - always return False for k8s
241238 User's token
242- """
243- authorization_header = request .headers .get ("Authorization" )
244- if not authorization_header :
245- raise HTTPException (
246- status_code = 401 , detail = "Unauthorized: No auth header found"
247- )
248-
249- token = _extract_bearer_token (authorization_header )
250- if not token :
251- raise HTTPException (
252- status_code = 401 ,
253- detail = "Unauthorized: Bearer token not found or invalid" ,
254- )
255239
240+ Raises:
241+ HTTPException: If authentication or authorization fails.
242+ """
243+ token = extract_user_token (request .headers )
256244 user_info = get_user_info (token )
245+
257246 if user_info is None :
258- raise HTTPException (
259- status_code = 403 , detail = "Forbidden: Invalid or expired token"
260- )
247+ response = UnauthorizedResponse ( cause = "Invalid or expired Kubernetes token" )
248+ raise HTTPException ( ** response . model_dump ())
249+
261250 if user_info .user .username == "kube:admin" :
262- user_info .user .uid = K8sClientSingleton .get_cluster_id ()
263- authorization_api = K8sClientSingleton .get_authz_api ()
264-
265- sar = kubernetes .client .V1SubjectAccessReview (
266- spec = kubernetes .client .V1SubjectAccessReviewSpec (
267- user = user_info .user .username ,
268- groups = user_info .user .groups ,
269- non_resource_attributes = kubernetes .client .V1NonResourceAttributes (
270- path = self .virtual_path , verb = "get"
271- ),
272- )
273- )
251+ try :
252+ user_info .user .uid = K8sClientSingleton .get_cluster_id ()
253+ except ClusterIDUnavailableError as e :
254+ logger .error ("Failed to get cluster ID: %s" , e )
255+ response = InternalServerErrorResponse (
256+ response = "Internal server error" ,
257+ cause = "Unable to retrieve cluster ID" ,
258+ )
259+ raise HTTPException (** response .model_dump ()) from e
260+
274261 try :
262+ authorization_api = K8sClientSingleton .get_authz_api ()
263+ sar = kubernetes .client .V1SubjectAccessReview (
264+ spec = kubernetes .client .V1SubjectAccessReviewSpec (
265+ user = user_info .user .username ,
266+ groups = user_info .user .groups ,
267+ non_resource_attributes = kubernetes .client .V1NonResourceAttributes (
268+ path = self .virtual_path , verb = "get"
269+ ),
270+ )
271+ )
275272 response = authorization_api .create_subject_access_review (sar )
273+
276274 if not response .status .allowed :
277- raise HTTPException (
278- status_code = 403 , detail = "Forbidden: User does not have access"
279- )
280- except ApiException as e :
275+ response = ForbiddenResponse .endpoint (user_id = user_info .user .uid )
276+ raise HTTPException (** response .model_dump ())
277+ except Exception as e :
281278 logger .error ("API exception during SubjectAccessReview: %s" , e )
282- raise HTTPException (status_code = 403 , detail = "Internal server error" ) from e
279+ response = ServiceUnavailableResponse (
280+ backend_name = "Kubernetes API" ,
281+ cause = "Unable to perform authorization check" ,
282+ )
283+ raise HTTPException (** response .model_dump ()) from e
283284
284285 return (
285286 user_info .user .uid ,
0 commit comments