From a566029289846b42c270c2edfc12282572fd7690 Mon Sep 17 00:00:00 2001 From: Jitesh Kumar Date: Thu, 11 Jun 2026 05:32:11 +0530 Subject: [PATCH] test/images/agnhost: add generic /envvar endpoint to netexec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the hardcoded /nodename endpoint (which exposed only NODE_NAME) with a parameterized /envvar?var= endpoint that reads any environment variable by name, following the pattern suggested in kubernetes/kubernetes#138737. - HTTP: GET /envvar?var= returns the plain-text value; 400 if the var parameter is missing or empty; 500 with the variable name quoted if the variable is not set - UDP/SCTP: send "envvar " as the command; returns the value or empty string if unset (no error path over datagram transports) - os.LookupEnv used throughout to distinguish unset from set-to-empty - Bumps agnhost to 2.64.0 The endpoint follows the pattern of /header?key=X-Forwarded-For — generic read access to a named datum — and avoids hardcoding NODE_NAME as a special case. The primary consumer will be e2e tests that inject NODE_NAME via the Downward API (spec.nodeName fieldRef) to obtain a reliable node identifier on hostNetwork pods regardless of --hostname-override. Signed-off-by: Jitesh Kumar --- test/images/agnhost/README.md | 4 +++ test/images/agnhost/VERSION | 2 +- test/images/agnhost/netexec/netexec.go | 48 ++++++++++++++++++++++++-- 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/test/images/agnhost/README.md b/test/images/agnhost/README.md index d9d8c99c185..7fd95270da9 100644 --- a/test/images/agnhost/README.md +++ b/test/images/agnhost/README.md @@ -487,6 +487,9 @@ Starts a HTTP(S) server on given port with the following endpoints: shutdown. - `wait`: The amount of time to wait before starting shutdown. Acceptable values are golang durations. If 0 the process will start shutdown immediately. +- `/envvar`: Returns the value of the environment variable specified by the `var` query + parameter (`/envvar?var=NODE_NAME`). Returns `400` if the `var` parameter is missing + or empty; `500` if the variable is not set. - `/healthz`: Returns `200 OK` if the server is ready, `412 Status Precondition Failed` otherwise. The server is considered not ready if the UDP server did not start yet or it exited. @@ -513,6 +516,7 @@ It will also start a UDP server on the indicated UDP port that responds to the f - `hostname`: Returns the server's hostname - `echo `: Returns the given `` +- `envvar `: Returns the value of the named environment variable (empty string if not set) - `clientip`: Returns the request's IP address The UDP server can be disabled by setting `--udp-port -1`. diff --git a/test/images/agnhost/VERSION b/test/images/agnhost/VERSION index d1524d4046f..9fbc3d99f8d 100644 --- a/test/images/agnhost/VERSION +++ b/test/images/agnhost/VERSION @@ -1 +1 @@ -2.64.0 +2.65.0 diff --git a/test/images/agnhost/netexec/netexec.go b/test/images/agnhost/netexec/netexec.go index 15513a17f85..68dc167ebae 100644 --- a/test/images/agnhost/netexec/netexec.go +++ b/test/images/agnhost/netexec/netexec.go @@ -100,6 +100,9 @@ var CmdNetexec = &cobra.Command{ server is healthy (don't kill it), but the it should not be sent traffic (remove from endpoints). - "/hostname": Returns the server's hostname. - "/hostName": Returns the server's hostname. +- "/envvar": Returns the value of the environment variable named by the "var" query + parameter ("/envvar?var=NODE_NAME"). Returns 400 if the parameter is missing or empty, + 500 if the variable is not set. For UDP/SCTP, send "envvar " as the command. - "/redirect": Returns a redirect response to the given "location", with the optional status "code" ("/redirect?location=/echo%3Fmsg=foobar&code=307"). - "/shell": Executes the given "shellCommand" or "cmd" ("/shell?cmd=some-command") and @@ -121,6 +124,7 @@ It will also start a UDP server on the indicated UDP port and addresses that res - "hostname": Returns the server's hostname - "echo ": Returns the given +- "envvar ": Returns the value of the named environment variable (empty string if not set) - "clientip": Returns the request's IP address - "serverport": Returns the server port @@ -219,6 +223,7 @@ func addRoutes(mux *http.ServeMux, sigTermReceived chan struct{}, exitCh chan sh mux.HandleFunc("/healthz", healthzHandler) mux.HandleFunc("/readyz", readyzHandler(sigTermReceived)) mux.HandleFunc("/hostname", hostnameHandler) + mux.HandleFunc("/envvar", envvarHandler) mux.HandleFunc("/redirect", redirectHandler) mux.HandleFunc("/shell", shellHandler) mux.HandleFunc("/upload", uploadHandler) @@ -331,6 +336,21 @@ func hostnameHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, getHostName()) } +func envvarHandler(w http.ResponseWriter, r *http.Request) { + varName := r.FormValue("var") + if varName == "" { + http.Error(w, "'var' query parameter is required", http.StatusBadRequest) + return + } + log.Printf("GET /envvar?var=%s", varName) + val, ok := os.LookupEnv(varName) + if !ok { + http.Error(w, fmt.Sprintf("env var %q is not set", varName), http.StatusInternalServerError) + return + } + _, _ = fmt.Fprint(w, val) +} + // healthHandler response with a 200 if the UDP server is ready. It also serves // as a health check of the HTTP server by virtue of being a HTTP handler. func healthzHandler(w http.ResponseWriter, r *http.Request) { @@ -640,11 +660,23 @@ func startUDPServer(address string, udpPort int) { for { n, clientAddress, err := serverConn.ReadFromUDP(buf) assertNoError(err, "failed accepting UDP connections") - receivedText := strings.ToLower(strings.TrimSpace(string(buf[0:n]))) + rawText := strings.TrimSpace(string(buf[0:n])) + receivedText := strings.ToLower(rawText) if receivedText == "hostname" { log.Println("Sending udp hostName response") _, err = serverConn.WriteToUDP([]byte(getHostName()), clientAddress) assertNoError(err, fmt.Sprintf("failed to write hostname to UDP client %s", clientAddress)) + } else if strings.HasPrefix(receivedText, "envvar ") { + parts := strings.SplitN(rawText, " ", 2) + if len(parts) != 2 { + log.Printf("Unknown UDP command received from %s: %v\n", clientAddress, receivedText) + continue + } + varName := strings.TrimSpace(parts[1]) + val, _ := os.LookupEnv(varName) + log.Printf("Sending UDP envvar response for %s", varName) + _, err = serverConn.WriteToUDP([]byte(val), clientAddress) + assertNoError(err, fmt.Sprintf("failed to write envvar to UDP client %s", clientAddress)) } else if strings.HasPrefix(receivedText, "echo ") { parts := strings.SplitN(receivedText, " ", 2) resp := "" @@ -694,7 +726,8 @@ func startSCTPServer(sctpPort int) { clientAddress := remoteAddr.String() n, err := conn.Read(buf) assertNoError(err, fmt.Sprintf("failed to read from SCTP client %s", clientAddress)) - receivedText := strings.ToLower(strings.TrimSpace(string(buf[0:n]))) + rawText := strings.TrimSpace(string(buf[0:n])) + receivedText := strings.ToLower(rawText) if receivedText == "hostname" { log.Println("Sending SCTP hostName response") _, err = conn.Write([]byte(getHostName())) @@ -716,6 +749,17 @@ func startSCTPServer(sctpPort int) { log.Printf("Sending server port to SCTP client %s\n", strconv.Itoa(sctpPort)) _, err = conn.Write([]byte(strconv.Itoa(sctpPort))) assertNoError(err, fmt.Sprintf("failed to write server port to SCTP client %s", clientAddress)) + } else if strings.HasPrefix(receivedText, "envvar ") { + parts := strings.SplitN(rawText, " ", 2) + if len(parts) != 2 { + log.Printf("Unknown SCTP command received from %s: %v\n", clientAddress, receivedText) + continue + } + varName := strings.TrimSpace(parts[1]) + val, _ := os.LookupEnv(varName) + log.Printf("Sending SCTP envvar response for %s", varName) + _, err = conn.Write([]byte(val)) + assertNoError(err, fmt.Sprintf("failed to write envvar to SCTP client %s", clientAddress)) } else if len(receivedText) > 0 { log.Printf("Unknown SCTP command received from %s: %v\n", clientAddress, receivedText) }