@@ -453,6 +453,11 @@ func createHeadlampHandler(config *HeadlampConfig) http.Handler {
453453 r = baseRoute .PathPrefix (config .BaseURL ).Subrouter ()
454454 }
455455
456+ if config .Telemetry != nil && config .Metrics != nil {
457+ r .Use (telemetry .TracingMiddleware ("headlamp-server" ))
458+ r .Use (config .Metrics .RequestCounterMiddleware )
459+ }
460+
456461 fmt .Println ("*** Headlamp Server ***" )
457462 fmt .Println (" API Routers:" )
458463
@@ -484,6 +489,22 @@ func createHeadlampHandler(config *HeadlampConfig) http.Handler {
484489
485490 addPluginRoutes (config , r )
486491
492+ // Setup port forwarding handlers.
493+ r .HandleFunc ("/clusters/{clusterName}/portforward" , func (w http.ResponseWriter , r * http.Request ) {
494+ portforward .StartPortForward (config .KubeConfigStore , config .cache , w , r )
495+ }).Methods ("POST" )
496+
497+ r .HandleFunc ("/clusters/{clusterName}/portforward" , func (w http.ResponseWriter , r * http.Request ) {
498+ portforward .StopOrDeletePortForward (config .cache , w , r )
499+ }).Methods ("DELETE" )
500+
501+ r .HandleFunc ("/clusters/{clusterName}/portforward/list" , func (w http.ResponseWriter , r * http.Request ) {
502+ portforward .GetPortForwards (config .cache , w , r )
503+ })
504+ r .HandleFunc ("/clusters/{clusterName}/portforward" , func (w http.ResponseWriter , r * http.Request ) {
505+ portforward .GetPortForwardByID (config .cache , w , r )
506+ }).Methods ("GET" )
507+
487508 config .handleClusterRequests (r )
488509
489510 r .HandleFunc ("/externalproxy" , func (w http.ResponseWriter , r * http.Request ) {
@@ -599,6 +620,9 @@ func createHeadlampHandler(config *HeadlampConfig) http.Handler {
599620 // Configuration
600621 r .HandleFunc ("/config" , config .getConfig ).Methods ("GET" )
601622
623+ // Auth token management
624+ r .HandleFunc ("/auth/set-token" , config .handleSetToken ).Methods ("POST" )
625+
602626 // Websocket connections
603627 r .HandleFunc ("/wsMultiplexer" , config .multiplexer .HandleClientWebSocket )
604628
@@ -672,24 +696,9 @@ func createHeadlampHandler(config *HeadlampConfig) http.Handler {
672696 http .Redirect (w , r , oauthConfig .AuthCodeURL (state ), http .StatusFound )
673697 }).Queries ("cluster" , "{cluster}" )
674698
675- r .HandleFunc ("/portforward" , func (w http.ResponseWriter , r * http.Request ) {
676- portforward .StartPortForward (config .KubeConfigStore , config .cache , w , r )
677- }).Methods ("POST" )
678-
679- r .HandleFunc ("/portforward" , func (w http.ResponseWriter , r * http.Request ) {
680- portforward .StopOrDeletePortForward (config .cache , w , r )
681- }).Methods ("DELETE" )
682-
683- r .HandleFunc ("/portforward/list" , func (w http.ResponseWriter , r * http.Request ) {
684- portforward .GetPortForwards (config .cache , w , r )
685- })
686-
687699 r .HandleFunc ("/drain-node" , config .handleNodeDrain ).Methods ("POST" )
688700 r .HandleFunc ("/drain-node-status" ,
689701 config .handleNodeDrainStatus ).Methods ("GET" ).Queries ("cluster" , "{cluster}" , "nodeName" , "{node}" )
690- r .HandleFunc ("/portforward" , func (w http.ResponseWriter , r * http.Request ) {
691- portforward .GetPortForwardByID (config .cache , w , r )
692- }).Methods ("GET" )
693702
694703 r .HandleFunc ("/oidc-callback" , func (w http.ResponseWriter , r * http.Request ) {
695704 state := r .URL .Query ().Get ("state" )
@@ -772,7 +781,10 @@ func createHeadlampHandler(config *HeadlampConfig) http.Handler {
772781 redirectURL += baseURL + "/"
773782 }
774783
775- redirectURL += fmt .Sprintf ("auth?cluster=%1s&token=%2s" , decodedState , rawUserToken )
784+ // Set auth cookie
785+ auth .SetTokenCookie (w , r , string (decodedState ), rawUserToken )
786+
787+ redirectURL += fmt .Sprintf ("auth?cluster=%1s" , decodedState )
776788 http .Redirect (w , r , redirectURL , http .StatusSeeOther )
777789 } else {
778790 http .Error (w , "invalid request" , http .StatusBadRequest )
@@ -805,9 +817,13 @@ func createHeadlampHandler(config *HeadlampConfig) http.Handler {
805817 "KUBECONFIG" , "X-HEADLAMP-USER-ID" ,
806818 })
807819 methods := handlers .AllowedMethods ([]string {"GET" , "POST" , "PUT" , "HEAD" , "DELETE" , "PATCH" , "OPTIONS" })
808- origins := handlers .AllowedOrigins ([]string {"*" })
809820
810- return handlers .CORS (headers , methods , origins )(r )
821+ return handlers .CORS (
822+ headers ,
823+ methods ,
824+ handlers .AllowCredentials (),
825+ handlers .AllowedOriginValidator (func (s string ) bool { return true }),
826+ )(r )
811827 }
812828
813829 return r
@@ -823,10 +839,17 @@ func parseClusterAndToken(r *http.Request) (string, string) {
823839 cluster = matches [1 ]
824840 }
825841
826- // get token
842+ // Try Authorization header first (for backward compatibility)
827843 token := r .Header .Get ("Authorization" )
828844 token = strings .TrimPrefix (token , "Bearer " )
829845
846+ // If no auth header, try cookie
847+ if token == "" && cluster != "" {
848+ if cookieToken , err := auth .GetTokenFromCookie (r , cluster ); err == nil {
849+ token = cookieToken
850+ }
851+ }
852+
830853 return cluster , token
831854}
832855
@@ -945,7 +968,7 @@ func cacheRefreshedToken(token *oauth2.Token, tokenType string, oldToken string,
945968
946969func (c * HeadlampConfig ) refreshAndSetToken (oidcAuthConfig * kubeconfig.OidcConfig ,
947970 cache cache.Cache [interface {}], token string ,
948- w http.ResponseWriter , cluster string , span trace.Span , ctx context.Context ,
971+ w http.ResponseWriter , r * http. Request , cluster string , span trace.Span , ctx context.Context ,
949972) {
950973 // The token type to use
951974 tokenType := "id_token"
@@ -967,12 +990,16 @@ func (c *HeadlampConfig) refreshAndSetToken(oidcAuthConfig *kubeconfig.OidcConfi
967990 c .telemetryHandler .RecordError (span , err , "Token refresh failed" )
968991 c .telemetryHandler .RecordErrorCount (ctx , attribute .String ("error" , "token_refresh_failure" ))
969992 } else if newToken != nil {
993+ var newTokenString string
970994 if c .oidcUseAccessToken {
971- w . Header (). Set ( "X-Authorization" , newToken .Extra ("access_token" ).(string ) )
995+ newTokenString = newToken .Extra ("access_token" ).(string )
972996 } else {
973- w . Header (). Set ( "X-Authorization" , newToken .Extra ("id_token" ).(string ) )
997+ newTokenString = newToken .Extra ("id_token" ).(string )
974998 }
975999
1000+ // Set refreshed token in cookie
1001+ auth .SetTokenCookie (w , r , cluster , newTokenString )
1002+
9761003 c .telemetryHandler .RecordEvent (span , "Token refreshed successfully" )
9771004 }
9781005}
@@ -1105,7 +1132,7 @@ func (c *HeadlampConfig) OIDCTokenRefreshMiddleware(next http.Handler) http.Hand
11051132 }
11061133
11071134 // refresh and cache new token
1108- c .refreshAndSetToken (oidcAuthConfig , c .cache , token , w , cluster , span , ctx )
1135+ c .refreshAndSetToken (oidcAuthConfig , c .cache , token , w , r , cluster , span , ctx )
11091136
11101137 next .ServeHTTP (w , r )
11111138 c .telemetryHandler .RecordDuration (ctx , start ,
@@ -1139,13 +1166,6 @@ func StartHeadlampServer(config *HeadlampConfig) {
11391166 config .Metrics = metrics
11401167 config .telemetryHandler = telemetry .NewRequestHandler (tel , metrics )
11411168
1142- router := mux .NewRouter ()
1143-
1144- if config .Telemetry != nil && config .Metrics != nil {
1145- router .Use (telemetry .TracingMiddleware ("headlamp-server" ))
1146- router .Use (config .Metrics .RequestCounterMiddleware )
1147- }
1148-
11491169 // Copy static files as squashFS is read-only (AppImage)
11501170 if config .StaticDir != "" {
11511171 dir , err := os .MkdirTemp (os .TempDir (), ".headlamp" )
@@ -1355,6 +1375,8 @@ func (c *HeadlampConfig) handleError(w http.ResponseWriter, ctx context.Context,
13551375// It parses the request and creates a proxy request to the cluster.
13561376// That proxy is saved in the cache with the context key.
13571377func handleClusterAPI (c * HeadlampConfig , router * mux.Router ) { //nolint:funlen
1378+ router .HandleFunc ("/clusters/{clusterName}/set-token" , c .handleSetToken ).Methods ("POST" )
1379+
13581380 router .PathPrefix ("/clusters/{clusterName}/{api:.*}" ).HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
13591381 start := time .Now ()
13601382 ctx := r .Context ()
@@ -1526,8 +1548,6 @@ func (c *HeadlampConfig) getClusters() []Cluster {
15261548 }
15271549
15281550 for _ , context := range contexts {
1529- context := context
1530-
15311551 if context .Error != "" {
15321552 clusters = append (clusters , Cluster {
15331553 Name : context .Name ,
@@ -1582,8 +1602,6 @@ func parseCustomNameClusters(contexts []kubeconfig.Context) ([]Cluster, []error)
15821602 var setupErrors []error
15831603
15841604 for _ , context := range contexts {
1585- context := context
1586-
15871605 info := context .KubeContext .Extensions ["headlamp_info" ]
15881606 if info != nil {
15891607 // Convert the runtime.Unknown object to a byte slice
@@ -2044,8 +2062,6 @@ func (c *HeadlampConfig) updateCustomContextToCache(config *api.Config, clusterN
20442062 }
20452063
20462064 for _ , context := range contexts {
2047- context := context
2048-
20492065 // Remove the old context from the store
20502066 if err := c .KubeConfigStore .RemoveContext (clusterName ); err != nil {
20512067 logger .Log (logger .LevelError , nil , err , "Removing context from the store" )
@@ -2430,3 +2446,32 @@ func (c *HeadlampConfig) handleNodeDrainStatus(w http.ResponseWriter, r *http.Re
24302446 logger .Log (logger .LevelInfo , map [string ]string {"duration_ms" : fmt .Sprintf ("%d" , time .Since (start ).Milliseconds ())},
24312447 nil , "handleNodeDrainStatus completed" )
24322448}
2449+
2450+ // handlerSetToken sets the authentication token in a cookie.
2451+ // If the token is an empty string, the cookie is cleared.
2452+ func (c * HeadlampConfig ) handleSetToken (w http.ResponseWriter , r * http.Request ) {
2453+ cluster := mux .Vars (r )["clusterName" ]
2454+
2455+ var req struct {
2456+ Token string `json:"token"`
2457+ }
2458+
2459+ if err := json .NewDecoder (r .Body ).Decode (& req ); err != nil {
2460+ http .Error (w , "Invalid JSON" , http .StatusBadRequest )
2461+ return
2462+ }
2463+
2464+ // Validate cluster name is provided
2465+ if cluster == "" {
2466+ http .Error (w , "Cluster name is required" , http .StatusBadRequest )
2467+ return
2468+ }
2469+
2470+ if req .Token == "" {
2471+ auth .ClearTokenCookie (w , r , cluster )
2472+ } else {
2473+ auth .SetTokenCookie (w , r , cluster , req .Token )
2474+ }
2475+
2476+ w .WriteHeader (http .StatusOK )
2477+ }
0 commit comments