mirror of
https://github.com/WJQSERVER-STUDIO/ghproxy.git
synced 2026-02-03 00:01:10 +08:00
24w20a
This commit is contained in:
parent
19f753a515
commit
92b3e19954
8 changed files with 70 additions and 50 deletions
|
|
@ -1,5 +1,12 @@
|
|||
# 更新日志
|
||||
|
||||
24w20a
|
||||
---
|
||||
- PRE-RELEASE: 此版本是v1.6.2的预发布版本,请勿在生产环境中使用
|
||||
- CHANGE: 大幅修改日志记录,对各个部分的日志记录进行统一格式,并对部分重复日志进行合并
|
||||
- CHANGE: 大幅优化一键部署脚本,使其更加易用,并增加更多的功能(已于早些时候hotfix)
|
||||
- CHANGE: 优化部分代码结构,提升性能
|
||||
|
||||
v1.6.1
|
||||
---
|
||||
- CHANGE: 根据社区建议,将`sizeLimit`由过去的以`byte`为单位,改为以`MB`为单位,以便于直观理解
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
24w19d
|
||||
24w20a
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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: 这些版本不再受支持 |
|
||||
|
||||
### 用户须知
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
19
auth/auth.go
19
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, ""
|
||||
}
|
||||
|
|
|
|||
14
main.go
14
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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue