diff --git a/.env.example b/.env.example index f022fab..2138721 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,9 @@ # set timezone to get correct log timestamp TZ=ETC/UTC +# Enable Prometheus Metrics +GODOXY_PROMETHEUS_ENABLED=true + # API/WebUI user password login credentials (optional) # These fields are not required for OIDC authentication GODOXY_API_USER=admin @@ -35,15 +38,5 @@ GODOXY_API_JWT_TOKEN_TTL=1h # Optional: Comma-separated list of allowed groups. # GODOXY_OIDC_ALLOWED_GROUPS=group1,group2 -# Proxy listening address -GODOXY_HTTP_ADDR=:80 -GODOXY_HTTPS_ADDR=:443 - -# API listening address -GODOXY_API_ADDR=127.0.0.1:8888 - -# Prometheus Metrics listening address (uncomment to enable) -#GODOXY_PROMETHEUS_ADDR=:8889 - # Debug mode GODOXY_DEBUG=false \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 1d52f7b..88c470e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,11 @@ +# trunk-ignore-all(checkov/CKV_DOCKER_3) # Stage 1: Builder FROM golang:1.23.5-alpine AS builder HEALTHCHECK NONE # package version does not matter # trunk-ignore(hadolint/DL3018) -RUN apk add --no-cache tzdata make libcap-setcap +RUN apk add --no-cache make WORKDIR /src @@ -35,30 +36,36 @@ RUN --mount=type=cache,target="/go/pkg/mod" \ mv bin/godoxy /app/godoxy # Stage 2: Final image -FROM scratch +FROM alpine:3 LABEL maintainer="yusing@6uo.me" LABEL proxy.exclude=1 -# copy timezone data -COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo +# trunk-ignore(hadolint/DL3018) +RUN apk add --no-cache tzdata ca-certificates runuser libcap-setcap socat # copy binary COPY --from=builder /app /app +# copy startup script +COPY scripts/docker-start.sh /app/docker-start.sh + +RUN chmod +x /app/docker-start.sh + # copy example config COPY config.example.yml /app/config/config.yml -# copy certs -COPY --from=builder /etc/ssl/certs /etc/ssl/certs - -ENV DOCKER_HOST=unix:///var/run/docker.sock +ENV SOCKET_FORK=/app/forked.sock +ENV DOCKER_HOST=unix://${SOCKET_FORK} ENV GODOXY_DEBUG=0 +ENV PUID=1002 +ENV PGID=1002 + EXPOSE 80 EXPOSE 8888 EXPOSE 443 WORKDIR /app -CMD ["/app/godoxy"] \ No newline at end of file +ENTRYPOINT [ "/app/docker-start.sh" ] \ No newline at end of file diff --git a/Makefile b/Makefile index a647a1c..e595627 100755 --- a/Makefile +++ b/Makefile @@ -45,9 +45,11 @@ get: build: mkdir -p bin go build ${BUILD_FLAGS} -o bin/godoxy ./cmd - if [ $(shell id -u) -eq 0 ]; \ - then setcap CAP_NET_BIND_SERVICE=+eip bin/godoxy; \ - else sudo setcap CAP_NET_BIND_SERVICE=+eip bin/godoxy; \ + if which setcap; then \ + if [ $(shell id -u) -eq 0 ]; \ + then setcap CAP_NET_BIND_SERVICE=+eip bin/godoxy; \ + else sudo setcap CAP_NET_BIND_SERVICE=+eip bin/godoxy; \ + fi \ fi run: diff --git a/compose.example.yml b/compose.example.yml index b30d0f5..45e9847 100755 --- a/compose.example.yml +++ b/compose.example.yml @@ -4,14 +4,12 @@ services: image: ghcr.io/yusing/go-proxy-frontend:latest container_name: godoxy-frontend restart: unless-stopped - network_mode: host env_file: .env depends_on: - app # modify below to fit your needs labels: - proxy.aliases: gp - proxy.#1.port: 3000 + proxy.aliases: godoxy # proxy.#1.middlewares.cidr_whitelist.status: 403 # proxy.#1.middlewares.cidr_whitelist.message: IP not allowed # proxy.#1.middlewares.cidr_whitelist.allow: | @@ -23,8 +21,13 @@ services: image: ghcr.io/yusing/go-proxy:latest container_name: godoxy restart: always - network_mode: host env_file: .env + ports: + - 80:80 # http + - 443:443 # https + - 8081:8081 # prometheus + extra_hosts: + - host.docker.internal:host-gateway volumes: - /var/run/docker.sock:/var/run/docker.sock - ./config:/app/config diff --git a/internal/api/v1/query/query.go b/internal/api/v1/query/query.go index 7b59772..0796799 100644 --- a/internal/api/v1/query/query.go +++ b/internal/api/v1/query/query.go @@ -2,19 +2,17 @@ package query import ( "encoding/json" - "fmt" "io" "net/http" v1 "github.com/yusing/go-proxy/internal/api/v1" U "github.com/yusing/go-proxy/internal/api/v1/utils" - "github.com/yusing/go-proxy/internal/common" E "github.com/yusing/go-proxy/internal/error" "github.com/yusing/go-proxy/internal/net/http/middleware" ) func ReloadServer() E.Error { - resp, err := U.Post(common.APIHTTPURL+"/v1/reload", "", nil) + resp, err := U.FetchAPI(http.MethodPost, "/v1/reload", nil) if err != nil { return E.From(err) } @@ -32,7 +30,7 @@ func ReloadServer() E.Error { } func List[T any](what string) (_ T, outErr E.Error) { - resp, err := U.Get(fmt.Sprintf("%s/v1/list/%s", common.APIHTTPURL, what)) + resp, err := U.FetchAPI(http.MethodGet, "/v1/list/"+what, nil) if err != nil { outErr = E.From(err) return diff --git a/internal/api/v1/utils/http_client.go b/internal/api/v1/utils/http_client.go index 48d743b..7546b0e 100644 --- a/internal/api/v1/utils/http_client.go +++ b/internal/api/v1/utils/http_client.go @@ -1,7 +1,9 @@ package utils import ( + "bytes" "crypto/tls" + "io" "net" "net/http" @@ -26,3 +28,15 @@ var ( Post = httpClient.Post Head = httpClient.Head ) + +func FetchAPI(method, endpoint string, body []byte) (*http.Response, error) { + var bodyReader io.Reader + if body != nil { + bodyReader = bytes.NewReader(body) + } + req, err := http.NewRequest(method, "http://localhost"+common.APIHTTPAddr+endpoint, bodyReader) + if err != nil { + return nil, err + } + return httpClient.Do(req) +} diff --git a/internal/common/constants.go b/internal/common/constants.go index cf6d0f2..c23a301 100644 --- a/internal/common/constants.go +++ b/internal/common/constants.go @@ -10,6 +10,13 @@ const ( KeepAlive = 60 * time.Second ) +const ( + ProxyHTTPAddr = ":80" + ProxyHTTPSAddr = ":443" + APIHTTPAddr = ":8080" + MetricsHTTPAddr = ":8081" +) + // file, folder structure const ( diff --git a/internal/common/env.go b/internal/common/env.go index 4427816..03f1024 100644 --- a/internal/common/env.go +++ b/internal/common/env.go @@ -23,26 +23,7 @@ var ( EnableLogStreaming = GetEnvBool("LOG_STREAMING", true) DebugMemLogger = GetEnvBool("DEBUG_MEM_LOGGER", false) && EnableLogStreaming - ProxyHTTPAddr, - ProxyHTTPHost, - ProxyHTTPPort, - ProxyHTTPURL = GetAddrEnv("HTTP_ADDR", ":80", "http") - - ProxyHTTPSAddr, - ProxyHTTPSHost, - ProxyHTTPSPort, - ProxyHTTPSURL = GetAddrEnv("HTTPS_ADDR", ":443", "https") - - APIHTTPAddr, - APIHTTPHost, - APIHTTPPort, - APIHTTPURL = GetAddrEnv("API_ADDR", "127.0.0.1:8888", "http") - - MetricsHTTPAddr, - MetricsHTTPHost, - MetricsHTTPPort, - MetricsHTTPURL = GetAddrEnv("PROMETHEUS_ADDR", "", "http") - PrometheusEnabled = MetricsHTTPURL != "" + PrometheusEnabled = GetEnvBool("PROMETHEUS_ENABLED", true) APIJWTSecret = decodeJWTKey(GetEnvString("API_JWT_SECRET", "")) APIJWTTokenTTL = GetDurationEnv("API_JWT_TOKEN_TTL", time.Hour) diff --git a/internal/docker/container.go b/internal/docker/container.go index 9b985ae..35fa390 100644 --- a/internal/docker/container.go +++ b/internal/docker/container.go @@ -124,30 +124,30 @@ func (c *Container) setPublicIP() { return } if strings.HasPrefix(c.DockerHost, "unix://") { - c.PublicIP = "127.0.0.1" + c.PublicIP = "localhost" return } url, err := url.Parse(c.DockerHost) if err != nil { - logging.Err(err).Msgf("invalid docker host %q, falling back to 127.0.0.1", c.DockerHost) - c.PublicIP = "127.0.0.1" + logging.Err(err).Msgf("invalid docker host %q, falling back to localhost", c.DockerHost) + c.PublicIP = "localhost" return } c.PublicIP = url.Hostname() } func (c *Container) setPrivateIP(helper containerHelper) { - if !strings.HasPrefix(c.DockerHost, "unix://") { - return - } - if helper.NetworkSettings == nil { - return - } - for _, v := range helper.NetworkSettings.Networks { - if v.IPAddress == "" { - continue - } - c.PrivateIP = v.IPAddress - return - } + // if !strings.HasPrefix(c.DockerHost, "unix://") { + // return + // } + // if helper.NetworkSettings == nil { + // return + // } + // for _, v := range helper.NetworkSettings.Networks { + // if v.IPAddress == "" { + // continue + // } + // c.PrivateIP = v.IPAddress + // return + // } } diff --git a/internal/net/http/loadbalancer/loadbalancer_test.go b/internal/net/http/loadbalancer/loadbalancer_test.go index 349565e..25199b5 100644 --- a/internal/net/http/loadbalancer/loadbalancer_test.go +++ b/internal/net/http/loadbalancer/loadbalancer_test.go @@ -4,14 +4,13 @@ import ( "testing" "github.com/yusing/go-proxy/internal/net/http/loadbalancer/types" - loadbalance "github.com/yusing/go-proxy/internal/net/http/loadbalancer/types" . "github.com/yusing/go-proxy/internal/utils/testing" ) func TestRebalance(t *testing.T) { t.Parallel() t.Run("zero", func(t *testing.T) { - lb := New(new(loadbalance.Config)) + lb := New(new(types.Config)) for range 10 { lb.AddServer(types.TestNewServer(0)) } @@ -19,7 +18,7 @@ func TestRebalance(t *testing.T) { ExpectEqual(t, lb.sumWeight, maxWeight) }) t.Run("less", func(t *testing.T) { - lb := New(new(loadbalance.Config)) + lb := New(new(types.Config)) lb.AddServer(types.TestNewServer(float64(maxWeight) * .1)) lb.AddServer(types.TestNewServer(float64(maxWeight) * .2)) lb.AddServer(types.TestNewServer(float64(maxWeight) * .3)) @@ -30,7 +29,7 @@ func TestRebalance(t *testing.T) { ExpectEqual(t, lb.sumWeight, maxWeight) }) t.Run("more", func(t *testing.T) { - lb := New(new(loadbalance.Config)) + lb := New(new(types.Config)) lb.AddServer(types.TestNewServer(float64(maxWeight) * .1)) lb.AddServer(types.TestNewServer(float64(maxWeight) * .2)) lb.AddServer(types.TestNewServer(float64(maxWeight) * .3)) diff --git a/internal/net/http/middleware/redirect_http.go b/internal/net/http/middleware/redirect_http.go index 95b7dcd..18239b8 100644 --- a/internal/net/http/middleware/redirect_http.go +++ b/internal/net/http/middleware/redirect_http.go @@ -22,7 +22,7 @@ func (redirectHTTP) before(w http.ResponseWriter, r *http.Request) (proceed bool if i := strings.Index(host, ":"); i != -1 { host = host[:i] // strip port number if present } - r.URL.Host = host + ":" + common.ProxyHTTPSPort + r.URL.Host = host + common.ProxyHTTPSAddr logging.Debug().Str("url", r.URL.String()).Msg("redirect to https") http.Redirect(w, r, r.URL.String(), http.StatusTemporaryRedirect) return true diff --git a/internal/net/http/middleware/redirect_http_test.go b/internal/net/http/middleware/redirect_http_test.go index 82c6581..7577b56 100644 --- a/internal/net/http/middleware/redirect_http_test.go +++ b/internal/net/http/middleware/redirect_http_test.go @@ -15,7 +15,7 @@ func TestRedirectToHTTPs(t *testing.T) { }) ExpectNoError(t, err) ExpectEqual(t, result.ResponseStatus, http.StatusTemporaryRedirect) - ExpectEqual(t, result.ResponseHeaders.Get("Location"), "https://example.com:"+common.ProxyHTTPSPort) + ExpectEqual(t, result.ResponseHeaders.Get("Location"), "https://example.com"+common.ProxyHTTPSAddr) } func TestNoRedirect(t *testing.T) { diff --git a/internal/route/types/http_config_test.go b/internal/route/types/http_config_test.go index 8fbd686..bf8ccaf 100644 --- a/internal/route/types/http_config_test.go +++ b/internal/route/types/http_config_test.go @@ -41,7 +41,7 @@ func TestHTTPConfigDeserialize(t *testing.T) { if err != nil { ExpectNoError(t, err) } - ExpectDeepEqual(t, cfg.HTTPConfig, &tt.expected) + ExpectDeepEqual(t, cfg.HTTPConfig, tt.expected) }) } } diff --git a/internal/route/types/raw_entry.go b/internal/route/types/raw_entry.go index b82bf5f..68d5ce0 100644 --- a/internal/route/types/raw_entry.go +++ b/internal/route/types/raw_entry.go @@ -116,6 +116,11 @@ func (e *RawEntry) Finalize() { } } + switch e.Host { + case "localhost", "127.0.0.1", "[::1]": + e.Host = "host.docker.internal" + } + if e.Scheme == "" && isDocker { switch { case e.Host == cont.PublicIP && cont.PublicPortMapping[pp].Type == "udp": diff --git a/scripts/docker-start.sh b/scripts/docker-start.sh new file mode 100755 index 0000000..453af56 --- /dev/null +++ b/scripts/docker-start.sh @@ -0,0 +1,25 @@ +#!/bin/sh +echo "Running as PUID: ${PUID}, PGID: ${PGID}" +echo "Creating user" +addgroup -S -g "${PGID}" godoxyg +adduser -S -D -H -s /bin/false -u "${PUID}" -g "${PGID}" godoxy + +echo "Setting up permissions" +chown -R godoxy:godoxyg /app +setcap CAP_NET_BIND_SERVICE=+eip /app/godoxy + +# fork docker socket if exists +if test -e /var/run/docker.sock; then + echo "Proxying docker socket" + socat -v "UNIX-LISTEN:${SOCKET_FORK}",fork UNIX-CONNECT:/var/run/docker.sock >/dev/null 2>&1 & + # wait for socket to be ready + while [ ! -S "${SOCKET_FORK}" ]; do + sleep 0.1 + done + chmod 660 "${SOCKET_FORK}" + chown godoxy:godoxyg "${SOCKET_FORK}" +fi + +echo "Done" + +runuser -u godoxy -g godoxyg -- /app/godoxy