run as non root and non host network mode

This commit is contained in:
yusing 2025-01-31 06:32:09 +08:00
parent 357897a0cd
commit b3290e2665
15 changed files with 107 additions and 73 deletions

View file

@ -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

View file

@ -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"]
ENTRYPOINT [ "/app/docker-start.sh" ]

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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)
}

View file

@ -10,6 +10,13 @@ const (
KeepAlive = 60 * time.Second
)
const (
ProxyHTTPAddr = ":80"
ProxyHTTPSAddr = ":443"
APIHTTPAddr = ":8080"
MetricsHTTPAddr = ":8081"
)
// file, folder structure
const (

View file

@ -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)

View file

@ -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
// }
}

View file

@ -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))

View file

@ -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

View file

@ -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) {

View file

@ -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)
})
}
}

View file

@ -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":

25
scripts/docker-start.sh Executable file
View file

@ -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