nightingale/models/configs.go

358 lines
9.3 KiB
Go

package models
import (
"fmt"
"log"
"os"
"regexp"
"time"
"github.com/ccfos/nightingale/v6/pkg/ctx"
"github.com/ccfos/nightingale/v6/pkg/poster"
"github.com/ccfos/nightingale/v6/pkg/secu"
"github.com/pkg/errors"
"github.com/toolkits/pkg/logger"
"github.com/toolkits/pkg/runner"
"github.com/toolkits/pkg/str"
)
type Configs struct { //ckey+external
Id int64 `json:"id" gorm:"primaryKey"`
Ckey string `json:"ckey"` // Before inserting external configs, check if they are already defined as built-in configs.
Cval string `json:"cval"`
Note string `json:"note"`
External int `json:"external"` //Controls frontend list display: 0 hides built-in (default), 1 shows external
Encrypted int `json:"encrypted"` //Indicates whether the value(cval) is encrypted (1 for ciphertext, 0 for plaintext(default))
CreateAt int64 `json:"create_at"`
CreateBy string `json:"create_by"`
UpdateAt int64 `json:"update_at"`
UpdateBy string `json:"update_by"`
}
func (Configs) TableName() string {
return "configs"
}
var (
ConfigExternal = 1 //external type
ConfigEncrypted = 1 //ciphertext
)
const (
SALT = "salt"
RSA_PRIVATE_KEY = "rsa_private_key"
RSA_PUBLIC_KEY = "rsa_public_key"
RSA_PASSWORD = "rsa_password"
JWT_SIGNING_KEY = "jwt_signing_key"
)
func InitJWTSigningKey(ctx *ctx.Context) string {
val, err := ConfigsGet(ctx, JWT_SIGNING_KEY)
if err != nil {
log.Fatalln("init jwt signing key in mysql", err)
}
if val != "" {
return val
}
content := fmt.Sprintf("%s%d%d%s", runner.Hostname, os.Getpid(), time.Now().UnixNano(), str.RandLetters(6))
key := str.MD5(content)
err = ConfigsSet(ctx, JWT_SIGNING_KEY, key)
if err != nil {
log.Fatalln("init jwt signing key in mysql", err)
}
return key
}
// InitSalt generate random salt
func InitSalt(ctx *ctx.Context) {
val, err := ConfigsGet(ctx, SALT)
if err != nil {
log.Fatalln("init salt in mysql", err)
}
if val != "" {
return
}
content := fmt.Sprintf("%s%d%d%s", runner.Hostname, os.Getpid(), time.Now().UnixNano(), str.RandLetters(6))
salt := str.MD5(content)
err = ConfigsSet(ctx, SALT, salt)
if err != nil {
log.Fatalln("init salt in mysql", err)
}
}
func InitRSAPassWord(ctx *ctx.Context) (string, error) {
val, err := ConfigsGet(ctx, RSA_PASSWORD)
if err != nil {
return "", errors.WithMessage(err, "failed to get rsa password")
}
if val != "" {
return val, nil
}
content := fmt.Sprintf("%s%d%d%s", runner.Hostname, os.Getpid(), time.Now().UnixNano(), str.RandLetters(6))
pwd := str.MD5(content)
err = ConfigsSet(ctx, RSA_PASSWORD, pwd)
if err != nil {
return "", errors.WithMessage(err, "failed to set rsa password")
}
return pwd, nil
}
func ConfigsGet(ctx *ctx.Context, ckey string) (string, error) { //select built-in type configs
if !ctx.IsCenter {
if !ctx.IsCenter {
s, err := poster.GetByUrls[string](ctx, "/v1/n9e/config?key="+ckey)
return s, err
}
}
var lst []string
err := DB(ctx).Model(&Configs{}).Where("ckey=? and external=? ", ckey, 0).Pluck("cval", &lst).Error
if err != nil {
return "", errors.WithMessage(err, "failed to query configs")
}
if len(lst) > 0 {
return lst[0], nil
}
return "", nil
}
func ConfigsSet(ctx *ctx.Context, ckey, cval string) error {
return ConfigsSetWithUname(ctx, ckey, cval, "default")
}
func ConfigsSetWithUname(ctx *ctx.Context, ckey, cval, uName string) error { //built-in
num, err := Count(DB(ctx).Model(&Configs{}).Where("ckey=? and external=?", ckey, 0)) //built-in type
if err != nil {
return errors.WithMessage(err, "failed to count configs")
}
now := time.Now().Unix()
if num == 0 {
// insert
err = DB(ctx).Create(&Configs{
Ckey: ckey,
Cval: cval,
CreateBy: uName,
UpdateBy: uName,
CreateAt: now,
UpdateAt: now,
}).Error
} else {
// update
err = DB(ctx).Model(&Configs{}).Where("ckey=?", ckey).Updates(map[string]interface{}{
"cval": cval,
"update_by": uName,
"update_at": now,
}).Error
}
return err
}
func ConfigsGetFlashDutyAppKey(ctx *ctx.Context) (string, error) {
configs, err := ConfigsSelectByCkey(ctx, "flashduty_app_key")
if err != nil {
return "", err
}
if len(configs) == 0 || configs[0].Cval == "" {
return "", errors.New("flashduty_app_key is empty")
}
return configs[0].Cval, nil
}
func ConfigsSelectByCkey(ctx *ctx.Context, ckey string) ([]Configs, error) {
if !ctx.IsCenter {
return []Configs{}, nil
}
var objs []Configs
err := DB(ctx).Where("ckey=?", ckey).Find(&objs).Error
if err != nil {
return nil, errors.WithMessage(err, "failed to select conf")
}
return objs, nil
}
func ConfigGet(ctx *ctx.Context, id int64) (*Configs, error) {
var objs []*Configs
err := DB(ctx).Where("id=?", id).Find(&objs).Error
if err != nil {
return nil, err
}
if len(objs) == 0 {
return nil, nil
}
return objs[0], nil
}
func ConfigsGets(ctx *ctx.Context, prefix string, limit, offset int) ([]*Configs, error) {
var objs []*Configs
session := DB(ctx)
if prefix != "" {
session = session.Where("ckey like ?", prefix+"%")
}
err := session.Order("id desc").Limit(limit).Offset(offset).Find(&objs).Error
return objs, err
}
func (c *Configs) Add(ctx *ctx.Context) error {
num, err := Count(DB(ctx).Model(&Configs{}).Where("ckey=? and external=? ", c.Ckey, c.External))
if err != nil {
return errors.WithMessage(err, "failed to count configs")
}
if num > 0 {
return errors.New("key is exists")
}
// insert
err = DB(ctx).Create(&Configs{
Ckey: c.Ckey,
Cval: c.Cval,
External: c.External,
CreateBy: c.CreateBy,
UpdateBy: c.CreateBy,
CreateAt: c.CreateAt,
UpdateAt: c.CreateAt,
}).Error
return err
}
func (c *Configs) Update(ctx *ctx.Context) error {
num, err := Count(DB(ctx).Model(&Configs{}).Where("id<>? and ckey=? and external=? ", c.Id, c.Ckey, c.External))
if err != nil {
return errors.WithMessage(err, "failed to count configs")
}
if num > 0 {
return errors.New("key is exists")
}
err = DB(ctx).Model(&Configs{}).Where("id=?", c.Id).Updates(c).Error
return err
}
func ConfigsDel(ctx *ctx.Context, ids []int64) error {
return DB(ctx).Where("id in ?", ids).Delete(&Configs{}).Error
}
func ConfigsGetUserVariable(context *ctx.Context) ([]Configs, error) {
var objs []Configs
tx := DB(context).Where("external = ?", ConfigExternal).Order("id desc")
err := tx.Find(&objs).Error
if err != nil {
return nil, errors.WithMessage(err, "failed to gets user variable")
}
return objs, nil
}
func ConfigsUserVariableInsert(context *ctx.Context, conf Configs) error {
conf.External = ConfigExternal
conf.Id = 0
err := userVariableCheck(context, conf.Ckey, conf.Id)
if err != nil {
return err
}
return DB(context).Create(&conf).Error
}
func ConfigsUserVariableUpdate(context *ctx.Context, conf Configs) error {
err := userVariableCheck(context, conf.Ckey, conf.Id)
if err != nil {
return err
}
configOld, _ := ConfigGet(context, conf.Id)
if configOld == nil || configOld.External != ConfigExternal { //not valid id
return fmt.Errorf("not valid configs(id)")
}
return DB(context).Model(&Configs{Id: conf.Id}).Select(
"ckey", "cval", "note", "encrypted", "update_by", "update_at").Updates(conf).Error
}
func isCStyleIdentifier(str string) bool {
regex := regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*$`)
return regex.MatchString(str)
}
func userVariableCheck(context *ctx.Context, ckey string, id int64) error {
var objs []*Configs
var err error
if !isCStyleIdentifier(ckey) {
return fmt.Errorf("invalid key(%q), please use ^[a-zA-Z_][a-zA-Z0-9_]*$ ", ckey)
}
// reserved words
words := []string{"Scheme", "Host", "Hostname", "Port", "Path", "Query", "Fragment"}
for _, word := range words {
if ckey == word {
return fmt.Errorf("invalid key(%q), reserved words, please use other key", ckey)
}
}
if id != 0 { //update
err = DB(context).Where("id <> ? and ckey = ? and external=?", &id, ckey, ConfigExternal).Find(&objs).Error
} else {
err = DB(context).Where("ckey = ? and external=?", ckey, ConfigExternal).Find(&objs).Error
}
if err != nil {
return err
}
if len(objs) == 0 {
return nil
}
return fmt.Errorf("duplicate ckey value found: %s", ckey)
}
func ConfigsUserVariableStatistics(context *ctx.Context) (*Statistics, error) {
if !context.IsCenter {
return poster.GetByUrls[*Statistics](context, "/v1/n9e/statistic?name=user_variable")
}
session := DB(context).Model(&Configs{}).Select(
"count(*) as total", "max(update_at) as last_updated").Where("external = ?", ConfigExternal)
var stats []*Statistics
err := session.Find(&stats).Error
if err != nil {
return nil, err
}
return stats[0], nil
}
func ConfigUserVariableGetDecryptMap(context *ctx.Context, privateKey []byte, passWord string) (map[string]string, error) {
if !context.IsCenter {
ret, err := poster.GetByUrls[map[string]string](context, "/v1/n9e/user-variable/decrypt")
if err != nil {
return nil, err
}
return ret, nil
}
lst, err := ConfigsGetUserVariable(context)
if err != nil {
return nil, err
}
ret := make(map[string]string, len(lst))
for i := 0; i < len(lst); i++ {
if lst[i].Encrypted != ConfigEncrypted {
ret[lst[i].Ckey] = lst[i].Cval
} else {
decCval, decErr := secu.Decrypt(lst[i].Cval, privateKey, passWord)
if decErr != nil {
logger.Errorf("RSA Decrypt failed: %v. Ckey: %s", decErr, lst[i].Ckey)
decCval = ""
}
ret[lst[i].Ckey] = decCval
}
}
return ret, nil
}