兼容开放导入未签名请求
This commit is contained in:
@@ -56,8 +56,8 @@ REALNAME_CLOUDMARKET_APPCODE=
|
||||
# 生成方式:openssl rand -hex 16
|
||||
PAYMENT_CONFIG_ENCRYPTION_KEY=
|
||||
|
||||
# 开放导入接口签名密钥;留空时 /api/open/listing-uploads 不可用。
|
||||
# 请求需携带 X-HFB-Timestamp 和 X-HFB-Signature。
|
||||
# 开放导入接口可选签名密钥;留空或请求未带签名时按旧方式导入。
|
||||
# 若调用方携带 X-HFB-Timestamp 和 X-HFB-Signature,则会校验 HMAC 签名。
|
||||
EXTERNAL_UPLOAD_SECRET=
|
||||
# 可选:逗号分隔的 IP 或 CIDR 白名单,例如 127.0.0.1,10.0.0.0/8。
|
||||
EXTERNAL_UPLOAD_ALLOWED_IPS=
|
||||
|
||||
@@ -62,7 +62,8 @@ REALNAME_CLOUDMARKET_APPCODE=
|
||||
# 警告:此密钥一旦设置不要更改,否则已有配置无法解密
|
||||
PAYMENT_CONFIG_ENCRYPTION_KEY=change-to-32-byte-encryption-key
|
||||
|
||||
# 开放导入接口签名密钥,生产环境必填;生成方式:openssl rand -hex 32
|
||||
EXTERNAL_UPLOAD_SECRET=change-to-a-long-random-upload-secret
|
||||
# 开放导入接口可选签名密钥;内部调用方暂不签名时可留空。
|
||||
# 若启用签名,生成方式:openssl rand -hex 32
|
||||
EXTERNAL_UPLOAD_SECRET=
|
||||
# 可选:逗号分隔的 IP 或 CIDR 白名单,例如 203.0.113.10,10.0.0.0/8。
|
||||
EXTERNAL_UPLOAD_ALLOWED_IPS=
|
||||
|
||||
@@ -120,9 +120,6 @@ func (c Config) ValidateProductionSecurity() error {
|
||||
if isPlaceholder(c.PaymentConfigEncryptionKey) || (keyLen != 16 && keyLen != 24 && keyLen != 32) {
|
||||
return errors.New("PAYMENT_CONFIG_ENCRYPTION_KEY must be 16, 24, or 32 bytes in production")
|
||||
}
|
||||
if strings.TrimSpace(c.ExternalUploadSecret) == "" || isPlaceholder(c.ExternalUploadSecret) {
|
||||
return errors.New("EXTERNAL_UPLOAD_SECRET is required in production")
|
||||
}
|
||||
if c.BootstrapAdminPassword != "" && isPlaceholder(c.BootstrapAdminPassword) {
|
||||
return errors.New("ADMIN_BOOTSTRAP_PASSWORD must not use the example placeholder in production")
|
||||
}
|
||||
|
||||
@@ -47,7 +47,6 @@ func (h *Handler) ImportExternalUpload(c *gin.Context) {
|
||||
}
|
||||
|
||||
var (
|
||||
errExternalUploadSecretMissing = errors.New("external upload secret missing")
|
||||
errExternalUploadForbiddenIP = errors.New("external upload forbidden ip")
|
||||
errExternalUploadTimestamp = errors.New("external upload timestamp invalid")
|
||||
errExternalUploadSignature = errors.New("external upload signature invalid")
|
||||
@@ -56,15 +55,18 @@ var (
|
||||
const externalUploadMaxClockSkew = 5 * time.Minute
|
||||
|
||||
func (h *Handler) verifyExternalUpload(c *gin.Context, raw []byte) error {
|
||||
secret := strings.TrimSpace(h.externalUploadSecret)
|
||||
if secret == "" {
|
||||
return errExternalUploadSecretMissing
|
||||
}
|
||||
if !externalUploadIPAllowed(c.ClientIP(), h.externalUploadAllowedIPs) {
|
||||
return errExternalUploadForbiddenIP
|
||||
}
|
||||
secret := strings.TrimSpace(h.externalUploadSecret)
|
||||
if secret == "" {
|
||||
return nil
|
||||
}
|
||||
timestamp := strings.TrimSpace(c.GetHeader("X-HFB-Timestamp"))
|
||||
signature := strings.TrimSpace(c.GetHeader("X-HFB-Signature"))
|
||||
if timestamp == "" && signature == "" {
|
||||
return nil
|
||||
}
|
||||
if timestamp == "" || signature == "" {
|
||||
return errExternalUploadSignature
|
||||
}
|
||||
@@ -111,8 +113,6 @@ func externalUploadIPAllowed(clientIP string, allowed []string) bool {
|
||||
|
||||
func writeExternalUploadAuthError(c *gin.Context, err error) {
|
||||
switch err {
|
||||
case errExternalUploadSecretMissing:
|
||||
response.ServiceUnavailable(c, "开放导入签名密钥未配置")
|
||||
case errExternalUploadForbiddenIP:
|
||||
response.Error(c, http.StatusForbidden, "forbidden", "来源 IP 不允许访问")
|
||||
case errExternalUploadTimestamp:
|
||||
|
||||
@@ -30,6 +30,28 @@ func TestVerifyExternalUploadSignature(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyExternalUploadAllowsUnsignedRequest(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
c, _ := gin.CreateTestContext(httptest.NewRecorder())
|
||||
c.Request = httptest.NewRequest("POST", "/api/open/listing-uploads", nil)
|
||||
|
||||
handler := &Handler{}
|
||||
if err := handler.verifyExternalUpload(c, []byte(`{}`)); err != nil {
|
||||
t.Fatalf("verifyExternalUpload() error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyExternalUploadAllowsUnsignedRequestWithSecret(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
c, _ := gin.CreateTestContext(httptest.NewRecorder())
|
||||
c.Request = httptest.NewRequest("POST", "/api/open/listing-uploads", nil)
|
||||
|
||||
handler := &Handler{externalUploadSecret: "test-upload-secret"}
|
||||
if err := handler.verifyExternalUpload(c, []byte(`{}`)); err != nil {
|
||||
t.Fatalf("verifyExternalUpload() error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyExternalUploadRejectsBadSignature(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
c, _ := gin.CreateTestContext(httptest.NewRecorder())
|
||||
|
||||
Reference in New Issue
Block a user