Files
flvx/go-backend/internal/http/handler/relay_auth.go
T
sagit fadc2b6c12 fix(relay): authenticate endpoints and fail closed on missing routes (#569)
* fix(relay): authenticate endpoints and fail closed on missing routes

* fix(tunnel): retain chains until receiver replacement is acknowledged

* docs: record final relay security review and validation
2026-11-08 23:46:18 +08:00

126 lines
3.5 KiB
Go

package handler
import (
"encoding/json"
"fmt"
"go-backend/internal/http/client"
"go-backend/internal/relaysecurity"
"go-backend/internal/store/repo"
"gorm.io/gorm"
)
func (h *Handler) resolveTunnelRelayAuth(tx *gorm.DB, state *tunnelCreateState, create bool) error {
if state.Type != 2 || state.RelayAuthMode == relaysecurity.Legacy {
return nil
}
if state.RelayAuthMode != relaysecurity.Required {
return fmt.Errorf("unsupported relay authentication mode")
}
for i := range state.OutNodes {
c, err := h.repo.RelayEndpointCredentials(tx, state.TunnelID, state.OutNodes[i].NodeID, 3, create)
if err != nil {
return err
}
state.OutNodes[i].RelayAuth = c
}
for hop := range state.ChainHops {
for i := range state.ChainHops[hop] {
c, err := h.repo.RelayEndpointCredentials(tx, state.TunnelID, state.ChainHops[hop][i].NodeID, 2, create)
if err != nil {
return err
}
state.ChainHops[hop][i].RelayAuth = c
}
}
return nil
}
func (h *Handler) resolveBestExitRelayAuth(tunnelID int64, targets []tunnelRuntimeNode) error {
tunnel, err := h.repo.GetTunnelRecord(tunnelID)
if err != nil {
return err
}
if tunnel == nil {
return fmt.Errorf("tunnel not found")
}
return h.resolveTunnelRelayAuth(nil, &tunnelCreateState{TunnelID: tunnelID, Type: tunnel.Type, RelayAuthMode: tunnel.RelayAuthMode, OutNodes: targets}, false)
}
func relayAuthVersion(c *relaysecurity.Credentials) int {
if c == nil {
return 0
}
return c.Version
}
func decodePersistedRelayAuth(raw string) (*relaysecurity.Credentials, error) {
if raw == "" {
return nil, nil
}
var c relaysecurity.Credentials
if err := json.Unmarshal([]byte(raw), &c); err != nil {
return nil, fmt.Errorf("invalid persisted relay authentication")
}
if err := c.Validate(); err != nil {
return nil, err
}
return &c, nil
}
func validateFederationRelayAuth(persisted string, c *relaysecurity.Credentials, targets []federationRuntimeTarget) error {
old, err := decodePersistedRelayAuth(persisted)
if err != nil {
return err
}
if old != nil && (c == nil || *old != *c) {
return fmt.Errorf("authenticated relay cannot be downgraded or rotated by reapply")
}
if c != nil {
if err := c.Validate(); err != nil {
return err
}
for _, target := range targets {
if err := target.RelayAuth.Validate(); err != nil {
return err
}
}
} else {
for _, target := range targets {
if target.RelayAuth != nil {
if err := target.RelayAuth.Validate(); err != nil {
return err
}
}
}
}
return nil
}
func (h *Handler) preflightFederationRelayAuth(state *tunnelCreateState, domain string) error {
if state.RelayAuthMode != relaysecurity.Required || state.Type != 2 {
return nil
}
for _, node := range state.Nodes {
if node == nil || node.IsRemote != 1 {
continue
}
info, err := client.NewFederationClient().Connect(node.RemoteURL, node.RemoteToken, domain)
if err != nil {
return fmt.Errorf("relay authentication capability check failed for remote node %d", node.ID)
}
if info.RelayAuthVersion != relaysecurity.Version {
return fmt.Errorf("remote node %d does not support required relay authentication", node.ID)
}
}
return nil
}
func decodeRuntimeRelayAuth(runtime *repo.PeerShareRuntime) (*relaysecurity.Credentials, error) {
c, err := decodePersistedRelayAuth(runtime.RelayAuth)
if err != nil {
return nil, err
}
if runtime.RelayAuthVersion != relayAuthVersion(c) {
return nil, fmt.Errorf("persisted relay authentication version or credentials unavailable")
}
return c, nil
}