mirror of
https://github.com/sipeed/NanoKVM.git
synced 2026-09-11 00:22:56 -05:00
- Keep internal loopback HTTP APIs reachable when the server is bound to a specific non-loopback host by adding a dedicated 127.0.0.1 listener. - Move listener address helpers into utils and normalize HTTPS redirect hosts so IPv6 request hosts are not double-bracketed.
95 lines
2.3 KiB
Go
95 lines
2.3 KiB
Go
package middleware
|
|
|
|
import (
|
|
"crypto/subtle"
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"NanoKVM-Server/config"
|
|
)
|
|
|
|
func ListenAndServeLoopbackHTTPRedirect(
|
|
httpAddr string,
|
|
httpsPort string,
|
|
handler http.Handler,
|
|
allowedPaths ...string,
|
|
) error {
|
|
allowlist := make(map[string]struct{}, len(allowedPaths))
|
|
for _, path := range allowedPaths {
|
|
if strings.TrimSpace(path) == "" {
|
|
continue
|
|
}
|
|
allowlist[path] = struct{}{}
|
|
}
|
|
|
|
return http.ListenAndServe(httpAddr, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
|
if isLoopbackAllowedPath(req, allowlist) {
|
|
if hasValidLoopbackHTTPToken(req) {
|
|
handler.ServeHTTP(w, req)
|
|
return
|
|
}
|
|
|
|
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
http.Redirect(w, req, "https://"+redirectHost(req.Host, httpsPort)+req.URL.RequestURI(), http.StatusTemporaryRedirect)
|
|
}))
|
|
}
|
|
|
|
func redirectHost(requestHost string, httpsPort string) string {
|
|
host := requestHost
|
|
if h, _, err := net.SplitHostPort(requestHost); err == nil {
|
|
host = h
|
|
} else if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") {
|
|
host = strings.TrimPrefix(strings.TrimSuffix(host, "]"), "[")
|
|
}
|
|
|
|
if httpsPort != "443" {
|
|
return net.JoinHostPort(host, httpsPort)
|
|
}
|
|
|
|
if strings.Contains(host, ":") && !strings.HasPrefix(host, "[") && !strings.HasSuffix(host, "]") {
|
|
return "[" + host + "]"
|
|
}
|
|
|
|
return host
|
|
}
|
|
|
|
func allowByLoopbackInternalToken(req *http.Request) bool {
|
|
return req != nil && isLoopbackRemote(req.RemoteAddr) && hasValidLoopbackHTTPToken(req)
|
|
}
|
|
|
|
func isLoopbackAllowedPath(req *http.Request, allowedPaths map[string]struct{}) bool {
|
|
if !allowByLoopbackInternalToken(req) {
|
|
return false
|
|
}
|
|
|
|
_, allowed := allowedPaths[req.URL.Path]
|
|
return allowed
|
|
}
|
|
|
|
func hasValidLoopbackHTTPToken(req *http.Request) bool {
|
|
if req == nil {
|
|
return false
|
|
}
|
|
token, err := config.GetPicoclawInternalToken()
|
|
if err != nil || token == "" {
|
|
return false
|
|
}
|
|
|
|
provided := req.Header.Get(config.PicoclawInternalTokenHeader)
|
|
return subtle.ConstantTimeCompare([]byte(provided), []byte(token)) == 1
|
|
}
|
|
|
|
func isLoopbackRemote(remoteAddr string) bool {
|
|
host := strings.TrimSpace(remoteAddr)
|
|
if parsedHost, _, err := net.SplitHostPort(remoteAddr); err == nil {
|
|
host = parsedHost
|
|
}
|
|
|
|
ip := net.ParseIP(strings.Trim(host, "[]"))
|
|
return ip != nil && ip.IsLoopback()
|
|
}
|