mirror of
https://github.com/yusing/godoxy.git
synced 2025-05-19 20:32:35 +02:00

- 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.
202 lines
5.5 KiB
Go
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)
|
|
}
|
|
})
|
|
}
|