Skip to content

Commit f782e0d

Browse files
committed
backend: cmd/pagination: add handlePagination func to get only limited
response size this helps to get only limited number of resources that the users want to access not just fetching full resource. and then set continueToken in the proxy URL.
1 parent 5a1fe0f commit f782e0d

File tree

1 file changed

+81
-36
lines changed

1 file changed

+81
-36
lines changed

backend/cmd/headlamp.go

Lines changed: 81 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -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

946969
func (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.
13571377
func 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

Comments
 (0)