diff --git a/CHANGELOG.md b/CHANGELOG.md index b7eaa07..f3cbcba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # 更新日志 +24w08b +--- +- CHANGE: 优化代码结构,提升性能 +- ADD & CHANGE: 新增仓库黑名单功能,改进Auth模块 +- ADD: 新增blacklist.yaml文件,用于配置仓库黑名单 +- CHANGE: 大幅度修改Config包,使其更加模块化 +- CHANGE: 与Config包同步修改config.yaml文件(不向前兼容) +- CHANGE: 修改config.yaml文件的格式,使其具备更好的可读性 +- WARNING: 由于config.yaml文件修改,此版本不再向前兼容,请注意备份文件并重新部署 + v1.2.0 --- - CHANGE: 优化代码结构,提升性能 diff --git a/DEV-VERSION b/DEV-VERSION index 6ffcd27..1a1e17d 100644 --- a/DEV-VERSION +++ b/DEV-VERSION @@ -1 +1 @@ -24w08a \ No newline at end of file +24w08b \ No newline at end of file diff --git a/README.md b/README.md index 9317fc9..547fad2 100644 --- a/README.md +++ b/README.md @@ -63,13 +63,31 @@ docker run -p 7210:80 -v ./ghproxy/log/run:/data/ghproxy/log -v ./ghproxy/log/ca 使用Docker部署时,慎重修改config.yaml,以免造成不必要的麻烦 ``` -port: 8080 # 监听端口 -host: "127.0.0.1" # 监听地址 -sizelimit: 131072000 # 125MB -logfilepath: "/data/ghproxy/log/ghproxy.log" # 日志文件路径 -CorsAllowOrigins: true # 是否允许跨域请求 -auth: true # 是否开启鉴权 -authtoken: "test" # 鉴权token +# 核心配置 +server: + port: 8080 # 监听端口(小白请勿修改) + host: "127.0.0.1" # 监听地址(小白请勿修改) + sizelimit: 131072000 # 125MB + +# 日志配置 +logger: + logfilepath: "/data/ghproxy/log/ghproxy.log" # 日志文件路径(小白请勿修改) + maxlogsize: 25 # MB + +# CORS 配置 +cors: + enabled: true # 是否开启CORS + +# 鉴权配置 +auth: + enabled: false # 是否开启鉴权 + authtoken: "test" # 鉴权Token + +# 黑名单配置 +blacklist: + enabled: true + blacklistfile: "/data/ghproxy/config/blacklist.yaml" + ``` ### Caddy反代配置 @@ -94,7 +112,9 @@ example.com { - [x] 允许更多参数通过config结构传入 - [x] 改进程序效率 - [x] 用户鉴权 +- [ ] 仓库黑名单 ### DEV - [x] Docker Pull 代理 +- [x] 仓库黑名单 diff --git a/auth/auth.go b/auth/auth.go index a8567bc..76995a0 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -11,7 +11,7 @@ var logw = logger.Logw func AuthHandler(c *gin.Context, cfg *config.Config) bool { // 如果身份验证未启用,直接返回 true - if !cfg.Auth { + if !cfg.Auth.Enabled { logw("auth PASSED") return true } @@ -26,10 +26,24 @@ func AuthHandler(c *gin.Context, cfg *config.Config) bool { return false } - isValid := authToken == cfg.AuthToken + isValid := authToken == cfg.Auth.AuthToken if !isValid { logw("auth FAILED: invalid auth_token: %s", authToken) } return isValid } + +func IsBlacklisted(username, repo string, blacklist map[string][]string, enabled bool) bool { + if !enabled { + return false + } + if repos, ok := blacklist[username]; ok { + for _, blacklistedRepo := range repos { + if blacklistedRepo == repo { + return true + } + } + } + return false +} diff --git a/config/blacklist.yaml b/config/blacklist.yaml new file mode 100644 index 0000000..db926d5 --- /dev/null +++ b/config/blacklist.yaml @@ -0,0 +1,9 @@ +blacklist: + username1: + - repo1 + - repo2 + username2: + - repo3 + - repo4 + username3: + - repo5 \ No newline at end of file diff --git a/config/config.go b/config/config.go index 3de2c1e..b87d819 100644 --- a/config/config.go +++ b/config/config.go @@ -7,26 +7,59 @@ import ( ) type Config struct { - Port int `yaml:"port"` - Host string `yaml:"host"` - SizeLimit int `yaml:"sizelimit"` - LogFilePath string `yaml:"logfilepath"` - MaxLogSize int `yaml:"maxlogsize"` - CORSOrigin bool `yaml:"CorsAllowOrigins"` - Auth bool `yaml:"auth"` - AuthToken string `yaml:"authtoken"` + Server struct { + Port int `yaml:"port"` + Host string `yaml:"host"` + SizeLimit int `yaml:"sizelimit"` + } `yaml:"server"` + + Log struct { + LogFilePath string `yaml:"logfilepath"` + MaxLogSize int `yaml:"maxlogsize"` + } `yaml:"logger"` + + CORS struct { + Enabled bool `yaml:"enabled"` + } `yaml:"cors"` + + Auth struct { + Enabled bool `yaml:"enabled"` + AuthToken string `yaml:"authtoken"` + } `yaml:"auth"` + + Blacklist struct { + Enabled bool `yaml:"enabled"` + BlacklistFile string `yaml:"blacklistfile"` + } `yaml:"blacklist"` +} + +type Blacklist struct { + Blacklist map[string][]string `yaml:"blacklist"` } // LoadConfig 从 YAML 配置文件加载配置 func LoadConfig(filePath string) (*Config, error) { var config Config - data, err := os.ReadFile(filePath) - if err != nil { - return nil, err - } - err = yaml.Unmarshal(data, &config) - if err != nil { + if err := loadYAML(filePath, &config); err != nil { return nil, err } return &config, nil } + +// LoadBlacklistConfig 从 YAML 配置文件加载黑名单配置 +func LoadBlacklistConfig(filePath string) (*Blacklist, error) { + var config Blacklist + if err := loadYAML(filePath, &config); err != nil { + return nil, err + } + return &config, nil +} + +// LoadyamlConfig 从 YAML 配置文件加载配置 +func loadYAML(filePath string, out interface{}) error { + data, err := os.ReadFile(filePath) + if err != nil { + return err + } + return yaml.Unmarshal(data, out) +} diff --git a/config/config.yaml b/config/config.yaml index bba4552..54f02b8 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -1,8 +1,24 @@ -port: 8080 -host: "127.0.0.1" -sizelimit: 131072000 # 125MB -logfilepath: "/data/ghproxy/log/ghproxy.log" -maxlogsize: 25 #MB -CorsAllowOrigins: true -auth: false -authtoken: "test" +# Server Configuration +server: + port: 8080 + host: "127.0.0.1" + sizelimit: 131072000 # 125MB + +# Logging Configuration +logger: + logfilepath: "/data/ghproxy/log/ghproxy.log" + maxlogsize: 25 # MB + +# CORS Configuration +cors: + enabled: true + +# Authentication Configuration +auth: + enabled: false + authtoken: "test" + +# Blacklist Configuration +blacklist: + enabled: true + blacklistfile: "/data/ghproxy/config/blacklist.yaml" diff --git a/docker/dockerfile/dev/Dockerfile b/docker/dockerfile/dev/Dockerfile index 87f9337..f01d08c 100644 --- a/docker/dockerfile/dev/Dockerfile +++ b/docker/dockerfile/dev/Dockerfile @@ -13,6 +13,7 @@ RUN wget -O /data/caddy/Caddyfile https://raw.githubusercontent.com/${USER}/${RE RUN VERSION=$(curl -s https://raw.githubusercontent.com/${USER}/${REPO}/main/DEV-VERSION) && \ wget -O /data/${APPLICATION}/${APPLICATION} https://github.com/${USER}/${REPO}/releases/download/$VERSION/${APPLICATION} RUN wget -O /data/${APPLICATION}/config.yaml https://raw.githubusercontent.com/${USER}/${REPO}/main/config/config.yaml +RUN wget -O /data/${APPLICATION}/blacklist.yaml https://raw.githubusercontent.com/${USER}/${REPO}/main/config/blacklist.yaml RUN wget -O /usr/local/bin/init.sh https://raw.githubusercontent.com/${USER}/${REPO}/main/init.sh RUN chmod +x /data/${APPLICATION}/${APPLICATION} RUN chmod +x /usr/local/bin/init.sh diff --git a/main.go b/main.go index 710b413..f84f30a 100644 --- a/main.go +++ b/main.go @@ -16,6 +16,7 @@ import ( var ( cfg *config.Config + blacklist *config.Blacklist logw = logger.Logw router *gin.Engine configfile = "/data/ghproxy/config/config.yaml" @@ -41,10 +42,19 @@ func loadConfig() { fmt.Printf("Loaded config: %v\n", cfg) } +func loadBlacklistConfig() { + // 初始化黑名单配置 + blacklist, err := config.LoadBlacklistConfig("/data/ghproxy/config/blacklist.yaml") + if err != nil { + log.Fatalf("Failed to load blacklist: %v", err) + } + logw("Loaded blacklist: %v", blacklist) +} + func setupLogger() { // 初始化日志模块 var err error - err = logger.Init(cfg.LogFilePath, cfg.MaxLogSize) // 传递日志文件路径 + err = logger.Init(cfg.Log.LogFilePath, cfg.Log.MaxLogSize) // 传递日志文件路径 if err != nil { log.Fatalf("Failed to initialize logger: %v", err) } @@ -55,6 +65,7 @@ func setupLogger() { func init() { loadConfig() setupLogger() + loadBlacklistConfig() // 设置 Gin 模式 gin.SetMode(gin.ReleaseMode) @@ -76,13 +87,13 @@ func init() { // 未匹配路由处理 router.NoRoute(func(c *gin.Context) { - proxy.NoRouteHandler(cfg)(c) + proxy.NoRouteHandler(cfg, blacklist)(c) }) } func main() { // 启动服务器 - err := router.Run(fmt.Sprintf("%s:%d", cfg.Host, cfg.Port)) + err := router.Run(fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port)) if err != nil { log.Fatalf("Error starting server: %v\n", err) } @@ -94,6 +105,6 @@ func api(c *gin.Context) { // 设置响应头 c.Writer.Header().Set("Content-Type", "application/json") json.NewEncoder(c.Writer).Encode(map[string]interface{}{ - "MaxResponseBodySize": cfg.SizeLimit, + "MaxResponseBodySize": cfg.Server.SizeLimit, }) } diff --git a/proxy/proxy.go b/proxy/proxy.go index 80ca730..882c51e 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -19,6 +19,7 @@ import ( var logw = logger.Logw var cfg *config.Config +var blacklist *config.Blacklist var exps = []*regexp.Regexp{ regexp.MustCompile(`^(?:https?://)?github\.com/([^/]+)/([^/]+)/(?:releases|archive)/.*`), @@ -28,7 +29,7 @@ var exps = []*regexp.Regexp{ regexp.MustCompile(`^(?:https?://)?gist\.github\.com/([^/]+)/.+?/.+`), } -func NoRouteHandler(cfg *config.Config) gin.HandlerFunc { +func NoRouteHandler(cfg *config.Config, blacklist *config.Blacklist) gin.HandlerFunc { return func(c *gin.Context) { rawPath := strings.TrimPrefix(c.Request.URL.RequestURI(), "/") re := regexp.MustCompile(`^(http:|https:)?/?/?(.*)`) @@ -36,9 +37,27 @@ func NoRouteHandler(cfg *config.Config) gin.HandlerFunc { rawPath = "https://" + matches[2] + // 提取用户名和仓库名,格式为 / + pathParts := strings.Split(matches[2], "/") + if len(pathParts) < 2 { + logw("Invalid path: %s", rawPath) + c.String(http.StatusForbidden, "Invalid path; expected username/repo.") + return + } + username := pathParts[0] + repo := pathParts[1] + logw("Blacklist Check > Username: %s, Repo: %s", username, repo) + + // 检查仓库是否在黑名单中 + if auth.IsBlacklisted(username, repo, blacklist.Blacklist, cfg.Blacklist.Enabled) { + c.String(http.StatusForbidden, "Access denied: repository is blacklisted.") + logw("Blacklisted repository: %s/%s", username, repo) + return + } + matches = CheckURL(rawPath) if matches == nil { - c.String(http.StatusForbidden, "Invalid input.") + c.AbortWithStatus(http.StatusNotFound) return } @@ -138,7 +157,7 @@ func HandleResponseSize(resp *req.Response, cfg *config.Config, c *gin.Context) contentLength := resp.Header.Get("Content-Length") if contentLength != "" { size, err := strconv.Atoi(contentLength) - if err == nil && size > cfg.SizeLimit { + if err == nil && size > cfg.Server.SizeLimit { finalURL := resp.Request.URL.String() c.Redirect(http.StatusMovedPermanently, finalURL) logw("Redirecting to %s due to size limit (%d bytes)", finalURL, size) @@ -166,7 +185,7 @@ func CopyResponseHeaders(resp *req.Response, c *gin.Context, cfg *config.Config) } c.Header("Access-Control-Allow-Origin", "") - if cfg.CORSOrigin { + if cfg.CORS.Enabled { c.Header("Access-Control-Allow-Origin", "*") } }