diff --git a/config/config_defaults.go b/config/config_defaults.go index fd3dce81f..6cecf64b4 100644 --- a/config/config_defaults.go +++ b/config/config_defaults.go @@ -159,53 +159,57 @@ const CGRATES_CFG_JSON = ` }, -"stor_db": { // database used to store offline tariff plans and CDRs - "db_type": "*mysql", // stor database type to use: <*mongo|*mysql|*postgres|*internal> - "db_host": "127.0.0.1", // the host to connect to - "db_port": 3306, // the port to reach the stor_db - "db_name": "cgrates", // stor database name - "db_user": "cgrates", // username to use when connecting to stor_db - "db_password": "CGRateS.org", // password to use when connecting to stor_db - "string_indexed_fields": [], // indexes on cdrs table to speed up queries, used in case of *mongo and *internal - "prefix_indexed_fields":[], // prefix indexes on cdrs table to speed up queries, used in case of *internal +"stor_db": { // database used to store offline tariff plans and CDRs + "db_type": "*mysql", // stor database type to use: <*mongo|*mysql|*postgres|*internal> + "db_host": "127.0.0.1", // the host to connect to + "db_port": 3306, // the port to reach the stor_db + "db_name": "cgrates", // stor database name + "db_user": "cgrates", // username to use when connecting to stor_db + "db_password": "CGRateS.org", // password to use when connecting to stor_db + "string_indexed_fields": [], // indexes on cdrs table to speed up queries, used in case of *mongo and *internal + "prefix_indexed_fields":[], // prefix indexes on cdrs table to speed up queries, used in case of *internal "opts": { - "sqlMaxOpenConns": 100, // maximum database connections opened, not applying for mongo - "sqlMaxIdleConns": 10, // maximum database connections idle, not applying for mongo - "sqlConnMaxLifetime": "0", // maximum amount of time a connection may be reused (0 for unlimited), not applying for mongo - "mysqlDSNParams": {}, // DSN extra paramss - "mongoQueryTimeout": "10s", // timeout for query when mongo is used - "mongoConnScheme": "mongodb", // scheme for MongoDB connection - "pgSSLMode": "disable", // pgSSLMode in case of *postgres - "mysqlLocation": "Local", // the location the time from mysql is retrieved - "pgSchema":"", //*postgres schema to use + "sqlMaxOpenConns": 100, // maximum database connections opened, not applying for mongo + "sqlMaxIdleConns": 10, // maximum database connections idle, not applying for mongo + "sqlConnMaxLifetime": "0", // maximum amount of time a connection may be reused (0 for unlimited), not applying for mongo + "mysqlDSNParams": {}, // DSN extra paramss + "mongoQueryTimeout": "10s", // timeout for query when mongo is used + "mongoConnScheme": "mongodb", // scheme for MongoDB connection + "mysqlLocation": "Local", // the location the time from mysql is retrieved + "pgSSLMode": "disable", // determines whether or with what priority a secure SSL TCP/IP connection will be negotiated with the server + "pgSSLCert": "", // file name of the client SSL certificate, replacing the default ~/.postgresql/postgresql.crt + "pgSSLKey": "", // location for the secret key used for the client certificate + "pgSSLPassword: "", // specifies the password for the secret key specified in pgSSLKey + "pgSSLRootCert": "", // name of a file containing SSL certificate authority (CA) certificate(s) + "pgSchema":"" // postgres schema to use }, "items":{ - "*session_costs": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, - "*cdrs": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, - "*tp_timings": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, + "*session_costs": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, + "*cdrs": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, + "*tp_timings": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, "*tp_destinations": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, - "*tp_rates": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, - "*tp_destination_rates": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, - "*tp_rating_plans": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, - "*tp_rating_profiles": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, - "*tp_shared_groups": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, - "*tp_actions": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, - "*tp_action_plans": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, - "*tp_action_triggers": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, - "*tp_account_actions": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, - "*tp_resources": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, + "*tp_rates": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, + "*tp_destination_rates": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, + "*tp_rating_plans": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, + "*tp_rating_profiles": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, + "*tp_shared_groups": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, + "*tp_actions": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, + "*tp_action_plans": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, + "*tp_action_triggers": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, + "*tp_account_actions": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, + "*tp_resources": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, "*tp_stats": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, - "*tp_rankings": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, - "*tp_trends": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, - "*tp_thresholds": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, + "*tp_rankings": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, + "*tp_trends": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, + "*tp_thresholds": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, "*tp_filters": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, - "*tp_routes": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, - "*tp_attributes": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, - "*tp_chargers": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, - "*versions": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, - "*tp_dispatcher_profiles": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, - "*tp_dispatcher_hosts": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, - }, + "*tp_routes": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, + "*tp_attributes": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, + "*tp_chargers": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, + "*versions": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, + "*tp_dispatcher_profiles": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false}, + "*tp_dispatcher_hosts": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate":false} + } }, diff --git a/config/config_json_test.go b/config/config_json_test.go index bb657fad9..b5778f79b 100644 --- a/config/config_json_test.go +++ b/config/config_json_test.go @@ -588,7 +588,7 @@ func TestDfStorDBJsonCfg(t *testing.T) { MongoConnScheme: utils.StringPointer("mongodb"), SQLConnMaxLifetime: utils.StringPointer("0"), MySQLDSNParams: make(map[string]string), - PgSSLMode: utils.StringPointer(utils.PostgresSSLModeDisable), + PgSSLMode: utils.PgSSLModeDisable, MySQLLocation: utils.StringPointer("Local"), PgSchema: utils.StringPointer(""), }, diff --git a/config/configsanity.go b/config/configsanity.go index 4572a1a3f..177623e08 100644 --- a/config/configsanity.go +++ b/config/configsanity.go @@ -964,10 +964,10 @@ func (cfg *CGRConfig) checkConfigSanity() error { } // StorDB sanity checks if cfg.storDbCfg.Type == utils.MetaPostgres { - if !slices.Contains([]string{utils.PostgresSSLModeDisable, utils.PostgressSSLModeAllow, - utils.PostgresSSLModePrefer, utils.PostgressSSLModeRequire, utils.PostgresSSLModeVerifyCa, - utils.PostgresSSLModeVerifyFull}, cfg.storDbCfg.Opts.PgSSLMode) { - return fmt.Errorf("<%s> unsupported sslmode for storDB", utils.StorDB) + if !slices.Contains([]string{utils.PgSSLModeDisable, utils.PgSSLModeAllow, + utils.PgSSLModePrefer, utils.PgSSLModeRequire, utils.PgSSLModeVerifyCA, + utils.PgSSLModeVerifyFull}, cfg.storDbCfg.Opts.PgSSLMode) { + return fmt.Errorf("<%s> unsupported pgSSLMode (sslmode) in storDB configuration", utils.StorDB) } } // DataDB sanity checks diff --git a/config/configsanity_test.go b/config/configsanity_test.go index 52f6875d6..4799e103d 100644 --- a/config/configsanity_test.go +++ b/config/configsanity_test.go @@ -1700,7 +1700,7 @@ func TestConfigSanityStorDB(t *testing.T) { PgSSLMode: "wrongSSLMode", }, } - expected := " unsupported sslmode for storDB" + expected := " unsupported pgSSLMode (sslmode) in storDB configuration" if err := cfg.checkConfigSanity(); err == nil || err.Error() != expected { t.Errorf("Expecting: %+q received: %+q", expected, err) } diff --git a/config/libconfig_json.go b/config/libconfig_json.go index 0b4d86396..92a18971e 100644 --- a/config/libconfig_json.go +++ b/config/libconfig_json.go @@ -123,9 +123,13 @@ type DBOptsJson struct { SQLMaxIdleConns *int `json:"sqlMaxIdleConns"` SQLConnMaxLifetime *string `json:"sqlConnMaxLifetime"` MySQLDSNParams map[string]string `json:"mysqlDSNParams"` - PgSSLMode *string `json:"pgSSLMode"` - MySQLLocation *string `json:"mysqlLocation"` + PgSSLMode string `json:"pgSSLMode"` + PgSSLCert string `json:"pgSSLCert"` + PgSSLKey string `json:"pgSSLKey"` + PgSSLPassword string `json:"pgSSLPassword"` + PgSSLRootCert string `json:"pgSSLRootCert"` PgSchema *string `json:"pgSchema"` + MySQLLocation *string `json:"mysqlLocation"` } // Database config diff --git a/config/stordbcfg.go b/config/stordbcfg.go index cf1a64099..b18ab6a84 100644 --- a/config/stordbcfg.go +++ b/config/stordbcfg.go @@ -34,16 +34,20 @@ type StorDBOpts struct { MongoQueryTimeout time.Duration MongoConnScheme string PgSSLMode string + PgSSLCert string + PgSSLKey string + PgSSLPassword string + PgSSLRootCert string + PgSchema string MySQLLocation string MySQLDSNParams map[string]string - PgSchema string } // StorDbCfg StroreDb config type StorDbCfg struct { Type string // Should reflect the database type used to store logs Host string // The host to connect to. Values that start with / are for UNIX domain sockets. - Port string // Th e port to bind to. + Port string // The port to bind to. Name string // The name of the database to connect to. User string // The user to sign in as. Password string // The user's password. @@ -82,15 +86,17 @@ func (dbOpts *StorDBOpts) loadFromJSONCfg(jsnCfg *DBOptsJson) (err error) { if jsnCfg.MongoConnScheme != nil { dbOpts.MongoConnScheme = *jsnCfg.MongoConnScheme } - if jsnCfg.PgSSLMode != nil { - dbOpts.PgSSLMode = *jsnCfg.PgSSLMode + dbOpts.PgSSLMode = jsnCfg.PgSSLMode + dbOpts.PgSSLCert = jsnCfg.PgSSLCert + dbOpts.PgSSLKey = jsnCfg.PgSSLKey + dbOpts.PgSSLPassword = jsnCfg.PgSSLPassword + dbOpts.PgSSLRootCert = jsnCfg.PgSSLRootCert + if jsnCfg.PgSchema != nil { + dbOpts.PgSchema = *jsnCfg.PgSchema } if jsnCfg.MySQLLocation != nil { dbOpts.MySQLLocation = *jsnCfg.MySQLLocation } - if jsnCfg.PgSchema != nil { - dbOpts.PgSchema = *jsnCfg.PgSchema - } return } @@ -173,8 +179,12 @@ func (dbOpts *StorDBOpts) Clone() *StorDBOpts { MongoQueryTimeout: dbOpts.MongoQueryTimeout, MongoConnScheme: dbOpts.MongoConnScheme, PgSSLMode: dbOpts.PgSSLMode, - MySQLLocation: dbOpts.MySQLLocation, + PgSSLCert: dbOpts.PgSSLCert, + PgSSLKey: dbOpts.PgSSLKey, + PgSSLPassword: dbOpts.PgSSLPassword, + PgSSLRootCert: dbOpts.PgSSLRootCert, PgSchema: dbOpts.PgSchema, + MySQLLocation: dbOpts.MySQLLocation, } } @@ -223,8 +233,20 @@ func (dbcfg *StorDbCfg) AsMapInterface() (mp map[string]any) { utils.MongoQueryTimeoutCfg: dbcfg.Opts.MongoQueryTimeout.String(), utils.MongoConnSchemeCfg: dbcfg.Opts.MongoConnScheme, utils.PgSSLModeCfg: dbcfg.Opts.PgSSLMode, - utils.MysqlLocation: dbcfg.Opts.MySQLLocation, utils.PgSchema: dbcfg.Opts.PgSchema, + utils.MysqlLocation: dbcfg.Opts.MySQLLocation, + } + if dbcfg.Opts.PgSSLCert != "" { + opts[utils.PgSSLCertCfg] = dbcfg.Opts.PgSSLCert + } + if dbcfg.Opts.PgSSLKey != "" { + opts[utils.PgSSLKeyCfg] = dbcfg.Opts.PgSSLKey + } + if dbcfg.Opts.PgSSLPassword != "" { + opts[utils.PgSSLPasswordCfg] = dbcfg.Opts.PgSSLPassword + } + if dbcfg.Opts.PgSSLRootCert != "" { + opts[utils.PgSSLRootCertCfg] = dbcfg.Opts.PgSSLRootCert } mp = map[string]any{ utils.DataDbTypeCfg: dbcfg.Type, diff --git a/config/stordbcfg_test.go b/config/stordbcfg_test.go index 6615e57eb..86c45d9b7 100644 --- a/config/stordbcfg_test.go +++ b/config/stordbcfg_test.go @@ -54,6 +54,7 @@ func TestStoreDbCfgloadFromJsonCfgCase1(t *testing.T) { MySQLDSNParams: make(map[string]string), MySQLLocation: utils.StringPointer("UTC"), MongoConnScheme: utils.StringPointer("mongodb"), + PgSSLMode: "disable", }, } expected := &StorDbCfg{ diff --git a/engine/storage_postgres.go b/engine/storage_postgres.go index 444618c4c..6cb756793 100644 --- a/engine/storage_postgres.go +++ b/engine/storage_postgres.go @@ -28,32 +28,48 @@ import ( ) // NewPostgresStorage returns the posgres storDB -func NewPostgresStorage(host, port, name, user, password, sslmode, pgSchema string, maxConn, maxIdleConn int, connMaxLifetime time.Duration) (*SQLStorage, error) { - connectString := fmt.Sprintf("host=%s port=%s dbname=%s user=%s password=%s sslmode=%s", host, port, name, user, password, sslmode) - db, err := gorm.Open(postgres.Open(connectString), &gorm.Config{AllowGlobalUpdate: true}) +func NewPostgresStorage(host, port, name, user, password, pgSchema, + sslmode, sslcert, sslkey, sslpassword, sslrootcert string, + maxConn, maxIdleConn int, connMaxLifetime time.Duration) (*SQLStorage, error) { + connStr := fmt.Sprintf( + "host=%s port=%s dbname=%s user=%s password=%s sslmode=%s", + host, port, name, user, password, sslmode) + if sslcert != "" { + connStr = connStr + " sslcert=" + sslcert + } + if sslkey != "" { + connStr = connStr + " sslkey=" + sslkey + } + if sslpassword != "" { + connStr = connStr + " sslpassword=" + sslpassword + } + if sslrootcert != "" { + connStr = connStr + " sslrootcert=" + sslrootcert + } + db, err := gorm.Open(postgres.Open(connStr), &gorm.Config{AllowGlobalUpdate: true}) if err != nil { return nil, err } - postgressStorage := new(PostgresStorage) - if postgressStorage.Db, err = db.DB(); err != nil { + pgStor := new(PostgresStorage) + if pgStor.Db, err = db.DB(); err != nil { return nil, err } - if err = postgressStorage.Db.Ping(); err != nil { + if err = pgStor.Db.Ping(); err != nil { return nil, err } if pgSchema != "" { - postgressStorage.Db.Exec(fmt.Sprintf("set search_path='%s'", pgSchema)) + pgStor.Db.Exec(fmt.Sprintf("set search_path='%s'", pgSchema)) } - postgressStorage.Db.SetMaxIdleConns(maxIdleConn) - postgressStorage.Db.SetMaxOpenConns(maxConn) - postgressStorage.Db.SetConnMaxLifetime(connMaxLifetime) + pgStor.Db.SetMaxIdleConns(maxIdleConn) + pgStor.Db.SetMaxOpenConns(maxConn) + pgStor.Db.SetConnMaxLifetime(connMaxLifetime) //db.LogMode(true) - postgressStorage.db = db + pgStor.db = db return &SQLStorage{ - Db: postgressStorage.Db, - db: postgressStorage.db, - StorDB: postgressStorage, - SQLImpl: postgressStorage, + Db: pgStor.Db, + db: pgStor.db, + StorDB: pgStor, + SQLImpl: pgStor, }, nil } diff --git a/engine/storage_utils.go b/engine/storage_utils.go index f34e3fc09..724e8a54f 100644 --- a/engine/storage_utils.go +++ b/engine/storage_utils.go @@ -70,7 +70,8 @@ func NewStorDBConn(dbType, host, port, name, user, pass, marshaler string, case utils.MetaMongo: db, err = NewMongoStorage(opts.MongoConnScheme, host, port, name, user, pass, marshaler, utils.StorDB, stringIndexedFields, opts.MongoQueryTimeout) case utils.MetaPostgres: - db, err = NewPostgresStorage(host, port, name, user, pass, opts.PgSSLMode, opts.PgSchema, + db, err = NewPostgresStorage(host, port, name, user, pass, opts.PgSchema, opts.PgSSLMode, + opts.PgSSLCert, opts.PgSSLKey, opts.PgSSLPassword, opts.PgSSLRootCert, opts.SQLMaxOpenConns, opts.SQLMaxIdleConns, opts.SQLConnMaxLifetime) case utils.MetaMySQL: db, err = NewMySQLStorage(host, port, name, user, pass, opts.SQLMaxOpenConns, opts.SQLMaxIdleConns, diff --git a/engine/z_stordb_it_test.go b/engine/z_stordb_it_test.go index 22366baa0..680f76a3b 100644 --- a/engine/z_stordb_it_test.go +++ b/engine/z_stordb_it_test.go @@ -108,7 +108,10 @@ func TestStorDBit(t *testing.T) { if storDB, err = NewPostgresStorage(storDBCfg.StorDbCfg().Host, storDBCfg.StorDbCfg().Port, storDBCfg.StorDbCfg().Name, storDBCfg.StorDbCfg().User, storDBCfg.StorDbCfg().Password, - storDBCfg.StorDbCfg().Opts.PgSSLMode, storDBCfg.StorDbCfg().Opts.PgSchema, 100, 10, 0); err != nil { + storDBCfg.StorDbCfg().Opts.PgSchema, storDBCfg.StorDbCfg().Opts.PgSSLMode, + storDBCfg.StorDbCfg().Opts.PgSSLCert, storDBCfg.StorDbCfg().Opts.PgSSLKey, + storDBCfg.StorDbCfg().Opts.PgSSLPassword, storDBCfg.StorDbCfg().Opts.PgSSLRootCert, + 100, 10, 0); err != nil { t.Fatal(err) } storDB.(*SQLStorage).db.Config.Logger = logger.Default.LogMode(logger.Silent) diff --git a/services/stordb.go b/services/stordb.go index 8dc385bd7..3dae2b9cb 100644 --- a/services/stordb.go +++ b/services/stordb.go @@ -196,5 +196,9 @@ func (db *StorDBService) needsConnectionReload() bool { return true } return db.cfg.StorDbCfg().Type == utils.MetaPostgres && - db.oldDBCfg.Opts.PgSSLMode != db.cfg.StorDbCfg().Opts.PgSSLMode + (db.oldDBCfg.Opts.PgSSLMode != db.cfg.StorDbCfg().Opts.PgSSLMode || + db.oldDBCfg.Opts.PgSSLCert != db.cfg.StorDbCfg().Opts.PgSSLCert || + db.oldDBCfg.Opts.PgSSLKey != db.cfg.StorDbCfg().Opts.PgSSLKey || + db.oldDBCfg.Opts.PgSSLPassword != db.cfg.StorDbCfg().Opts.PgSSLPassword || + db.oldDBCfg.Opts.PgSSLRootCert != db.cfg.StorDbCfg().Opts.PgSSLRootCert) } diff --git a/utils/consts.go b/utils/consts.go index 0820524a9..e7c98b80f 100644 --- a/utils/consts.go +++ b/utils/consts.go @@ -2062,12 +2062,12 @@ const ( // StorDB var ( - PostgresSSLModeDisable = "disable" - PostgressSSLModeAllow = "allow" - PostgresSSLModePrefer = "prefer" - PostgressSSLModeRequire = "require" - PostgresSSLModeVerifyCa = "verify-ca" - PostgresSSLModeVerifyFull = "verify-full" + PgSSLModeDisable = "disable" + PgSSLModeAllow = "allow" + PgSSLModePrefer = "prefer" + PgSSLModeRequire = "require" + PgSSLModeVerifyCA = "verify-ca" + PgSSLModeVerifyFull = "verify-full" ) // GeneralCfg @@ -2112,12 +2112,15 @@ const ( MongoQueryTimeoutCfg = "mongoQueryTimeout" MongoConnSchemeCfg = "mongoConnScheme" PgSSLModeCfg = "pgSSLMode" + PgSSLCertCfg = "pgSSLCert" + PgSSLKeyCfg = "pgSSLKey" + PgSSLPasswordCfg = "pgSSLPassword" + PgSSLRootCertCfg = "pgSSLRootCert" + PgSchema = "pgSchema" ItemsCfg = "items" OptsCfg = "opts" Tenants = "tenants" MysqlLocation = "mysqlLocation" - SSLMode = "sslMode" - PgSchema = "pgSchema" ) // DataDbCfg