GoDoxy/agent/pkg/handler/handler.go
yusing 455a85e6a0
Some checks are pending
Docker Image CI (nightly) / build-nightly (push) Waiting to run
Docker Image CI (nightly) / build-nightly-agent (push) Waiting to run
feat(docker): add Docker socket proxy support and related configurations
- Introduced Docker socket proxy handling in the agent.
- Added environment variables for Docker socket configuration.
- Implemented new Docker handler with endpoint permissions based on environment settings.
- Added tests for Docker handler functionality.
- Updated go.mod to include gorilla/mux for routing.
2025-05-08 20:59:32 +08:00

202 lines
5.5 KiB
Go

package handler
import (
"fmt"
"io"
"net/http"
"strings"
"github.com/gorilla/mux"
"github.com/yusing/go-proxy/agent/pkg/agent"
"github.com/yusing/go-proxy/agent/pkg/env"
"github.com/yusing/go-proxy/internal/common"
"github.com/yusing/go-proxy/internal/logging/memlogger"
"github.com/yusing/go-proxy/internal/metrics/systeminfo"
"github.com/yusing/go-proxy/internal/utils/strutils"
"github.com/yusing/go-proxy/pkg"
)
type ServeMux struct{ *http.ServeMux }
func (mux ServeMux) HandleMethods(methods, endpoint string, handler http.HandlerFunc) {
for _, m := range strutils.CommaSeperatedList(methods) {
mux.ServeMux.HandleFunc(m+" "+agent.APIEndpointBase+endpoint, handler)
}
}
func (mux ServeMux) HandleFunc(endpoint string, handler http.HandlerFunc) {
mux.ServeMux.HandleFunc(agent.APIEndpointBase+endpoint, handler)
}
type NopWriteCloser struct {
io.Writer
}
func (NopWriteCloser) Close() error {
return nil
}
func NewAgentHandler() http.Handler {
mux := ServeMux{http.NewServeMux()}
mux.HandleFunc(agent.EndpointProxyHTTP+"/{path...}", ProxyHTTP)
mux.HandleMethods("GET", agent.EndpointVersion, pkg.GetVersionHTTPHandler())
mux.HandleMethods("GET", agent.EndpointName, func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, env.AgentName)
})
mux.HandleMethods("GET", agent.EndpointHealth, CheckHealth)
mux.HandleMethods("GET", agent.EndpointLogs, memlogger.HandlerFunc())
mux.HandleMethods("GET", agent.EndpointSystemInfo, systeminfo.Poller.ServeHTTP)
mux.ServeMux.HandleFunc("/", DockerSocketHandler())
return mux
}
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 NewDockerHandler() http.Handler {
r := mux.NewRouter()
var socketHandler http.HandlerFunc
if common.IsTest {
socketHandler = mockDockerSocketHandler()
} else {
socketHandler = DockerSocketHandler()
}
const apiVersionPrefix = `/{version:(?:v[\d\.]+)?}`
const containerPath = "/containers/{id:[a-zA-Z0-9_.-]+}"
allowedPaths := []string{}
deniedPaths := []string{}
if env.DockerContainers {
allowedPaths = append(allowedPaths, "/containers")
if !env.DockerRestarts {
deniedPaths = append(deniedPaths, containerPath+"/stop")
deniedPaths = append(deniedPaths, containerPath+"/restart")
deniedPaths = append(deniedPaths, containerPath+"/kill")
}
if !env.DockerStart {
deniedPaths = append(deniedPaths, containerPath+"/start")
}
if !env.DockerStop && env.DockerRestarts {
deniedPaths = append(deniedPaths, containerPath+"/stop")
}
}
if env.DockerAuth {
allowedPaths = append(allowedPaths, "/auth")
}
if env.DockerBuild {
allowedPaths = append(allowedPaths, "/build")
}
if env.DockerCommit {
allowedPaths = append(allowedPaths, "/commit")
}
if env.DockerConfigs {
allowedPaths = append(allowedPaths, "/configs")
}
if env.DockerDistributions {
allowedPaths = append(allowedPaths, "/distributions")
}
if env.DockerEvents {
allowedPaths = append(allowedPaths, "/events")
}
if env.DockerExec {
allowedPaths = append(allowedPaths, "/exec")
}
if env.DockerGrpc {
allowedPaths = append(allowedPaths, "/grpc")
}
if env.DockerImages {
allowedPaths = append(allowedPaths, "/images")
}
if env.DockerInfo {
allowedPaths = append(allowedPaths, "/info")
}
if env.DockerNetworks {
allowedPaths = append(allowedPaths, "/networks")
}
if env.DockerNodes {
allowedPaths = append(allowedPaths, "/nodes")
}
if env.DockerPing {
allowedPaths = append(allowedPaths, "/_ping")
}
if env.DockerPlugins {
allowedPaths = append(allowedPaths, "/plugins")
}
if env.DockerSecrets {
allowedPaths = append(allowedPaths, "/secrets")
}
if env.DockerServices {
allowedPaths = append(allowedPaths, "/services")
}
if env.DockerSession {
allowedPaths = append(allowedPaths, "/session")
}
if env.DockerSwarm {
allowedPaths = append(allowedPaths, "/swarm")
}
if env.DockerSystem {
allowedPaths = append(allowedPaths, "/system")
}
if env.DockerTasks {
allowedPaths = append(allowedPaths, "/tasks")
}
if env.DockerVersion {
allowedPaths = append(allowedPaths, "/version")
}
if env.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 !env.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)
}
})
}