Added sentrypeer filter type

This commit is contained in:
gezimbll
2023-07-26 10:56:31 -04:00
committed by Dan Christian Bogos
parent fcaa3fb360
commit 5b5a159706
11 changed files with 316 additions and 9 deletions

View File

@@ -590,6 +590,12 @@ func storeDiffSection(ctx *context.Context, section string, db ConfigDB, v1, v2
return
}
return db.SetSection(ctx, section, diffAPIBanJsonCfg(jsn, v1.APIBanCfg(), v2.APIBanCfg()))
case SentryPeerJSON:
jsn := new(SentryPeerJsonCfg)
if err = db.GetSection(ctx, section, jsn); err != nil {
return
}
return db.SetSection(ctx, section, diffSentryPeerJsonCfg(jsn, v1.SentryPeerCfg(), v2.SentryPeerCfg()))
case CoreSJSON:
jsn := new(CoreSJsonCfg)
if err = db.GetSection(ctx, section, jsn); err != nil {

View File

@@ -245,10 +245,11 @@ func newCGRConfig(config []byte) (cfg *CGRConfig, err error) {
ProfileIgnoreFilters: []*utils.DynamicBoolOpt{},
PosterAttempts: []*utils.DynamicIntOpt{},
}},
sipAgentCfg: new(SIPAgentCfg),
configSCfg: new(ConfigSCfg),
apiBanCfg: new(APIBanCfg),
coreSCfg: new(CoreSCfg),
sipAgentCfg: new(SIPAgentCfg),
configSCfg: new(ConfigSCfg),
apiBanCfg: new(APIBanCfg),
sentryPeerCfg: new(SentryPeerCfg),
coreSCfg: new(CoreSCfg),
accountSCfg: &AccountSCfg{Opts: &AccountsOpts{
ProfileIDs: []*utils.DynamicStringSliceOpt{},
Usage: []*utils.DynamicDecimalBigOpt{},
@@ -367,6 +368,7 @@ type CGRConfig struct {
sipAgentCfg *SIPAgentCfg // SIPAgent config
configSCfg *ConfigSCfg // ConfigS config
apiBanCfg *APIBanCfg // APIBan config
sentryPeerCfg *SentryPeerCfg //SentryPeer config
coreSCfg *CoreSCfg // CoreS config
accountSCfg *AccountSCfg // AccountS config
tpeSCfg *TpeSCfg // TpeS config
@@ -761,6 +763,12 @@ func (cfg *CGRConfig) APIBanCfg() *APIBanCfg {
return cfg.apiBanCfg
}
func (cfg *CGRConfig) SentryPeerCfg() *SentryPeerCfg {
cfg.lks[SentryPeerJSON].Lock()
defer cfg.lks[SentryPeerJSON].Unlock()
return cfg.sentryPeerCfg
}
// CoreSCfg reads the CoreS configuration
func (cfg *CGRConfig) CoreSCfg() *CoreSCfg {
cfg.lks[CoreSJSON].Lock()
@@ -1073,6 +1081,7 @@ func (cfg *CGRConfig) Clone() (cln *CGRConfig) {
sipAgentCfg: cfg.sipAgentCfg.Clone(),
configSCfg: cfg.configSCfg.Clone(),
apiBanCfg: cfg.apiBanCfg.Clone(),
sentryPeerCfg: cfg.sentryPeerCfg.Clone(),
coreSCfg: cfg.coreSCfg.Clone(),
actionSCfg: cfg.actionSCfg.Clone(),
accountSCfg: cfg.accountSCfg.Clone(),

View File

@@ -288,7 +288,8 @@ const CGRATES_CFG_JSON = `
"*rpc_connections": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate": false}, // RPC connections caching
"*uch": {"limit": -1, "ttl": "3h", "static_ttl": false, "remote":false, "replicate": false}, // User cache
"*stir": {"limit": -1, "ttl": "3h", "static_ttl": false, "remote":false, "replicate": false}, // stirShaken cache keys
"*apiban":{"limit": -1, "ttl": "2m", "static_ttl": false, "remote":false, "replicate": false},
"*apiban":{"limit": -1, "ttl": "2m", "static_ttl": false, "remote":false, "replicate": false},
"*sentrypeer":{"limit": -1, "ttl": "24h", "static_ttl": true, "remote":false, "replicate": false},
"*caps_events": {"limit": -1, "ttl": "", "static_ttl": false, "remote":false, "replicate": false}, // caps cached samples
"*replication_hosts": {"limit": 0, "ttl": "", "static_ttl": false, "remote":false, "replicate": false}, // the replication hosts cache(used when replication_filtered is enbled)
},
@@ -1800,6 +1801,16 @@ const CGRATES_CFG_JSON = `
"keys": [],
},
"sentrypeer":{
"client_id":"",
"client_secret":"",
"token_url":"https://authz.sentrypeer.com/oauth/token",
"ips_url":"https://sentrypeer.com/api/ip-addresses",
"numbers_url":"https://sentrypeer.com/api/phone-numbers",
"audience":"https://sentrypeer.com/api",
"grant_type":"client_credentials",
},
"actions": { // ActionS config
"enabled": false, // starts attribute service: <true|false>

View File

@@ -69,6 +69,7 @@ const (
TemplatesJSON = "templates"
ConfigSJSON = "configs"
APIBanJSON = "apiban"
SentryPeerJSON = "sentrypeer"
CoreSJSON = "cores"
AccountSJSON = "accounts"
ConfigDBJSON = "config_db"
@@ -197,6 +198,7 @@ func newSections(cfg *CGRConfig) Sections {
cfg.coreSCfg,
cfg.configSCfg,
cfg.apiBanCfg,
cfg.sentryPeerCfg,
cfg.configDBCfg,
cfg.sureTaxCfg,
cfg.tpeSCfg,

118
config/sentrypeercfg.go Normal file
View File

@@ -0,0 +1,118 @@
package config
import (
"github.com/cgrates/birpc/context"
"github.com/cgrates/cgrates/utils"
)
type SentryPeerCfg struct {
ClientID string
ClientSecret string
TokenUrl string
IpsUrl string
NumbersUrl string
Audience string
GrantType string
}
func (sentrypeer *SentryPeerCfg) Load(ctx *context.Context, jsnCfg ConfigDB, _ *CGRConfig) (err error) {
jsnSentryPeerCfg := new(SentryPeerJsonCfg)
if err = jsnCfg.GetSection(ctx, SentryPeerJSON, jsnSentryPeerCfg); err != nil {
return
}
return sentrypeer.loadFromJSONCfg(jsnSentryPeerCfg)
}
func (sentrypeer *SentryPeerCfg) loadFromJSONCfg(jsnCfg *SentryPeerJsonCfg) (err error) {
if jsnCfg == nil {
return
}
if jsnCfg.Client_id != nil {
sentrypeer.ClientID = *jsnCfg.Client_id
}
if jsnCfg.Client_secret != nil {
sentrypeer.ClientSecret = *jsnCfg.Client_secret
}
if jsnCfg.Token_url != nil {
sentrypeer.TokenUrl = *jsnCfg.Token_url
}
if jsnCfg.Ips_url != nil {
sentrypeer.IpsUrl = *jsnCfg.Ips_url
}
if jsnCfg.Numbers_url != nil {
sentrypeer.NumbersUrl = *jsnCfg.Numbers_url
}
if jsnCfg.Audience != nil {
sentrypeer.Audience = *jsnCfg.Audience
}
if jsnCfg.Grant_type != nil {
sentrypeer.GrantType = *jsnCfg.Grant_type
}
return
}
func (sentrypeer SentryPeerCfg) AsMapInterface(string) any {
return map[string]any{
utils.ClientIDCfg: sentrypeer.ClientID,
utils.ClientSecretCfg: sentrypeer.ClientSecret,
utils.TokenUrlCfg: sentrypeer.TokenUrl,
utils.IpsUrlCfg: sentrypeer.IpsUrl,
utils.NumbersUrlCfg: sentrypeer.NumbersUrl,
utils.AudienceCfg: sentrypeer.Audience,
utils.GrantTypeCfg: sentrypeer.GrantType,
}
}
func (SentryPeerCfg) SName() string { return SentryPeerJSON }
func (sentrypeer SentryPeerCfg) CloneSection() Section { return sentrypeer.Clone() }
func (sentrypeer SentryPeerCfg) Clone() (cln *SentryPeerCfg) {
return &SentryPeerCfg{
ClientID: sentrypeer.ClientID,
ClientSecret: sentrypeer.ClientSecret,
TokenUrl: sentrypeer.TokenUrl,
IpsUrl: sentrypeer.IpsUrl,
NumbersUrl: sentrypeer.NumbersUrl,
Audience: sentrypeer.Audience,
GrantType: sentrypeer.GrantType,
}
}
type SentryPeerJsonCfg struct {
Client_id *string
Client_secret *string
Token_url *string
Ips_url *string
Numbers_url *string
Audience *string
Grant_type *string
}
func diffSentryPeerJsonCfg(d *SentryPeerJsonCfg, v1, v2 *SentryPeerCfg) *SentryPeerJsonCfg {
if d == nil {
d = new(SentryPeerJsonCfg)
}
if v1.ClientID != v2.ClientID {
d.Client_id = utils.StringPointer(v2.ClientID)
}
if v1.ClientSecret != v2.ClientSecret {
d.Client_secret = utils.StringPointer(v2.ClientSecret)
}
if v1.TokenUrl != v2.TokenUrl {
d.Token_url = utils.StringPointer(v2.TokenUrl)
}
if v1.IpsUrl != v2.IpsUrl {
d.Ips_url = utils.StringPointer(v2.IpsUrl)
}
if v1.NumbersUrl != v2.NumbersUrl {
d.Numbers_url = utils.StringPointer(v2.NumbersUrl)
}
if v1.Audience != v2.Audience {
d.Audience = utils.StringPointer(v2.Audience)
}
if v1.GrantType != v2.GrantType {
d.Grant_type = utils.StringPointer(v2.GrantType)
}
return d
}

View File

@@ -120,7 +120,7 @@ func (dm *DataManager) CacheDataFromDB(ctx *context.Context, prfx string, ids []
return
}
// *apiban and *dispatchers are not stored in database
if prfx == utils.MetaAPIBan || prfx == utils.MetaDispatchers { // no need for ids in this case
if prfx == utils.MetaAPIBan || prfx == utils.MetaDispatchers || prfx == utils.MetaSentryPeer { // no need for ids in this case
ids = []string{utils.EmptyString}
} else if len(ids) != 0 && ids[0] == utils.MetaAny {
if mustBeCached {

View File

@@ -19,6 +19,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package engine
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"github.com/cgrates/birpc/context"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/guardian"
@@ -137,3 +144,124 @@ func BlockerFromDynamics(ctx *context.Context, dBs []*utils.DynamicBlocker,
}
return false, nil
}
func GetSentryPeer(ctx *context.Context, val string, sentryPeerCfg *config.SentryPeerCfg, dataType string) (found bool, err error) {
itemId := utils.ConcatenatedKey(dataType, val)
var (
isCached bool
apiUrl string
token string
)
if x, ok := Cache.Get(utils.MetaSentryPeer, itemId); ok && x != nil { // Attempt to find in cache first
return x.(bool), nil
}
var cachedToken any
if cachedToken, isCached = Cache.Get(utils.MetaSentryPeer,
utils.MetaToken); isCached && cachedToken != nil {
token = cachedToken.(string)
}
switch dataType {
case utils.MetaIp:
apiUrl, err = url.JoinPath(sentryPeerCfg.IpsUrl, val)
case utils.MetaNumber:
apiUrl, err = url.JoinPath(sentryPeerCfg.NumbersUrl, val)
}
if err != nil {
return
}
if !isCached {
if token, err = sentrypeerGetToken(sentryPeerCfg.TokenUrl, sentryPeerCfg.ClientID, sentryPeerCfg.ClientSecret,
sentryPeerCfg.Audience, sentryPeerCfg.GrantType); err != nil {
utils.Logger.Err(fmt.Sprintf("sentrypeer token auth got err <%v> ", err.Error()))
return
}
if err = Cache.Set(ctx, utils.MetaSentryPeer, utils.MetaToken,
token, nil, true, utils.NonTransactional); err != nil {
return
}
}
for i := 0; i < 2; i++ {
if found, err = sentrypeerHasData(itemId, token, apiUrl); err == nil {
if err = Cache.Set(ctx, utils.MetaSentryPeer, itemId, found,
nil, true, ""); err != nil {
return
}
break
} else if err != utils.ErrNotAuthorized {
utils.Logger.Err(err.Error())
break
}
utils.Logger.Warning("Sentrypeer token expired !Getting new one.")
Cache.Remove(ctx, utils.MetaSentryPeer, utils.MetaToken, true, utils.EmptyString)
if token, err = sentrypeerGetToken(sentryPeerCfg.TokenUrl, sentryPeerCfg.ClientID, sentryPeerCfg.ClientSecret,
sentryPeerCfg.Audience, sentryPeerCfg.GrantType); err != nil {
return
}
if err = Cache.Set(ctx, utils.MetaSentryPeer, utils.MetaToken, token,
nil, true, utils.NonTransactional); err != nil {
return
}
}
return
}
// Returns a new token from sentrypeer api
func sentrypeerGetToken(tokenUrl, clientID, clientSecret, audience, grantType string) (token string, err error) {
var resp *http.Response
payload := map[string]string{
utils.ClientIDCfg: clientID,
utils.ClientSecretCfg: clientSecret,
utils.AudienceCfg: audience,
utils.GrantTypeCfg: grantType,
}
jsonPayload, _ := json.Marshal(payload)
resp, err = getHTTP(http.MethodPost, tokenUrl, bytes.NewBuffer(jsonPayload), map[string][]string{"Content-Type": {"application/json"}})
if err != nil {
return
}
defer resp.Body.Close()
var m struct {
AccessToken string `json:"access_token"`
}
err = json.NewDecoder(resp.Body).Decode(&m)
if err != nil {
return
}
token = m.AccessToken
return
}
// sentrypeerHasData return a boolean based on query response on finding ip/number
func sentrypeerHasData(itemId, token, url string) (found bool, err error) {
var resp *http.Response
resp, err = getHTTP(http.MethodGet, url, nil, map[string][]string{"Authorization": {fmt.Sprintf("Bearer %s", token)}})
if err != nil {
return
}
defer resp.Body.Close()
switch {
case resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden:
return false, utils.ErrNotAuthorized
case resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices:
return false, nil
case resp.StatusCode == http.StatusNotFound:
return true, nil
case resp.StatusCode >= http.StatusBadRequest && resp.StatusCode < http.StatusInternalServerError:
err = fmt.Errorf("sentrypeer api got client err <%v>", resp.Status)
case resp.StatusCode >= http.StatusInternalServerError:
err = fmt.Errorf("sentrypeer api got server err <%s>", resp.Status)
default:
err = fmt.Errorf("sentrypeer api got unexpected err <%s>", resp.Status)
}
return false, err
}
func getHTTP(method, url string, payload io.Reader, headers map[string][]string) (resp *http.Response, err error) {
var req *http.Request
if req, err = http.NewRequest(method, url, payload); err != nil {
return
}
req.Header = headers
return http.DefaultClient.Do(req)
}

View File

@@ -226,18 +226,18 @@ var supportedFiltersType utils.StringSet = utils.NewStringSet([]string{
utils.MetaCronExp, utils.MetaRSR, utils.MetaEmpty,
utils.MetaExists, utils.MetaLessThan, utils.MetaLessOrEqual,
utils.MetaGreaterThan, utils.MetaGreaterOrEqual, utils.MetaEqual,
utils.MetaIPNet, utils.MetaAPIBan, utils.MetaActivationInterval,
utils.MetaIPNet, utils.MetaAPIBan, utils.MetaSentryPeer, utils.MetaActivationInterval,
utils.MetaRegex, utils.MetaNever})
var needsFieldName utils.StringSet = utils.NewStringSet([]string{
utils.MetaString, utils.MetaPrefix, utils.MetaSuffix,
utils.MetaCronExp, utils.MetaRSR, utils.MetaLessThan,
utils.MetaEmpty, utils.MetaExists, utils.MetaLessOrEqual, utils.MetaGreaterThan,
utils.MetaGreaterOrEqual, utils.MetaEqual, utils.MetaIPNet, utils.MetaAPIBan,
utils.MetaGreaterOrEqual, utils.MetaEqual, utils.MetaIPNet, utils.MetaAPIBan, utils.MetaSentryPeer,
utils.MetaActivationInterval, utils.MetaRegex})
var needsValues utils.StringSet = utils.NewStringSet([]string{
utils.MetaString, utils.MetaPrefix, utils.MetaSuffix, utils.MetaCronExp, utils.MetaRSR,
utils.MetaLessThan, utils.MetaLessOrEqual, utils.MetaGreaterThan, utils.MetaGreaterOrEqual,
utils.MetaEqual, utils.MetaIPNet, utils.MetaAPIBan, utils.MetaActivationInterval,
utils.MetaEqual, utils.MetaIPNet, utils.MetaAPIBan, utils.MetaSentryPeer, utils.MetaActivationInterval,
utils.MetaRegex})
// NewFilterRule returns a new filter
@@ -365,6 +365,8 @@ func (fltr *FilterRule) Pass(ctx *context.Context, dDP utils.DataProvider) (resu
result, err = fltr.passIPNet(dDP)
case utils.MetaAPIBan, utils.MetaNotAPIBan:
result, err = fltr.passAPIBan(ctx, dDP)
case utils.MetaSentryPeer, utils.MetaNotSentryPeer:
result, err = fltr.passSentryPeer(ctx, dDP)
case utils.MetaActivationInterval, utils.MetaNotActivationInterval:
result, err = fltr.passActivationInterval(dDP)
case utils.MetaRegex, utils.MetaNotRegex:
@@ -627,6 +629,20 @@ func (fltr *FilterRule) passAPIBan(ctx *context.Context, dDP utils.DataProvider)
return GetAPIBan(ctx, strVal, config.CgrConfig().APIBanCfg().Keys, fltr.Values[0] != utils.MetaAll, true, true)
}
func (fltr *FilterRule) passSentryPeer(ctx *context.Context, dDP utils.DataProvider) (bool, error) {
strVal, err := fltr.rsrElement.ParseDataProvider(dDP)
if err != nil {
if err == utils.ErrNotFound {
return false, nil
}
return false, err
}
if fltr.Values[0] != utils.MetaNumber && fltr.Values[0] != utils.MetaIp {
return false, fmt.Errorf("invalid value for sentrypeer filter: <%s>", fltr.Values[0])
}
return GetSentryPeer(ctx, strVal, config.CgrConfig().SentryPeerCfg(), fltr.Values[0])
}
func parseTime(rsr *config.RSRParser, dDp utils.DataProvider) (_ time.Time, err error) {
var str string
if str, err = rsr.ParseDataProvider(dDp); err != nil {

View File

@@ -417,6 +417,7 @@ func GetDefaultEmptyCacheStats() map[string]*ltcache.CacheStats {
utils.CacheEventCharges: {},
utils.CacheReverseFilterIndexes: {},
utils.MetaAPIBan: {},
utils.MetaSentryPeer: {},
utils.CacheCapsEvents: {},
utils.CacheActionProfiles: {},
utils.CacheActionProfilesFilterIndexes: {},

View File

@@ -1060,6 +1060,10 @@ const (
MetaEqual = "*eq"
MetaIPNet = "*ipnet"
MetaAPIBan = "*apiban"
MetaSentryPeer = "*sentrypeer"
MetaToken = "*token"
MetaIp = "*ip"
MetaNumber = "*number"
MetaActivationInterval = "*ai"
MetaRegex = "*regex"
MetaNever = "*never"
@@ -1076,6 +1080,7 @@ const (
MetaNotEqual = "*noteq"
MetaNotIPNet = "*notipnet"
MetaNotAPIBan = "*notapiban"
MetaNotSentryPeer = "*notsentrypeer"
MetaNotActivationInterval = "*notai"
MetaNotRegex = "*notregex"
@@ -2212,6 +2217,16 @@ const (
KeysCfg = "keys"
)
const (
ClientIDCfg = "client_id"
ClientSecretCfg = "client_secret"
TokenUrlCfg = "token_url"
IpsUrlCfg = "ips_url"
NumbersUrlCfg = "numbers_url"
AudienceCfg = "audience"
GrantTypeCfg = "grant_type"
)
// STIR/SHAKEN
const (
STIRAlg = "ES256"

View File

@@ -65,6 +65,7 @@ var (
ErrNotConnected = errors.New("NOT_CONNECTED")
DispatcherErrorPrefix = "DISPATCHER_ERROR"
RateSErrPrfx = "RATES_ERROR"
ErrNotAuthorized = errors.New("NOT_AUTHORIZED")
AccountSErrPrfx = "ACCOUNTS_ERROR"
ErrLoggerChanged = errors.New("LOGGER_CHANGED")
ErrUnsupportedFormat = errors.New("UNSUPPORTED_FORMAT")