рабочий вариант, но скороть 10 МБит
build / backend (push) Has been cancelled
build / node-agent (push) Has been cancelled
build / worker (push) Has been cancelled

This commit is contained in:
2026-05-22 21:46:49 +03:00
parent 469fa0e860
commit 20d361a886
280 changed files with 954890 additions and 18524 deletions
@@ -22,7 +22,7 @@ import (
const (
ModeStrict = "strict"
ModeLegacy = "legacy"
ModeCompat = "compat"
ActivationSchemaVersion = "rap.installation.activation.v1"
@@ -60,7 +60,7 @@ type Verifier struct {
func NewVerifier(cfg config.InstallationConfig) (*Verifier, error) {
mode := strings.ToLower(strings.TrimSpace(cfg.AuthorityMode))
if mode == "" {
mode = ModeLegacy
mode = ModeCompat
}
verifier := &Verifier{
mode: mode,
@@ -69,7 +69,7 @@ func NewVerifier(cfg config.InstallationConfig) (*Verifier, error) {
}
switch mode {
case ModeLegacy:
case ModeCompat:
return verifier, nil
case ModeStrict:
publicKey, err := decodeEd25519PublicKey(cfg.ProductRootPublicKeyBase64)
@@ -87,7 +87,7 @@ func NewVerifier(cfg config.InstallationConfig) (*Verifier, error) {
func (v *Verifier) Mode() string {
if v == nil || v.mode == "" {
return ModeLegacy
return ModeCompat
}
return v.mode
}
@@ -162,7 +162,7 @@ func EffectivePlatformRole(ctx context.Context, db postgresplatform.DBTX, verifi
return PlatformRoleUser, nil
}
if verifier == nil || !verifier.Strict() {
return legacyPlatformRole(ctx, db, userID)
return storedPlatformRole(ctx, db, userID)
}
var email string
@@ -220,7 +220,7 @@ END, prg.granted_at DESC
} else if ok {
return role, nil
}
return legacyPlatformRole(ctx, db, userID)
return storedPlatformRole(ctx, db, userID)
}
return bestRole, nil
}
@@ -257,7 +257,7 @@ WHERE u.id = $1::uuid
}
}
func legacyPlatformRole(ctx context.Context, db postgresplatform.DBTX, userID string) (string, error) {
func storedPlatformRole(ctx context.Context, db postgresplatform.DBTX, userID string) (string, error) {
var role string
if err := db.QueryRow(ctx, `SELECT platform_role FROM users WHERE id = $1::uuid`, userID).Scan(&role); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
+23 -13
View File
@@ -10,17 +10,18 @@ import (
)
type Config struct {
App AppConfig
HTTP HTTPConfig
Postgres PostgresConfig
Redis RedisConfig
Auth AuthConfig
Installation InstallationConfig
DataPlane DataPlaneConfig
Secret SecretConfig
Session SessionConfig
Worker WorkerConfig
WebSocket WebSocketConfig
App AppConfig
HTTP HTTPConfig
FabricControl FabricControlConfig
Postgres PostgresConfig
Redis RedisConfig
Auth AuthConfig
Installation InstallationConfig
DataPlane DataPlaneConfig
Secret SecretConfig
Session SessionConfig
Worker WorkerConfig
WebSocket WebSocketConfig
}
type AppConfig struct {
@@ -37,6 +38,11 @@ type HTTPConfig struct {
ShutdownTimeout time.Duration
}
type FabricControlConfig struct {
Enabled bool
ListenAddr string
}
type PostgresConfig struct {
DSN string
MaxConns int32
@@ -118,6 +124,10 @@ func Load() (Config, error) {
IdleTimeout: getDuration("HTTP_IDLE_TIMEOUT", 60*time.Second),
ShutdownTimeout: getDuration("HTTP_SHUTDOWN_TIMEOUT", 10*time.Second),
},
FabricControl: FabricControlConfig{
Enabled: getBool("FABRIC_CONTROL_QUIC_ENABLED", false),
ListenAddr: getEnv("FABRIC_CONTROL_QUIC_LISTEN_ADDR", ":19191"),
},
Postgres: PostgresConfig{
DSN: getEnv("POSTGRES_DSN", ""),
MaxConns: int32(getInt("POSTGRES_MAX_CONNS", 20)),
@@ -235,13 +245,13 @@ func Load() (Config, error) {
func normalizeInstallationAuthorityMode(mode string, rootPublicKey string) string {
mode = strings.ToLower(strings.TrimSpace(mode))
switch mode {
case "strict", "legacy":
case "strict", "compat":
return mode
case "":
if strings.TrimSpace(rootPublicKey) != "" {
return "strict"
}
return "legacy"
return "compat"
default:
return mode
}
@@ -0,0 +1,78 @@
package fabriccontrol
import (
"encoding/binary"
"errors"
"fmt"
"io"
)
const (
frameMagic uint32 = 0x52415046
frameVersion uint8 = 1
frameHeaderSize = 32
maxPayload = 1024 * 1024
frameData uint8 = 5
trafficClassReliable uint8 = 4
controlForwardQUICStream uint64 = 3
)
type frame struct {
Type uint8
TrafficClass uint8
StreamID uint64
Sequence uint64
Payload []byte
}
var errInvalidFrame = errors.New("invalid fabric frame")
func readFrame(r io.Reader) (frame, error) {
header := make([]byte, frameHeaderSize)
if _, err := io.ReadFull(r, header); err != nil {
return frame{}, err
}
if binary.BigEndian.Uint32(header[0:4]) != frameMagic || header[4] != frameVersion {
return frame{}, errInvalidFrame
}
payloadLen := int(binary.BigEndian.Uint32(header[28:32]))
if payloadLen > maxPayload {
return frame{}, fmt.Errorf("%w: payload too large", errInvalidFrame)
}
out := frame{
Type: header[5],
TrafficClass: header[8],
StreamID: binary.BigEndian.Uint64(header[12:20]),
Sequence: binary.BigEndian.Uint64(header[20:28]),
}
if payloadLen > 0 {
out.Payload = make([]byte, payloadLen)
if _, err := io.ReadFull(r, out.Payload); err != nil {
return frame{}, err
}
}
return out, nil
}
func writeFrame(w io.Writer, f frame) error {
if len(f.Payload) > maxPayload {
return fmt.Errorf("%w: payload too large", errInvalidFrame)
}
header := make([]byte, frameHeaderSize)
binary.BigEndian.PutUint32(header[0:4], frameMagic)
header[4] = frameVersion
header[5] = f.Type
header[8] = f.TrafficClass
binary.BigEndian.PutUint64(header[12:20], f.StreamID)
binary.BigEndian.PutUint64(header[20:28], f.Sequence)
binary.BigEndian.PutUint32(header[28:32], uint32(len(f.Payload)))
if _, err := w.Write(header); err != nil {
return err
}
if len(f.Payload) == 0 {
return nil
}
_, err := w.Write(f.Payload)
return err
}
@@ -0,0 +1,195 @@
package fabriccontrol
import (
"bytes"
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/json"
"fmt"
"math/big"
"net/http"
"net/http/httptest"
"strings"
"time"
"github.com/quic-go/quic-go"
)
const nextProto = "rap-fabric-data-session-v1"
type Config struct {
Enabled bool
ListenAddr string
}
type Server struct {
cfg Config
router http.Handler
ln *quic.Listener
}
type rawControlRequest struct {
Method string `json:"method"`
Path string `json:"path"`
Body json.RawMessage `json:"body,omitempty"`
}
type rawControlResponse struct {
StatusCode int `json:"status_code"`
Body json.RawMessage `json:"body,omitempty"`
}
type controlEnvelope struct {
Payload json.RawMessage `json:"payload,omitempty"`
Error string `json:"error,omitempty"`
}
func New(cfg Config, router http.Handler) *Server {
return &Server{cfg: cfg, router: router}
}
func (s *Server) ListenAndServe(ctx context.Context) error {
if s == nil || !s.cfg.Enabled {
return nil
}
listenAddr := strings.TrimSpace(s.cfg.ListenAddr)
if listenAddr == "" {
listenAddr = ":19191"
}
ln, err := quic.ListenAddr(listenAddr, selfSignedTLSConfig(), nil)
if err != nil {
return err
}
s.ln = ln
go func() {
<-ctx.Done()
_ = ln.Close()
}()
for {
conn, err := ln.Accept(ctx)
if err != nil {
if ctx.Err() != nil {
return nil
}
return err
}
go s.handleConn(ctx, conn)
}
}
func (s *Server) Close() error {
if s == nil || s.ln == nil {
return nil
}
return s.ln.Close()
}
func (s *Server) handleConn(ctx context.Context, conn *quic.Conn) {
for {
stream, err := conn.AcceptStream(ctx)
if err != nil {
return
}
go s.handleStream(ctx, stream)
}
}
func (s *Server) handleStream(ctx context.Context, stream *quic.Stream) {
defer stream.Close()
for {
reqFrame, err := readFrame(stream)
if err != nil {
return
}
if reqFrame.Type != frameData || reqFrame.StreamID != controlForwardQUICStream {
continue
}
payload, err := s.handlePayload(ctx, reqFrame.Payload)
envelope := controlEnvelope{Payload: payload}
if err != nil {
envelope = controlEnvelope{Error: err.Error()}
}
raw, _ := json.Marshal(envelope)
_ = writeFrame(stream, frame{
Type: frameData,
TrafficClass: trafficClassReliable,
StreamID: controlForwardQUICStream,
Sequence: reqFrame.Sequence,
Payload: raw,
})
}
}
func (s *Server) handlePayload(ctx context.Context, payload []byte) (json.RawMessage, error) {
var req rawControlRequest
if err := json.Unmarshal(payload, &req); err != nil {
return nil, fmt.Errorf("invalid fabric control request")
}
method := strings.ToUpper(strings.TrimSpace(req.Method))
if method == "" {
method = http.MethodGet
}
path := normalizeControlPath(req.Path)
if path == "" {
return nil, fmt.Errorf("fabric control path is not allowed")
}
httpReq := httptest.NewRequest(method, path, bytes.NewReader(req.Body)).WithContext(ctx)
httpReq.Header.Set("Content-Type", "application/json")
httpReq.Header.Set("X-RAP-Fabric-Control", "quic")
rec := httptest.NewRecorder()
s.router.ServeHTTP(rec, httpReq)
body := append(json.RawMessage(nil), rec.Body.Bytes()...)
raw, err := json.Marshal(rawControlResponse{StatusCode: rec.Code, Body: body})
if err != nil {
return nil, err
}
return raw, nil
}
func normalizeControlPath(path string) string {
path = strings.TrimSpace(path)
if path == "" || strings.Contains(path, "://") || strings.Contains(path, "..") {
return ""
}
if !strings.HasPrefix(path, "/") {
path = "/" + path
}
if strings.HasPrefix(path, "/api/v1/") {
return path
}
switch {
case strings.HasPrefix(path, "/clusters/"),
strings.HasPrefix(path, "/organizations/"),
strings.HasPrefix(path, "/node-agents/"),
strings.HasPrefix(path, "/auth/"):
return "/api/v1" + path
default:
return ""
}
}
func selfSignedTLSConfig() *tls.Config {
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
panic(err)
}
tmpl := x509.Certificate{
SerialNumber: big.NewInt(time.Now().UnixNano()),
Subject: pkix.Name{CommonName: "rap-fabric-control"},
NotBefore: time.Now().Add(-time.Hour),
NotAfter: time.Now().Add(365 * 24 * time.Hour),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
}
der, err := x509.CreateCertificate(rand.Reader, &tmpl, &tmpl, &key.PublicKey, key)
if err != nil {
panic(err)
}
cert := tls.Certificate{Certificate: [][]byte{der}, PrivateKey: key}
return &tls.Config{Certificates: []tls.Certificate{cert}, NextProtos: []string{nextProto}}
}
+24 -7
View File
@@ -24,6 +24,7 @@ import (
"github.com/example/remote-access-platform/backend/internal/modules/worker"
"github.com/example/remote-access-platform/backend/internal/platform/authority"
"github.com/example/remote-access-platform/backend/internal/platform/config"
"github.com/example/remote-access-platform/backend/internal/platform/fabriccontrol"
"github.com/example/remote-access-platform/backend/internal/platform/httpserver"
"github.com/example/remote-access-platform/backend/internal/platform/logging"
"github.com/example/remote-access-platform/backend/internal/platform/module"
@@ -33,12 +34,13 @@ import (
)
type App struct {
cfg config.Config
logger *slog.Logger
httpServer *http.Server
workers []backgroundRunner
db closeFunc
redis closeFunc
cfg config.Config
logger *slog.Logger
httpServer *http.Server
fabricControl *fabriccontrol.Server
workers []backgroundRunner
db closeFunc
redis closeFunc
}
type closeFunc func() error
@@ -138,7 +140,11 @@ func NewApp(ctx context.Context) (*App, error) {
cfg: cfg,
logger: logger,
httpServer: httpserver.New(cfg.HTTP, router),
workers: []backgroundRunner{workerEvents.Run, leaseMonitor.Run},
fabricControl: fabriccontrol.New(fabriccontrol.Config{
Enabled: cfg.FabricControl.Enabled,
ListenAddr: cfg.FabricControl.ListenAddr,
}, router),
workers: []backgroundRunner{workerEvents.Run, leaseMonitor.Run},
db: func() error {
db.Close()
return nil
@@ -167,6 +173,14 @@ func (a *App) Run(ctx context.Context) error {
}
}()
}
if a.fabricControl != nil && a.cfg.FabricControl.Enabled {
go func() {
a.logger.Info("fabric control quic starting", "addr", a.cfg.FabricControl.ListenAddr, "service", a.cfg.App.Name)
if err := a.fabricControl.ListenAndServe(ctx); err != nil {
errCh <- err
}
}()
}
select {
case <-ctx.Done():
@@ -184,6 +198,9 @@ func (a *App) Run(ctx context.Context) error {
if err := a.httpServer.Shutdown(shutdownCtx); err != nil {
return fmt.Errorf("shutdown http server: %w", err)
}
if a.fabricControl != nil {
_ = a.fabricControl.Close()
}
if err := a.redis(); err != nil {
return fmt.Errorf("close redis: %w", err)