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:
Chever John 2026-06-17 23:16:17 +08:00
parent 08b6c7cbeb
commit 24c449ecae
No known key found for this signature in database
GPG Key ID: 2894D52ED1211DF0
11 changed files with 81 additions and 44 deletions

View File

@ -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

View File

@ -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"),
}
}

View File

@ -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"]

View File

@ -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:

View File

@ -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)

View File

@ -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"),

View File

@ -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
}
}

View File

@ -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:

View File

@ -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"),
}

View File

@ -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"),
}

View File

@ -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"),
}