mirror of
https://github.com/yusing/godoxy.git
synced 2025-05-19 20:32:35 +02:00
run as non root and non host network mode
This commit is contained in:
parent
357897a0cd
commit
b3290e2665
15 changed files with 107 additions and 73 deletions
13
.env.example
13
.env.example
|
@ -1,6 +1,9 @@
|
||||||
# set timezone to get correct log timestamp
|
# set timezone to get correct log timestamp
|
||||||
TZ=ETC/UTC
|
TZ=ETC/UTC
|
||||||
|
|
||||||
|
# Enable Prometheus Metrics
|
||||||
|
GODOXY_PROMETHEUS_ENABLED=true
|
||||||
|
|
||||||
# API/WebUI user password login credentials (optional)
|
# API/WebUI user password login credentials (optional)
|
||||||
# These fields are not required for OIDC authentication
|
# These fields are not required for OIDC authentication
|
||||||
GODOXY_API_USER=admin
|
GODOXY_API_USER=admin
|
||||||
|
@ -35,15 +38,5 @@ GODOXY_API_JWT_TOKEN_TTL=1h
|
||||||
# Optional: Comma-separated list of allowed groups.
|
# Optional: Comma-separated list of allowed groups.
|
||||||
# GODOXY_OIDC_ALLOWED_GROUPS=group1,group2
|
# 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
|
# Debug mode
|
||||||
GODOXY_DEBUG=false
|
GODOXY_DEBUG=false
|
25
Dockerfile
25
Dockerfile
|
@ -1,10 +1,11 @@
|
||||||
|
# trunk-ignore-all(checkov/CKV_DOCKER_3)
|
||||||
# Stage 1: Builder
|
# Stage 1: Builder
|
||||||
FROM golang:1.23.5-alpine AS builder
|
FROM golang:1.23.5-alpine AS builder
|
||||||
HEALTHCHECK NONE
|
HEALTHCHECK NONE
|
||||||
|
|
||||||
# package version does not matter
|
# package version does not matter
|
||||||
# trunk-ignore(hadolint/DL3018)
|
# trunk-ignore(hadolint/DL3018)
|
||||||
RUN apk add --no-cache tzdata make libcap-setcap
|
RUN apk add --no-cache make
|
||||||
|
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
|
||||||
|
@ -35,30 +36,36 @@ RUN --mount=type=cache,target="/go/pkg/mod" \
|
||||||
mv bin/godoxy /app/godoxy
|
mv bin/godoxy /app/godoxy
|
||||||
|
|
||||||
# Stage 2: Final image
|
# Stage 2: Final image
|
||||||
FROM scratch
|
FROM alpine:3
|
||||||
|
|
||||||
LABEL maintainer="yusing@6uo.me"
|
LABEL maintainer="yusing@6uo.me"
|
||||||
LABEL proxy.exclude=1
|
LABEL proxy.exclude=1
|
||||||
|
|
||||||
# copy timezone data
|
# trunk-ignore(hadolint/DL3018)
|
||||||
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
|
RUN apk add --no-cache tzdata ca-certificates runuser libcap-setcap socat
|
||||||
|
|
||||||
# copy binary
|
# copy binary
|
||||||
COPY --from=builder /app /app
|
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 example config
|
||||||
COPY config.example.yml /app/config/config.yml
|
COPY config.example.yml /app/config/config.yml
|
||||||
|
|
||||||
# copy certs
|
ENV SOCKET_FORK=/app/forked.sock
|
||||||
COPY --from=builder /etc/ssl/certs /etc/ssl/certs
|
ENV DOCKER_HOST=unix://${SOCKET_FORK}
|
||||||
|
|
||||||
ENV DOCKER_HOST=unix:///var/run/docker.sock
|
|
||||||
ENV GODOXY_DEBUG=0
|
ENV GODOXY_DEBUG=0
|
||||||
|
|
||||||
|
ENV PUID=1002
|
||||||
|
ENV PGID=1002
|
||||||
|
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
EXPOSE 8888
|
EXPOSE 8888
|
||||||
EXPOSE 443
|
EXPOSE 443
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
CMD ["/app/godoxy"]
|
ENTRYPOINT [ "/app/docker-start.sh" ]
|
8
Makefile
8
Makefile
|
@ -45,9 +45,11 @@ get:
|
||||||
build:
|
build:
|
||||||
mkdir -p bin
|
mkdir -p bin
|
||||||
go build ${BUILD_FLAGS} -o bin/godoxy ./cmd
|
go build ${BUILD_FLAGS} -o bin/godoxy ./cmd
|
||||||
if [ $(shell id -u) -eq 0 ]; \
|
if which setcap; then \
|
||||||
then setcap CAP_NET_BIND_SERVICE=+eip bin/godoxy; \
|
if [ $(shell id -u) -eq 0 ]; \
|
||||||
else sudo setcap CAP_NET_BIND_SERVICE=+eip bin/godoxy; \
|
then setcap CAP_NET_BIND_SERVICE=+eip bin/godoxy; \
|
||||||
|
else sudo setcap CAP_NET_BIND_SERVICE=+eip bin/godoxy; \
|
||||||
|
fi \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
run:
|
run:
|
||||||
|
|
|
@ -4,14 +4,12 @@ services:
|
||||||
image: ghcr.io/yusing/go-proxy-frontend:latest
|
image: ghcr.io/yusing/go-proxy-frontend:latest
|
||||||
container_name: godoxy-frontend
|
container_name: godoxy-frontend
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
network_mode: host
|
|
||||||
env_file: .env
|
env_file: .env
|
||||||
depends_on:
|
depends_on:
|
||||||
- app
|
- app
|
||||||
# modify below to fit your needs
|
# modify below to fit your needs
|
||||||
labels:
|
labels:
|
||||||
proxy.aliases: gp
|
proxy.aliases: godoxy
|
||||||
proxy.#1.port: 3000
|
|
||||||
# proxy.#1.middlewares.cidr_whitelist.status: 403
|
# proxy.#1.middlewares.cidr_whitelist.status: 403
|
||||||
# proxy.#1.middlewares.cidr_whitelist.message: IP not allowed
|
# proxy.#1.middlewares.cidr_whitelist.message: IP not allowed
|
||||||
# proxy.#1.middlewares.cidr_whitelist.allow: |
|
# proxy.#1.middlewares.cidr_whitelist.allow: |
|
||||||
|
@ -23,8 +21,13 @@ services:
|
||||||
image: ghcr.io/yusing/go-proxy:latest
|
image: ghcr.io/yusing/go-proxy:latest
|
||||||
container_name: godoxy
|
container_name: godoxy
|
||||||
restart: always
|
restart: always
|
||||||
network_mode: host
|
|
||||||
env_file: .env
|
env_file: .env
|
||||||
|
ports:
|
||||||
|
- 80:80 # http
|
||||||
|
- 443:443 # https
|
||||||
|
- 8081:8081 # prometheus
|
||||||
|
extra_hosts:
|
||||||
|
- host.docker.internal:host-gateway
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
- ./config:/app/config
|
- ./config:/app/config
|
||||||
|
|
|
@ -2,19 +2,17 @@ package query
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
v1 "github.com/yusing/go-proxy/internal/api/v1"
|
v1 "github.com/yusing/go-proxy/internal/api/v1"
|
||||||
U "github.com/yusing/go-proxy/internal/api/v1/utils"
|
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"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
"github.com/yusing/go-proxy/internal/net/http/middleware"
|
"github.com/yusing/go-proxy/internal/net/http/middleware"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ReloadServer() E.Error {
|
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 {
|
if err != nil {
|
||||||
return E.From(err)
|
return E.From(err)
|
||||||
}
|
}
|
||||||
|
@ -32,7 +30,7 @@ func ReloadServer() E.Error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func List[T any](what string) (_ T, outErr 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 {
|
if err != nil {
|
||||||
outErr = E.From(err)
|
outErr = E.From(err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
@ -26,3 +28,15 @@ var (
|
||||||
Post = httpClient.Post
|
Post = httpClient.Post
|
||||||
Head = httpClient.Head
|
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)
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,13 @@ const (
|
||||||
KeepAlive = 60 * time.Second
|
KeepAlive = 60 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ProxyHTTPAddr = ":80"
|
||||||
|
ProxyHTTPSAddr = ":443"
|
||||||
|
APIHTTPAddr = ":8080"
|
||||||
|
MetricsHTTPAddr = ":8081"
|
||||||
|
)
|
||||||
|
|
||||||
// file, folder structure
|
// file, folder structure
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -23,26 +23,7 @@ var (
|
||||||
EnableLogStreaming = GetEnvBool("LOG_STREAMING", true)
|
EnableLogStreaming = GetEnvBool("LOG_STREAMING", true)
|
||||||
DebugMemLogger = GetEnvBool("DEBUG_MEM_LOGGER", false) && EnableLogStreaming
|
DebugMemLogger = GetEnvBool("DEBUG_MEM_LOGGER", false) && EnableLogStreaming
|
||||||
|
|
||||||
ProxyHTTPAddr,
|
PrometheusEnabled = GetEnvBool("PROMETHEUS_ENABLED", true)
|
||||||
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 != ""
|
|
||||||
|
|
||||||
APIJWTSecret = decodeJWTKey(GetEnvString("API_JWT_SECRET", ""))
|
APIJWTSecret = decodeJWTKey(GetEnvString("API_JWT_SECRET", ""))
|
||||||
APIJWTTokenTTL = GetDurationEnv("API_JWT_TOKEN_TTL", time.Hour)
|
APIJWTTokenTTL = GetDurationEnv("API_JWT_TOKEN_TTL", time.Hour)
|
||||||
|
|
|
@ -124,30 +124,30 @@ func (c *Container) setPublicIP() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(c.DockerHost, "unix://") {
|
if strings.HasPrefix(c.DockerHost, "unix://") {
|
||||||
c.PublicIP = "127.0.0.1"
|
c.PublicIP = "localhost"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
url, err := url.Parse(c.DockerHost)
|
url, err := url.Parse(c.DockerHost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.Err(err).Msgf("invalid docker host %q, falling back to 127.0.0.1", c.DockerHost)
|
logging.Err(err).Msgf("invalid docker host %q, falling back to localhost", c.DockerHost)
|
||||||
c.PublicIP = "127.0.0.1"
|
c.PublicIP = "localhost"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.PublicIP = url.Hostname()
|
c.PublicIP = url.Hostname()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) setPrivateIP(helper containerHelper) {
|
func (c *Container) setPrivateIP(helper containerHelper) {
|
||||||
if !strings.HasPrefix(c.DockerHost, "unix://") {
|
// if !strings.HasPrefix(c.DockerHost, "unix://") {
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
if helper.NetworkSettings == nil {
|
// if helper.NetworkSettings == nil {
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
for _, v := range helper.NetworkSettings.Networks {
|
// for _, v := range helper.NetworkSettings.Networks {
|
||||||
if v.IPAddress == "" {
|
// if v.IPAddress == "" {
|
||||||
continue
|
// continue
|
||||||
}
|
// }
|
||||||
c.PrivateIP = v.IPAddress
|
// c.PrivateIP = v.IPAddress
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,14 +4,13 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/yusing/go-proxy/internal/net/http/loadbalancer/types"
|
"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"
|
. "github.com/yusing/go-proxy/internal/utils/testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRebalance(t *testing.T) {
|
func TestRebalance(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
t.Run("zero", func(t *testing.T) {
|
t.Run("zero", func(t *testing.T) {
|
||||||
lb := New(new(loadbalance.Config))
|
lb := New(new(types.Config))
|
||||||
for range 10 {
|
for range 10 {
|
||||||
lb.AddServer(types.TestNewServer(0))
|
lb.AddServer(types.TestNewServer(0))
|
||||||
}
|
}
|
||||||
|
@ -19,7 +18,7 @@ func TestRebalance(t *testing.T) {
|
||||||
ExpectEqual(t, lb.sumWeight, maxWeight)
|
ExpectEqual(t, lb.sumWeight, maxWeight)
|
||||||
})
|
})
|
||||||
t.Run("less", func(t *testing.T) {
|
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) * .1))
|
||||||
lb.AddServer(types.TestNewServer(float64(maxWeight) * .2))
|
lb.AddServer(types.TestNewServer(float64(maxWeight) * .2))
|
||||||
lb.AddServer(types.TestNewServer(float64(maxWeight) * .3))
|
lb.AddServer(types.TestNewServer(float64(maxWeight) * .3))
|
||||||
|
@ -30,7 +29,7 @@ func TestRebalance(t *testing.T) {
|
||||||
ExpectEqual(t, lb.sumWeight, maxWeight)
|
ExpectEqual(t, lb.sumWeight, maxWeight)
|
||||||
})
|
})
|
||||||
t.Run("more", func(t *testing.T) {
|
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) * .1))
|
||||||
lb.AddServer(types.TestNewServer(float64(maxWeight) * .2))
|
lb.AddServer(types.TestNewServer(float64(maxWeight) * .2))
|
||||||
lb.AddServer(types.TestNewServer(float64(maxWeight) * .3))
|
lb.AddServer(types.TestNewServer(float64(maxWeight) * .3))
|
||||||
|
|
|
@ -22,7 +22,7 @@ func (redirectHTTP) before(w http.ResponseWriter, r *http.Request) (proceed bool
|
||||||
if i := strings.Index(host, ":"); i != -1 {
|
if i := strings.Index(host, ":"); i != -1 {
|
||||||
host = host[:i] // strip port number if present
|
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")
|
logging.Debug().Str("url", r.URL.String()).Msg("redirect to https")
|
||||||
http.Redirect(w, r, r.URL.String(), http.StatusTemporaryRedirect)
|
http.Redirect(w, r, r.URL.String(), http.StatusTemporaryRedirect)
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -15,7 +15,7 @@ func TestRedirectToHTTPs(t *testing.T) {
|
||||||
})
|
})
|
||||||
ExpectNoError(t, err)
|
ExpectNoError(t, err)
|
||||||
ExpectEqual(t, result.ResponseStatus, http.StatusTemporaryRedirect)
|
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) {
|
func TestNoRedirect(t *testing.T) {
|
||||||
|
|
|
@ -41,7 +41,7 @@ func TestHTTPConfigDeserialize(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ExpectNoError(t, err)
|
ExpectNoError(t, err)
|
||||||
}
|
}
|
||||||
ExpectDeepEqual(t, cfg.HTTPConfig, &tt.expected)
|
ExpectDeepEqual(t, cfg.HTTPConfig, tt.expected)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
if e.Scheme == "" && isDocker {
|
||||||
switch {
|
switch {
|
||||||
case e.Host == cont.PublicIP && cont.PublicPortMapping[pp].Type == "udp":
|
case e.Host == cont.PublicIP && cont.PublicPortMapping[pp].Type == "udp":
|
||||||
|
|
25
scripts/docker-start.sh
Executable file
25
scripts/docker-start.sh
Executable 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
|
Loading…
Add table
Reference in a new issue