Compare commits

...

4 Commits

Author SHA1 Message Date
smx_Morgan
af97aae3f2 fix : add offset to es date_histogam (#2757) 2025-07-03 19:42:33 +08:00
ning
73d1500a1c refactor: change eval for duration check 2025-06-30 15:11:34 +08:00
ning
0691e499a2 refactor: change eval for duration check 2025-06-30 15:11:17 +08:00
Yening Qin
91a3d7bd4c fix: api for agent auth (#2748) 2025-06-17 20:45:16 +08:00
7 changed files with 150 additions and 12 deletions

View File

@@ -18,7 +18,7 @@ func LogEvent(event *models.AlertCurEvent, location string, err ...error) {
}
logger.Infof(
"event(%s %s) %s: rule_id=%d sub_id:%d notify_rule_ids:%v cluster:%s %v%s@%d %s",
"event(%s %s) %s: rule_id=%d sub_id:%d notify_rule_ids:%v cluster:%s %v%s@%d last_eval_time:%d %s",
event.Hash,
status,
location,
@@ -29,6 +29,7 @@ func LogEvent(event *models.AlertCurEvent, location string, err ...error) {
event.TagsJSON,
event.TriggerValue,
event.TriggerTime,
event.LastEvalTime,
message,
)
}

View File

@@ -428,17 +428,18 @@ func (p *Processor) handleEvent(events []*models.AlertCurEvent) {
continue
}
var preTriggerTime int64 // 第一个 pending event 的触发时间
var preEvalTime int64 // 第一个 pending event 的检测时间
preEvent, has := p.pendings.Get(event.Hash)
if has {
p.pendings.UpdateLastEvalTime(event.Hash, event.LastEvalTime)
preTriggerTime = preEvent.TriggerTime
preEvalTime = preEvent.FirstEvalTime
} else {
event.FirstEvalTime = event.LastEvalTime
p.pendings.Set(event.Hash, event)
preTriggerTime = event.TriggerTime
preEvalTime = event.FirstEvalTime
}
if event.LastEvalTime-preTriggerTime+int64(event.PromEvalInterval) >= int64(p.rule.PromForDuration) {
if event.LastEvalTime-preEvalTime+int64(event.PromEvalInterval) >= int64(p.rule.PromForDuration) {
fireEvents = append(fireEvents, event)
if severity > event.Severity {
severity = event.Severity

View File

@@ -416,7 +416,9 @@ func QueryData(ctx context.Context, queryParam interface{}, cliTimeout int64, ve
MinDocCount(1)
if strings.HasPrefix(version, "7") {
tsAggr.FixedInterval(fmt.Sprintf("%ds", param.Interval))
// 添加偏移量使第一个分桶bucket的左边界对齐为 start 时间
offset := (start % param.Interval) - param.Interval
tsAggr.FixedInterval(fmt.Sprintf("%ds", param.Interval)).Offset(fmt.Sprintf("%ds", offset))
} else {
// 兼容 7.0 以下的版本
// OpenSearch 也使用这个字段

View File

@@ -65,6 +65,7 @@ type AlertCurEvent struct {
NotifyUsersObj []*User `json:"notify_users_obj,omitempty" gorm:"-"` // for notify.py
LastEvalTime int64 `json:"last_eval_time" gorm:"-"` // for notify.py 上次计算的时间
LastSentTime int64 `json:"last_sent_time" gorm:"-"` // 上次发送时间
FirstEvalTime int64 `json:"first_eval_time" gorm:"-"` // 首次异常检测时间
NotifyCurNumber int `json:"notify_cur_number"` // notify: current number
FirstTriggerTime int64 `json:"first_trigger_time"` // 连续告警的首次告警时间
ExtraConfig interface{} `json:"extra_config" gorm:"-"`

102
pkg/ginx/auth.go Normal file
View File

@@ -0,0 +1,102 @@
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
package ginx
import (
"crypto/subtle"
"encoding/base64"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
)
// AuthUserKey is the cookie name for user credential in basic auth.
const AuthUserKey = "user"
// Accounts defines a key/value for user/pass list of authorized logins.
type Accounts []Account
type Account struct {
User string
Password string
}
type authPair struct {
value string
user string
}
type authPairs []authPair
func (a authPairs) searchCredential(authValue string) (string, bool) {
if authValue == "" {
return "", false
}
for _, pair := range a {
if subtle.ConstantTimeCompare(StringToBytes(pair.value), StringToBytes(authValue)) == 1 {
return pair.user, true
}
}
return "", false
}
// BasicAuthForRealm returns a Basic HTTP Authorization middleware. It takes as arguments a map[string]string where
// the key is the user name and the value is the password, as well as the name of the Realm.
// If the realm is empty, "Authorization Required" will be used by default.
// (see http://tools.ietf.org/html/rfc2617#section-1.2)
func BasicAuthForRealm(accounts Accounts, realm string) gin.HandlerFunc {
if realm == "" {
realm = "Authorization Required"
}
realm = "Basic realm=" + strconv.Quote(realm)
pairs := processAccounts(accounts)
return func(c *gin.Context) {
// Search user in the slice of allowed credentials
user, found := pairs.searchCredential(c.Request.Header.Get("Authorization"))
if !found {
// Credentials doesn't match, we return 401 and abort handlers chain.
c.Header("WWW-Authenticate", realm)
c.AbortWithStatus(http.StatusUnauthorized)
return
}
// The user credentials was found, set user's id to key AuthUserKey in this context, the user's id can be read later using
// c.MustGet(gin.AuthUserKey).
c.Set(AuthUserKey, user)
}
}
// BasicAuth returns a Basic HTTP Authorization middleware. It takes as argument a map[string]string where
// the key is the user name and the value is the password.
func BasicAuth(accounts Accounts) gin.HandlerFunc {
return BasicAuthForRealm(accounts, "")
}
func processAccounts(accounts Accounts) authPairs {
length := len(accounts)
assert1(length > 0, "Empty list of authorized credentials")
pairs := make(authPairs, 0, length)
for _, account := range accounts {
assert1(account.User != "", "User can not be empty")
value := authorizationHeader(account.User, account.Password)
pairs = append(pairs, authPair{
value: value,
user: account.User,
})
}
return pairs
}
func authorizationHeader(user, password string) string {
base := user + ":" + password
return "Basic " + base64.StdEncoding.EncodeToString(StringToBytes(base))
}
func assert1(guard bool, text string) {
if !guard {
panic(text)
}
}

23
pkg/ginx/bytesconv.go Normal file
View File

@@ -0,0 +1,23 @@
// Copyright 2023 Gin Core Team. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
//go:build go1.20
package ginx
import (
"unsafe"
)
// StringToBytes converts string to byte slice without a memory allocation.
// For more details, see https://github.com/golang/go/issues/53003#issuecomment-1140276077.
func StringToBytes(s string) []byte {
return unsafe.Slice(unsafe.StringData(s), len(s))
}
// BytesToString converts byte slice to string without a memory allocation.
// For more details, see https://github.com/golang/go/issues/53003#issuecomment-1140276077.
func BytesToString(b []byte) string {
return unsafe.String(unsafe.SliceData(b), len(b))
}

View File

@@ -11,6 +11,7 @@ import (
"github.com/ccfos/nightingale/v6/center/metas"
"github.com/ccfos/nightingale/v6/memsto"
"github.com/ccfos/nightingale/v6/pkg/ctx"
"github.com/ccfos/nightingale/v6/pkg/ginx"
"github.com/ccfos/nightingale/v6/pkg/httpx"
"github.com/ccfos/nightingale/v6/pushgw/idents"
"github.com/ccfos/nightingale/v6/pushgw/pconf"
@@ -86,15 +87,22 @@ func (rt *Router) Config(r *gin.Engine) {
if len(rt.HTTP.APIForAgent.BasicAuth) > 0 {
// enable basic auth
accounts := make(gin.Accounts)
accounts := make(ginx.Accounts, 0)
for username, password := range rt.HTTP.APIForAgent.BasicAuth {
accounts[username] = password
}
for username, password := range rt.HTTP.APIForService.BasicAuth {
accounts[username] = password
accounts = append(accounts, ginx.Account{
User: username,
Password: password,
})
}
auth := gin.BasicAuth(accounts)
for username, password := range rt.HTTP.APIForService.BasicAuth {
accounts = append(accounts, ginx.Account{
User: username,
Password: password,
})
}
auth := ginx.BasicAuth(accounts)
r.POST("/opentsdb/put", auth, rt.openTSDBPut)
r.POST("/openfalcon/push", auth, rt.falconPush)
r.POST("/prometheus/v1/write", auth, rt.remoteWrite)