package config import ( "log" "os" "strings" ) // Valores que estuvieron publicados en el repositorio y deben considerarse // comprometidos. Si siguen en uso, cualquiera que haya visto el repo puede // fabricarse una sesión de administrador o descifrar las contraseñas guardadas. const ( jwtAppComprometido = "SECRET_APP" jwtApiComprometido = "SECRET_API" appKeyComprometida = "1894cde6c936a294a478cff0a9227fd276d86df6573b51af5dc59c9064edf426" ) // VerificarSecretos se ejecuta al arrancar. Los secretos JWT comprometidos // abortan el arranque (permiten suplantar a cualquier usuario y el único costo // de rotarlos es volver a iniciar sesión). La clave de cifrado solo advierte, // porque cambiarla invalida las contraseñas de integraciones ya guardadas y eso // requiere volver a capturarlas a mano desde el panel. func (cfg *AppConfig) VerificarSecretos() { var criticos []string if esSecretoDebil(cfg.Token.AppJwtSecret, jwtAppComprometido) { criticos = append(criticos, "APP_JWT_SECRET") } if esSecretoDebil(cfg.Token.ApiJwtSecret, jwtApiComprometido) { criticos = append(criticos, "API_JWT_SECRET") } if len(criticos) > 0 { log.Printf("[SEGURIDAD] Los siguientes secretos siguen con el valor publicado en el repositorio: %s", strings.Join(criticos, ", ")) log.Printf("[SEGURIDAD] Con ese valor cualquiera puede firmarse una cookie de sesión y entrar como administrador.") log.Printf("[SEGURIDAD] Define variables de entorno con valores nuevos y aleatorios antes de arrancar, por ejemplo:") for _, nombre := range criticos { log.Printf("[SEGURIDAD] %s=$(openssl rand -hex 32)", nombre) } log.Fatalf("[SEGURIDAD] Arranque abortado para no exponer el sistema. Al rotarlos, las sesiones abiertas se cierran y hay que volver a iniciar sesión.") } if cfg.Server.Key == "" || cfg.Server.Key == appKeyComprometida { log.Printf("[SEGURIDAD] APP_KEY tiene el valor por defecto publicado en el repositorio.") log.Printf("[SEGURIDAD] Con esa clave, cualquiera con acceso a la base de datos puede descifrar las contraseñas SMTP y de integraciones.") log.Printf("[SEGURIDAD] Define APP_KEY=$(openssl rand -hex 32) y vuelve a guardar las contraseñas de SMTP e integraciones desde el panel") log.Printf("[SEGURIDAD] (al cambiar la clave, las contraseñas cifradas con la anterior dejan de poder descifrarse).") } } // esSecretoDebil indica si el secreto está vacío o es el valor comprometido. func esSecretoDebil(valor, comprometido string) bool { v := strings.TrimSpace(valor) return v == "" || v == comprometido } // SecretoDesdeEntorno lee un secreto del entorno con un valor por defecto. // Se usa para credenciales que no deben vivir en config.yml. func SecretoDesdeEntorno(nombre, porDefecto string) string { if v := strings.TrimSpace(os.Getenv(nombre)); v != "" { return v } return porDefecto }