feat: 实例级隔离 — salt + 指纹版本可配置
- 新增 gateway.instance_salt: 不同 sub2api 实例对相同输入产生不同 hash 影响 user_id 重写和 session hash,防止跨实例指纹关联 - 新增 gateway.fingerprint_defaults: CLI 版本号/SDK 版本/OS/Arch 可配置 每个实例可设不同值,与其他 sub2api 部署区分 - constants.go + identity_service.go 支持启动时覆盖默认指纹 - wire_gen.go 启动时读取配置并应用覆盖
This commit is contained in:
parent
43506e4f78
commit
73bbbb415c
@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/Wei-Shaw/sub2api/internal/config"
|
"github.com/Wei-Shaw/sub2api/internal/config"
|
||||||
"github.com/Wei-Shaw/sub2api/internal/handler"
|
"github.com/Wei-Shaw/sub2api/internal/handler"
|
||||||
"github.com/Wei-Shaw/sub2api/internal/handler/admin"
|
"github.com/Wei-Shaw/sub2api/internal/handler/admin"
|
||||||
|
"github.com/Wei-Shaw/sub2api/internal/pkg/claude"
|
||||||
"github.com/Wei-Shaw/sub2api/internal/repository"
|
"github.com/Wei-Shaw/sub2api/internal/repository"
|
||||||
"github.com/Wei-Shaw/sub2api/internal/server"
|
"github.com/Wei-Shaw/sub2api/internal/server"
|
||||||
"github.com/Wei-Shaw/sub2api/internal/server/middleware"
|
"github.com/Wei-Shaw/sub2api/internal/server/middleware"
|
||||||
@ -35,6 +36,10 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
// 应用实例级指纹覆盖(不同 sub2api 实例可设不同的默认版本号)
|
||||||
|
fpd := configConfig.Gateway.FingerprintDefaults
|
||||||
|
claude.ApplyFingerprintOverrides(fpd.ClaudeCLIVersion, fpd.StainlessPackageVersion, fpd.StainlessRuntimeVersion, fpd.StainlessOS, fpd.StainlessArch)
|
||||||
|
service.ApplyDefaultFingerprintOverrides(fpd.ClaudeCLIVersion, fpd.StainlessPackageVersion, fpd.StainlessRuntimeVersion, fpd.StainlessOS, fpd.StainlessArch)
|
||||||
client, err := repository.ProvideEnt(configConfig)
|
client, err := repository.ProvideEnt(configConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -166,7 +171,7 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
billingService := service.NewBillingService(configConfig, pricingService)
|
billingService := service.NewBillingService(configConfig, pricingService)
|
||||||
identityService := service.NewIdentityService(identityCache)
|
identityService := service.NewIdentityServiceWithSalt(identityCache, configConfig.Gateway.InstanceSalt)
|
||||||
deferredService := service.ProvideDeferredService(accountRepository, timingWheelService)
|
deferredService := service.ProvideDeferredService(accountRepository, timingWheelService)
|
||||||
claudeTokenProvider := service.ProvideClaudeTokenProvider(accountRepository, geminiTokenCache, oAuthService, oauthRefreshAPI)
|
claudeTokenProvider := service.ProvideClaudeTokenProvider(accountRepository, geminiTokenCache, oAuthService, oauthRefreshAPI)
|
||||||
digestSessionStore := service.NewDigestSessionStore()
|
digestSessionStore := service.NewDigestSessionStore()
|
||||||
|
|||||||
@ -461,6 +461,18 @@ type GatewayConfig struct {
|
|||||||
// 实现天然 JA3/JA4 指纹匹配(无需 uTLS 模拟)
|
// 实现天然 JA3/JA4 指纹匹配(无需 uTLS 模拟)
|
||||||
NodeTLSProxy NodeTLSProxyConfig `mapstructure:"node_tls_proxy"`
|
NodeTLSProxy NodeTLSProxyConfig `mapstructure:"node_tls_proxy"`
|
||||||
|
|
||||||
|
// InstanceSalt: 实例级隔离盐值
|
||||||
|
// 用于 user_id 重写和 session hash 的种子混淆,
|
||||||
|
// 不同 sub2api 实例设置不同的 salt,确保相同输入产生不同输出。
|
||||||
|
// 为空时使用默认行为(无 salt),建议生产环境必须配置。
|
||||||
|
// 生成方法: openssl rand -hex 32
|
||||||
|
InstanceSalt string `mapstructure:"instance_salt"`
|
||||||
|
|
||||||
|
// FingerprintDefaults: 指纹默认值覆盖
|
||||||
|
// 允许每个实例配置不同的 Claude CLI 版本号,与其他 sub2api 实例区分。
|
||||||
|
// 为空时使用代码内置默认值。
|
||||||
|
FingerprintDefaults FingerprintDefaultsConfig `mapstructure:"fingerprint_defaults"`
|
||||||
|
|
||||||
// UsageRecord: 使用量记录异步队列配置(有界队列 + 固定 worker)
|
// UsageRecord: 使用量记录异步队列配置(有界队列 + 固定 worker)
|
||||||
UsageRecord GatewayUsageRecordConfig `mapstructure:"usage_record"`
|
UsageRecord GatewayUsageRecordConfig `mapstructure:"usage_record"`
|
||||||
|
|
||||||
@ -696,6 +708,23 @@ type NodeTLSProxyConfig struct {
|
|||||||
ProxyHosts []string `mapstructure:"proxy_hosts"`
|
ProxyHosts []string `mapstructure:"proxy_hosts"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FingerprintDefaultsConfig 指纹默认值配置
|
||||||
|
// 允许每个 sub2api 实例设置不同的默认指纹值,与其他实例区分。
|
||||||
|
// 所有字段为空时使用代码内置默认值。
|
||||||
|
type FingerprintDefaultsConfig struct {
|
||||||
|
// ClaudeCLIVersion: Claude CLI 版本号(如 "2.1.81"),
|
||||||
|
// 最终 User-Agent 为 "claude-cli/{version} (external, cli)"
|
||||||
|
ClaudeCLIVersion string `mapstructure:"claude_cli_version"`
|
||||||
|
// StainlessPackageVersion: @anthropic-ai/sdk 版本(如 "0.80.0")
|
||||||
|
StainlessPackageVersion string `mapstructure:"stainless_package_version"`
|
||||||
|
// StainlessRuntimeVersion: Node.js 版本(如 "v24.13.0")
|
||||||
|
StainlessRuntimeVersion string `mapstructure:"stainless_runtime_version"`
|
||||||
|
// StainlessOS: 操作系统(如 "Linux", "Darwin")
|
||||||
|
StainlessOS string `mapstructure:"stainless_os"`
|
||||||
|
// StainlessArch: 架构(如 "arm64", "x64")
|
||||||
|
StainlessArch string `mapstructure:"stainless_arch"`
|
||||||
|
}
|
||||||
|
|
||||||
// GatewaySchedulingConfig accounts scheduling configuration.
|
// GatewaySchedulingConfig accounts scheduling configuration.
|
||||||
type GatewaySchedulingConfig struct {
|
type GatewaySchedulingConfig struct {
|
||||||
// 粘性会话排队配置
|
// 粘性会话排队配置
|
||||||
|
|||||||
@ -61,6 +61,30 @@ var DefaultHeaders = map[string]string{
|
|||||||
"Anthropic-Dangerous-Direct-Browser-Access": "true",
|
"Anthropic-Dangerous-Direct-Browser-Access": "true",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ApplyFingerprintOverrides 用配置覆盖默认指纹值(每个实例可设不同值)
|
||||||
|
// cliVersion: Claude CLI 版本(如 "2.1.81")
|
||||||
|
// pkgVersion: SDK 版本(如 "0.80.0")
|
||||||
|
// runtimeVersion: Node.js 版本(如 "v24.13.0")
|
||||||
|
// os_: 操作系统(如 "Linux")
|
||||||
|
// arch: 架构(如 "arm64")
|
||||||
|
func ApplyFingerprintOverrides(cliVersion, pkgVersion, runtimeVersion, os_, arch string) {
|
||||||
|
if cliVersion != "" {
|
||||||
|
DefaultHeaders["User-Agent"] = "claude-cli/" + cliVersion + " (external, cli)"
|
||||||
|
}
|
||||||
|
if pkgVersion != "" {
|
||||||
|
DefaultHeaders["X-Stainless-Package-Version"] = pkgVersion
|
||||||
|
}
|
||||||
|
if runtimeVersion != "" {
|
||||||
|
DefaultHeaders["X-Stainless-Runtime-Version"] = runtimeVersion
|
||||||
|
}
|
||||||
|
if os_ != "" {
|
||||||
|
DefaultHeaders["X-Stainless-OS"] = os_
|
||||||
|
}
|
||||||
|
if arch != "" {
|
||||||
|
DefaultHeaders["X-Stainless-Arch"] = arch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Model 表示一个 Claude 模型
|
// Model 表示一个 Claude 模型
|
||||||
type Model struct {
|
type Model struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
|
|||||||
@ -35,6 +35,25 @@ var defaultFingerprint = Fingerprint{
|
|||||||
StainlessRuntimeVersion: "v24.13.0",
|
StainlessRuntimeVersion: "v24.13.0",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ApplyDefaultFingerprintOverrides 用配置覆盖 identity_service 的默认指纹
|
||||||
|
func ApplyDefaultFingerprintOverrides(cliVersion, pkgVersion, runtimeVersion, os_, arch string) {
|
||||||
|
if cliVersion != "" {
|
||||||
|
defaultFingerprint.UserAgent = "claude-cli/" + cliVersion + " (external, cli)"
|
||||||
|
}
|
||||||
|
if pkgVersion != "" {
|
||||||
|
defaultFingerprint.StainlessPackageVersion = pkgVersion
|
||||||
|
}
|
||||||
|
if runtimeVersion != "" {
|
||||||
|
defaultFingerprint.StainlessRuntimeVersion = runtimeVersion
|
||||||
|
}
|
||||||
|
if os_ != "" {
|
||||||
|
defaultFingerprint.StainlessOS = os_
|
||||||
|
}
|
||||||
|
if arch != "" {
|
||||||
|
defaultFingerprint.StainlessArch = arch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Fingerprint represents account fingerprint data
|
// Fingerprint represents account fingerprint data
|
||||||
type Fingerprint struct {
|
type Fingerprint struct {
|
||||||
ClientID string
|
ClientID string
|
||||||
@ -63,7 +82,8 @@ type IdentityCache interface {
|
|||||||
|
|
||||||
// IdentityService 管理OAuth账号的请求身份指纹
|
// IdentityService 管理OAuth账号的请求身份指纹
|
||||||
type IdentityService struct {
|
type IdentityService struct {
|
||||||
cache IdentityCache
|
cache IdentityCache
|
||||||
|
instanceSalt string // 实例级隔离盐值,不同 sub2api 实例产生不同的 hash 输出
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewIdentityService 创建新的IdentityService
|
// NewIdentityService 创建新的IdentityService
|
||||||
@ -71,6 +91,11 @@ func NewIdentityService(cache IdentityCache) *IdentityService {
|
|||||||
return &IdentityService{cache: cache}
|
return &IdentityService{cache: cache}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewIdentityServiceWithSalt 创建带实例盐值的 IdentityService
|
||||||
|
func NewIdentityServiceWithSalt(cache IdentityCache, salt string) *IdentityService {
|
||||||
|
return &IdentityService{cache: cache, instanceSalt: salt}
|
||||||
|
}
|
||||||
|
|
||||||
// GetOrCreateFingerprint 获取或创建账号的指纹
|
// GetOrCreateFingerprint 获取或创建账号的指纹
|
||||||
// 如果缓存存在,检测user-agent版本,新版本则更新
|
// 如果缓存存在,检测user-agent版本,新版本则更新
|
||||||
// 如果缓存不存在,生成随机ClientID并从请求头创建指纹,然后缓存
|
// 如果缓存不存在,生成随机ClientID并从请求头创建指纹,然后缓存
|
||||||
@ -241,8 +266,9 @@ func (s *IdentityService) RewriteUserID(body []byte, accountID int64, accountUUI
|
|||||||
|
|
||||||
sessionTail := parsed.SessionID // 原始session UUID
|
sessionTail := parsed.SessionID // 原始session UUID
|
||||||
|
|
||||||
// 生成新的session hash: SHA256(accountID::sessionTail) -> UUID格式
|
// 生成新的session hash: SHA256(salt::accountID::sessionTail) -> UUID格式
|
||||||
seed := fmt.Sprintf("%d::%s", accountID, sessionTail)
|
// instanceSalt 使不同 sub2api 实例对相同输入产生不同的 hash
|
||||||
|
seed := fmt.Sprintf("%s::%d::%s", s.instanceSalt, accountID, sessionTail)
|
||||||
newSessionHash := generateUUIDFromSeed(seed)
|
newSessionHash := generateUUIDFromSeed(seed)
|
||||||
|
|
||||||
// 根据客户端版本选择输出格式
|
// 根据客户端版本选择输出格式
|
||||||
|
|||||||
@ -397,6 +397,24 @@ gateway:
|
|||||||
# Upstream target host / 上游目标主机
|
# Upstream target host / 上游目标主机
|
||||||
upstream_host: "api.anthropic.com"
|
upstream_host: "api.anthropic.com"
|
||||||
|
|
||||||
|
# Instance isolation salt / 实例隔离盐值
|
||||||
|
# IMPORTANT: Each sub2api deployment MUST set a unique salt to prevent
|
||||||
|
# cross-instance fingerprint correlation. Generate: openssl rand -hex 32
|
||||||
|
# 重要:每个 sub2api 实例必须设置唯一的 salt,防止不同实例之间的指纹关联。
|
||||||
|
# 生成方法: openssl rand -hex 32
|
||||||
|
instance_salt: ""
|
||||||
|
|
||||||
|
# Fingerprint defaults override / 指纹默认值覆盖
|
||||||
|
# Each instance can set different version numbers to differentiate from
|
||||||
|
# other sub2api deployments. Empty values use built-in defaults.
|
||||||
|
# 每个实例可设置不同的版本号,与其他 sub2api 部署区分。空值使用内置默认值。
|
||||||
|
fingerprint_defaults:
|
||||||
|
# claude_cli_version: "2.1.81"
|
||||||
|
# stainless_package_version: "0.80.0"
|
||||||
|
# stainless_runtime_version: "v24.13.0"
|
||||||
|
# stainless_os: "Linux" # Linux / Darwin
|
||||||
|
# stainless_arch: "arm64" # arm64 / x64
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Logging Configuration
|
# Logging Configuration
|
||||||
# 日志配置
|
# 日志配置
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user