GoDoxy/socket-proxy/pkg/handler.go
2025-05-10 11:24:28 +08:00

180 lines
4.5 KiB
Go

package socketproxy
import (
"context"
"net"
"net/http"
"net/http/httputil"
"strings"
"time"
"github.com/gorilla/mux"
)
var dialer = &net.Dialer{KeepAlive: 1 * time.Second}
func dialDockerSocket(ctx context.Context, _, _ string) (net.Conn, error) {
return dialer.DialContext(ctx, "unix", DockerSocket)
}
var DockerSocketHandler = dockerSocketHandler
func dockerSocketHandler() http.HandlerFunc {
rp := &httputil.ReverseProxy{
Director: func(req *http.Request) {
req.URL.Scheme = "http"
req.URL.Host = "api.moby.localhost"
req.RequestURI = req.URL.String()
},
Transport: &http.Transport{
DialContext: dialDockerSocket,
},
}
return rp.ServeHTTP
}
func endpointNotAllowed(w http.ResponseWriter, _ *http.Request) {
http.Error(w, "Endpoint not allowed", http.StatusForbidden)
}
// ref: https://github.com/Tecnativa/docker-socket-proxy/blob/master/haproxy.cfg
func NewHandler() http.Handler {
r := mux.NewRouter()
socketHandler := DockerSocketHandler()
const apiVersionPrefix = `/{version:(?:v[\d\.]+)?}`
const containerPath = "/containers/{id:[a-zA-Z0-9_.-]+}"
allowedPaths := []string{}
deniedPaths := []string{}
if DockerContainers {
allowedPaths = append(allowedPaths, "/containers")
if !DockerRestarts {
deniedPaths = append(deniedPaths, containerPath+"/stop")
deniedPaths = append(deniedPaths, containerPath+"/restart")
deniedPaths = append(deniedPaths, containerPath+"/kill")
}
if !DockerStart {
deniedPaths = append(deniedPaths, containerPath+"/start")
}
if !DockerStop && DockerRestarts {
deniedPaths = append(deniedPaths, containerPath+"/stop")
}
}
if DockerAuth {
allowedPaths = append(allowedPaths, "/auth")
}
if DockerBuild {
allowedPaths = append(allowedPaths, "/build")
}
if DockerCommit {
allowedPaths = append(allowedPaths, "/commit")
}
if DockerConfigs {
allowedPaths = append(allowedPaths, "/configs")
}
if DockerDistribution {
allowedPaths = append(allowedPaths, "/distribution")
}
if DockerEvents {
allowedPaths = append(allowedPaths, "/events")
}
if DockerExec {
allowedPaths = append(allowedPaths, "/exec")
}
if DockerGrpc {
allowedPaths = append(allowedPaths, "/grpc")
}
if DockerImages {
allowedPaths = append(allowedPaths, "/images")
}
if DockerInfo {
allowedPaths = append(allowedPaths, "/info")
}
if DockerNetworks {
allowedPaths = append(allowedPaths, "/networks")
}
if DockerNodes {
allowedPaths = append(allowedPaths, "/nodes")
}
if DockerPing {
allowedPaths = append(allowedPaths, "/_ping")
}
if DockerPlugins {
allowedPaths = append(allowedPaths, "/plugins")
}
if DockerSecrets {
allowedPaths = append(allowedPaths, "/secrets")
}
if DockerServices {
allowedPaths = append(allowedPaths, "/services")
}
if DockerSession {
allowedPaths = append(allowedPaths, "/session")
}
if DockerSwarm {
allowedPaths = append(allowedPaths, "/swarm")
}
if DockerSystem {
allowedPaths = append(allowedPaths, "/system")
}
if DockerTasks {
allowedPaths = append(allowedPaths, "/tasks")
}
if DockerVersion {
allowedPaths = append(allowedPaths, "/version")
}
if DockerVolumes {
allowedPaths = append(allowedPaths, "/volumes")
}
// Helper to determine if a path should be treated as a prefix
isPrefixPath := func(path string) bool {
return strings.Count(path, "/") == 1
}
// 1. Register Denied Paths (specific)
for _, path := range deniedPaths {
// Handle with version prefix
r.HandleFunc(apiVersionPrefix+path, endpointNotAllowed)
// Handle without version prefix
r.HandleFunc(path, endpointNotAllowed)
}
// 2. Register Allowed Paths
for _, p := range allowedPaths {
fullPathWithVersion := apiVersionPrefix + p
if isPrefixPath(p) {
r.PathPrefix(fullPathWithVersion).Handler(socketHandler)
r.PathPrefix(p).Handler(socketHandler)
} else {
r.HandleFunc(fullPathWithVersion, socketHandler)
r.HandleFunc(p, socketHandler)
}
}
// 3. Add fallback for any other routes
r.PathPrefix("/").HandlerFunc(endpointNotAllowed)
// HTTP method filtering
if !DockerPost {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
switch req.Method {
case http.MethodGet:
r.ServeHTTP(w, req)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
})
}
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
switch req.Method {
case http.MethodPost, http.MethodGet:
r.ServeHTTP(w, req)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
})
}