security: remove hardcoded credentials and add WebSocket auth/CORS
- Clear default passwords in all service configs, require env vars
- Add JWT auth + role middleware to WebSocket endpoints
- Add origin whitelist to WebSocket upgrader (CORS protection)
- Fix goroutine leak in WS proxy (double errCh read)
- Update docker-compose to require secrets via ${VAR:?...} syntax
- Mark deprecated k8s configmap passwords as REPLACE_AT_DEPLOY_TIME
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
08b6c7cbeb
commit
24c449ecae
10
.env.example
10
.env.example
@ -1,18 +1,20 @@
|
||||
# iloom 微服务环境变量配置
|
||||
# Copy this file to .env and fill in real values. NEVER commit .env to git.
|
||||
|
||||
# MySQL (共享 muyu_wms 数据库)
|
||||
DB_DSN=root:muyu2026@tcp(mysql:3306)/muyu_wms?parseTime=true&charset=utf8mb4
|
||||
MYSQL_ROOT_PASSWORD=your_mysql_password_here
|
||||
DB_DSN=root:your_password@tcp(mysql:3306)/muyu_wms?parseTime=true&charset=utf8mb4
|
||||
|
||||
# JWT (复用 muyu 认证)
|
||||
JWT_SECRET=muyu-wms-jwt-secret-key-2026
|
||||
JWT_REFRESH_SECRET=muyu-wms-jwt-refresh-2026
|
||||
JWT_SECRET=your_jwt_secret_here
|
||||
JWT_REFRESH_SECRET=your_jwt_refresh_secret_here
|
||||
|
||||
# muyu gRPC 地址
|
||||
MUYU_SYSTEM_RPC_ADDR=system-rpc:9001
|
||||
MUYU_INVENTORY_RPC_ADDR=inventory-rpc:9002
|
||||
|
||||
# 服务间调用密钥
|
||||
INTERNAL_SERVICE_KEY=internal-service-shared-secret
|
||||
INTERNAL_SERVICE_KEY=your_internal_service_key_here
|
||||
|
||||
# 服务端口
|
||||
GATEWAY_PORT=8080
|
||||
|
||||
@ -13,9 +13,9 @@ type Config struct {
|
||||
func Load() *Config {
|
||||
return &Config{
|
||||
Port: getEnv("PORT", "8081"),
|
||||
DBDSN: getEnv("DB_DSN", "root:muyu2026@tcp(127.0.0.1:3306)/muyu_wms?parseTime=true&charset=utf8mb4"),
|
||||
JWTSecret: getEnv("JWT_SECRET", "muyu-wms-jwt-secret-key-2026"),
|
||||
JWTRefreshSecret: getEnv("JWT_REFRESH_SECRET", "muyu-wms-jwt-refresh-2026"),
|
||||
DBDSN: getEnv("DB_DSN", ""),
|
||||
JWTSecret: getEnv("JWT_SECRET", ""),
|
||||
JWTRefreshSecret: getEnv("JWT_REFRESH_SECRET", ""),
|
||||
SystemRPCAddr: getEnv("MUYU_SYSTEM_RPC_ADDR", "127.0.0.1:9001"),
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ services:
|
||||
- "${GATEWAY_PORT:-8080}:8080"
|
||||
environment:
|
||||
- PORT=8080
|
||||
- JWT_SECRET=${JWT_SECRET:-muyu-wms-jwt-secret-key-2026}
|
||||
- JWT_SECRET=${JWT_SECRET:?Set JWT_SECRET in .env}
|
||||
- AUTH_SERVICE_URL=http://auth-service:8081
|
||||
- PURCHASER_SERVICE_URL=http://purchaser-service:8082
|
||||
- TEXTILE_SERVICE_URL=http://textile-service:8083
|
||||
@ -37,9 +37,9 @@ services:
|
||||
- "${AUTH_SERVICE_PORT:-8081}:8081"
|
||||
environment:
|
||||
- PORT=8081
|
||||
- DB_DSN=root:muyu2026@tcp(muyu-mysql:3306)/muyu_wms?parseTime=true&charset=utf8mb4
|
||||
- JWT_SECRET=${JWT_SECRET:-muyu-wms-jwt-secret-key-2026}
|
||||
- JWT_REFRESH_SECRET=${JWT_REFRESH_SECRET:-muyu-wms-jwt-refresh-2026}
|
||||
- DB_DSN=${DB_DSN:?Set DB_DSN in .env}
|
||||
- JWT_SECRET=${JWT_SECRET:?Set JWT_SECRET in .env}
|
||||
- JWT_REFRESH_SECRET=${JWT_REFRESH_SECRET:?Set JWT_REFRESH_SECRET in .env}
|
||||
- MUYU_SYSTEM_RPC_ADDR=muyu-system-rpc:9001
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-qO-", "http://localhost:8081/health"]
|
||||
@ -58,8 +58,8 @@ services:
|
||||
- "${PURCHASER_SERVICE_PORT:-8082}:8082"
|
||||
environment:
|
||||
- PORT=8082
|
||||
- DB_DSN=root:muyu2026@tcp(muyu-mysql:3306)/muyu_wms?parseTime=true&charset=utf8mb4
|
||||
- INTERNAL_SERVICE_KEY=${INTERNAL_SERVICE_KEY:-internal-service-shared-secret}
|
||||
- DB_DSN=${DB_DSN:?Set DB_DSN in .env}
|
||||
- INTERNAL_SERVICE_KEY=${INTERNAL_SERVICE_KEY:?Set INTERNAL_SERVICE_KEY in .env}
|
||||
- MUYU_INVENTORY_RPC_ADDR=muyu-inventory-rpc:9002
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-qO-", "http://localhost:8082/health"]
|
||||
@ -78,8 +78,8 @@ services:
|
||||
- "${TEXTILE_SERVICE_PORT:-8083}:8083"
|
||||
environment:
|
||||
- PORT=8083
|
||||
- DB_DSN=root:muyu2026@tcp(muyu-mysql:3306)/muyu_wms?parseTime=true&charset=utf8mb4
|
||||
- INTERNAL_SERVICE_KEY=${INTERNAL_SERVICE_KEY:-internal-service-shared-secret}
|
||||
- DB_DSN=${DB_DSN:?Set DB_DSN in .env}
|
||||
- INTERNAL_SERVICE_KEY=${INTERNAL_SERVICE_KEY:?Set INTERNAL_SERVICE_KEY in .env}
|
||||
- PURCHASER_SERVICE_URL=http://purchaser-service:8082
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-qO-", "http://localhost:8083/health"]
|
||||
@ -98,8 +98,8 @@ services:
|
||||
- "${WASHING_SERVICE_PORT:-8084}:8084"
|
||||
environment:
|
||||
- PORT=8084
|
||||
- DB_DSN=root:muyu2026@tcp(muyu-mysql:3306)/muyu_wms?parseTime=true&charset=utf8mb4
|
||||
- INTERNAL_SERVICE_KEY=${INTERNAL_SERVICE_KEY:-internal-service-shared-secret}
|
||||
- DB_DSN=${DB_DSN:?Set DB_DSN in .env}
|
||||
- INTERNAL_SERVICE_KEY=${INTERNAL_SERVICE_KEY:?Set INTERNAL_SERVICE_KEY in .env}
|
||||
- PURCHASER_SERVICE_URL=http://purchaser-service:8082
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-qO-", "http://localhost:8084/health"]
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
# 集成模式(与 muyu-apiserver 联合):
|
||||
# 1. cd ../muyu-apiserver/deploy && docker compose up
|
||||
# 2. 设置环境变量指向 muyu 的服务:
|
||||
# export DB_DSN="root:muyu2026@tcp(muyu-mysql:3306)/muyu_wms?parseTime=true&charset=utf8mb4"
|
||||
# export DB_DSN="root:yourpassword@tcp(muyu-mysql:3306)/muyu_wms?parseTime=true&charset=utf8mb4"
|
||||
# export MUYU_SYSTEM_RPC_ADDR=muyu-system-rpc:9001
|
||||
# export MUYU_INVENTORY_RPC_ADDR=muyu-inventory-rpc:9002
|
||||
# 3. docker compose --profile integrated up
|
||||
@ -15,7 +15,7 @@ services:
|
||||
mysql:
|
||||
image: mysql:8.0
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-muyu2026}
|
||||
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:?Set MYSQL_ROOT_PASSWORD in .env}
|
||||
MYSQL_DATABASE: ${MYSQL_DATABASE:-muyu_wms}
|
||||
TZ: Asia/Shanghai
|
||||
ports:
|
||||
@ -26,7 +26,7 @@ services:
|
||||
- ./scripts/init-iloom.sql:/docker-entrypoint-initdb.d/02-iloom.sql
|
||||
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-p${MYSQL_ROOT_PASSWORD:-muyu2026}"]
|
||||
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-p${MYSQL_ROOT_PASSWORD}"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
@ -39,7 +39,7 @@ services:
|
||||
- "${GATEWAY_PORT:-8080}:8080"
|
||||
environment:
|
||||
- PORT=8080
|
||||
- JWT_SECRET=${JWT_SECRET:-muyu-wms-jwt-secret-key-2026}
|
||||
- JWT_SECRET=${JWT_SECRET:?Set JWT_SECRET in .env}
|
||||
- AUTH_SERVICE_URL=http://auth-service:8081
|
||||
- PURCHASER_SERVICE_URL=http://purchaser-service:8082
|
||||
- TEXTILE_SERVICE_URL=http://textile-service:8083
|
||||
@ -63,9 +63,9 @@ services:
|
||||
- "${AUTH_SERVICE_PORT:-8081}:8081"
|
||||
environment:
|
||||
- PORT=8081
|
||||
- DB_DSN=${DB_DSN:-root:muyu2026@tcp(mysql:3306)/muyu_wms?parseTime=true&charset=utf8mb4}
|
||||
- JWT_SECRET=${JWT_SECRET:-muyu-wms-jwt-secret-key-2026}
|
||||
- JWT_REFRESH_SECRET=${JWT_REFRESH_SECRET:-muyu-wms-jwt-refresh-2026}
|
||||
- DB_DSN=${DB_DSN:?Set DB_DSN in .env}
|
||||
- JWT_SECRET=${JWT_SECRET:?Set JWT_SECRET in .env}
|
||||
- JWT_REFRESH_SECRET=${JWT_REFRESH_SECRET:?Set JWT_REFRESH_SECRET in .env}
|
||||
- MUYU_SYSTEM_RPC_ADDR=${MUYU_SYSTEM_RPC_ADDR:-system-rpc:9001}
|
||||
depends_on:
|
||||
mysql:
|
||||
@ -84,8 +84,8 @@ services:
|
||||
- "${PURCHASER_SERVICE_PORT:-8082}:8082"
|
||||
environment:
|
||||
- PORT=8082
|
||||
- DB_DSN=${DB_DSN:-root:muyu2026@tcp(mysql:3306)/muyu_wms?parseTime=true&charset=utf8mb4}
|
||||
- INTERNAL_SERVICE_KEY=${INTERNAL_SERVICE_KEY:-internal-service-shared-secret}
|
||||
- DB_DSN=${DB_DSN:?Set DB_DSN in .env}
|
||||
- INTERNAL_SERVICE_KEY=${INTERNAL_SERVICE_KEY:?Set INTERNAL_SERVICE_KEY in .env}
|
||||
- MUYU_INVENTORY_RPC_ADDR=${MUYU_INVENTORY_RPC_ADDR:-inventory-rpc:9002}
|
||||
depends_on:
|
||||
mysql:
|
||||
@ -104,8 +104,8 @@ services:
|
||||
- "${TEXTILE_SERVICE_PORT:-8083}:8083"
|
||||
environment:
|
||||
- PORT=8083
|
||||
- DB_DSN=${DB_DSN:-root:muyu2026@tcp(mysql:3306)/muyu_wms?parseTime=true&charset=utf8mb4}
|
||||
- INTERNAL_SERVICE_KEY=${INTERNAL_SERVICE_KEY:-internal-service-shared-secret}
|
||||
- DB_DSN=${DB_DSN:?Set DB_DSN in .env}
|
||||
- INTERNAL_SERVICE_KEY=${INTERNAL_SERVICE_KEY:?Set INTERNAL_SERVICE_KEY in .env}
|
||||
- PURCHASER_SERVICE_URL=http://purchaser-service:8082
|
||||
depends_on:
|
||||
mysql:
|
||||
@ -124,8 +124,8 @@ services:
|
||||
- "${WASHING_SERVICE_PORT:-8084}:8084"
|
||||
environment:
|
||||
- PORT=8084
|
||||
- DB_DSN=${DB_DSN:-root:muyu2026@tcp(mysql:3306)/muyu_wms?parseTime=true&charset=utf8mb4}
|
||||
- INTERNAL_SERVICE_KEY=${INTERNAL_SERVICE_KEY:-internal-service-shared-secret}
|
||||
- DB_DSN=${DB_DSN:?Set DB_DSN in .env}
|
||||
- INTERNAL_SERVICE_KEY=${INTERNAL_SERVICE_KEY:?Set INTERNAL_SERVICE_KEY in .env}
|
||||
- PURCHASER_SERVICE_URL=http://purchaser-service:8082
|
||||
depends_on:
|
||||
mysql:
|
||||
|
||||
@ -58,10 +58,22 @@ func main() {
|
||||
washing.Any("/*path", proxy.NewReverseProxy(cfg.WashingServiceURL))
|
||||
}
|
||||
|
||||
// WebSocket proxy — route by role
|
||||
r.GET("/ws/v1/purchaser", proxy.NewWSProxy(cfg.PurchaserServiceURL))
|
||||
r.GET("/ws/v1/textile", proxy.NewWSProxy(cfg.TextileServiceURL))
|
||||
r.GET("/ws/v1/washing", proxy.NewWSProxy(cfg.WashingServiceURL))
|
||||
// WebSocket proxy — route by role (JWT + role check applied)
|
||||
r.GET("/ws/v1/purchaser",
|
||||
middleware.AuthRequired(cfg.JWTSecret),
|
||||
middleware.RoleRequired("purchaser"),
|
||||
proxy.NewWSProxy(cfg.PurchaserServiceURL, cfg.AllowOrigins),
|
||||
)
|
||||
r.GET("/ws/v1/textile",
|
||||
middleware.AuthRequired(cfg.JWTSecret),
|
||||
middleware.RoleRequired("textile"),
|
||||
proxy.NewWSProxy(cfg.TextileServiceURL, cfg.AllowOrigins),
|
||||
)
|
||||
r.GET("/ws/v1/washing",
|
||||
middleware.AuthRequired(cfg.JWTSecret),
|
||||
middleware.RoleRequired("washing"),
|
||||
proxy.NewWSProxy(cfg.WashingServiceURL, cfg.AllowOrigins),
|
||||
)
|
||||
|
||||
addr := ":" + cfg.Port
|
||||
log.Printf("gateway listening on %s", addr)
|
||||
|
||||
@ -18,7 +18,7 @@ type Config struct {
|
||||
func Load() *Config {
|
||||
return &Config{
|
||||
Port: getEnv("PORT", "8080"),
|
||||
JWTSecret: getEnv("JWT_SECRET", "muyu-wms-jwt-secret-key-2026"),
|
||||
JWTSecret: getEnv("JWT_SECRET", ""),
|
||||
AuthServiceURL: getEnv("AUTH_SERVICE_URL", "http://localhost:8081"),
|
||||
PurchaserServiceURL: getEnv("PURCHASER_SERVICE_URL", "http://localhost:8082"),
|
||||
TextileServiceURL: getEnv("TEXTILE_SERVICE_URL", "http://localhost:8083"),
|
||||
|
||||
@ -11,11 +11,29 @@ import (
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
var upgrader = websocket.Upgrader{
|
||||
CheckOrigin: func(r *http.Request) bool { return true },
|
||||
func newUpgrader(allowedOrigins []string) websocket.Upgrader {
|
||||
originSet := make(map[string]bool, len(allowedOrigins))
|
||||
for _, o := range allowedOrigins {
|
||||
originSet[strings.TrimSpace(o)] = true
|
||||
}
|
||||
|
||||
func NewWSProxy(targetURL string) gin.HandlerFunc {
|
||||
return websocket.Upgrader{
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
if originSet["*"] {
|
||||
return true
|
||||
}
|
||||
origin := r.Header.Get("Origin")
|
||||
if origin == "" {
|
||||
return false
|
||||
}
|
||||
return originSet[origin]
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func NewWSProxy(targetURL string, allowedOrigins []string) gin.HandlerFunc {
|
||||
upgrader := newUpgrader(allowedOrigins)
|
||||
|
||||
return func(c *gin.Context) {
|
||||
clientConn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
@ -50,6 +68,7 @@ func NewWSProxy(targetURL string) gin.HandlerFunc {
|
||||
go pumpMessages(backendConn, clientConn, errCh)
|
||||
|
||||
<-errCh
|
||||
<-errCh
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
## DEPRECATED: These raw K8s manifests are superseded by iloom-deploy/charts/.
|
||||
## DataSource values must be injected via Kubernetes Secrets at deploy time.
|
||||
## See iloom-deploy/charts/infra/templates/secrets.yaml for the Helm-based approach.
|
||||
##
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
@ -13,7 +17,7 @@ data:
|
||||
- etcd:2379
|
||||
Key: system.rpc
|
||||
|
||||
DataSource: root:muyu2026@tcp(mysql:3306)/muyu_wms?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai
|
||||
DataSource: REPLACE_AT_DEPLOY_TIME
|
||||
|
||||
Cache:
|
||||
- Host: redis:6379
|
||||
@ -38,7 +42,7 @@ data:
|
||||
- etcd:2379
|
||||
Key: inventory.rpc
|
||||
|
||||
DataSource: root:muyu2026@tcp(mysql:3306)/muyu_wms?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai
|
||||
DataSource: REPLACE_AT_DEPLOY_TIME
|
||||
|
||||
Cache:
|
||||
- Host: redis:6379
|
||||
@ -60,7 +64,7 @@ data:
|
||||
Port: 8888
|
||||
|
||||
Auth:
|
||||
AccessSecret: muyu-wms-jwt-secret-key-2026
|
||||
AccessSecret: REPLACE_AT_DEPLOY_TIME
|
||||
AccessExpire: 7200
|
||||
|
||||
SystemRpc:
|
||||
|
||||
@ -12,7 +12,7 @@ type Config struct {
|
||||
func Load() *Config {
|
||||
return &Config{
|
||||
Port: getEnv("PORT", "8082"),
|
||||
DBDSN: getEnv("DB_DSN", "root:muyu2026@tcp(127.0.0.1:3306)/muyu_wms?parseTime=true&charset=utf8mb4"),
|
||||
DBDSN: getEnv("DB_DSN", ""),
|
||||
InternalServiceKey: getEnv("INTERNAL_SERVICE_KEY", "dev-internal-key"),
|
||||
InventoryRPCAddr: getEnv("MUYU_INVENTORY_RPC_ADDR", "127.0.0.1:9002"),
|
||||
}
|
||||
|
||||
@ -12,7 +12,7 @@ type Config struct {
|
||||
func Load() *Config {
|
||||
return &Config{
|
||||
Port: getEnv("PORT", "8083"),
|
||||
DBDSN: getEnv("DB_DSN", "root:muyu2026@tcp(127.0.0.1:3306)/muyu_wms?parseTime=true&charset=utf8mb4"),
|
||||
DBDSN: getEnv("DB_DSN", ""),
|
||||
InternalServiceKey: getEnv("INTERNAL_SERVICE_KEY", "dev-internal-key"),
|
||||
PurchaserServiceURL: getEnv("PURCHASER_SERVICE_URL", "http://localhost:8082"),
|
||||
}
|
||||
|
||||
@ -12,7 +12,7 @@ type Config struct {
|
||||
func Load() *Config {
|
||||
return &Config{
|
||||
Port: getEnv("PORT", "8084"),
|
||||
DBDSN: getEnv("DB_DSN", "root:muyu2026@tcp(127.0.0.1:3306)/muyu_wms?parseTime=true&charset=utf8mb4"),
|
||||
DBDSN: getEnv("DB_DSN", ""),
|
||||
InternalServiceKey: getEnv("INTERNAL_SERVICE_KEY", "dev-internal-key"),
|
||||
PurchaserServiceURL: getEnv("PURCHASER_SERVICE_URL", "http://localhost:8082"),
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user