Files
easyai-ai-gateway/apps/api/internal/executionpool/identity.go
T
wangbo 7786692d32 feat(routing): 引入多执行池智能调度
将 Worker 发现、路由画像、容量与执行传输抽象为平台无关接口,新增 Kubernetes 和静态容量适配器,并以 shadow 模式接入生产配置。

实现网络与容量评分、路由防抖、池队列、同步 Worker 租约、一次性执行令牌,以及提交状态不明时禁止重复分配的安全语义。

新增 0105 兼容迁移、管理接口、指标、OpenAPI 和回归测试。已执行全量 Go 测试、go vet、OpenAPI、迁移安全、Compose 与 Kustomize 验证。
2026-08-05 22:25:37 +08:00

138 lines
4.2 KiB
Go

package executionpool
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"net"
"net/url"
"strings"
"time"
)
func QueueName(poolID string) string {
poolID = strings.TrimSpace(poolID)
if poolID == "" {
return "gateway_tasks"
}
digest := sha256.Sum256([]byte(poolID))
return "gateway_pool_" + fmt.Sprintf("%x", digest[:8])
}
func RouteProfileKey(provider, protocol, endpointHost, proxyMode, configRevision string) string {
value := strings.Join([]string{
strings.ToLower(strings.TrimSpace(provider)),
strings.ToLower(strings.TrimSpace(protocol)),
strings.ToLower(strings.TrimSpace(endpointHost)),
strings.ToLower(strings.TrimSpace(proxyMode)),
strings.TrimSpace(configRevision),
}, "\x00")
digest := sha256.Sum256([]byte(value))
return fmt.Sprintf("route_%x", digest[:16])
}
func EndpointHost(rawURL string) string {
parsed, err := url.Parse(strings.TrimSpace(rawURL))
if err != nil {
return ""
}
return strings.ToLower(parsed.Hostname())
}
func ValidateAdvertisedEndpoint(rawURL string, allowedSuffixes []string, allowPrivate bool) error {
parsed, err := url.Parse(strings.TrimSpace(rawURL))
if err != nil || parsed.Host == "" || parsed.User != nil {
return errors.New("worker endpoint must be an absolute URL without user info")
}
if parsed.Scheme != "https" && parsed.Scheme != "http" {
return errors.New("worker endpoint scheme must be http or https")
}
host := strings.ToLower(parsed.Hostname())
if host == "" {
return errors.New("worker endpoint host is required")
}
if ip := net.ParseIP(host); ip != nil {
if allowPrivate && (ip.IsPrivate() || ip.IsLoopback()) {
return nil
}
return errors.New("worker endpoint IP is outside the configured trust boundary")
}
for _, suffix := range allowedSuffixes {
suffix = strings.ToLower(strings.TrimSpace(suffix))
if suffix != "" && (host == strings.TrimPrefix(suffix, ".") || strings.HasSuffix(host, "."+strings.TrimPrefix(suffix, "."))) {
return nil
}
}
return errors.New("worker endpoint host is outside the configured trust boundary")
}
type ExecutionClaims struct {
Audience string `json:"aud"`
TaskID string `json:"task_id"`
PoolID string `json:"pool_id"`
WorkerID string `json:"worker_id"`
Nonce string `json:"nonce"`
ExpiresAt int64 `json:"exp"`
}
type TokenSigner struct {
Secret []byte
Now func() time.Time
}
func (s TokenSigner) Sign(claims ExecutionClaims) (string, error) {
if len(s.Secret) < 32 {
return "", errors.New("execution token secret must be at least 32 bytes")
}
if claims.Audience == "" || claims.TaskID == "" || claims.PoolID == "" || claims.WorkerID == "" || claims.Nonce == "" || claims.ExpiresAt <= 0 {
return "", errors.New("execution token claims are incomplete")
}
payload, err := json.Marshal(claims)
if err != nil {
return "", err
}
encoded := base64.RawURLEncoding.EncodeToString(payload)
mac := hmac.New(sha256.New, s.Secret)
_, _ = mac.Write([]byte(encoded))
signature := base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
return encoded + "." + signature, nil
}
func (s TokenSigner) Verify(token, audience string) (ExecutionClaims, error) {
var claims ExecutionClaims
if len(s.Secret) < 32 {
return claims, errors.New("execution token secret must be at least 32 bytes")
}
parts := strings.Split(token, ".")
if len(parts) != 2 {
return claims, errors.New("invalid execution token")
}
signature, err := base64.RawURLEncoding.DecodeString(parts[1])
if err != nil {
return claims, errors.New("invalid execution token signature")
}
mac := hmac.New(sha256.New, s.Secret)
_, _ = mac.Write([]byte(parts[0]))
if !hmac.Equal(signature, mac.Sum(nil)) {
return claims, errors.New("invalid execution token signature")
}
payload, err := base64.RawURLEncoding.DecodeString(parts[0])
if err != nil || json.Unmarshal(payload, &claims) != nil {
return ExecutionClaims{}, errors.New("invalid execution token payload")
}
now := time.Now()
if s.Now != nil {
now = s.Now()
}
if claims.ExpiresAt <= now.Unix() {
return ExecutionClaims{}, errors.New("execution token expired")
}
if claims.Audience != audience {
return ExecutionClaims{}, errors.New("execution token audience mismatch")
}
return claims, nil
}