[API节点]显示API节点运行日志 [用户]增加可用功能控制、AccessKey管理

This commit is contained in:
刘祥超
2020-12-30 22:01:01 +08:00
parent 4147e0d8bc
commit f905bb7066
15 changed files with 556 additions and 17 deletions

View File

@@ -0,0 +1,71 @@
package models
import (
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
)
const (
SubUserStateEnabled = 1 // 已启用
SubUserStateDisabled = 0 // 已禁用
)
type SubUserDAO dbs.DAO
func NewSubUserDAO() *SubUserDAO {
return dbs.NewDAO(&SubUserDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeSubUsers",
Model: new(SubUser),
PkName: "id",
},
}).(*SubUserDAO)
}
var SharedSubUserDAO *SubUserDAO
func init() {
dbs.OnReady(func() {
SharedSubUserDAO = NewSubUserDAO()
})
}
// 启用条目
func (this *SubUserDAO) EnableSubUser(id uint32) error {
_, err := this.Query().
Pk(id).
Set("state", SubUserStateEnabled).
Update()
return err
}
// 禁用条目
func (this *SubUserDAO) DisableSubUser(id uint32) error {
_, err := this.Query().
Pk(id).
Set("state", SubUserStateDisabled).
Update()
return err
}
// 查找启用中的条目
func (this *SubUserDAO) FindEnabledSubUser(id uint32) (*SubUser, error) {
result, err := this.Query().
Pk(id).
Attr("state", SubUserStateEnabled).
Find()
if result == nil {
return nil, err
}
return result.(*SubUser), err
}
// 根据主键查找名称
func (this *SubUserDAO) FindSubUserName(id uint32) (string, error) {
return this.Query().
Pk(id).
Result("name").
FindStringCol("")
}

View File

@@ -0,0 +1,5 @@
package models
import (
_ "github.com/go-sql-driver/mysql"
)

View File

@@ -0,0 +1,26 @@
package models
// 子用户
type SubUser struct {
Id uint32 `field:"id"` // ID
UserId uint32 `field:"userId"` // 所属主用户ID
IsOn uint8 `field:"isOn"` // 是否启用
Name string `field:"name"` // 名称
Username string `field:"username"` // 用户名
Password string `field:"password"` // 密码
State uint8 `field:"state"` // 状态
}
type SubUserOperator struct {
Id interface{} // ID
UserId interface{} // 所属主用户ID
IsOn interface{} // 是否启用
Name interface{} // 名称
Username interface{} // 用户名
Password interface{} // 密码
State interface{} // 状态
}
func NewSubUserOperator() *SubUserOperator {
return &SubUserOperator{}
}

View File

@@ -0,0 +1 @@
package models

View File

@@ -0,0 +1,111 @@
package models
import (
"github.com/TeaOSLab/EdgeAPI/internal/errors"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/rands"
)
const (
UserAccessKeyStateEnabled = 1 // 已启用
UserAccessKeyStateDisabled = 0 // 已禁用
)
type UserAccessKeyDAO dbs.DAO
func NewUserAccessKeyDAO() *UserAccessKeyDAO {
return dbs.NewDAO(&UserAccessKeyDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeUserAccessKeys",
Model: new(UserAccessKey),
PkName: "id",
},
}).(*UserAccessKeyDAO)
}
var SharedUserAccessKeyDAO *UserAccessKeyDAO
func init() {
dbs.OnReady(func() {
SharedUserAccessKeyDAO = NewUserAccessKeyDAO()
})
}
// 启用条目
func (this *UserAccessKeyDAO) EnableUserAccessKey(id int64) error {
_, err := this.Query().
Pk(id).
Set("state", UserAccessKeyStateEnabled).
Update()
return err
}
// 禁用条目
func (this *UserAccessKeyDAO) DisableUserAccessKey(id int64) error {
_, err := this.Query().
Pk(id).
Set("state", UserAccessKeyStateDisabled).
Update()
return err
}
// 查找启用中的条目
func (this *UserAccessKeyDAO) FindEnabledUserAccessKey(id int64) (*UserAccessKey, error) {
result, err := this.Query().
Pk(id).
Attr("state", UserAccessKeyStateEnabled).
Find()
if result == nil {
return nil, err
}
return result.(*UserAccessKey), err
}
// 创建Key
func (this *UserAccessKeyDAO) CreateAccessKey(userId int64, description string) (int64, error) {
if userId <= 0 {
return 0, errors.New("invalid userId")
}
op := NewUserAccessKeyOperator()
op.UserId = userId
op.Description = description
op.UniqueId = rands.String(16)
op.Secret = rands.String(32)
op.IsOn = true
op.State = UserAccessKeyStateEnabled
return this.SaveInt64(op)
}
// 查找用户所有的Key
func (this *UserAccessKeyDAO) FindAllEnabledAccessKeys(userId int64) (result []*UserAccessKey, err error) {
_, err = this.Query().
State(UserAccessKeyStateEnabled).
DescPk().
Slice(&result).
FindAll()
return
}
// 检查用户的AccessKey
func (this *UserAccessKeyDAO) CheckUserAccessKey(userId int64, accessKeyId int64) (bool, error) {
return this.Query().
Pk(accessKeyId).
State(UserAccessKeyStateEnabled).
Attr("userId", userId).
Exist()
}
// 设置是否启用
func (this *UserAccessKeyDAO) UpdateAccessKeyIsOn(accessKeyId int64, isOn bool) error {
if accessKeyId <= 0 {
return errors.New("invalid accessKeyId")
}
_, err := this.Query().
Pk(accessKeyId).
Set("isOn", isOn).
Update()
return err
}

View File

@@ -0,0 +1,5 @@
package models
import (
_ "github.com/go-sql-driver/mysql"
)

View File

@@ -0,0 +1,28 @@
package models
// AccessKey
type UserAccessKey struct {
Id uint32 `field:"id"` // ID
UserId uint32 `field:"userId"` // 用户ID
SubUserId uint32 `field:"subUserId"` // 子用户ID
IsOn uint8 `field:"isOn"` // 是否启用
UniqueId string `field:"uniqueId"` // 唯一的Key
Secret string `field:"secret"` // 密钥
Description string `field:"description"` // 备注
State uint8 `field:"state"` // 状态
}
type UserAccessKeyOperator struct {
Id interface{} // ID
UserId interface{} // 用户ID
SubUserId interface{} // 子用户ID
IsOn interface{} // 是否启用
UniqueId interface{} // 唯一的Key
Secret interface{} // 密钥
Description interface{} // 备注
State interface{} // 状态
}
func NewUserAccessKeyOperator() *UserAccessKeyOperator {
return &UserAccessKeyOperator{}
}

View File

@@ -0,0 +1 @@
package models

View File

@@ -1,6 +1,7 @@
package models
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
@@ -229,3 +230,54 @@ func (this *UserDAO) FindUserClusterId(userId int64) (int64, error) {
Result("clusterId").
FindInt64Col(0)
}
// 更新用户Features
func (this *UserDAO) UpdateUserFeatures(userId int64, featuresJSON []byte) error {
if userId <= 0 {
return errors.New("invalid userId")
}
if len(featuresJSON) == 0 {
featuresJSON = []byte("[]")
}
_, err := this.Query().
Pk(userId).
Set("features", featuresJSON).
Update()
if err != nil {
return err
}
return nil
}
// 查找用户Features
func (this *UserDAO) FindUserFeatures(userId int64) ([]*UserFeature, error) {
featuresJSON, err := this.Query().
Pk(userId).
Result("features").
FindStringCol("")
if err != nil {
return nil, err
}
if len(featuresJSON) == 0 {
return nil, nil
}
featureCodes := []string{}
err = json.Unmarshal([]byte(featuresJSON), &featureCodes)
if err != nil {
return nil, err
}
// 检查是否还存在以及设置名称
result := []*UserFeature{}
if len(featureCodes) > 0 {
for _, featureCode := range featureCodes {
f := FindUserFeature(featureCode)
if f != nil {
result = append(result, &UserFeature{Name: f.Name, Code: f.Code, Description: f.Description})
}
}
}
return result, nil
}

View File

@@ -0,0 +1,50 @@
package models
import "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
var (
// 所有功能列表,注意千万不能在运行时进行修改
allUserFeatures = []*UserFeature{
{
Name: "记录访问日志",
Code: "server.accessLog",
Description: "用户可以开启服务的访问日志",
},
{
Name: "转发访问日志",
Code: "server.accessLog.forward",
Description: "用户可以配置访问日志转发到自定义的API",
},
{
Name: "开启WAF",
Code: "server.waf",
Description: "用户可以开启WAF功能并可以设置黑白名单等",
},
}
)
// 用户功能
type UserFeature struct {
Name string `json:"name"`
Code string `json:"code"`
Description string `json:"description"`
}
func (this *UserFeature) ToPB() *pb.UserFeature {
return &pb.UserFeature{Name: this.Name, Code: this.Code, Description: this.Description}
}
// 所有功能列表
func FindAllUserFeatures() []*UserFeature {
return allUserFeatures
}
// 查询单个功能
func FindUserFeature(code string) *UserFeature {
for _, feature := range allUserFeatures {
if feature.Code == code {
return feature
}
}
return nil
}

View File

@@ -17,6 +17,7 @@ type User struct {
State uint8 `field:"state"` // 状态
Source string `field:"source"` // 来源
ClusterId uint32 `field:"clusterId"` // 集群ID
Features string `field:"features"` // 允许操作的特征
}
type UserOperator struct {
@@ -35,6 +36,7 @@ type UserOperator struct {
State interface{} // 状态
Source interface{} // 来源
ClusterId interface{} // 集群ID
Features interface{} // 允许操作的特征
}
func NewUserOperator() *UserOperator {

View File

@@ -6,6 +6,7 @@ import (
"github.com/TeaOSLab/EdgeAPI/internal/configs"
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services"
"github.com/TeaOSLab/EdgeAPI/internal/setup"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
@@ -72,12 +73,12 @@ func (this *APINode) Start() {
go NewNodeStatusExecutor().Listen()
// 监听RPC服务
logs.Println("[API_NODE]starting rpc ...")
remotelogs.Println("API_NODE", "starting RPC server ...")
// HTTP
httpConfig, err := apiNode.DecodeHTTP()
if err != nil {
logs.Println("[API_NODE]decode http config: " + err.Error())
remotelogs.Error("API_NODE", "decode http config: "+err.Error())
return
}
isListening := false
@@ -86,13 +87,13 @@ func (this *APINode) Start() {
for _, addr := range listen.Addresses() {
listener, err := net.Listen("tcp", addr)
if err != nil {
logs.Println("[API_NODE]listening '" + addr + "' failed: " + err.Error())
remotelogs.Error("API_NODE", "listening '"+addr+"' failed: "+err.Error())
continue
}
go func() {
err := this.listenRPC(listener, nil)
if err != nil {
logs.Println("[API_NODE]listening '" + addr + "' rpc: " + err.Error())
remotelogs.Error("API_NODE", "listening '"+addr+"' rpc: "+err.Error())
return
}
}()
@@ -104,7 +105,7 @@ func (this *APINode) Start() {
// HTTPS
httpsConfig, err := apiNode.DecodeHTTPS()
if err != nil {
logs.Println("[API_NODE]decode https config: " + err.Error())
remotelogs.Error("API_NODE", "decode https config: "+err.Error())
return
}
if httpsConfig != nil &&
@@ -122,7 +123,7 @@ func (this *APINode) Start() {
for _, addr := range listen.Addresses() {
listener, err := net.Listen("tcp", addr)
if err != nil {
logs.Println("[API_NODE]listening '" + addr + "' failed: " + err.Error())
remotelogs.Error("API_NODE", "listening '"+addr+"' failed: "+err.Error())
continue
}
go func() {
@@ -130,7 +131,7 @@ func (this *APINode) Start() {
Certificates: certs,
})
if err != nil {
logs.Println("[API_NODE]listening '" + addr + "' rpc: " + err.Error())
remotelogs.Error("API_NODE", "listening '"+addr+"' rpc: "+err.Error())
return
}
}()
@@ -142,7 +143,7 @@ func (this *APINode) Start() {
// HTTP接口
if !isListening {
logs.Println("[API_NODE]the api node does have a listening address")
remotelogs.Error("API_NODE", "the api node require at least one listening address")
return
}
@@ -154,7 +155,7 @@ func (this *APINode) Start() {
func (this *APINode) listenRPC(listener net.Listener, tlsConfig *tls.Config) error {
var rpcServer *grpc.Server
if tlsConfig == nil {
logs.Println("[API_NODE]listening http://" + listener.Addr().String() + " ...")
remotelogs.Println("API_NODE", "listening http://"+listener.Addr().String()+" ...")
rpcServer = grpc.NewServer()
} else {
logs.Println("[API_NODE]listening https://" + listener.Addr().String() + " ...")
@@ -212,6 +213,7 @@ func (this *APINode) listenRPC(listener net.Listener, tlsConfig *tls.Config) err
pb.RegisterUserBillServiceServer(rpcServer, &services.UserBillService{})
pb.RegisterUserNodeServiceServer(rpcServer, &services.UserNodeService{})
pb.RegisterLoginServiceServer(rpcServer, &services.LoginService{})
pb.RegisterUserAccessKeyServiceServer(rpcServer, &services.UserAccessKeyService{})
err := rpcServer.Serve(listener)
if err != nil {
return errors.New("[API_NODE]start rpc failed: " + err.Error())
@@ -252,11 +254,13 @@ func (this *APINode) autoUpgrade() error {
return nil
}
}
// 不使用remotelogs(),因为此时还没有启动完成
logs.Println("[API_NODE]upgrade database starting ...")
err = setup.NewSQLExecutor(dbConfig).Run()
if err != nil {
return errors.New("execute sql failed: " + err.Error())
}
// 不使用remotelogs
logs.Println("[API_NODE]upgrade database done")
return nil
}

View File

@@ -1,8 +1,9 @@
package remotelogs
import (
"github.com/TeaOSLab/EdgeAPI/internal/configs"
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/logs"
"time"
@@ -15,7 +16,10 @@ func init() {
ticker := time.NewTicker(60 * time.Second)
go func() {
for range ticker.C {
// TODO
err := uploadLogs()
if err != nil {
logs.Println("[LOG]" + err.Error())
}
}
}()
}
@@ -24,7 +28,7 @@ func init() {
func Println(tag string, description string) {
logs.Println("[" + tag + "]" + description)
nodeConfig, _ := nodeconfigs.SharedNodeConfig()
nodeConfig, _ := configs.SharedAPIConfig()
if nodeConfig == nil {
return
}
@@ -35,7 +39,7 @@ func Println(tag string, description string) {
Tag: tag,
Description: description,
Level: "info",
NodeId: nodeConfig.Id,
NodeId: nodeConfig.NumberId(),
CreatedAt: time.Now().Unix(),
}:
default:
@@ -47,7 +51,7 @@ func Println(tag string, description string) {
func Warn(tag string, description string) {
logs.Println("[" + tag + "]" + description)
nodeConfig, _ := nodeconfigs.SharedNodeConfig()
nodeConfig, _ := configs.SharedAPIConfig()
if nodeConfig == nil {
return
}
@@ -58,7 +62,7 @@ func Warn(tag string, description string) {
Tag: tag,
Description: description,
Level: "warning",
NodeId: nodeConfig.Id,
NodeId: nodeConfig.NumberId(),
CreatedAt: time.Now().Unix(),
}:
default:
@@ -70,7 +74,7 @@ func Warn(tag string, description string) {
func Error(tag string, description string) {
logs.Println("[" + tag + "]" + description)
nodeConfig, _ := nodeconfigs.SharedNodeConfig()
nodeConfig, _ := configs.SharedAPIConfig()
if nodeConfig == nil {
return
}
@@ -81,10 +85,28 @@ func Error(tag string, description string) {
Tag: tag,
Description: description,
Level: "error",
NodeId: nodeConfig.Id,
NodeId: nodeConfig.NumberId(),
CreatedAt: time.Now().Unix(),
}:
default:
}
}
// 上传日志
func uploadLogs() error {
Loop:
for {
select {
case log := <-logChan:
err := models.SharedNodeLogDAO.CreateLog(models.NodeRoleAPI, log.NodeId, log.Level, log.Tag, log.Description, log.CreatedAt)
if err != nil {
return err
}
default:
break Loop
}
}
return nil
}

View File

@@ -2,6 +2,7 @@ package services
import (
"context"
"encoding/json"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
rpcutils "github.com/TeaOSLab/EdgeAPI/internal/rpc/utils"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
@@ -359,3 +360,61 @@ func (this *UserService) FindUserNodeClusterId(ctx context.Context, req *pb.Find
}
return &pb.FindUserNodeClusterIdResponse{NodeClusterId: clusterId}, nil
}
// 设置用户能使用的功能
func (this *UserService) UpdateUserFeatures(ctx context.Context, req *pb.UpdateUserFeaturesRequest) (*pb.RPCSuccess, error) {
_, err := this.ValidateAdmin(ctx, 0)
if err != nil {
return nil, err
}
featuresJSON, err := json.Marshal(req.FeatureCodes)
if err != nil {
return nil, err
}
err = models.SharedUserDAO.UpdateUserFeatures(req.UserId, featuresJSON)
if err != nil {
return nil, err
}
return this.Success()
}
// 获取用户所有的功能列表
func (this *UserService) FindUserFeatures(ctx context.Context, req *pb.FindUserFeaturesRequest) (*pb.FindUserFeaturesResponse, error) {
_, userId, err := this.ValidateAdminAndUser(ctx, 0, req.UserId)
if err != nil {
return nil, err
}
if userId > 0 {
if userId != req.UserId {
return nil, this.PermissionError()
}
}
features, err := models.SharedUserDAO.FindUserFeatures(req.UserId)
if err != nil {
return nil, err
}
result := []*pb.UserFeature{}
for _, feature := range features {
result = append(result, feature.ToPB())
}
return &pb.FindUserFeaturesResponse{Features: result}, nil
}
// 获取所有的功能定义
func (this *UserService) FindAllUserFeatureDefinitions(ctx context.Context, req *pb.FindAllUserFeatureDefinitionsRequest) (*pb.FindAllUserFeatureDefinitionsResponse, error) {
_, err := this.ValidateAdmin(ctx, 0)
if err != nil {
return nil, err
}
features := models.FindAllUserFeatures()
result := []*pb.UserFeature{}
for _, feature := range features {
result = append(result, feature.ToPB())
}
return &pb.FindAllUserFeatureDefinitionsResponse{Features: result}, nil
}

View File

@@ -0,0 +1,102 @@
package services
import (
"context"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
// 用户AccessKey相关服务
type UserAccessKeyService struct {
BaseService
}
// 创建AccessKey
func (this *UserAccessKeyService) CreateUserAccessKey(ctx context.Context, req *pb.CreateUserAccessKeyRequest) (*pb.CreateUserAccessKeyResponse, error) {
_, _, err := this.ValidateAdminAndUser(ctx, 0, req.UserId)
if err != nil {
return nil, err
}
userAccessKeyId, err := models.SharedUserAccessKeyDAO.CreateAccessKey(req.UserId, req.Description)
if err != nil {
return nil, err
}
return &pb.CreateUserAccessKeyResponse{UserAccessKeyId: userAccessKeyId}, nil
}
// 查找所有的AccessKey
func (this *UserAccessKeyService) FindAllEnabledUserAccessKeys(ctx context.Context, req *pb.FindAllEnabledUserAccessKeysRequest) (*pb.FindAllEnabledUserAccessKeysResponse, error) {
_, _, err := this.ValidateAdminAndUser(ctx, 0, req.UserId)
if err != nil {
return nil, err
}
accessKeys, err := models.SharedUserAccessKeyDAO.FindAllEnabledAccessKeys(req.UserId)
if err != nil {
return nil, err
}
result := []*pb.UserAccessKey{}
for _, accessKey := range accessKeys {
result = append(result, &pb.UserAccessKey{
Id: int64(accessKey.Id),
UserId: int64(accessKey.UserId),
SubUserId: int64(accessKey.SubUserId),
IsOn: accessKey.IsOn == 1,
UniqueId: accessKey.UniqueId,
Secret: accessKey.Secret,
Description: accessKey.Description,
})
}
return &pb.FindAllEnabledUserAccessKeysResponse{UserAccessKeys: result}, nil
}
// 删除AccessKey
func (this *UserAccessKeyService) DeleteUserAccessKey(ctx context.Context, req *pb.DeleteUserAccessKeyRequest) (*pb.RPCSuccess, error) {
_, userId, err := this.ValidateAdminAndUser(ctx, 0, 0)
if err != nil {
return nil, err
}
if userId > 0 {
ok, err := models.SharedUserAccessKeyDAO.CheckUserAccessKey(userId, req.UserAccessKeyId)
if err != nil {
return nil, err
}
if !ok {
return nil, this.PermissionError()
}
}
err = models.SharedUserAccessKeyDAO.DisableUserAccessKey(req.UserAccessKeyId)
if err != nil {
return nil, err
}
return this.Success()
}
// 设置是否启用AccessKey
func (this *UserAccessKeyService) UpdateUserAccessKeyIsOn(ctx context.Context, req *pb.UpdateUserAccessKeyIsOnRequest) (*pb.RPCSuccess, error) {
_, userId, err := this.ValidateAdminAndUser(ctx, 0, 0)
if err != nil {
return nil, err
}
if userId > 0 {
ok, err := models.SharedUserAccessKeyDAO.CheckUserAccessKey(userId, req.UserAccessKeyId)
if err != nil {
return nil, err
}
if !ok {
return nil, this.PermissionError()
}
}
err = models.SharedUserAccessKeyDAO.UpdateAccessKeyIsOn(req.UserAccessKeyId, req.IsOn)
if err != nil {
return nil, err
}
return this.Success()
}