mirror of
https://github.com/yusing/godoxy.git
synced 2026-01-15 06:43:35 +01:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b3bf84148 | ||
|
|
62a667758d | ||
|
|
ddd27156fc | ||
|
|
af8e2d56b2 | ||
|
|
74a215b894 | ||
|
|
ccdc0046fd | ||
|
|
2f7fdc4c51 | ||
|
|
de1f4da126 | ||
|
|
a48ccb4423 | ||
|
|
193fd9a249 | ||
|
|
0bc4c4af77 | ||
|
|
5fa1417add | ||
|
|
b763c92645 | ||
|
|
09b14a47e9 | ||
|
|
83a69322fa | ||
|
|
3aba5a1911 | ||
|
|
ca805edfe0 | ||
|
|
7205bf47de |
4
.vscode/settings.example.json
vendored
4
.vscode/settings.example.json
vendored
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"yaml.schemas": {
|
||||
"https://github.com/yusing/godoxy-webui/raw/refs/heads/main/src/types/godoxy/config.schema.json": [
|
||||
"https://github.com/yusing/godoxy-webui/raw/refs/heads/main/types/godoxy/config.schema.json": [
|
||||
"config.example.yml",
|
||||
"config.yml"
|
||||
],
|
||||
"https://github.com/yusing/godoxy-webui/raw/refs/heads/main/src/types/godoxy/routes.schema.json": [
|
||||
"https://github.com/yusing/godoxy-webui/raw/refs/heads/main/types/godoxy/routes.schema.json": [
|
||||
"providers.example.yml"
|
||||
]
|
||||
}
|
||||
|
||||
15
README.md
15
README.md
@@ -33,6 +33,7 @@ Have questions? Ask [ChatGPT](https://chatgpt.com/g/g-6825390374b481919ad482f2e4
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Setup](#setup)
|
||||
- [How does GoDoxy work](#how-does-godoxy-work)
|
||||
- [Update / Uninstall system agent](#update--uninstall-system-agent)
|
||||
- [Screenshots](#screenshots)
|
||||
- [idlesleeper](#idlesleeper)
|
||||
- [Metrics and Logs](#metrics-and-logs)
|
||||
@@ -128,6 +129,20 @@ Configure Wildcard DNS Record(s) to point to machine running `GoDoxy`, e.g.
|
||||
>
|
||||
> For example, with the label `proxy.aliases: qbt` you can access your app via `qbt.domain.com`.
|
||||
|
||||
## Update / Uninstall system agent
|
||||
|
||||
Update:
|
||||
|
||||
```bash
|
||||
bash -c "$(curl -fsSL https://github.com/yusing/godoxy/raw/refs/heads/main/scripts/install-agent.sh)" -- update
|
||||
```
|
||||
|
||||
Uninstall:
|
||||
|
||||
```bash
|
||||
bash -c "$(curl -fsSL https://github.com/yusing/godoxy/raw/refs/heads/main/scripts/install-agent.sh)" -- uninstall
|
||||
```
|
||||
|
||||
## Screenshots
|
||||
|
||||
### idlesleeper
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
- [安裝](#安裝)
|
||||
- [手動安裝](#手動安裝)
|
||||
- [資料夾結構](#資料夾結構)
|
||||
- [更新 / 卸載系統代理 (System Agent)](#更新--卸載系統代理-system-agent)
|
||||
- [截圖](#截圖)
|
||||
- [閒置休眠](#閒置休眠)
|
||||
- [監控](#監控)
|
||||
@@ -144,6 +145,20 @@
|
||||
└── .env
|
||||
```
|
||||
|
||||
## 更新 / 卸載系統代理 (System Agent)
|
||||
|
||||
更新:
|
||||
|
||||
```bash
|
||||
bash -c "$(curl -fsSL https://github.com/yusing/godoxy/raw/refs/heads/main/scripts/install-agent.sh)" -- update
|
||||
```
|
||||
|
||||
卸載:
|
||||
|
||||
```bash
|
||||
bash -c "$(curl -fsSL https://github.com/yusing/godoxy/raw/refs/heads/main/scripts/install-agent.sh)" -- uninstall
|
||||
```
|
||||
|
||||
## 截圖
|
||||
|
||||
### 閒置休眠
|
||||
|
||||
@@ -71,6 +71,7 @@ require (
|
||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||
github.com/oschwald/maxminddb-golang v1.13.1 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/pires/go-proxyproto v0.8.1 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||
|
||||
@@ -128,6 +128,8 @@ github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5
|
||||
github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
|
||||
github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
type AgentConfig struct {
|
||||
Addr string `json:"addr"`
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Version pkg.Version `json:"version"`
|
||||
Runtime ContainerRuntime `json:"runtime"`
|
||||
|
||||
httpClient *http.Client
|
||||
@@ -148,11 +148,10 @@ func (cfg *AgentConfig) StartWithCerts(ctx context.Context, ca, crt, key []byte)
|
||||
return fmt.Errorf("failed to get agent runtime: HTTP %d %s", status, runtimeBytes)
|
||||
}
|
||||
|
||||
cfg.Version = string(agentVersionBytes)
|
||||
agentVersion := pkg.ParseVersion(cfg.Version)
|
||||
cfg.Version = pkg.ParseVersion(string(agentVersionBytes))
|
||||
|
||||
if serverVersion.IsNewerMajorThan(agentVersion) {
|
||||
log.Warn().Msgf("agent %s major version mismatch: server: %s, agent: %s", cfg.Name, serverVersion, agentVersion)
|
||||
if serverVersion.IsNewerThanMajor(cfg.Version) {
|
||||
log.Warn().Msgf("agent %s major version mismatch: server: %s, agent: %s", cfg.Name, serverVersion, cfg.Version)
|
||||
}
|
||||
|
||||
log.Info().Msgf("agent %q initialized", cfg.Name)
|
||||
|
||||
@@ -19,7 +19,6 @@ func (cfg *AgentConfig) Do(ctx context.Context, method, endpoint string, body io
|
||||
}
|
||||
|
||||
func (cfg *AgentConfig) Forward(req *http.Request, endpoint string) (*http.Response, error) {
|
||||
req = req.WithContext(req.Context())
|
||||
req.URL.Host = AgentHost
|
||||
req.URL.Scheme = "https"
|
||||
req.URL.Path = APIEndpointBase + endpoint
|
||||
@@ -56,17 +55,11 @@ func (cfg *AgentConfig) Websocket(ctx context.Context, endpoint string) (*websoc
|
||||
//
|
||||
// It will create a new request with the same context, method, and body, but with the agent host and scheme, and the endpoint
|
||||
// If the request has a query, it will be added to the proxy request's URL
|
||||
func (cfg *AgentConfig) ReverseProxy(w http.ResponseWriter, req *http.Request, endpoint string) error {
|
||||
func (cfg *AgentConfig) ReverseProxy(w http.ResponseWriter, req *http.Request, endpoint string) {
|
||||
rp := reverseproxy.NewReverseProxy("agent", nettypes.NewURL(AgentURL), cfg.Transport())
|
||||
uri := APIEndpointBase + endpoint
|
||||
if req.URL.RawQuery != "" {
|
||||
uri += "?" + req.URL.RawQuery
|
||||
}
|
||||
r, err := http.NewRequestWithContext(req.Context(), req.Method, uri, req.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.Header = req.Header
|
||||
rp.ServeHTTP(w, r)
|
||||
return nil
|
||||
req.URL.Host = AgentHost
|
||||
req.URL.Scheme = "https"
|
||||
req.URL.Path = endpoint
|
||||
req.RequestURI = ""
|
||||
rp.ServeHTTP(w, req)
|
||||
}
|
||||
|
||||
76
agent/pkg/agentproxy/config.go
Normal file
76
agent/pkg/agentproxy/config.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package agentproxy
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
route "github.com/yusing/go-proxy/internal/route/types"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Scheme string `json:"scheme,omitempty"`
|
||||
Host string `json:"host,omitempty"` // host or host:port
|
||||
|
||||
route.HTTPConfig
|
||||
}
|
||||
|
||||
func ConfigFromHeaders(h http.Header) (Config, error) {
|
||||
cfg, err := proxyConfigFromHeaders(h)
|
||||
if err != nil {
|
||||
return cfg, err
|
||||
}
|
||||
if cfg.Host == "" {
|
||||
cfg = proxyConfigFromHeadersLegacy(h)
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func proxyConfigFromHeadersLegacy(h http.Header) (cfg Config) {
|
||||
cfg.Host = h.Get(HeaderXProxyHost)
|
||||
isHTTPS, _ := strconv.ParseBool(h.Get(HeaderXProxyHTTPS))
|
||||
cfg.NoTLSVerify, _ = strconv.ParseBool(h.Get(HeaderXProxySkipTLSVerify))
|
||||
responseHeaderTimeout, err := strconv.Atoi(h.Get(HeaderXProxyResponseHeaderTimeout))
|
||||
if err != nil {
|
||||
responseHeaderTimeout = 0
|
||||
}
|
||||
cfg.ResponseHeaderTimeout = time.Duration(responseHeaderTimeout) * time.Second
|
||||
|
||||
cfg.Scheme = "http"
|
||||
if isHTTPS {
|
||||
cfg.Scheme = "https"
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func proxyConfigFromHeaders(h http.Header) (cfg Config, err error) {
|
||||
cfg.Scheme = h.Get(HeaderXProxyScheme)
|
||||
cfg.Host = h.Get(HeaderXProxyHost)
|
||||
|
||||
cfgBase64 := h.Get(HeaderXProxyConfig)
|
||||
cfgJSON, err := base64.StdEncoding.DecodeString(cfgBase64)
|
||||
if err != nil {
|
||||
return cfg, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(cfgJSON, &cfg)
|
||||
return cfg, err
|
||||
}
|
||||
|
||||
func (cfg *Config) SetAgentProxyConfigHeadersLegacy(h http.Header) {
|
||||
h.Set(HeaderXProxyHost, cfg.Host)
|
||||
h.Set(HeaderXProxyHTTPS, strconv.FormatBool(cfg.Scheme == "https"))
|
||||
h.Set(HeaderXProxySkipTLSVerify, strconv.FormatBool(cfg.NoTLSVerify))
|
||||
h.Set(HeaderXProxyResponseHeaderTimeout, strconv.Itoa(int(cfg.ResponseHeaderTimeout.Round(time.Second).Seconds())))
|
||||
}
|
||||
|
||||
func (cfg *Config) SetAgentProxyConfigHeaders(h http.Header) {
|
||||
h.Set(HeaderXProxyHost, cfg.Host)
|
||||
h.Set(HeaderXProxyScheme, string(cfg.Scheme))
|
||||
cfgJSON, _ := json.Marshal(cfg.HTTPConfig)
|
||||
cfgBase64 := base64.StdEncoding.EncodeToString(cfgJSON)
|
||||
h.Set(HeaderXProxyConfig, cfgBase64)
|
||||
}
|
||||
@@ -1,27 +1,14 @@
|
||||
package agentproxy
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
const (
|
||||
HeaderXProxyScheme = "X-Proxy-Scheme"
|
||||
HeaderXProxyHost = "X-Proxy-Host"
|
||||
HeaderXProxyConfig = "X-Proxy-Config"
|
||||
)
|
||||
|
||||
// deprecated
|
||||
const (
|
||||
HeaderXProxyHost = "X-Proxy-Host"
|
||||
HeaderXProxyHTTPS = "X-Proxy-Https"
|
||||
HeaderXProxySkipTLSVerify = "X-Proxy-Skip-Tls-Verify"
|
||||
HeaderXProxyResponseHeaderTimeout = "X-Proxy-Response-Header-Timeout"
|
||||
)
|
||||
|
||||
type AgentProxyHeaders struct {
|
||||
Host string
|
||||
IsHTTPS bool
|
||||
SkipTLSVerify bool
|
||||
ResponseHeaderTimeout int
|
||||
}
|
||||
|
||||
func SetAgentProxyHeaders(r *http.Request, headers *AgentProxyHeaders) {
|
||||
r.Header.Set(HeaderXProxyHost, headers.Host)
|
||||
r.Header.Set(HeaderXProxyHTTPS, strconv.FormatBool(headers.IsHTTPS))
|
||||
r.Header.Set(HeaderXProxySkipTLSVerify, strconv.FormatBool(headers.SkipTLSVerify))
|
||||
r.Header.Set(HeaderXProxyResponseHeaderTimeout, strconv.Itoa(headers.ResponseHeaderTimeout))
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/yusing/go-proxy/agent/pkg/agent"
|
||||
@@ -24,31 +23,24 @@ func NewTransport() *http.Transport {
|
||||
}
|
||||
|
||||
func ProxyHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
host := r.Header.Get(agentproxy.HeaderXProxyHost)
|
||||
isHTTPS, _ := strconv.ParseBool(r.Header.Get(agentproxy.HeaderXProxyHTTPS))
|
||||
skipTLSVerify, _ := strconv.ParseBool(r.Header.Get(agentproxy.HeaderXProxySkipTLSVerify))
|
||||
responseHeaderTimeout, err := strconv.Atoi(r.Header.Get(agentproxy.HeaderXProxyResponseHeaderTimeout))
|
||||
cfg, err := agentproxy.ConfigFromHeaders(r.Header)
|
||||
if err != nil {
|
||||
responseHeaderTimeout = 0
|
||||
}
|
||||
|
||||
if host == "" {
|
||||
http.Error(w, "missing required headers", http.StatusBadRequest)
|
||||
http.Error(w, fmt.Sprintf("failed to parse agent proxy config: %s", err.Error()), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
scheme := "http"
|
||||
if isHTTPS {
|
||||
scheme = "https"
|
||||
}
|
||||
|
||||
transport := NewTransport()
|
||||
if skipTLSVerify {
|
||||
transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
if cfg.ResponseHeaderTimeout > 0 {
|
||||
transport.ResponseHeaderTimeout = cfg.ResponseHeaderTimeout
|
||||
}
|
||||
if cfg.DisableCompression {
|
||||
transport.DisableCompression = true
|
||||
}
|
||||
|
||||
if responseHeaderTimeout > 0 {
|
||||
transport.ResponseHeaderTimeout = time.Duration(responseHeaderTimeout) * time.Second
|
||||
transport.TLSClientConfig, err = cfg.BuildTLSConfig(r.URL)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("failed to build TLS client config: %s", err.Error()), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
r.URL.Scheme = ""
|
||||
@@ -58,8 +50,8 @@ func ProxyHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
rp := &httputil.ReverseProxy{
|
||||
Director: func(r *http.Request) {
|
||||
r.URL.Scheme = scheme
|
||||
r.URL.Host = host
|
||||
r.URL.Scheme = cfg.Scheme
|
||||
r.URL.Host = cfg.Host
|
||||
},
|
||||
Transport: transport,
|
||||
}
|
||||
|
||||
@@ -39,5 +39,5 @@ func StartAgentServer(parent task.Parent, opt Options) {
|
||||
TLSConfig: tlsConfig,
|
||||
}
|
||||
|
||||
server.Start(parent, agentServer, nil, &log.Logger)
|
||||
server.Start(parent, agentServer, server.WithLogger(&log.Logger))
|
||||
}
|
||||
|
||||
@@ -17,6 +17,10 @@
|
||||
|
||||
# 3. other providers, see https://docs.godoxy.dev/DNS-01-Providers
|
||||
|
||||
# Access Control
|
||||
# When enabled, it will be applied globally at connection level,
|
||||
# all incoming connections (web, tcp and udp) will be checked against the ACL rules.
|
||||
|
||||
# acl:
|
||||
# default: allow # or deny (default: allow)
|
||||
# allow_local: true # or false (default: true)
|
||||
@@ -37,6 +41,11 @@
|
||||
# keep: last 10 # (default: none)
|
||||
|
||||
entrypoint:
|
||||
# Proxy Protocol: https://www.haproxy.com/blog/use-the-proxy-protocol-to-preserve-a-clients-ip-address
|
||||
# When set to true, web entrypoint and all tcp routeswill be wrapped with Proxy Protocol listener in order to preserve the client's IP address.
|
||||
# Note that HTTP/3 with proxy protocol is not supported yet.
|
||||
support_proxy_protocol: false
|
||||
|
||||
# Below define an example of middleware config
|
||||
# 1. set security headers
|
||||
# 2. block non local IP connections
|
||||
@@ -57,14 +66,6 @@ entrypoint:
|
||||
X-Frame-Options: SAMEORIGIN
|
||||
Referrer-Policy: same-origin
|
||||
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
|
||||
# - use: CIDRWhitelist
|
||||
# allow:
|
||||
# - "127.0.0.1"
|
||||
# - "10.0.0.0/8"
|
||||
# - "172.16.0.0/12"
|
||||
# - "192.168.0.0/16"
|
||||
# status: 403
|
||||
# message: "Forbidden"
|
||||
# - use: RedirectHTTP
|
||||
|
||||
# below enables access log
|
||||
@@ -115,8 +116,8 @@ providers:
|
||||
# secret: aaaa-bbbb-cccc-dddd
|
||||
# no_tls_verify: true
|
||||
|
||||
# Check https://docs.godoxy.dev/Certificates-and-domain-matching
|
||||
# for explaination of `match_domains`
|
||||
# Match domains
|
||||
# See https://docs.godoxy.dev/Certificates-and-domain-matching
|
||||
#
|
||||
# match_domains:
|
||||
# - my.site
|
||||
|
||||
@@ -1,23 +1,17 @@
|
||||
# Stage 1: deps
|
||||
FROM golang:1.25.0-alpine AS deps
|
||||
FROM alpine:3.22 AS deps
|
||||
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 tzdata
|
||||
|
||||
# Stage 3: Final image
|
||||
FROM alpine:3.22
|
||||
# Stage 2: Final image
|
||||
FROM deps
|
||||
|
||||
LABEL maintainer="yusing@6uo.me"
|
||||
LABEL proxy.exclude=1
|
||||
|
||||
# copy timezone data
|
||||
COPY --from=deps /usr/share/zoneinfo /usr/share/zoneinfo
|
||||
|
||||
# copy certs
|
||||
COPY --from=deps /etc/ssl/certs /etc/ssl/certs
|
||||
|
||||
ARG TARGET
|
||||
ENV TARGET=${TARGET}
|
||||
|
||||
|
||||
1
go.mod
1
go.mod
@@ -213,6 +213,7 @@ require (
|
||||
|
||||
require (
|
||||
github.com/gin-gonic/gin v1.10.1
|
||||
github.com/pires/go-proxyproto v0.8.1
|
||||
github.com/yusing/ds v0.1.0
|
||||
)
|
||||
|
||||
|
||||
2
go.sum
2
go.sum
@@ -1424,6 +1424,8 @@ github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi
|
||||
github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
|
||||
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
|
||||
github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
|
||||
@@ -46,6 +46,7 @@ func SystemInfo(c *gin.Context) {
|
||||
systeminfo.Poller.ServeHTTP(c)
|
||||
return
|
||||
}
|
||||
c.Request.URL.RawQuery = query.Encode()
|
||||
|
||||
agent, ok := agentPkg.GetAgent(agentAddr)
|
||||
if !ok {
|
||||
@@ -69,10 +70,6 @@ func SystemInfo(c *gin.Context) {
|
||||
c.Status(resp.StatusCode)
|
||||
io.Copy(c.Writer, resp.Body)
|
||||
} else {
|
||||
err := agent.ReverseProxy(c.Writer, c.Request, agentPkg.EndpointSystemInfo+"?"+query.Encode())
|
||||
if err != nil {
|
||||
c.Error(apitypes.InternalServerError(err, "failed to reverse proxy"))
|
||||
return
|
||||
}
|
||||
agent.ReverseProxy(c.Writer, c.Request, agentPkg.EndpointSystemInfo)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/go-acme/lego/v4/certcrypto"
|
||||
"github.com/go-acme/lego/v4/challenge"
|
||||
"github.com/go-acme/lego/v4/challenge/dns01"
|
||||
"github.com/go-acme/lego/v4/lego"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/yusing/go-proxy/internal/common"
|
||||
@@ -27,6 +28,8 @@ type Config struct {
|
||||
Provider string `json:"provider,omitempty"`
|
||||
Options map[string]any `json:"options,omitempty"`
|
||||
|
||||
Resolvers []string `json:"resolvers,omitempty"`
|
||||
|
||||
// Custom ACME CA
|
||||
CADirURL string `json:"ca_dir_url,omitempty"`
|
||||
CACerts []string `json:"ca_certs,omitempty"`
|
||||
@@ -111,6 +114,12 @@ func (cfg *Config) Validate() gperr.Error {
|
||||
return b.Error()
|
||||
}
|
||||
|
||||
func (cfg *Config) dns01Options() []dns01.ChallengeOption {
|
||||
return []dns01.ChallengeOption{
|
||||
dns01.CondOption(len(cfg.Resolvers) > 0, dns01.AddRecursiveNameservers(cfg.Resolvers)),
|
||||
}
|
||||
}
|
||||
|
||||
func (cfg *Config) GetLegoConfig() (*User, *lego.Config, gperr.Error) {
|
||||
if err := cfg.Validate(); err != nil {
|
||||
return nil, nil, err
|
||||
|
||||
@@ -286,7 +286,7 @@ func (p *Provider) initClient() error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = legoClient.Challenge.SetDNS01Provider(p.cfg.challengeProvider)
|
||||
err = legoClient.Challenge.SetDNS01Provider(p.cfg.challengeProvider, p.cfg.dns01Options()...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -204,12 +204,13 @@ func (cfg *Config) StartServers(opts ...*StartServersOptions) {
|
||||
opt := opts[0]
|
||||
if opt.Proxy {
|
||||
server.StartServer(cfg.task, server.Options{
|
||||
Name: "proxy",
|
||||
CertProvider: cfg.AutoCertProvider(),
|
||||
HTTPAddr: common.ProxyHTTPAddr,
|
||||
HTTPSAddr: common.ProxyHTTPSAddr,
|
||||
Handler: cfg.entrypoint,
|
||||
ACL: cfg.value.ACL,
|
||||
Name: "proxy",
|
||||
CertProvider: cfg.AutoCertProvider(),
|
||||
HTTPAddr: common.ProxyHTTPAddr,
|
||||
HTTPSAddr: common.ProxyHTTPSAddr,
|
||||
Handler: cfg.entrypoint,
|
||||
ACL: cfg.value.ACL,
|
||||
SupportProxyProtocol: cfg.value.Entrypoint.SupportProxyProtocol,
|
||||
})
|
||||
}
|
||||
if opt.API {
|
||||
|
||||
@@ -37,8 +37,9 @@ type (
|
||||
MaxMind *maxmind.Config `json:"maxmind" yaml:"maxmind,omitempty"`
|
||||
}
|
||||
Entrypoint struct {
|
||||
Middlewares []map[string]any `json:"middlewares"`
|
||||
AccessLog *accesslog.RequestLoggerConfig `json:"access_log" validate:"omitempty"`
|
||||
SupportProxyProtocol bool `json:"support_proxy_protocol"`
|
||||
Middlewares []map[string]any `json:"middlewares"`
|
||||
AccessLog *accesslog.RequestLoggerConfig `json:"access_log" validate:"omitempty"`
|
||||
}
|
||||
HomepageConfig struct {
|
||||
UseDefaultCategories bool `json:"use_default_categories"`
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/pires/go-proxyproto"
|
||||
h2proxy "github.com/pires/go-proxyproto/helper/http2"
|
||||
"github.com/quic-go/quic-go/http3"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
@@ -28,6 +30,7 @@ type Server struct {
|
||||
https *http.Server
|
||||
startTime time.Time
|
||||
acl *acl.Config
|
||||
proxyProto bool
|
||||
|
||||
l zerolog.Logger
|
||||
}
|
||||
@@ -39,6 +42,8 @@ type Options struct {
|
||||
CertProvider CertProvider
|
||||
Handler http.Handler
|
||||
ACL *acl.Config
|
||||
|
||||
SupportProxyProtocol bool
|
||||
}
|
||||
|
||||
type httpServer interface {
|
||||
@@ -86,6 +91,7 @@ func NewServer(opt Options) (s *Server) {
|
||||
https: httpsSer,
|
||||
l: logger,
|
||||
acl: opt.ACL,
|
||||
proxyProto: opt.SupportProxyProtocol,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,30 +105,86 @@ func (s *Server) Start(parent task.Parent) {
|
||||
subtask := parent.Subtask("server."+s.Name, false)
|
||||
|
||||
if s.https != nil && common.HTTP3Enabled {
|
||||
s.https.TLSConfig.NextProtos = []string{http3.NextProtoH3, "h2", "http/1.1"}
|
||||
h3 := &http3.Server{
|
||||
Addr: s.https.Addr,
|
||||
Handler: s.https.Handler,
|
||||
TLSConfig: http3.ConfigureTLSConfig(s.https.TLSConfig),
|
||||
if s.proxyProto {
|
||||
// TODO: support proxy protocol for HTTP/3
|
||||
s.l.Warn().Msg("HTTP/3 is enabled, but proxy protocol is yet not supported for HTTP/3")
|
||||
} else {
|
||||
s.https.TLSConfig.NextProtos = []string{http3.NextProtoH3, "h2", "http/1.1"}
|
||||
h3 := &http3.Server{
|
||||
Addr: s.https.Addr,
|
||||
Handler: s.https.Handler,
|
||||
TLSConfig: http3.ConfigureTLSConfig(s.https.TLSConfig),
|
||||
}
|
||||
Start(subtask, h3, WithProxyProtocolSupport(s.proxyProto), WithACL(s.acl), WithLogger(&s.l))
|
||||
if s.http != nil {
|
||||
s.http.Handler = advertiseHTTP3(s.http.Handler, h3)
|
||||
}
|
||||
// s.https is not nil (checked above)
|
||||
s.https.Handler = advertiseHTTP3(s.https.Handler, h3)
|
||||
}
|
||||
Start(subtask, h3, s.acl, &s.l)
|
||||
if s.http != nil {
|
||||
s.http.Handler = advertiseHTTP3(s.http.Handler, h3)
|
||||
}
|
||||
// s.https is not nil (checked above)
|
||||
s.https.Handler = advertiseHTTP3(s.https.Handler, h3)
|
||||
}
|
||||
|
||||
Start(subtask, s.http, s.acl, &s.l)
|
||||
Start(subtask, s.https, s.acl, &s.l)
|
||||
Start(subtask, s.http, WithProxyProtocolSupport(s.proxyProto), WithACL(s.acl), WithLogger(&s.l))
|
||||
Start(subtask, s.https, WithProxyProtocolSupport(s.proxyProto), WithACL(s.acl), WithLogger(&s.l))
|
||||
}
|
||||
|
||||
func Start[Server httpServer](parent task.Parent, srv Server, acl *acl.Config, logger *zerolog.Logger) (port int) {
|
||||
type ServerStartOptions struct {
|
||||
tcpWrappers []func(l net.Listener) net.Listener
|
||||
udpWrappers []func(l net.PacketConn) net.PacketConn
|
||||
logger *zerolog.Logger
|
||||
proxyProto bool
|
||||
}
|
||||
|
||||
type ServerStartOption func(opts *ServerStartOptions)
|
||||
|
||||
func WithTCPWrappers(wrappers ...func(l net.Listener) net.Listener) ServerStartOption {
|
||||
return func(opts *ServerStartOptions) {
|
||||
opts.tcpWrappers = wrappers
|
||||
}
|
||||
}
|
||||
|
||||
func WithUDPWrappers(wrappers ...func(l net.PacketConn) net.PacketConn) ServerStartOption {
|
||||
return func(opts *ServerStartOptions) {
|
||||
opts.udpWrappers = wrappers
|
||||
}
|
||||
}
|
||||
|
||||
func WithLogger(logger *zerolog.Logger) ServerStartOption {
|
||||
return func(opts *ServerStartOptions) {
|
||||
opts.logger = logger
|
||||
}
|
||||
}
|
||||
|
||||
func WithACL(acl *acl.Config) ServerStartOption {
|
||||
return func(opts *ServerStartOptions) {
|
||||
if acl == nil {
|
||||
return
|
||||
}
|
||||
opts.tcpWrappers = append(opts.tcpWrappers, acl.WrapTCP)
|
||||
opts.udpWrappers = append(opts.udpWrappers, acl.WrapUDP)
|
||||
}
|
||||
}
|
||||
|
||||
func WithProxyProtocolSupport(value bool) ServerStartOption {
|
||||
return func(opts *ServerStartOptions) {
|
||||
opts.proxyProto = value
|
||||
}
|
||||
}
|
||||
|
||||
func Start[Server httpServer](parent task.Parent, srv Server, optFns ...ServerStartOption) (port int) {
|
||||
if srv == nil {
|
||||
return
|
||||
}
|
||||
|
||||
setDebugLogger(srv, logger)
|
||||
var opts ServerStartOptions
|
||||
for _, optFn := range optFns {
|
||||
optFn(&opts)
|
||||
}
|
||||
if opts.logger == nil {
|
||||
opts.logger = &log.Logger
|
||||
}
|
||||
|
||||
setDebugLogger(srv, opts.logger)
|
||||
|
||||
proto := proto(srv)
|
||||
task := parent.Subtask(proto, true)
|
||||
@@ -137,40 +199,47 @@ func Start[Server httpServer](parent task.Parent, srv Server, acl *acl.Config, l
|
||||
}
|
||||
l, err := lc.Listen(task.Context(), "tcp", srv.Addr)
|
||||
if err != nil {
|
||||
HandleError(logger, err, "failed to listen on port")
|
||||
HandleError(opts.logger, err, "failed to listen on port")
|
||||
return
|
||||
}
|
||||
port = l.Addr().(*net.TCPAddr).Port
|
||||
if opts.proxyProto {
|
||||
l = &proxyproto.Listener{Listener: l}
|
||||
}
|
||||
if srv.TLSConfig != nil {
|
||||
l = tls.NewListener(l, srv.TLSConfig)
|
||||
}
|
||||
if acl != nil {
|
||||
l = acl.WrapTCP(l)
|
||||
for _, wrapper := range opts.tcpWrappers {
|
||||
l = wrapper(l)
|
||||
}
|
||||
if opts.proxyProto {
|
||||
serveFunc = getServeFunc(l, h2proxy.NewServer(srv, nil).Serve)
|
||||
} else {
|
||||
serveFunc = getServeFunc(l, srv.Serve)
|
||||
}
|
||||
serveFunc = getServeFunc(l, srv.Serve)
|
||||
task.OnCancel("stop", func() {
|
||||
stop(srv, l, logger)
|
||||
stop(srv, l, opts.logger)
|
||||
})
|
||||
case *http3.Server:
|
||||
l, err := lc.ListenPacket(task.Context(), "udp", srv.Addr)
|
||||
if err != nil {
|
||||
HandleError(logger, err, "failed to listen on port")
|
||||
HandleError(opts.logger, err, "failed to listen on port")
|
||||
return
|
||||
}
|
||||
port = l.LocalAddr().(*net.UDPAddr).Port
|
||||
if acl != nil {
|
||||
l = acl.WrapUDP(l)
|
||||
for _, wrapper := range opts.udpWrappers {
|
||||
l = wrapper(l)
|
||||
}
|
||||
serveFunc = getServeFunc(l, srv.Serve)
|
||||
task.OnCancel("stop", func() {
|
||||
stop(srv, l, logger)
|
||||
stop(srv, l, opts.logger)
|
||||
})
|
||||
}
|
||||
logStarted(srv, logger)
|
||||
logStarted(srv, opts.logger)
|
||||
go func() {
|
||||
err := convertError(serveFunc())
|
||||
if err != nil {
|
||||
HandleError(logger, err, "failed to serve "+proto+" server")
|
||||
HandleError(opts.logger, err, "failed to serve "+proto+" server")
|
||||
}
|
||||
task.Finish(err)
|
||||
}()
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
@@ -20,6 +19,7 @@ import (
|
||||
"github.com/yusing/go-proxy/internal/task"
|
||||
"github.com/yusing/go-proxy/internal/types"
|
||||
"github.com/yusing/go-proxy/internal/watcher/health/monitor"
|
||||
"github.com/yusing/go-proxy/pkg"
|
||||
)
|
||||
|
||||
type ReveseProxyRoute struct {
|
||||
@@ -44,10 +44,12 @@ func NewReverseProxyRoute(base *Route) (*ReveseProxyRoute, gperr.Error) {
|
||||
trans = a.Transport()
|
||||
proxyURL = nettypes.NewURL(agent.HTTPProxyURL)
|
||||
} else {
|
||||
trans = gphttp.NewTransport()
|
||||
if httpConfig.NoTLSVerify {
|
||||
trans.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} //nolint:gosec
|
||||
tlsConfig, err := httpConfig.BuildTLSConfig(&base.ProxyURL.URL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
trans = gphttp.NewTransportWithTLSConfig(tlsConfig)
|
||||
if httpConfig.ResponseHeaderTimeout > 0 {
|
||||
trans.ResponseHeaderTimeout = httpConfig.ResponseHeaderTimeout
|
||||
}
|
||||
@@ -67,15 +69,19 @@ func NewReverseProxyRoute(base *Route) (*ReveseProxyRoute, gperr.Error) {
|
||||
}
|
||||
|
||||
if a != nil {
|
||||
headers := &agentproxy.AgentProxyHeaders{
|
||||
Host: base.ProxyURL.Host,
|
||||
IsHTTPS: base.ProxyURL.Scheme == "https",
|
||||
SkipTLSVerify: httpConfig.NoTLSVerify,
|
||||
ResponseHeaderTimeout: int(httpConfig.ResponseHeaderTimeout.Seconds()),
|
||||
cfg := agentproxy.Config{
|
||||
Scheme: base.ProxyURL.Scheme,
|
||||
Host: base.ProxyURL.Host,
|
||||
HTTPConfig: httpConfig,
|
||||
}
|
||||
setHeaderFunc := cfg.SetAgentProxyConfigHeaders
|
||||
if !a.Version.IsOlderThan(pkg.Ver(0, 18, 6)) {
|
||||
setHeaderFunc = cfg.SetAgentProxyConfigHeadersLegacy
|
||||
}
|
||||
|
||||
ori := rp.HandlerFunc
|
||||
rp.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
|
||||
agentproxy.SetAgentProxyHeaders(r, headers)
|
||||
setHeaderFunc(r.Header)
|
||||
ori(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,14 +4,16 @@ import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"github.com/pires/go-proxyproto"
|
||||
"github.com/rs/zerolog"
|
||||
config "github.com/yusing/go-proxy/internal/config/types"
|
||||
nettypes "github.com/yusing/go-proxy/internal/net/types"
|
||||
"github.com/yusing/go-proxy/internal/utils"
|
||||
"go.uber.org/atomic"
|
||||
)
|
||||
|
||||
type TCPTCPStream struct {
|
||||
listener *net.TCPListener
|
||||
listener net.Listener
|
||||
laddr *net.TCPAddr
|
||||
dst *net.TCPAddr
|
||||
|
||||
@@ -34,12 +36,20 @@ func NewTCPTCPStream(listenAddr, dstAddr string) (nettypes.Stream, error) {
|
||||
}
|
||||
|
||||
func (s *TCPTCPStream) ListenAndServe(ctx context.Context, preDial, onRead nettypes.HookFunc) {
|
||||
listener, err := net.ListenTCP("tcp", s.laddr)
|
||||
var err error
|
||||
s.listener, err = net.ListenTCP("tcp", s.laddr)
|
||||
if err != nil {
|
||||
logErr(s, err, "failed to listen")
|
||||
return
|
||||
}
|
||||
s.listener = listener
|
||||
|
||||
if proxyProto := config.GetInstance().Value().Entrypoint.SupportProxyProtocol; proxyProto {
|
||||
s.listener = &proxyproto.Listener{Listener: s.listener}
|
||||
}
|
||||
if acl := config.GetInstance().Value().ACL; acl != nil {
|
||||
s.listener = acl.WrapTCP(s.listener)
|
||||
}
|
||||
|
||||
s.preDial = preDial
|
||||
s.onRead = onRead
|
||||
go s.listen(ctx)
|
||||
@@ -60,7 +70,14 @@ func (s *TCPTCPStream) LocalAddr() net.Addr {
|
||||
}
|
||||
|
||||
func (s *TCPTCPStream) MarshalZerologObject(e *zerolog.Event) {
|
||||
e.Str("protocol", "tcp-tcp").Str("listen", s.listener.Addr().String()).Str("dst", s.dst.String())
|
||||
e.Str("protocol", "tcp-tcp")
|
||||
|
||||
if s.listener != nil {
|
||||
e.Str("listen", s.listener.Addr().String())
|
||||
}
|
||||
if s.dst != nil {
|
||||
e.Str("dst", s.dst.String())
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TCPTCPStream) listen(ctx context.Context) {
|
||||
|
||||
@@ -3,12 +3,14 @@ package stream
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"maps"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
config "github.com/yusing/go-proxy/internal/config/types"
|
||||
nettypes "github.com/yusing/go-proxy/internal/net/types"
|
||||
"github.com/yusing/go-proxy/internal/utils/synk"
|
||||
"go.uber.org/atomic"
|
||||
@@ -16,7 +18,7 @@ import (
|
||||
|
||||
type UDPUDPStream struct {
|
||||
name string
|
||||
listener *net.UDPConn
|
||||
listener net.PacketConn
|
||||
|
||||
laddr *net.UDPAddr
|
||||
dst *net.UDPAddr
|
||||
@@ -34,7 +36,7 @@ type UDPUDPStream struct {
|
||||
type udpUDPConn struct {
|
||||
srcAddr *net.UDPAddr
|
||||
dstConn *net.UDPConn
|
||||
listener *net.UDPConn
|
||||
listener net.PacketConn
|
||||
lastUsed atomic.Time
|
||||
closed atomic.Bool
|
||||
mu sync.Mutex
|
||||
@@ -66,12 +68,15 @@ func NewUDPUDPStream(listenAddr, dstAddr string) (nettypes.Stream, error) {
|
||||
}
|
||||
|
||||
func (s *UDPUDPStream) ListenAndServe(ctx context.Context, preDial, onRead nettypes.HookFunc) {
|
||||
listener, err := net.ListenUDP("udp", s.laddr)
|
||||
var err error
|
||||
s.listener, err = net.ListenUDP("udp", s.laddr)
|
||||
if err != nil {
|
||||
logErr(s, err, "failed to listen")
|
||||
return
|
||||
}
|
||||
s.listener = listener
|
||||
if acl := config.GetInstance().Value().ACL; acl != nil {
|
||||
s.listener = acl.WrapUDP(s.listener)
|
||||
}
|
||||
s.preDial = preDial
|
||||
s.onRead = onRead
|
||||
go s.listen(ctx)
|
||||
@@ -108,7 +113,13 @@ func (s *UDPUDPStream) LocalAddr() net.Addr {
|
||||
}
|
||||
|
||||
func (s *UDPUDPStream) MarshalZerologObject(e *zerolog.Event) {
|
||||
e.Str("protocol", "udp-udp").Str("name", s.name).Str("dst", s.dst.String())
|
||||
e.Str("protocol", "udp-udp")
|
||||
if s.name != "" {
|
||||
e.Str("name", s.name)
|
||||
}
|
||||
if s.dst != nil {
|
||||
e.Str("dst", s.dst.String())
|
||||
}
|
||||
}
|
||||
|
||||
func (s *UDPUDPStream) listen(ctx context.Context) {
|
||||
@@ -120,7 +131,7 @@ func (s *UDPUDPStream) listen(ctx context.Context) {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
n, srcAddr, err := s.listener.ReadFromUDP(buf)
|
||||
n, srcAddr, err := s.listener.ReadFrom(buf)
|
||||
if err != nil {
|
||||
if s.closed.Load() {
|
||||
return
|
||||
@@ -129,6 +140,12 @@ func (s *UDPUDPStream) listen(ctx context.Context) {
|
||||
continue
|
||||
}
|
||||
|
||||
srcAddrUDP, ok := srcAddr.(*net.UDPAddr)
|
||||
if !ok {
|
||||
logErr(s, fmt.Errorf("unexpected source address type: %T", srcAddr), "unexpected source address type")
|
||||
continue
|
||||
}
|
||||
|
||||
logDebugf(s, "read %d bytes from %s", n, srcAddr)
|
||||
|
||||
if s.onRead != nil {
|
||||
@@ -139,7 +156,7 @@ func (s *UDPUDPStream) listen(ctx context.Context) {
|
||||
}
|
||||
|
||||
// Get or create connection, passing the initial data
|
||||
go s.getOrCreateConnection(ctx, srcAddr, bytes.Clone(buf[:n]))
|
||||
go s.getOrCreateConnection(ctx, srcAddrUDP, bytes.Clone(buf[:n]))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -233,7 +250,7 @@ func (conn *udpUDPConn) handleResponses(ctx context.Context) {
|
||||
_ = conn.dstConn.SetReadDeadline(time.Time{})
|
||||
|
||||
// Forward response back to client using the listener
|
||||
_, err = conn.listener.WriteToUDP(buf[:n], conn.srcAddr)
|
||||
_, err = conn.listener.WriteTo(buf[:n], conn.srcAddr)
|
||||
if err != nil {
|
||||
if !conn.closed.Load() {
|
||||
logErrf(conn, err, "failed to write %d bytes to client", n)
|
||||
|
||||
@@ -1,11 +1,118 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/yusing/go-proxy/internal/gperr"
|
||||
)
|
||||
|
||||
type HTTPConfig struct {
|
||||
NoTLSVerify bool `json:"no_tls_verify,omitempty"`
|
||||
ResponseHeaderTimeout time.Duration `json:"response_header_timeout,omitempty" swaggertype:"primitive,integer"`
|
||||
DisableCompression bool `json:"disable_compression,omitempty"`
|
||||
|
||||
// SSL/TLS proxy options (nginx-like)
|
||||
SSLServerName *string `json:"ssl_server_name,omitempty"` // SNI server name
|
||||
SSLTrustedCertificate string `json:"ssl_trusted_certificate,omitempty"` // Path to trusted CA certificates
|
||||
SSLCertificate string `json:"ssl_certificate,omitempty"` // Path to client certificate
|
||||
SSLCertificateKey string `json:"ssl_certificate_key,omitempty"` // Path to client certificate key
|
||||
SSLProtocols []string `json:"ssl_protocols,omitempty"` // Allowed TLS protocols
|
||||
}
|
||||
|
||||
// BuildTLSConfig creates a TLS configuration based on the HTTP config options.
|
||||
func (cfg *HTTPConfig) BuildTLSConfig(targetURL *url.URL) (*tls.Config, gperr.Error) {
|
||||
tlsConfig := &tls.Config{}
|
||||
|
||||
// Handle InsecureSkipVerify (legacy NoTLSVerify option)
|
||||
if cfg.NoTLSVerify {
|
||||
tlsConfig.InsecureSkipVerify = true
|
||||
}
|
||||
|
||||
// Handle ssl_server_name (SNI)
|
||||
if cfg.SSLServerName != nil {
|
||||
switch *cfg.SSLServerName {
|
||||
case "off":
|
||||
// Disable SNI by setting empty string
|
||||
tlsConfig.ServerName = ""
|
||||
case "on", "":
|
||||
// Use hostname from target URL for SNI
|
||||
tlsConfig.ServerName = targetURL.Hostname()
|
||||
default:
|
||||
tlsConfig.ServerName = *cfg.SSLServerName
|
||||
}
|
||||
} else {
|
||||
// Default behavior - use hostname for SNI
|
||||
tlsConfig.ServerName = targetURL.Hostname()
|
||||
}
|
||||
|
||||
// Handle ssl_trusted_certificate
|
||||
if cfg.SSLTrustedCertificate != "" {
|
||||
caCertData, err := os.ReadFile(cfg.SSLTrustedCertificate)
|
||||
if err != nil {
|
||||
return nil, gperr.New("failed to read trusted certificate file").
|
||||
Subject(cfg.SSLTrustedCertificate).
|
||||
With(err)
|
||||
}
|
||||
|
||||
caCertPool := x509.NewCertPool()
|
||||
if !caCertPool.AppendCertsFromPEM(caCertData) {
|
||||
return nil, gperr.New("failed to parse trusted certificates").
|
||||
Subject(cfg.SSLTrustedCertificate)
|
||||
}
|
||||
tlsConfig.RootCAs = caCertPool
|
||||
}
|
||||
|
||||
// Handle ssl_certificate and ssl_certificate_key (client certificates)
|
||||
if cfg.SSLCertificate != "" {
|
||||
if cfg.SSLCertificateKey == "" {
|
||||
return nil, gperr.New("ssl_certificate_key is required when ssl_certificate is specified")
|
||||
}
|
||||
|
||||
clientCert, err := tls.LoadX509KeyPair(cfg.SSLCertificate, cfg.SSLCertificateKey)
|
||||
if err != nil {
|
||||
return nil, gperr.New("failed to load client certificate").
|
||||
Subject(cfg.SSLCertificate).
|
||||
With(err)
|
||||
}
|
||||
tlsConfig.Certificates = []tls.Certificate{clientCert}
|
||||
}
|
||||
|
||||
// Handle ssl_protocols (TLS versions)
|
||||
if len(cfg.SSLProtocols) > 0 {
|
||||
var minVersion, maxVersion uint16
|
||||
|
||||
for _, protocol := range cfg.SSLProtocols {
|
||||
var version uint16
|
||||
switch strings.ToLower(protocol) {
|
||||
case "tlsv1.0":
|
||||
version = tls.VersionTLS10
|
||||
case "tlsv1.1":
|
||||
version = tls.VersionTLS11
|
||||
case "tlsv1.2":
|
||||
version = tls.VersionTLS12
|
||||
case "tlsv1.3":
|
||||
version = tls.VersionTLS13
|
||||
default:
|
||||
return nil, gperr.New("unsupported TLS protocol").
|
||||
Subject(protocol)
|
||||
}
|
||||
|
||||
if minVersion == 0 || version < minVersion {
|
||||
minVersion = version
|
||||
}
|
||||
if maxVersion == 0 || version > maxVersion {
|
||||
maxVersion = version
|
||||
}
|
||||
}
|
||||
|
||||
tlsConfig.MinVersion = minVersion
|
||||
tlsConfig.MaxVersion = maxVersion
|
||||
}
|
||||
|
||||
return tlsConfig, nil
|
||||
}
|
||||
|
||||
@@ -43,8 +43,8 @@ func init() {
|
||||
|
||||
type Version struct{ Generation, Major, Minor int }
|
||||
|
||||
func Ver(major, minor, patch int) Version {
|
||||
return Version{major, minor, patch}
|
||||
func Ver(gen, major, minor int) Version {
|
||||
return Version{gen, major, minor}
|
||||
}
|
||||
|
||||
func (v Version) String() string {
|
||||
@@ -55,13 +55,38 @@ func (v Version) MarshalText() ([]byte, error) {
|
||||
return []byte(v.String()), nil
|
||||
}
|
||||
|
||||
func (v Version) IsNewerMajorThan(other Version) bool {
|
||||
func (v Version) IsNewerThan(other Version) bool {
|
||||
if v.Generation != other.Generation {
|
||||
return v.Generation > other.Generation
|
||||
}
|
||||
if v.Major != other.Major {
|
||||
return v.Major > other.Major
|
||||
}
|
||||
return v.Minor > other.Minor
|
||||
}
|
||||
|
||||
func (v Version) IsNewerThanMajor(other Version) bool {
|
||||
if v.Generation != other.Generation {
|
||||
return v.Generation > other.Generation
|
||||
}
|
||||
return v.Major > other.Major
|
||||
}
|
||||
|
||||
func (v Version) IsOlderThan(other Version) bool {
|
||||
return !v.IsNewerThan(other)
|
||||
}
|
||||
|
||||
func (v Version) IsOlderThanMajor(other Version) bool {
|
||||
if v.Generation != other.Generation {
|
||||
return v.Generation < other.Generation
|
||||
}
|
||||
return v.Major < other.Major
|
||||
}
|
||||
|
||||
func (v Version) IsOlderMajorThan(other Version) bool {
|
||||
return !v.IsNewerThanMajor(other)
|
||||
}
|
||||
|
||||
func (v Version) IsEqual(other Version) bool {
|
||||
return v.Generation == other.Generation && v.Major == other.Major && v.Minor == other.Minor
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user