diff --git a/apier/cdre.go b/apier/cdre.go index 4866b7bcf..505964e5e 100644 --- a/apier/cdre.go +++ b/apier/cdre.go @@ -132,7 +132,7 @@ func (self *ApierV1) ExportCdrsToFile(attr utils.AttrExpFileCdrs, reply *utils.E } defer fileOut.Close() cdrexp, err := cdre.NewCdrExporter(cdrs, self.LogDb, exportTemplate, exportId, - dataUsageMultiplyFactor, costMultiplyFactor, costShiftDigits, roundingDecimals, self.Config.RoundingDecimals, maskDestId, maskLen) + dataUsageMultiplyFactor, costMultiplyFactor, costShiftDigits, roundingDecimals, self.Config.RoundingDecimals, maskDestId, maskLen, self.Config.HttpSkipTlsVerify) if err != nil { return fmt.Errorf("%s:%s", utils.ERR_SERVER_ERROR, err.Error()) } diff --git a/cdre/cdrexporter.go b/cdre/cdrexporter.go index fedf0aa5d..c21185d0a 100644 --- a/cdre/cdrexporter.go +++ b/cdre/cdrexporter.go @@ -52,7 +52,7 @@ const ( var err error func NewCdrExporter(cdrs []*utils.StoredCdr, logDb engine.LogStorage, exportTpl *config.CdreConfig, exportId string, - dataUsageMultiplyFactor, costMultiplyFactor float64, costShiftDigits, roundDecimals, cgrPrecision int, maskDestId string, maskLen int) (*CdrExporter, error) { + dataUsageMultiplyFactor, costMultiplyFactor float64, costShiftDigits, roundDecimals, cgrPrecision int, maskDestId string, maskLen int, httpSkipTlsCheck bool) (*CdrExporter, error) { if len(cdrs) == 0 { // Nothing to export return nil, nil } @@ -67,6 +67,7 @@ func NewCdrExporter(cdrs []*utils.StoredCdr, logDb engine.LogStorage, exportTpl roundDecimals: roundDecimals, cgrPrecision: cgrPrecision, maskDestId: maskDestId, + httpSkipTlsCheck: httpSkipTlsCheck, maskLen: maskLen, negativeExports: make(map[string]string), } @@ -85,6 +86,7 @@ type CdrExporter struct { costShiftDigits, roundDecimals, cgrPrecision int maskDestId string maskLen int + httpSkipTlsCheck bool header, trailer []string // Header and Trailer fields content [][]string // Rows of cdr fields firstCdrATime, lastCdrATime time.Time @@ -261,8 +263,12 @@ func (cdre *CdrExporter) processCdr(cdr *utils.StoredCdr) error { case utils.CDRFIELD: outVal, err = cdre.cdrFieldValue(cdr, cfgFld.ValueAsRSRField(), cfgFld.Layout) case HTTP_POST: - if outValByte, err := utils.HttpJsonPost(cfgFld.Value, cdr); err == nil { + var outValByte []byte + if outValByte, err = utils.HttpJsonPost(cfgFld.Value, cdre.httpSkipTlsCheck, cdr); err == nil { outVal = string(outValByte) + if len(outVal) == 0 && cfgFld.Mandatory { + err = fmt.Errorf("Empty result for http_post field: %s", cfgFld.Name) + } } case CONCATENATED_CDRFIELD: for _, fld := range strings.Split(cfgFld.Value, ",") { diff --git a/cdre/csv_test.go b/cdre/csv_test.go index 7a5b284d4..056d024aa 100644 --- a/cdre/csv_test.go +++ b/cdre/csv_test.go @@ -39,7 +39,7 @@ func TestCsvCdrWriter(t *testing.T) { Usage: time.Duration(10) * time.Second, MediationRunId: utils.DEFAULT_RUNID, ExtraFields: map[string]string{"extra1": "val_extra1", "extra2": "val_extra2", "extra3": "val_extra3"}, Cost: 1.01, } - cdre, err := NewCdrExporter([]*utils.StoredCdr{storedCdr1}, logDb, cfg.CdreDefaultInstance, "firstexport", 0.0, 0.0, 0, 4, cfg.RoundingDecimals, "", 0) + cdre, err := NewCdrExporter([]*utils.StoredCdr{storedCdr1}, logDb, cfg.CdreDefaultInstance, "firstexport", 0.0, 0.0, 0, 4, cfg.RoundingDecimals, "", 0, cfg.HttpSkipTlsVerify) if err != nil { t.Error("Unexpected error received: ", err) } diff --git a/cdre/fixedwidth_test.go b/cdre/fixedwidth_test.go index 21b9c3b0f..5f73604c8 100644 --- a/cdre/fixedwidth_test.go +++ b/cdre/fixedwidth_test.go @@ -94,7 +94,7 @@ func TestWriteCdr(t *testing.T) { Usage: time.Duration(10) * time.Second, MediationRunId: utils.DEFAULT_RUNID, Cost: 2.34567, ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, } - cdre, err := NewCdrExporter([]*utils.StoredCdr{cdr}, logDb, exportTpl.AsCdreConfig(), "fwv_1", 0.0, 0.0, 0, 4, cfg.RoundingDecimals, "", -1) + cdre, err := NewCdrExporter([]*utils.StoredCdr{cdr}, logDb, exportTpl.AsCdreConfig(), "fwv_1", 0.0, 0.0, 0, 4, cfg.RoundingDecimals, "", -1, cfg.HttpSkipTlsVerify) if err != nil { t.Error(err) } @@ -167,7 +167,8 @@ func TestWriteCdrs(t *testing.T) { ExtraFields: map[string]string{"productnumber": "12344", "fieldextr2": "valextr2"}, } cfg, _ := config.NewDefaultCGRConfig() - cdre, err := NewCdrExporter([]*utils.StoredCdr{cdr1, cdr2, cdr3, cdr4}, logDb, exportTpl.AsCdreConfig(), "fwv_1", 0.0, 0.0, 0, 4, cfg.RoundingDecimals, "", -1) + cdre, err := NewCdrExporter([]*utils.StoredCdr{cdr1, cdr2, cdr3, cdr4}, logDb, exportTpl.AsCdreConfig(), + "fwv_1", 0.0, 0.0, 0, 4, cfg.RoundingDecimals, "", -1, cfg.HttpSkipTlsVerify) if err != nil { t.Error(err) } diff --git a/config/config.go b/config/config.go index 410613a87..fb909d37d 100644 --- a/config/config.go +++ b/config/config.go @@ -82,6 +82,7 @@ type CGRConfig struct { DefaultTenant string // set default tenant DefaultSubject string // set default rating subject, useful in case of fallback RoundingDecimals int // Number of decimals to round end prices at + HttpSkipTlsVerify bool // If enabled Http Client will accept any TLS certificate XmlCfgDocument *CgrXmlCfgDocument // Load additional configuration inside xml document RaterEnabled bool // start standalone server (no balancer) RaterBalancer string // balancer address host:port @@ -153,6 +154,7 @@ func (self *CGRConfig) setDefaults() error { self.DefaultTenant = "cgrates.org" self.DefaultSubject = "cgrates" self.RoundingDecimals = 10 + self.HttpSkipTlsVerify = false self.XmlCfgDocument = nil self.RaterEnabled = false self.RaterBalancer = "" @@ -350,6 +352,9 @@ func loadConfig(c *conf.ConfigFile) (*CGRConfig, error) { if hasOpt = c.HasOption("global", "rounding_decimals"); hasOpt { cfg.RoundingDecimals, _ = c.GetInt("global", "rounding_decimals") } + if hasOpt = c.HasOption("global", "http_skip_tls_veify"); hasOpt { + cfg.HttpSkipTlsVerify, _ = c.GetBool("global", "http_skip_tls_veify") + } // XML config path defined, try loading the document if hasOpt = c.HasOption("global", "xmlcfg_path"); hasOpt { xmlCfgPath, _ := c.GetString("global", "xmlcfg_path") diff --git a/config/config_test.go b/config/config_test.go index a0ad5657e..03c1aece4 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -73,6 +73,7 @@ func TestDefaults(t *testing.T) { eCfg.DefaultTenant = "cgrates.org" eCfg.DefaultSubject = "cgrates" eCfg.RoundingDecimals = 10 + eCfg.HttpSkipTlsVerify = false eCfg.XmlCfgDocument = nil eCfg.RaterEnabled = false eCfg.RaterBalancer = "" @@ -196,6 +197,7 @@ func TestConfigFromFile(t *testing.T) { eCfg.DefaultTenant = "test" eCfg.DefaultSubject = "test" eCfg.RoundingDecimals = 99 + eCfg.HttpSkipTlsVerify = true eCfg.RaterEnabled = true eCfg.RaterBalancer = "test" eCfg.BalancerEnabled = true diff --git a/config/test_data.txt b/config/test_data.txt index 94095517b..bd9e050d9 100644 --- a/config/test_data.txt +++ b/config/test_data.txt @@ -29,6 +29,7 @@ default_category = test # Default Type of Record to consider when missing from default_tenant = test # Default Tenant to consider when missing from requests. default_subject = test # Default rating Subject to consider when missing from requests. rounding_decimals = 99 # Number of decimals to round floats/costs at +http_skip_tls_veify = true # If enabled Http Client will accept any TLS certificate [balancer] enabled = true # Start Balancer service: . diff --git a/config/xmlcdre_test.go b/config/xmlcdre_test.go index df82c360c..dae9b5700 100644 --- a/config/xmlcdre_test.go +++ b/config/xmlcdre_test.go @@ -110,6 +110,20 @@ func TestXmlCdreCfgParseXmlConfig(t *testing.T) { + + + + + + + + + + + + + + ` var err error reader := strings.NewReader(cfgXmlStr) @@ -118,7 +132,7 @@ func TestXmlCdreCfgParseXmlConfig(t *testing.T) { } else if cfgDoc == nil { t.Fatal("Could not parse xml configuration document") } - if len(cfgDoc.cdres) != 1 { + if len(cfgDoc.cdres) != 2 { t.Error("Did not cache") } } @@ -137,6 +151,13 @@ func TestXmlCdreCfgGetCdreCfg(t *testing.T) { if len(cdreFWCfg["CDRE-FW1"].Trailer.Fields) != 9 { t.Error("Unexpected number of trailer fields parsed", len(cdreFWCfg["CDRE-FW1"].Trailer.Fields)) } + cdreCsvCfg1 := cfgDoc.GetCdreCfgs("CHECK-CSV1") + if cdreCsvCfg1 == nil { + t.Error("Could not parse CdreFw instance") + } + if len(cdreCsvCfg1["CHECK-CSV1"].Content.Fields) != 5 { + t.Error("Unexpected number of content fields parsed", len(cdreCsvCfg1["CHECK-CSV1"].Content.Fields)) + } } func TestXmlCdreCfgAsCdreConfig(t *testing.T) { diff --git a/data/conf/cgrates.cfg b/data/conf/cgrates.cfg index 7a0a47a5e..70da87d56 100644 --- a/data/conf/cgrates.cfg +++ b/data/conf/cgrates.cfg @@ -32,6 +32,7 @@ # default_tenant = cgrates.org # Default Tenant to consider when missing from requests. # default_subject = cgrates # Default rating Subject to consider when missing from requests. # rounding_decimals = 10 # System level precision for floats +# http_skip_tls_veify = false # If enabled Http Client will accept any TLS certificate # xmlcfg_path = # Path towards additional config defined in xml file [balancer] @@ -53,9 +54,9 @@ # cdr_format = csv # Exported CDRs format # data_usage_multiply_factor = 0.0 # Multiply data usage before export (eg: convert from KBytes to Bytes) # cost_multiply_factor = 0.0 # Multiply cost before export (0.0 to disable), eg: add VAT -# cost_rounding_decimals = -1 # Rounding decimals for Cost values. -1 to disable rounding -# cost_shift_digits = 0 # Shift digits in the cost on export (eg: convert from EUR to cents) -# mask_destination_id = # Destination id containing called addresses to be masked on export +# cost_rounding_decimals = -1 # Rounding decimals for Cost values. -1 to disable rounding +# cost_shift_digits = 0 # Shift digits in the cost on export (eg: convert from EUR to cents) +# mask_destination_id = # Destination id containing called addresses to be masked on export # mask_length = 0 # Length of the destination suffix to be masked # export_dir = /var/log/cgrates/cdre # Path where the exported CDRs will be placed # export_template = cgrid,mediation_runid,tor,accid,reqtype,direction,tenant,category,account,subject,destination,setup_time,answer_time,usage,cost diff --git a/engine/action.go b/engine/action.go index ce2ed100c..2c25bab68 100644 --- a/engine/action.go +++ b/engine/action.go @@ -213,7 +213,8 @@ func genericReset(ub *Account) error { } func callUrl(ub *Account, a *Action) error { - _, err := utils.HttpJsonPost(a.ExtraParameters, ub) + cfg := config.CgrConfig() + _, err := utils.HttpJsonPost(a.ExtraParameters, cfg.HttpSkipTlsVerify, ub) return err } @@ -223,9 +224,10 @@ func callUrlAsync(ub *Account, a *Action) error { if err != nil { return err } + cfg := config.CgrConfig() go func() { for i := 0; i < 5; i++ { // Loop so we can increase the success rate on best effort - if _, err = utils.HttpJsonPost(a.ExtraParameters, ub); err == nil { + if _, err = utils.HttpJsonPost(a.ExtraParameters, cfg.HttpSkipTlsVerify, ub); err == nil { break // Success, no need to reinterate } else if i == 4 { // Last iteration, syslog the warning Logger.Warning(fmt.Sprintf(" WARNING: Failed calling url: [%s], error: [%s], balance: %s", a.ExtraParameters, err.Error(), ubJson)) diff --git a/utils/coreutils.go b/utils/coreutils.go index a3981c289..dcffef59e 100644 --- a/utils/coreutils.go +++ b/utils/coreutils.go @@ -22,6 +22,7 @@ import ( "bytes" "crypto/rand" "crypto/sha1" + "crypto/tls" "encoding/hex" "encoding/json" "errors" @@ -260,12 +261,16 @@ func AccountAliasKey(tenant, account string) string { return ConcatenatedKey(tenant, account) } -func HttpJsonPost(url string, content interface{}) ([]byte, error) { +func HttpJsonPost(url string, skipTlsVerify bool, content interface{}) ([]byte, error) { body, err := json.Marshal(content) if err != nil { return nil, err } - resp, err := http.Post(url, "application/json", bytes.NewBuffer(body)) + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: skipTlsVerify}, + } + client := &http.Client{Transport: tr} + resp, err := client.Post(url, "application/json", bytes.NewBuffer(body)) if err != nil { return nil, err } diff --git a/utils/utils_local_test.go b/utils/utils_local_test.go index bffcdd663..3a10ac364 100644 --- a/utils/utils_local_test.go +++ b/utils/utils_local_test.go @@ -36,7 +36,7 @@ func TestHttpJsonPost(t *testing.T) { SetupTime: time.Date(2013, 11, 7, 8, 42, 20, 0, time.UTC), AnswerTime: time.Date(2013, 11, 7, 8, 42, 26, 0, time.UTC), MediationRunId: DEFAULT_RUNID, Usage: 0.00000001, ExtraFields: map[string]string{"field_extr1": "val_extr1", "fieldextr2": "valextr2"}, Cost: 1.01, } - if _, err := HttpJsonPost("http://localhost:8000", cdrOut); err == nil || err.Error() != "Post http://localhost:8000: dial tcp 127.0.0.1:8000: connection refused" { + if _, err := HttpJsonPost("http://localhost:8000", false, cdrOut); err == nil || err.Error() != "Post http://localhost:8000: dial tcp 127.0.0.1:8000: connection refused" { t.Error(err) } }