Files
lingniu-vehicle-ingest/vehicle-data-platform/apps/api/internal/app/oneos_local_isolation.go
T
lingniu d6b957dfc8 feat: sync vehicle monitoring and accumulated platform updates
Add monitoring metrics, numeric sorting, report-time display, vehicle number labels, and map layout fixes. Include accumulated mileage, hydrogen, OneOS authentication, open platform, ingestion, deployment, and UI updates.

Exclude temporary files, generated reports, and credentials. Validate frontend 632 tests and production build, API packages, vehicle-map tests, and release installer tests. Gateway suite has one pre-existing date-sensitive identity throttling test failure reproduced on the parent commit.
2026-10-26 18:05:59 +08:00

130 lines
6.4 KiB
Go

package app
import (
"fmt"
"net"
"net/http"
"strings"
"github.com/go-sql-driver/mysql"
"github.com/taosdata/driver-go/v3/taosRestful"
"lingniu/vehicle-data-platform/apps/api/internal/config"
)
// ValidateLocalIsolation runs before constructing any database, external client,
// scheduler or listener. It is an opt-in development boundary, not an auth bypass.
func validOneOSDevBaseURL(value string) bool {
return value == "https://dev-oneos.lnh2e.com/prod-api" || value == "https://dev-oneos.lnh2e.com/prod-api/" || value == "https://lnoneos.com/prod-api" || value == "https://lnoneos.com/prod-api/"
}
func ValidateLocalIsolation(cfg config.Config) error {
if err := validateOneOSDevDataProxy(cfg); err != nil {
return err
}
if cfg.OneOSLocalOnly && cfg.OneOSDevIntegration {
return fmt.Errorf("ONEOS_LOCAL_ONLY and ONEOS_DEV_INTEGRATION are mutually exclusive")
}
if cfg.OneOSLocalBackend && !cfg.OneOSDevIntegration {
return fmt.Errorf("local backend requires dev integration")
}
if !cfg.OneOSLocalOnly && !cfg.OneOSDevIntegration {
return nil
}
if cfg.OneOSDevIntegration {
if !validOneOSDevBaseURL(cfg.OneOSProfileBaseURL) {
return fmt.Errorf("ONEOS_DEV_INTEGRATION requires an explicitly allowed OneOS HTTPS origin")
}
if !validOneOSOrigin(cfg.OneOSPublicOrigin, true) {
return fmt.Errorf("ONEOS_DEV_INTEGRATION requires a literal loopback public origin")
}
}
host, _, err := net.SplitHostPort(cfg.HTTPAddr)
if err != nil || net.ParseIP(host) == nil || !net.ParseIP(host).IsLoopback() {
return fmt.Errorf("local OneOS testing requires an explicit loopback HTTP_ADDR")
}
if cfg.OneOSLocalBackend {
if !cfg.OneOSDevIntegration || cfg.OneOSLocalOnly || !cfg.OneOSAuthEnabled || !cfg.OneOSVehicleQueryScope || cfg.DataMode != "production" || cfg.AuthMode != "enforce" || cfg.OneOSDevDataProxyURL != "" {
return fmt.Errorf("local backend requires dev OneOS, vehicle-query scope, production store and enforced authentication")
}
db, err := mysql.ParseDSN(cfg.MySQLDSN)
if err != nil || db.Net != "tcp" || !loopbackAddress(db.Addr) {
return fmt.Errorf("local backend requires a loopback MySQL tunnel")
}
if cfg.RedisAddr != "" && !loopbackAddress(cfg.RedisAddr) {
return fmt.Errorf("local backend requires a loopback Redis tunnel")
}
if cfg.TDengineDSN != "" {
if cfg.TDengineDriver == "taosRestful" {
rest, err := taosRestful.ParseDSN(cfg.TDengineDSN)
if err != nil || rest.Net != "http" || rest.Port < 1 || rest.Port > 65535 || !loopbackAddress(net.JoinHostPort(rest.Addr, fmt.Sprint(rest.Port))) {
return fmt.Errorf("local backend requires a loopback TDengine REST tunnel")
}
} else {
if cfg.TDengineDriver != "" && cfg.TDengineDriver != "taosWS" {
return fmt.Errorf("local backend requires a supported TDengine driver")
}
marker := strings.LastIndex(cfg.TDengineDSN, "@ws(")
if marker < 0 {
return fmt.Errorf("local backend requires a TDengine websocket tunnel")
}
address := strings.SplitN(cfg.TDengineDSN[marker+4:], ")", 2)
if len(address) != 2 || !loopbackAddress(address[0]) {
return fmt.Errorf("local backend requires a loopback TDengine tunnel")
}
}
}
if cfg.DailyGeographyEnabled || cfg.HistoryCleanupAutomation || cfg.OneOSSSOEnabled || cfg.CapacityCheckBin != "" || cfg.AlertStreamMode != "disabled" || len(cfg.AlertStreamKafkaBrokers) != 0 || cfg.AlertNotificationSMSURL != "" || cfg.AlertNotificationEmailURL != "" || cfg.AlertNotificationWeComURL != "" {
return fmt.Errorf("local backend must not start production workers or notification delivery")
}
return nil
}
if cfg.DataMode != "mock" || cfg.AuthMode != "enforce" {
return fmt.Errorf("local OneOS testing requires DATA_MODE=mock and AUTH_MODE=enforce")
}
values := []string{
cfg.MySQLDSN, cfg.RedisAddr, cfg.TDengineDSN, cfg.CapacityCheckBin,
cfg.AMapAPIKey, cfg.AMapSecurityCode,
cfg.AuthToken, cfg.AuthTokensJSON, cfg.BootstrapAdminPassword,
cfg.AlertNotificationSMSURL, cfg.AlertNotificationEmailURL, cfg.AlertNotificationWeComURL,
cfg.OneOSIntrospectionURL,
}
if cfg.OneOSDevDataProxyURL == "" && (cfg.AMapWebJSKey != "" || cfg.AMapServiceHost != "") {
return fmt.Errorf("isolated OneOS testing rejects external map configuration")
}
if cfg.OneOSDevDataProxyURL != "" && cfg.AMapServiceHost != "" && cfg.AMapServiceHost != "/_AMapService" {
return fmt.Errorf("real-data development requires the local map proxy path")
}
for _, value := range values {
if strings.TrimSpace(value) != "" {
return fmt.Errorf("local OneOS testing rejects external data, legacy auth and notification configuration")
}
}
if cfg.DailyGeographyEnabled || cfg.HistoryCleanupAutomation || cfg.OneOSSSOEnabled ||
(cfg.AlertStreamMode != "" && cfg.AlertStreamMode != "disabled") || len(cfg.AlertStreamKafkaBrokers) != 0 {
return fmt.Errorf("local OneOS testing rejects external workers and legacy SSO")
}
return nil
}
func loopbackAddress(address string) bool {
host, _, err := net.SplitHostPort(address)
ip := net.ParseIP(host)
return err == nil && ip != nil && ip.IsLoopback()
}
func withOneOSLocalIsolationHeaders(next http.Handler, realData ...bool) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// The local UI must not load map SDKs, analytics, APIs or images remotely.
// Existing index.html contains an inline boot/error indicator. Inline code
// remains local; every external resource and connection is still excluded.
w.Header().Set("Content-Security-Policy", "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self'; worker-src 'self' blob:; frame-src 'none'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'")
if len(realData) > 0 && realData[0] {
w.Header().Set("Content-Security-Policy", "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://webapi.amap.com https://jsapi-service.amap.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https://*.amap.com https://*.autonavi.com; font-src 'self' data:; connect-src 'self' https://*.amap.com https://*.autonavi.com; worker-src 'self' blob:; frame-src 'none'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'")
}
w.Header().Set("Referrer-Policy", "no-referrer")
w.Header().Set("X-Content-Type-Options", "nosniff")
next.ServeHTTP(w, r)
})
}