diff --git a/CHANGELOG.md b/CHANGELOG.md index 361d8bc..6b988a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # 更新日志 +24w20a +--- +- PRE-RELEASE: 此版本是v1.6.2的预发布版本,请勿在生产环境中使用 +- CHANGE: 大幅修改日志记录,对各个部分的日志记录进行统一格式,并对部分重复日志进行合并 +- CHANGE: 大幅优化一键部署脚本,使其更加易用,并增加更多的功能(已于早些时候hotfix) +- CHANGE: 优化部分代码结构,提升性能 + v1.6.1 --- - CHANGE: 根据社区建议,将`sizeLimit`由过去的以`byte`为单位,改为以`MB`为单位,以便于直观理解 diff --git a/DEV-VERSION b/DEV-VERSION index 9cde107..dc757e9 100644 --- a/DEV-VERSION +++ b/DEV-VERSION @@ -1 +1 @@ -24w19d \ No newline at end of file +24w20a \ No newline at end of file diff --git a/README.md b/README.md index 51b653b..c7e63cb 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ git clone https://ghproxy.1888866.xyz/github.com/WJQSERVER-STUDIO/ghproxy.git docker run -p 7210:80 -v ./ghproxy/log/run:/data/ghproxy/log -v ./ghproxy/log/caddy:/data/caddy/log -v ./ghproxy/config:/data/ghproxy/config --restart always wjqserver/ghproxy ``` -- Docker-Compose +- Docker-Compose (建议使用) 参看[docker-compose.yml](https://github.com/WJQSERVER-STUDIO/ghproxy/blob/main/docker/compose/docker-compose.yml) diff --git a/SECURITY.MD b/SECURITY.MD index 2bd9114..8fc021c 100644 --- a/SECURITY.MD +++ b/SECURITY.MD @@ -7,7 +7,7 @@ | 版本 | 是否支持 | | --- | --- | | v1.x.x | :white_check_mark: | -| **w**a/b/c... | :warning: 此为PRE-RELEASE版本,用于开发与测试,可能存在未知的问题 | +| 24w*a/b/c... | :warning: 此为PRE-RELEASE版本,用于开发与测试,可能存在未知的问题 | | v0.x.x | :x: 这些版本不再受支持 | ### 用户须知 diff --git a/api/api.go b/api/api.go index 3214d92..42f11ae 100644 --- a/api/api.go +++ b/api/api.go @@ -46,6 +46,7 @@ func InitHandleRouter(cfg *config.Config, router *gin.Engine) { func SizeLimitHandler(cfg *config.Config, c *gin.Context) { sizeLimit := cfg.Server.SizeLimit + logInfo("%s %s %s %s %s", c.ClientIP(), c.Request.Method, c.Request.URL.Path, c.Request.UserAgent(), c.Request.Proto) c.Writer.Header().Set("Content-Type", "application/json") json.NewEncoder(c.Writer).Encode(map[string]interface{}{ "MaxResponseBodySize": sizeLimit, @@ -53,6 +54,7 @@ func SizeLimitHandler(cfg *config.Config, c *gin.Context) { } func WhiteListStatusHandler(c *gin.Context, cfg *config.Config) { + logInfo("%s %s %s %s %s", c.ClientIP(), c.Request.Method, c.Request.URL.Path, c.Request.UserAgent(), c.Request.Proto) c.Writer.Header().Set("Content-Type", "application/json") json.NewEncoder(c.Writer).Encode(map[string]interface{}{ "Whitelist": cfg.Whitelist.Enabled, @@ -60,6 +62,7 @@ func WhiteListStatusHandler(c *gin.Context, cfg *config.Config) { } func BlackListStatusHandler(c *gin.Context, cfg *config.Config) { + logInfo("%s %s %s %s %s", c.ClientIP(), c.Request.Method, c.Request.URL.Path, c.Request.UserAgent(), c.Request.Proto) c.Writer.Header().Set("Content-Type", "application/json") json.NewEncoder(c.Writer).Encode(map[string]interface{}{ "Blacklist": cfg.Blacklist.Enabled, @@ -67,6 +70,7 @@ func BlackListStatusHandler(c *gin.Context, cfg *config.Config) { } func CorsStatusHandler(c *gin.Context, cfg *config.Config) { + logInfo("%s %s %s %s %s", c.ClientIP(), c.Request.Method, c.Request.URL.Path, c.Request.UserAgent(), c.Request.Proto) c.Writer.Header().Set("Content-Type", "application/json") json.NewEncoder(c.Writer).Encode(map[string]interface{}{ "Cors": cfg.CORS.Enabled, @@ -74,6 +78,7 @@ func CorsStatusHandler(c *gin.Context, cfg *config.Config) { } func HealthcheckHandler(c *gin.Context) { + logInfo("%s %s %s %s %s", c.ClientIP(), c.Request.Method, c.Request.URL.Path, c.Request.UserAgent(), c.Request.Proto) c.Writer.Header().Set("Content-Type", "application/json") json.NewEncoder(c.Writer).Encode(map[string]interface{}{ "Status": "OK", diff --git a/auth/auth.go b/auth/auth.go index 4f5a80d..35cd8d0 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -1,6 +1,7 @@ package auth import ( + "fmt" "ghproxy/config" "ghproxy/logger" @@ -26,27 +27,29 @@ func Init(cfg *config.Config) { logInfo("Auth Init") } -func AuthHandler(c *gin.Context, cfg *config.Config) bool { +func AuthHandler(c *gin.Context, cfg *config.Config) (isValid bool, err string) { // 如果身份验证未启用,直接返回 true if !cfg.Auth.Enabled { - return true + return true, "" } // 获取 auth_token 参数 authToken := c.Query("auth_token") - logInfo("auth_token received: %s", authToken) + // IP METHOD URL USERAGENT PROTO TOKEN + logInfo("%s %s %s %s %s AUTH_TOKEN: %s", c.ClientIP(), c.Request.Method, c.Request.URL.Path, c.Request.UserAgent(), c.Request.Proto, authToken) // 验证 token if authToken == "" { - logWarning("auth FAILED: no auth_token provided") - return false + err := "Auth token == nil" + return false, err } - isValid := authToken == cfg.Auth.AuthToken + isValid = authToken == cfg.Auth.AuthToken if !isValid { - logWarning("auth FAILED: invalid auth_token: %s", authToken) + err := fmt.Sprintf("Auth token incorrect: %s", authToken) + return false, err } logInfo("auth SUCCESS: %t", isValid) - return isValid + return isValid, "" } diff --git a/main.go b/main.go index 3363c6b..7dca0a0 100644 --- a/main.go +++ b/main.go @@ -22,7 +22,6 @@ var ( cfgfile string ) -// 日志模块 var ( logw = logger.Logw logInfo = logger.LogInfo @@ -36,7 +35,6 @@ func readFlag() { func loadConfig() { var err error - // 初始化配置 cfg, err = config.LoadConfig(cfgfile) if err != nil { log.Fatalf("Failed to load config: %v", err) @@ -46,9 +44,8 @@ func loadConfig() { } func setupLogger(cfg *config.Config) { - // 初始化日志模块 var err error - err = logger.Init(cfg.Log.LogFilePath, cfg.Log.MaxLogSize) // 传递日志文件路径 + err = logger.Init(cfg.Log.LogFilePath, cfg.Log.MaxLogSize) if err != nil { log.Fatalf("Failed to initialize logger: %v", err) } @@ -89,8 +86,8 @@ func init() { router.StaticFile("/favicon.ico", faviconPath) } else if !cfg.Pages.Enabled { router.GET("/", func(c *gin.Context) { - c.String(http.StatusForbidden, "403 Forbidden This route is not allowed to access.") - logWarning("Forbidden: IP:%s UA:%s METHOD:%s HTTPv:%s", c.ClientIP(), c.Request.UserAgent(), c.Request.Method, c.Request.Proto) + c.String(http.StatusForbidden, "403 Forbidden Access") + logWarning("403 > Path:/ IP:%s UA:%s METHOD:%s HTTPv:%s", c.ClientIP(), c.Request.UserAgent(), c.Request.Method, c.Request.Proto) }) } @@ -100,11 +97,10 @@ func init() { } func main() { - // 启动服务器 err := router.Run(fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port)) if err != nil { - logError("Error starting server: %v\n", err) + logError("Failed to start server: %v\n", err) } - fmt.Println("Program finished") + fmt.Println("Program Exit") } diff --git a/proxy/proxy.go b/proxy/proxy.go index a511dca..91e40cb 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -1,4 +1,3 @@ -// proxy/proxy.go 实验性 package proxy import ( @@ -40,8 +39,9 @@ func NoRouteHandler(cfg *config.Config) gin.HandlerFunc { matches := re.FindStringSubmatch(rawPath) if len(matches) < 3 { - logWarning("Invalid URL: %s", rawPath) - c.String(http.StatusForbidden, "Invalid URL.") + errMsg := fmt.Sprintf("%s %s %s %s %s Invalid URL", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto) + logWarning(errMsg) + c.String(http.StatusForbidden, "Invalid URL Format. Path: %s", rawPath) return } @@ -49,34 +49,37 @@ func NoRouteHandler(cfg *config.Config) gin.HandlerFunc { username, repo := MatchUserRepo(rawPath, cfg, c, matches) - logWarning("Blacklist Check > Username: %s, Repo: %s", username, repo) + logInfo("%s %s %s %s %s Matched-Username: %s, Matched-Repo: %s", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, username, repo) fullrepo := fmt.Sprintf("%s/%s", username, repo) // 白名单检查 if cfg.Whitelist.Enabled { - whitelistpass := auth.CheckWhitelist(fullrepo) - if !whitelistpass { + whitelist := auth.CheckWhitelist(fullrepo) + if !whitelist { + logErrMsg := fmt.Sprintf("%s %s %s %s %s Whitelist Blocked repo: %s", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, fullrepo) errMsg := fmt.Sprintf("Whitelist Blocked repo: %s", fullrepo) c.JSON(http.StatusForbidden, gin.H{"error": errMsg}) - logWarning(errMsg) + logWarning(logErrMsg) return } } // 黑名单检查 if cfg.Blacklist.Enabled { - blacklistpass := auth.CheckBlacklist(fullrepo) - if blacklistpass { + blacklist := auth.CheckBlacklist(fullrepo) + if blacklist { + logErrMsg := fmt.Sprintf("%s %s %s %s %s Whitelist Blocked repo: %s", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, fullrepo) errMsg := fmt.Sprintf("Blacklist Blocked repo: %s", fullrepo) c.JSON(http.StatusForbidden, gin.H{"error": errMsg}) - logWarning(errMsg) + logWarning(logErrMsg) return } } - matches = CheckURL(rawPath) + matches = CheckURL(rawPath, c) if matches == nil { c.AbortWithStatus(http.StatusNotFound) + logError("%s %s %s %s %s 404-NOMATCH", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto) return } @@ -84,13 +87,16 @@ func NoRouteHandler(cfg *config.Config) gin.HandlerFunc { rawPath = strings.Replace(rawPath, "/blob/", "/raw/", 1) } - if !auth.AuthHandler(c, cfg) { + // 鉴权 + authcheck, err := auth.AuthHandler(c, cfg) + if !authcheck { c.AbortWithStatusJSON(401, gin.H{"error": "Unauthorized"}) - logWarning("Unauthorized request: %s", rawPath) + logWarning("%s %s %s %s %s Auth-Error: %v", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, err) return } - logInfo("Matches: %v", matches) + // IP METHOD URL USERAGENT PROTO MATCHES + logInfo("%s %s %s %s %s Matches: %v", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, matches) switch { case exps[0].MatchString(rawPath), exps[1].MatchString(rawPath), exps[3].MatchString(rawPath), exps[4].MatchString(rawPath): @@ -99,6 +105,7 @@ func NoRouteHandler(cfg *config.Config) gin.HandlerFunc { ProxyRequest(c, rawPath, cfg, "git") default: c.String(http.StatusForbidden, "Invalid input.") + fmt.Println("Invalid input.") return } } @@ -110,24 +117,24 @@ func MatchUserRepo(rawPath string, cfg *config.Config, c *gin.Context, matches [ var gistmatches []string if gistregex.MatchString(rawPath) { gistmatches = gistregex.FindStringSubmatch(rawPath) - logInfo("Gist Matched > Username: %s, URL: %s", gistmatches[1], rawPath) + logInfo("%s %s %s %s %s Matched-Username: %s", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, gistmatches[1]) return gistmatches[1], "" } - // 定义路径匹配的正则表达式 + // 定义路径 pathRegex := regexp.MustCompile(`^([^/]+)/([^/]+)/([^/]+)/.*`) if pathMatches := pathRegex.FindStringSubmatch(matches[2]); len(pathMatches) >= 4 { return pathMatches[2], pathMatches[3] } // 返回错误信息 - logWarning("Invalid path: %s", rawPath) - c.String(http.StatusForbidden, "Invalid path; expected username/repo.") + errMsg := fmt.Sprintf("%s %s %s %s %s Invalid URL", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto) + logWarning(errMsg) + c.String(http.StatusForbidden, "Invalid path; expected username/repo, Path: %s", rawPath) return "", "" } func ProxyRequest(c *gin.Context, u string, cfg *config.Config, mode string) { method := c.Request.Method - // 记录日志 IP 地址、请求方法、请求 URL、请求头 User-Agent 、HTTP版本 logInfo("%s %s %s %s %s", c.ClientIP(), method, u, c.Request.Header.Get("User-Agent"), c.Request.Proto) client := createHTTPClient(mode) @@ -141,7 +148,7 @@ func ProxyRequest(c *gin.Context, u string, cfg *config.Config, mode string) { req := client.R().SetBody(body) setRequestHeaders(c, req) - resp, err := SendRequest(req, method, u) + resp, err := SendRequest(c, req, method, u) if err != nil { HandleError(c, fmt.Sprintf("Failed to send request: %v", err)) return @@ -149,17 +156,18 @@ func ProxyRequest(c *gin.Context, u string, cfg *config.Config, mode string) { defer resp.Body.Close() if err := HandleResponseSize(resp, cfg, c); err != nil { - logWarning("Error handling response size: %v", err) + logWarning("%s %s %s %s %s Response-Size-Error: %v", c.ClientIP(), method, u, c.Request.Header.Get("User-Agent"), c.Request.Proto, err) return } CopyResponseHeaders(resp, c, cfg) c.Status(resp.StatusCode) if err := copyResponseBody(c, resp.Body); err != nil { - logError("Failed to copy response body: %v", err) + logError("%s %s %s %s %s Response-Copy-Error: %v", c.ClientIP(), method, u, c.Request.Header.Get("User-Agent"), c.Request.Proto, err) } } +// 判断并选择TLS指纹 func createHTTPClient(mode string) *req.Client { client := req.C() switch mode { @@ -198,7 +206,7 @@ func copyResponseBody(c *gin.Context, respBody io.Reader) error { return err } -func SendRequest(req *req.Request, method, url string) (*req.Response, error) { +func SendRequest(c *gin.Context, req *req.Request, method, url string) (*req.Response, error) { switch method { case "GET": return req.Get(url) @@ -209,8 +217,10 @@ func SendRequest(req *req.Request, method, url string) (*req.Response, error) { case "DELETE": return req.Delete(url) default: - logInfo("Unsupported method: %s", method) - return nil, fmt.Errorf("unsupported method: %s", method) + // IP METHOD URL USERAGENT PROTO UNSUPPORTED-METHOD + errmsg := fmt.Sprintf("%s %s %s %s %s Unsupported method", c.ClientIP(), method, url, c.Request.Header.Get("User-Agent"), c.Request.Proto) + logWarning(errmsg) + return nil, fmt.Errorf(errmsg) } } @@ -222,8 +232,8 @@ func HandleResponseSize(resp *req.Response, cfg *config.Config, c *gin.Context) if err == nil && size > sizelimit { finalURL := resp.Request.URL.String() c.Redirect(http.StatusMovedPermanently, finalURL) - logWarning("Size limit exceeded: %s, Size: %d", finalURL, size) - return fmt.Errorf("size limit exceeded: %d", size) + logWarning("%s %s %s %s %s Final-URL: %s Size-Limit-Exceeded: %d", c.ClientIP(), c.Request.Method, c.Request.URL.String(), c.Request.Header.Get("User-Agent"), c.Request.Proto, finalURL, size) + return fmt.Errorf("Path: %s size limit exceeded: %d", finalURL, size) } } return nil @@ -282,14 +292,13 @@ func HandleError(c *gin.Context, message string) { logWarning(message) } -func CheckURL(u string) []string { +func CheckURL(u string, c *gin.Context) []string { for _, exp := range exps { if matches := exp.FindStringSubmatch(u); matches != nil { - logInfo("URL matched: %s, Matches: %v", u, matches[1:]) return matches[1:] } } - errMsg := fmt.Sprintf("Invalid URL: %s", u) + errMsg := fmt.Sprintf("%s %s %s %s %s Invalid URL", c.ClientIP(), c.Request.Method, u, c.Request.Header.Get("User-Agent"), c.Request.Proto) logWarning(errMsg) return nil }