This commit is contained in:
WJQSERVER 2024-10-05 22:42:43 +08:00
parent 822c08d4c0
commit 89e6be7709
10 changed files with 173 additions and 40 deletions

View file

@ -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 v1.2.0
--- ---
- CHANGE: 优化代码结构,提升性能 - CHANGE: 优化代码结构,提升性能

View file

@ -1 +1 @@
24w08a 24w08b

View file

@ -63,13 +63,31 @@ docker run -p 7210:80 -v ./ghproxy/log/run:/data/ghproxy/log -v ./ghproxy/log/ca
使用Docker部署时,慎重修改config.yaml,以免造成不必要的麻烦 使用Docker部署时,慎重修改config.yaml,以免造成不必要的麻烦
``` ```
port: 8080 # 监听端口 # 核心配置
host: "127.0.0.1" # 监听地址 server:
port: 8080 # 监听端口(小白请勿修改)
host: "127.0.0.1" # 监听地址(小白请勿修改)
sizelimit: 131072000 # 125MB sizelimit: 131072000 # 125MB
logfilepath: "/data/ghproxy/log/ghproxy.log" # 日志文件路径
CorsAllowOrigins: true # 是否允许跨域请求 # 日志配置
auth: true # 是否开启鉴权 logger:
authtoken: "test" # 鉴权token 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反代配置 ### Caddy反代配置
@ -94,7 +112,9 @@ example.com {
- [x] 允许更多参数通过config结构传入 - [x] 允许更多参数通过config结构传入
- [x] 改进程序效率 - [x] 改进程序效率
- [x] 用户鉴权 - [x] 用户鉴权
- [ ] 仓库黑名单
### DEV ### DEV
- [x] Docker Pull 代理 - [x] Docker Pull 代理
- [x] 仓库黑名单

View file

@ -11,7 +11,7 @@ var logw = logger.Logw
func AuthHandler(c *gin.Context, cfg *config.Config) bool { func AuthHandler(c *gin.Context, cfg *config.Config) bool {
// 如果身份验证未启用,直接返回 true // 如果身份验证未启用,直接返回 true
if !cfg.Auth { if !cfg.Auth.Enabled {
logw("auth PASSED") logw("auth PASSED")
return true return true
} }
@ -26,10 +26,24 @@ func AuthHandler(c *gin.Context, cfg *config.Config) bool {
return false return false
} }
isValid := authToken == cfg.AuthToken isValid := authToken == cfg.Auth.AuthToken
if !isValid { if !isValid {
logw("auth FAILED: invalid auth_token: %s", authToken) logw("auth FAILED: invalid auth_token: %s", authToken)
} }
return isValid 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
}

9
config/blacklist.yaml Normal file
View file

@ -0,0 +1,9 @@
blacklist:
username1:
- repo1
- repo2
username2:
- repo3
- repo4
username3:
- repo5

View file

@ -7,26 +7,59 @@ import (
) )
type Config struct { type Config struct {
Server struct {
Port int `yaml:"port"` Port int `yaml:"port"`
Host string `yaml:"host"` Host string `yaml:"host"`
SizeLimit int `yaml:"sizelimit"` SizeLimit int `yaml:"sizelimit"`
} `yaml:"server"`
Log struct {
LogFilePath string `yaml:"logfilepath"` LogFilePath string `yaml:"logfilepath"`
MaxLogSize int `yaml:"maxlogsize"` MaxLogSize int `yaml:"maxlogsize"`
CORSOrigin bool `yaml:"CorsAllowOrigins"` } `yaml:"logger"`
Auth bool `yaml:"auth"`
CORS struct {
Enabled bool `yaml:"enabled"`
} `yaml:"cors"`
Auth struct {
Enabled bool `yaml:"enabled"`
AuthToken string `yaml:"authtoken"` 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 配置文件加载配置 // LoadConfig 从 YAML 配置文件加载配置
func LoadConfig(filePath string) (*Config, error) { func LoadConfig(filePath string) (*Config, error) {
var config Config var config Config
data, err := os.ReadFile(filePath) if err := loadYAML(filePath, &config); err != nil {
if err != nil {
return nil, err
}
err = yaml.Unmarshal(data, &config)
if err != nil {
return nil, err return nil, err
} }
return &config, nil 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)
}

View file

@ -1,8 +1,24 @@
# Server Configuration
server:
port: 8080 port: 8080
host: "127.0.0.1" host: "127.0.0.1"
sizelimit: 131072000 # 125MB sizelimit: 131072000 # 125MB
# Logging Configuration
logger:
logfilepath: "/data/ghproxy/log/ghproxy.log" logfilepath: "/data/ghproxy/log/ghproxy.log"
maxlogsize: 25 # MB maxlogsize: 25 # MB
CorsAllowOrigins: true
auth: false # CORS Configuration
cors:
enabled: true
# Authentication Configuration
auth:
enabled: false
authtoken: "test" authtoken: "test"
# Blacklist Configuration
blacklist:
enabled: true
blacklistfile: "/data/ghproxy/config/blacklist.yaml"

View file

@ -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) && \ 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} 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}/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 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 /data/${APPLICATION}/${APPLICATION}
RUN chmod +x /usr/local/bin/init.sh RUN chmod +x /usr/local/bin/init.sh

19
main.go
View file

@ -16,6 +16,7 @@ import (
var ( var (
cfg *config.Config cfg *config.Config
blacklist *config.Blacklist
logw = logger.Logw logw = logger.Logw
router *gin.Engine router *gin.Engine
configfile = "/data/ghproxy/config/config.yaml" configfile = "/data/ghproxy/config/config.yaml"
@ -41,10 +42,19 @@ func loadConfig() {
fmt.Printf("Loaded config: %v\n", cfg) 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() { func setupLogger() {
// 初始化日志模块 // 初始化日志模块
var err error var err error
err = logger.Init(cfg.LogFilePath, cfg.MaxLogSize) // 传递日志文件路径 err = logger.Init(cfg.Log.LogFilePath, cfg.Log.MaxLogSize) // 传递日志文件路径
if err != nil { if err != nil {
log.Fatalf("Failed to initialize logger: %v", err) log.Fatalf("Failed to initialize logger: %v", err)
} }
@ -55,6 +65,7 @@ func setupLogger() {
func init() { func init() {
loadConfig() loadConfig()
setupLogger() setupLogger()
loadBlacklistConfig()
// 设置 Gin 模式 // 设置 Gin 模式
gin.SetMode(gin.ReleaseMode) gin.SetMode(gin.ReleaseMode)
@ -76,13 +87,13 @@ func init() {
// 未匹配路由处理 // 未匹配路由处理
router.NoRoute(func(c *gin.Context) { router.NoRoute(func(c *gin.Context) {
proxy.NoRouteHandler(cfg)(c) proxy.NoRouteHandler(cfg, blacklist)(c)
}) })
} }
func main() { 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 { if err != nil {
log.Fatalf("Error starting server: %v\n", err) 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") c.Writer.Header().Set("Content-Type", "application/json")
json.NewEncoder(c.Writer).Encode(map[string]interface{}{ json.NewEncoder(c.Writer).Encode(map[string]interface{}{
"MaxResponseBodySize": cfg.SizeLimit, "MaxResponseBodySize": cfg.Server.SizeLimit,
}) })
} }

View file

@ -19,6 +19,7 @@ import (
var logw = logger.Logw var logw = logger.Logw
var cfg *config.Config var cfg *config.Config
var blacklist *config.Blacklist
var exps = []*regexp.Regexp{ var exps = []*regexp.Regexp{
regexp.MustCompile(`^(?:https?://)?github\.com/([^/]+)/([^/]+)/(?:releases|archive)/.*`), regexp.MustCompile(`^(?:https?://)?github\.com/([^/]+)/([^/]+)/(?:releases|archive)/.*`),
@ -28,7 +29,7 @@ var exps = []*regexp.Regexp{
regexp.MustCompile(`^(?:https?://)?gist\.github\.com/([^/]+)/.+?/.+`), 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) { return func(c *gin.Context) {
rawPath := strings.TrimPrefix(c.Request.URL.RequestURI(), "/") rawPath := strings.TrimPrefix(c.Request.URL.RequestURI(), "/")
re := regexp.MustCompile(`^(http:|https:)?/?/?(.*)`) re := regexp.MustCompile(`^(http:|https:)?/?/?(.*)`)
@ -36,9 +37,27 @@ func NoRouteHandler(cfg *config.Config) gin.HandlerFunc {
rawPath = "https://" + matches[2] rawPath = "https://" + matches[2]
// 提取用户名和仓库名,格式为 <username>/<repo>
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) matches = CheckURL(rawPath)
if matches == nil { if matches == nil {
c.String(http.StatusForbidden, "Invalid input.") c.AbortWithStatus(http.StatusNotFound)
return return
} }
@ -138,7 +157,7 @@ func HandleResponseSize(resp *req.Response, cfg *config.Config, c *gin.Context)
contentLength := resp.Header.Get("Content-Length") contentLength := resp.Header.Get("Content-Length")
if contentLength != "" { if contentLength != "" {
size, err := strconv.Atoi(contentLength) size, err := strconv.Atoi(contentLength)
if err == nil && size > cfg.SizeLimit { if err == nil && size > cfg.Server.SizeLimit {
finalURL := resp.Request.URL.String() finalURL := resp.Request.URL.String()
c.Redirect(http.StatusMovedPermanently, finalURL) c.Redirect(http.StatusMovedPermanently, finalURL)
logw("Redirecting to %s due to size limit (%d bytes)", finalURL, size) 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", "") c.Header("Access-Control-Allow-Origin", "")
if cfg.CORSOrigin { if cfg.CORS.Enabled {
c.Header("Access-Control-Allow-Origin", "*") c.Header("Access-Control-Allow-Origin", "*")
} }
} }