mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-22 19:31:44 +00:00
Merge pull request #92850 from tallclair/netexec
Enhance agnhost netexec for SSRF E2Es
This commit is contained in:
commit
17376e6aef
@ -393,7 +393,7 @@ Starts a HTTP(S) server on given port with the following endpoints:
|
||||
- `protocol`: The protocol which will be used when making the request. Default value: `http`.
|
||||
Acceptable values: `http`, `udp`, `sctp`.
|
||||
- `tries`: The number of times the request will be performed. Default value: `1`.
|
||||
- `/echo`: Returns the given `msg` (`/echo?msg=echoed_msg`)
|
||||
- `/echo`: Returns the given `msg` (`/echo?msg=echoed_msg`), with the optional status `code`.
|
||||
- `/exit`: Closes the server with the given code and graceful shutdown. The endpoint's parameters
|
||||
are:
|
||||
- `code`: The exit code for the process. Default value: 0. Allows an integer [0-127].
|
||||
@ -407,6 +407,8 @@ Starts a HTTP(S) server on given port with the following endpoints:
|
||||
it exited.
|
||||
- `/hostname`: Returns the server's hostname.
|
||||
- `/hostName`: Returns the server's hostname.
|
||||
- `/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
|
||||
returns a JSON containing the fields `output` (command's output) and `error` (command's
|
||||
error message). Returns `200 OK` if the command succeeded, `417 Expectation Failed` if not.
|
||||
@ -419,6 +421,9 @@ If `--tls-cert-file` is added (ideally in conjunction with `--tls-private-key-fi
|
||||
will be upgraded to HTTPS. The image has default, `localhost`-based cert/privkey files at
|
||||
`/localhost.crt` and `/localhost.key` (see: [`porter` subcommand](#porter))
|
||||
|
||||
If `--http-override` is set, the HTTP(S) server will always serve the override path & options,
|
||||
ignoring the request URL.
|
||||
|
||||
It will also start a UDP server on the indicated UDP port that responds to the following commands:
|
||||
|
||||
- `hostname`: Returns the server's hostname
|
||||
|
@ -47,6 +47,7 @@ var (
|
||||
serverReady = &atomicBool{0}
|
||||
certFile = ""
|
||||
privKeyFile = ""
|
||||
httpOverride = ""
|
||||
)
|
||||
|
||||
// CmdNetexec is used by agnhost Cobra.
|
||||
@ -69,7 +70,7 @@ var CmdNetexec = &cobra.Command{
|
||||
- "protocol": The protocol which will be used when making the request. Default value: "http".
|
||||
Acceptable values: "http", "udp", "sctp".
|
||||
- "tries": The number of times the request will be performed. Default value: "1".
|
||||
- "/echo": Returns the given "msg" ("/echo?msg=echoed_msg")
|
||||
- "/echo": Returns the given "msg" ("/echo?msg=echoed_msg"), with the optional status "code".
|
||||
- "/exit": Closes the server with the given code and graceful shutdown. The endpoint's parameters
|
||||
are:
|
||||
- "code": The exit code for the process. Default value: 0. Allows an integer [0-127].
|
||||
@ -83,6 +84,8 @@ var CmdNetexec = &cobra.Command{
|
||||
it exited.
|
||||
- "/hostname": Returns the server's hostname.
|
||||
- "/hostName": Returns the server's hostname.
|
||||
- "/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
|
||||
returns a JSON containing the fields "output" (command's output) and "error" (command's
|
||||
error message). Returns "200 OK" if the command succeeded, "417 Expectation Failed" if not.
|
||||
@ -91,6 +94,13 @@ var CmdNetexec = &cobra.Command{
|
||||
Returns a JSON with the fields "output" (containing the file's name on the server) and
|
||||
"error" containing any potential server side errors.
|
||||
|
||||
If "--tls-cert-file" is added (ideally in conjunction with "--tls-private-key-file", the HTTP server
|
||||
will be upgraded to HTTPS. The image has default, "localhost"-based cert/privkey files at
|
||||
"/localhost.crt" and "/localhost.key" (see: "porter" subcommand)
|
||||
|
||||
If "--http-override" is set, the HTTP(S) server will always serve the override path & options,
|
||||
ignoring the request URL.
|
||||
|
||||
It will also start a UDP server on the indicated UDP port that responds to the following commands:
|
||||
|
||||
- "hostname": Returns the server's hostname
|
||||
@ -112,6 +122,7 @@ func init() {
|
||||
"File containing an x509 private key matching --tls-cert-file")
|
||||
CmdNetexec.Flags().IntVar(&udpPort, "udp-port", 8081, "UDP Listen Port")
|
||||
CmdNetexec.Flags().IntVar(&sctpPort, "sctp-port", -1, "SCTP Listen Port")
|
||||
CmdNetexec.Flags().StringVar(&httpOverride, "http-override", "", "Override the HTTP handler to always respond as if it were a GET with this path & params")
|
||||
}
|
||||
|
||||
// atomicBool uses load/store operations on an int32 to simulate an atomic boolean.
|
||||
@ -135,7 +146,21 @@ func (a *atomicBool) get() bool {
|
||||
|
||||
func main(cmd *cobra.Command, args []string) {
|
||||
exitCh := make(chan shutdownRequest)
|
||||
addRoutes(exitCh)
|
||||
if httpOverride != "" {
|
||||
mux := http.NewServeMux()
|
||||
addRoutes(mux, exitCh)
|
||||
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
overrideReq, err := http.NewRequestWithContext(r.Context(), "GET", httpOverride, nil)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("override request failed: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
mux.ServeHTTP(w, overrideReq)
|
||||
})
|
||||
} else {
|
||||
addRoutes(http.DefaultServeMux, exitCh)
|
||||
}
|
||||
|
||||
go startUDPServer(udpPort)
|
||||
if sctpPort != -1 {
|
||||
@ -150,22 +175,24 @@ func main(cmd *cobra.Command, args []string) {
|
||||
}
|
||||
}
|
||||
|
||||
func addRoutes(exitCh chan shutdownRequest) {
|
||||
http.HandleFunc("/", rootHandler)
|
||||
http.HandleFunc("/clientip", clientIPHandler)
|
||||
http.HandleFunc("/echo", echoHandler)
|
||||
http.HandleFunc("/exit", func(w http.ResponseWriter, req *http.Request) { exitHandler(w, req, exitCh) })
|
||||
http.HandleFunc("/hostname", hostnameHandler)
|
||||
http.HandleFunc("/shell", shellHandler)
|
||||
http.HandleFunc("/upload", uploadHandler)
|
||||
http.HandleFunc("/dial", dialHandler)
|
||||
http.HandleFunc("/healthz", healthzHandler)
|
||||
func addRoutes(mux *http.ServeMux, exitCh chan shutdownRequest) {
|
||||
mux.HandleFunc("/", rootHandler)
|
||||
mux.HandleFunc("/clientip", clientIPHandler)
|
||||
mux.HandleFunc("/dial", dialHandler)
|
||||
mux.HandleFunc("/echo", echoHandler)
|
||||
mux.HandleFunc("/exit", func(w http.ResponseWriter, req *http.Request) { exitHandler(w, req, exitCh) })
|
||||
mux.HandleFunc("/healthz", healthzHandler)
|
||||
mux.HandleFunc("/hostname", hostnameHandler)
|
||||
mux.HandleFunc("/redirect", redirectHandler)
|
||||
mux.HandleFunc("/shell", shellHandler)
|
||||
mux.HandleFunc("/upload", uploadHandler)
|
||||
// older handlers
|
||||
http.HandleFunc("/hostName", hostNameHandler)
|
||||
http.HandleFunc("/shutdown", shutdownHandler)
|
||||
mux.HandleFunc("/hostName", hostNameHandler)
|
||||
mux.HandleFunc("/shutdown", shutdownHandler)
|
||||
}
|
||||
|
||||
func startServer(server *http.Server, exitCh chan shutdownRequest, fn func() error) {
|
||||
log.Printf("Started HTTP server on port %d", httpPort)
|
||||
go func() {
|
||||
re := <-exitCh
|
||||
ctx, cancelFn := context.WithTimeout(context.Background(), re.timeout)
|
||||
@ -190,8 +217,18 @@ func rootHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func echoHandler(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("GET /echo?msg=%s", r.FormValue("msg"))
|
||||
fmt.Fprintf(w, "%s", r.FormValue("msg"))
|
||||
msg := r.FormValue("msg")
|
||||
codeString := r.FormValue("code")
|
||||
log.Printf("GET /echo?msg=%s&code=%s", msg, codeString)
|
||||
if codeString != "" {
|
||||
code, err := strconv.Atoi(codeString)
|
||||
if err != nil && codeString != "" {
|
||||
fmt.Fprintf(w, "argument 'code' must be an integer or empty, got %q\n", codeString)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(code)
|
||||
}
|
||||
fmt.Fprintf(w, "%s", msg)
|
||||
}
|
||||
|
||||
func clientIPHandler(w http.ResponseWriter, r *http.Request) {
|
||||
@ -488,6 +525,22 @@ func hostNameHandler(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprint(w, getHostName())
|
||||
}
|
||||
|
||||
func redirectHandler(w http.ResponseWriter, r *http.Request) {
|
||||
location := r.FormValue("location")
|
||||
codeString := r.FormValue("code")
|
||||
log.Printf("%s /redirect?msg=%s&code=%s", r.Method, location, codeString)
|
||||
code := http.StatusFound
|
||||
if codeString != "" {
|
||||
var err error
|
||||
code, err = strconv.Atoi(codeString)
|
||||
if err != nil && codeString != "" {
|
||||
fmt.Fprintf(w, "argument 'code' must be an integer or empty, got %q\n", codeString)
|
||||
return
|
||||
}
|
||||
}
|
||||
http.Redirect(w, r, location, code)
|
||||
}
|
||||
|
||||
// udp server supports the hostName, echo and clientIP commands.
|
||||
func startUDPServer(udpPort int) {
|
||||
serverAddress, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%d", udpPort))
|
||||
@ -497,7 +550,7 @@ func startUDPServer(udpPort int) {
|
||||
defer serverConn.Close()
|
||||
buf := make([]byte, 2048)
|
||||
|
||||
log.Printf("Started UDP server")
|
||||
log.Printf("Started UDP server on port %d", udpPort)
|
||||
// Start responding to readiness probes.
|
||||
serverReady.set(true)
|
||||
defer func() {
|
||||
|
Loading…
Reference in New Issue
Block a user