Latest Updates done, before integrating
This commit is contained in:
@@ -2,6 +2,7 @@ package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
@@ -191,7 +192,8 @@ func (f *ForecastConfig) FetchIntervalParsed() time.Duration {
|
||||
// EMSConfig holds operational settings for the EMS daemon.
|
||||
type EMSConfig struct {
|
||||
PollInterval string `yaml:"poll_interval"`
|
||||
ListenAddr string `yaml:"listen_addr"`
|
||||
ListenAddr string `yaml:"listen_addr"` // web UI address (authenticated)
|
||||
MetricsAddr string `yaml:"metrics_addr"` // Prometheus /metrics address (no auth, internal only)
|
||||
LogLevel string `yaml:"log_level"`
|
||||
StateFile string `yaml:"state_file"`
|
||||
RecoveryTimeout string `yaml:"recovery_timeout"`
|
||||
@@ -201,6 +203,8 @@ type EMSConfig struct {
|
||||
WWBoostDisableFile string `yaml:"ww_boost_disable_file"` // flag file path: presence = WW boost disabled
|
||||
TripGoalFile string `yaml:"trip_goal_file"` // persisted active trip goal
|
||||
SessionLogFile string `yaml:"session_log_file"` // JSONL log of completed charge sessions
|
||||
HTTPUsername string `yaml:"http_username"` // login username (empty = no auth)
|
||||
HTTPPassword string `yaml:"http_password"` // login password
|
||||
}
|
||||
|
||||
func (e *EMSConfig) PollIntervalParsed() time.Duration {
|
||||
@@ -226,6 +230,13 @@ func (e *EMSConfig) OverrideTimeoutParsed() time.Duration {
|
||||
|
||||
// Load reads and parses the YAML config file at the given path.
|
||||
func Load(path string) (*Config, error) {
|
||||
// Warn if config file is readable by group or others (contains credentials).
|
||||
if info, err := os.Stat(path); err == nil {
|
||||
if info.Mode().Perm()&0o077 != 0 {
|
||||
fmt.Fprintf(os.Stderr, "WARNING: config file %s has permissions %o — should be 0600 (contains credentials)\n", path, info.Mode().Perm())
|
||||
}
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading config file: %w", err)
|
||||
@@ -256,5 +267,21 @@ func (c *Config) validate() error {
|
||||
if c.EMS.ListenAddr == "" {
|
||||
return fmt.Errorf("ems.listen_addr is required")
|
||||
}
|
||||
// Validate Shelly IPs to prevent SSRF via config manipulation.
|
||||
for name, dev := range map[string]ShellyDevice{
|
||||
"sg_ready": c.Shelly.SGReady,
|
||||
"wallbox_a": c.Shelly.WallboxA,
|
||||
"wallbox_b": c.Shelly.WallboxB,
|
||||
} {
|
||||
if dev.IP != "" && net.ParseIP(dev.IP) == nil {
|
||||
return fmt.Errorf("shelly.%s.ip %q is not a valid IP address", name, dev.IP)
|
||||
}
|
||||
}
|
||||
if c.EMS.HTTPUsername != "" && c.EMS.HTTPPassword == "" {
|
||||
return fmt.Errorf("ems.http_username is set but ems.http_password is empty")
|
||||
}
|
||||
if c.EMS.HTTPPassword != "" && c.EMS.HTTPUsername == "" {
|
||||
return fmt.Errorf("ems.http_password is set but ems.http_username is empty")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user