package handler
import (
"github.com/gin-gonic/gin"
"sshfortress/model"
)
func MachineAll(c *gin.Context) {
q := model.MachineQ{}
err := c.ShouldBindQuery(&q)
if handleError(c, err) {
return
}
mdl := q.Machine
query := &(q.PaginationQ)
thisU, err := mwJwtUser(c)
list, total, err := mdl.All(query, thisU)
if handleError(c, err) {
return
}
jsonPagination(c, list, total, query)
}
func MachineOne(c *gin.Context) {
var mdl model.Machine
var err error
id, err := parseParamID(c)
if handleError(c, err) {
return
}
mdl.Id = id
err = mdl.One()
if handleError(c, err) {
return
}
jsonData(c, mdl)
}
func MachineCreate(c *gin.Context) {
var mdl model.Machine
err := c.ShouldBind(&mdl)
if handleError(c, err) {
return
}
err = mdl.Create()
if handleError(c, err) {
return
}
jsonData(c, mdl)
}
func MachineUpdate(c *gin.Context) {
var mdl model.Machine
err := c.ShouldBind(&mdl)
if handleError(c, err) {
return
}
err = mdl.Update()
if handleError(c, err) {
return
}
jsonSuccess(c)
}
func MachineDelete(c *gin.Context) {
u, err := mwJwtUser(c)
if handleError(c, err) {
return
}
var mdl model.Machine
id, err := parseParamID(c)
if handleError(c, err) {
return
}
mdl.Id = id
err = mdl.Delete(u)
if handleError(c, err) {
return
}
jsonSuccess(c)
}
//MachineHardware 获取机器的物理信息
func MachineHardware(c *gin.Context) {
id, err := parseParamID(c)
if handleError(c, err) {
return
}
hi, err := model.CreateHardwareInfo(id)
if handleError(c, err) {
return
}
jsonData(c, hi)
}
数据model
package model
import (
"errors"
)
type MachineQ struct {
Machine
PaginationQ
}
type Machine struct {
BaseModel
Name string `gorm:"type:varchar(50);unique_index" json:"name" form:"name"`
SshIp string `json:"ssh_ip" form:"ssh_ip"`
SshPort uint `json:"ssh_port"`
LanIp string `json:"lan_ip" form:"lan_ip"`
WanIp string `json:"wan_ip" form:"wan_ip"`
Cate uint `gorm:"default:'2'" json:"cate" comment:"机器性质:2:无外网ip 4:外网可以访问"`
ClusterSshId uint `gorm:"index" json:"cluster_ssh_id" comment:"关联集群管理ssh账号"`
ClusterJumperId *uint `gorm:"index,default:'0'" json:"cluster_jumper_id" form:"cluster_jumper_id" comment:"集群代理ID 关联clusterJumper表"`
UserId uint `gorm:"index" json:"user_id" comment:"机器的添加者"`
Status uint `gorm:"default:'0'" json:"status" form:"status" comment:"机器状态 0-未知 2-连接错误 4-ssh认证错误 8-正常 "`
User User `gorm:"association_autoupdate:false;association_autocreate:false" json:"user"`
ClusterSsh *ClusterSsh `gorm:"association_autoupdate:false;association_autocreate:false" json:"cluster_ssh,omitempty"`
ClusterJumper *ClusterJumper `gorm:"association_autoupdate:false;association_autocreate:false" json:"cluster_jumper,omitempty"`
//Hardware HardwareInfo `gorm:"type:json" json:"hardware"`
}
func (m *Machine) AfterFind() (err error) {
return
}
//One
func (m *Machine) One() error {
return crudOne(m)
}
//All
func (m Machine) All(q *PaginationQ, user *User) (list *[]Machine, total uint, err error) {
tx := db.Model(m).Preload("ClusterSsh").Preload("ClusterJumper") //.Where("ancestor_path like ?", m.qAncetorPath())
list = &[]Machine{}
//role ==2
//显示全部的机器
if m.Name != "" {
tx = tx.Where("`name` like ?", "%"+m.Name+"%")
}
if m.SshIp != "" {
tx = tx.Where("`ssh_ip` like ?", "%"+m.SshIp+"%")
}
if m.WanIp != "" {
tx = tx.Where("`wan_ip` like ?", "%"+m.WanIp+"%")
}
if m.LanIp != "" {
tx = tx.Where("`lan_ip` like ?", "%"+m.LanIp+"%")
}
if user.Role == 4 {
//普通用户显示自由机器和授权的机器
machineIds := []uint{}
err = db.Model(MachineUser{}).Where("user_id = ?", user.Id).Pluck("machine_id", &machineIds).Error
if err != nil {
return nil, 0, err
}
if len(machineIds) > 0 {
tx = tx.Where("`user_id` = ? OR `id` in (?)", user.Id, machineIds)
} else {
tx = tx.Where("`user_id` = ?", user.Id)
}
}
total, err = crudAll(q, tx, list)
return
}
//Update
func (m *Machine) Update() (err error) {
if m.Id < 1 {
return errors.New("id必须大于0")
}
return db.Model(m).Update(m).Error
}
//Create
func (m *Machine) Create() (err error) {
m.Id = 0
return db.Create(m).Error
}
//Delete
func (m *Machine) Delete(u *User) (err error) {
if m.Id < 2 {
return errors.New("id必须大于2")
}
//删除用户与机器的关联
err = db.Where("machine_id = ?", m.Id).Delete(MachineUser{}).Error
if err != nil {
return
}
if u.Role == UserRoleAdmin {
return crudDelete(m)
}
err = db.Unscoped().Where("`id` = ? AND `user_id` = ?", m.Id, u.Id).Delete(m).Error
return
}
// handler help
package handler
import (
"errors"
"fmt"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"github.com/sirupsen/logrus"
"net/http"
"sshfortress/model"
"sshfortress/stat"
"strconv"
"time"
)
func jsonError(c *gin.Context, msg interface{}) {
stat.GaugeVecApiError.WithLabelValues("API").Inc()
var ms string
switch v := msg.(type) {
case string:
ms = v
case error:
ms = v.Error()
default:
ms = ""
}
c.AbortWithStatusJSON(200, gin.H{"ok": false, "msg": ms})
}
func jsonAuthError(c *gin.Context, msg interface{}) {
c.AbortWithStatusJSON(http.StatusPreconditionFailed, gin.H{"ok": false, "msg": msg})
}
func jsonData(c *gin.Context, data interface{}) {
c.AbortWithStatusJSON(200, gin.H{"ok": true, "data": data})
}
//func jsonPagination(c *gin.Context, list interface{}, total uint, query *model.PaginationQ) {
// c.AbortWithStatusJSON(200, gin.H{"ok": true, "data": list, "total": total, "offset": query.Offset, "limit": query.Size})
//}
func jsonSuccess(c *gin.Context) {
c.AbortWithStatusJSON(200, gin.H{"ok": true, "msg": "success"})
}
func jsonPagination(c *gin.Context, list interface{}, total uint, query *model.PaginationQ) {
c.JSON(200, gin.H{"ok": true, "data": list, "total": total, "page": query.Page, "size": query.Size})
}
func handleError(c *gin.Context, err error) bool {
if err != nil {
//logrus.WithError(err).Error("gin context http handler error")
jsonError(c, err.Error())
return true
}
return false
}
func handlerAuthMiddlewareError(c *gin.Context, err error) bool {
if err != nil {
c.AbortWithStatusJSON(401, gin.H{"msg": err.Error()})
return true
}
return false
}
func wshandleError(err error) bool {
if err != nil {
stat.GaugeVecApiError.WithLabelValues("WS").Inc()
logrus.WithError(err).Error("handler ws ERROR:")
return true
}
return false
}
func wshandleErrorPro(wc *websocket.Conn, err error) bool {
if err != nil {
logrus.WithError(err).Error("ssh-websocket error")
wc.WriteControl(websocket.CloseMessage, []byte(err.Error()), time.Now())
return true
}
return false
}
func parseParamID(c *gin.Context) (uint, error) {
id := c.Param("id")
parseId, err := strconv.ParseUint(id, 10, 32)
if err != nil {
return 0, errors.New("id must be an unsigned int")
}
return uint(parseId), nil
}
func mwJwtUser(c *gin.Context) (*model.User, error) {
uid, err := mwJwtUid(c)
if err != nil {
return nil, err
}
sessionUserKey := fmt.Sprintf("thisUser:%d", uid)
vu, ok := c.Get(sessionUserKey)
if ok {
tu, okay := vu.(model.User)
if okay {
return &tu, nil
} else {
return nil, fmt.Errorf("gin.context %s 不是model.User", sessionUserKey)
}
}
//session context 没有值则mysql数据库中取,并设置context 值
user := model.User{}
user.Id = uid
err = user.One()
if err != nil {
return nil, fmt.Errorf("context can not get user of %d,error:%s", user.Id, err)
}
c.Set(sessionUserKey, user)
return &user, nil
}
func mwJwtUid(c *gin.Context) (uint, error) {
return getCtxUint(c, jwtCtxUidKey)
}
func getCtxUint(c *gin.Context, key string) (uint, error) {
v, exist := c.Get(key)
if !exist {
return 0, fmt.Errorf("context has no value for %s", key)
}
uintV, ok := v.(uint)
if ok {
return uintV, nil
}
return 0, fmt.Errorf("key for %s in gin.Context value is %v but not a uint type", key, v)
}
func queryUint(c *gin.Context, key string) (v uint, err error) {
sv, ok := c.GetQuery(key)
if !ok {
err = fmt.Errorf("query of %s is not exist", key)
return
}
parseId, err := strconv.ParseUint(sv, 10, 32)
if err != nil {
err = fmt.Errorf("query of %s is not a uint", key)
return
}
return uint(parseId), nil
}
func checkJwtUserAdmin(c *gin.Context) (u *model.User, err error) {
u, err = mwJwtUser(c)
if err != nil {
return
}
if u.Role != model.UserRoleAdmin {
return nil, errors.New("没有管理员权限")
}
return
}
//model help
package model
import (
"errors"
"github.com/jinzhu/gorm"
"time"
)
//PaginationQ gin handler query binding struct
type PaginationQ struct {
Size uint `form:"size" json:"size"`
Page uint `form:"page" json:"page"`
Total uint `json:"total" form:"-"`
Data interface{} `json:"data" form:"-" comment:"data 必须是[]interface{} 指针"`
Ok bool `json:"ok" form:"-"`
}
func (p *PaginationQ) Search(tx *gorm.DB) (err error) {
p.Ok = true
if p.Size == 9999 {
return tx.Find(p.Data).Error
}
if p.Size < 1 {
p.Size = 10
}
if p.Page < 1 {
p.Page = 1
}
var total uint
err = tx.Count(&total).Error
if err != nil {
return err
}
offset := p.Size * (p.Page - 1)
err = tx.Limit(p.Size).Offset(offset).Find(p.Data).Error
if err != nil {
return err
}
p.Total = total
return
}
func crudAll(p *PaginationQ, queryTx *gorm.DB, list interface{}) (uint, error) {
if p.Size == 9999 {
return 0, queryTx.Find(list).Error
}
if p.Size < 1 {
p.Size = 10
}
if p.Page < 1 {
p.Page = 1
}
var total uint
err := queryTx.Count(&total).Error
if err != nil {
return 0, err
}
offset := p.Size * (p.Page - 1)
err = queryTx.Limit(p.Size).Offset(offset).Find(list).Error
if err != nil {
return 0, err
}
return total, err
}
func crudOne(m interface{}) (err error) {
if db.First(m).RecordNotFound() {
return errors.New("resource is not found")
}
return nil
}
func crudDelete(m interface{}) (err error) {
//WARNING When delete a record, you need to ensure it’s primary field has value, and GORM will use the primary key to delete the record, if primary field’s blank, GORM will delete all records for the model
//primary key must be not zero value
db := db.Unscoped().Delete(m)
if err = db.Error; err != nil {
return
}
if db.RowsAffected != 1 {
return errors.New("resource is not found to destroy")
}
return nil
}
func mysqlTime(t time.Time) string {
return t.Format("2006-01-02 15:04:05")
}
//路由
package cmd
import (
"github.com/gin-gonic/gin"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"log"
"sshfortress/cronjob"
"sshfortress/felixbin"
"sshfortress/handler"
"sshfortress/model"
"time"
)
// appCmd represents the sshw command
var appCmd = &cobra.Command{
Use: "run",
Short: "运行sshfortress 项目",
Long: `the demo website is http://sshfortress.mojotv.cn:3333
如果没有配置文件就创建SQLite数据库
`,
Run: func(cmd *cobra.Command, args []string) {
if err := initViperConfigFile(configFile); err != nil {
logrus.WithError(err).Fatal("加载配置文件失败")
}
model.AppSecret = viper.GetString("app.secret")
model.ExpireTime = time.Hour * time.Duration(viper.GetInt("app.jwt_expire"))
model.AppIss = viper.GetString("app.name")
err := model.CreateMysqlDb(verbose)
if err != nil {
logrus.WithError(err).Fatal("数据库加载失败")
}
defer model.Close()
go cronjob.RunsshfortressCron()
//执行数据库迁移
if isMigrate {
err = model.RunMigrate()
if err != nil {
log.Fatal(err)
}
}
//加载 ssh 命令配置策略到内存中从数据库
err = runApiAndH5()
if err != nil {
log.Fatal(err)
}
},
}
var configFile, addr string
var isMigrate bool
func init() {
rootCmd.AddCommand(appCmd)
appCmd.Flags().StringVarP(&configFile, "config", "c", "config", "指定加载的配置文件")
appCmd.Flags().StringVarP(&addr, "listen", "l", ":8360", "app监听的端口")
appCmd.Flags().BoolVarP(&isMigrate, "migrate", "m", true, "应用启动是否执行数据库迁移默认TRUE")
}
func runApiAndH5() error {
//config jwt variables
if !verbose {
gin.SetMode(gin.ReleaseMode)
}
r := gin.New()
//开发接口
{
//chromedp run screen shot
r.GET("open/chromedp/shot", handler.ChromedpShot)
}
r.GET("metrics", gin.WrapH(promhttp.Handler()))
r.MaxMultipartMemory = 1024 * 1024 * 50 //100M 上传文件路径 todo::配置文件中进行配置
//sever static file in http's root path
binStaticMiddleware, err := felixbin.NewGinStaticBinMiddleware("/")
if err != nil {
return err
}
r.Use(binStaticMiddleware)
api := r.Group("api").Use(handler.MwPrometheusHttp)
//监控检查给docker health check 使用
api.GET("health-check", func(context *gin.Context) {
context.AbortWithStatusJSON(200, gin.H{"ok": true})
})
//不需要token
{
r.POST("api/login", handler.MwCaptchaCheck(), handler.Login)
r.GET("api/login-github", handler.LoginGithub)
r.POST("api/register", handler.UserCreate)
r.GET("api/captcha", handler.GetCaptcha)
r.GET("api/meta", handler.Meta)
}
api.GET("ws-any-term", handler.AnyWebTerminal)
mwAdmin := handler.MwUserRole(2, "需要管理员权限")
authG := api.Use(handler.JwtMiddleware)
{
//websocket ssh
authG.GET("ws/:id", handler.MachineWsSshTerm)
{
authG.GET("cluster-ssh", mwAdmin, handler.ClusterSshAll)
authG.POST("cluster-ssh", mwAdmin, handler.ClusterSshCreate)
authG.PATCH("cluster-ssh", mwAdmin, handler.ClusterSshUpdate)
authG.DELETE("cluster-ssh/:id", mwAdmin, handler.ClusterSshDelete)
authG.GET("cluster-ssh/:id", handler.ClusterSshOne)
authG.POST("cluster-ssh-bind", mwAdmin, handler.ClusterSshBindMachines)
}
{
// config table in SQL database
authG.GET("config", mwAdmin, handler.ConfigAll)
authG.PATCH("config", mwAdmin, handler.ConfigUpdate)
}
{
authG.GET("cluster-jumper", mwAdmin, handler.ClusterJumperAll)
authG.POST("cluster-jumper", mwAdmin, handler.ClusterJumperCreate)
authG.PATCH("cluster-jumper", mwAdmin, handler.ClusterJumperUpdate)
authG.DELETE("cluster-jumper/:id", mwAdmin, handler.ClusterJumperDelete)
authG.GET("cluster-jumper/:id", handler.ClusterJumperOne)
authG.POST("cluster-jumper-bind", mwAdmin, handler.ClusterJumperBindMachines)
}
//用户组权限控制
{
authG.GET("filter-group", mwAdmin, handler.SshFilterGroupAll)
authG.POST("filter-group", mwAdmin, handler.SshFilterGroupCreate)
authG.PATCH("filter-group", mwAdmin, handler.SshFilterGroupUpdate)
authG.DELETE("filter-group/:id", mwAdmin, handler.SshFilterGroupDelete)
authG.GET("filter-group/:id", handler.SshFilterGroupOne)
}
{
authG.GET("machine", handler.MachineAll)
authG.POST("machine", handler.MachineCreate)
authG.PATCH("machine", handler.MachineUpdate)
authG.DELETE("machine/:id", handler.MachineDelete)
authG.GET("machine/:id", handler.MachineOne)
authG.GET("machine/:id/hardware", handler.MachineHardware)
}
{
authG.GET("ssh-log", handler.SshLogAll)
authG.PATCH("ssh-log", handler.SshLogUpdate)
authG.POST("ssh-log-rm", handler.SshLogDelete)
}
{
authG.GET("sftp-log", handler.SftpLogAll)
authG.PATCH("sftp-log", handler.SftpLogUpdate)
authG.POST("sftp-log-rm", handler.SftpLogDelete)
}
{
authG.GET("feedback", mwAdmin, handler.FeedbackAll)
authG.POST("feedback", handler.FeedbackCreate)
authG.PATCH("feedback", mwAdmin, handler.FeedbackUpdate)
authG.DELETE("feedback-rm", mwAdmin, handler.FeedbackDelete)
}
{
authG.GET("user", mwAdmin, handler.UserAll)
authG.POST("user", mwAdmin, handler.UserCreate)
authG.DELETE("user/:id", mwAdmin, handler.UserDelete)
authG.GET("user/:id", mwAdmin, handler.UserOne)
authG.PATCH("user", handler.UserUpdate)
}
{
authG.GET("sftp/:id", handler.SftpLs)
authG.GET("sftp/:id/dl", handler.SftpDl)
authG.GET("sftp/:id/cat", handler.SftpCat)
authG.GET("sftp/:id/rm", handler.SftpRm)
authG.GET("sftp/:id/rename", handler.SftpRename)
authG.GET("sftp/:id/mkdir", handler.SftpMkdir)
authG.POST("sftp/:id/up", handler.SftpUp)
}
{
authG.GET("machine-user/user-ids", mwAdmin, handler.MachineUserUserIds)
authG.GET("machine-user/machine-ids", mwAdmin, handler.MachineUserMachineIds)
authG.POST("machine-user/bind-machine", mwAdmin, handler.MachineUserBindMachines)
authG.POST("machine-user/bind-user", mwAdmin, handler.MachineUserBindUsers)
}
{
authG.GET("signin-log", mwAdmin, handler.SigninLogAll)
authG.POST("signin-log-rm", mwAdmin, handler.SigninLogDelete)
}
}
if err := r.Run(addr); err != nil {
return err
}
return nil
}
package main
import (
"Yearning-go/src/model"
"encoding/json"
"fmt"
"github.com/cookieY/yee/logger"
"github.com/google/uuid"
"strconv"
)
type originOther struct {
Limit string `json:"limit"`
IDC []string `json:"idc"`
Query bool `json:"query"`
Register bool `json:"register"`
Export bool `json:"export"`
ExQueryTime int `json:"ex_query_time"`
}
type originLDAP struct {
Url string `json:"url"`
User string `json:"user"`
Password string `json:"password"`
Type string `json:"type"`
Sc string `json:"sc"`
Ldaps bool `json:"ldaps"`
}
func main() {
model.DBNew("./conf.toml")
var s []model.CoreDataSource
model.DB().AutoMigrate(model.CoreDataSource{})
model.DB().Model(model.CoreDataSource{}).Find(&s)
for _, i := range s {
if i.SourceId == "" {
model.DB().Model(model.CoreDataSource{}).Where("id =?", i.ID).Updates(&model.CoreDataSource{SourceId: uuid.New().String()})
}
}
model.DB().Exec("alter table core_query_orders change COLUMN query_per status tinyint(2) not null")
model.DB().Migrator().DropColumn(model.CoreQueryOrder{}, "id_c")
model.DB().Migrator().DropColumn(model.CoreQueryOrder{}, "ex_date")
model.DB().Exec("alter table core_query_records change COLUMN base_name `schema` varchar(50) not null")
model.DB().Migrator().DropColumn(model.CoreAccount{}, "rule")
model.DB().Migrator().DropColumn(model.CoreSqlOrder{}, "percent")
model.DB().Migrator().DropColumn(model.CoreSqlOrder{}, "current")
model.DB().Migrator().DropColumn(model.CoreSqlOrder{}, "executor")
model.DB().Exec("alter table core_query_orders change COLUMN realname `real_name` varchar(50) not null")
model.DB().AutoMigrate(model.CoreRoleGroup{})
var r []model.CoreRoleGroup
model.DB().Model(model.CoreRoleGroup{}).Find(&r)
for _, i := range r {
var px model.PermissionList
var dml []string
var ddl []string
var query []string
json.Unmarshal(i.Permissions, &px)
for _, j1 := range px.DMLSource {
var sdml model.CoreDataSource
model.DB().Model(model.CoreDataSource{}).Select("source_id").Where("source =?", j1).First(&sdml)
dml = append(dml, sdml.SourceId)
}
for _, j1 := range px.DDLSource {
var sddl model.CoreDataSource
model.DB().Model(model.CoreDataSource{}).Select("source_id").Where("source =?", j1).First(&sddl)
ddl = append(ddl, sddl.SourceId)
}
for _, j1 := range px.QuerySource {
var squery model.CoreDataSource
model.DB().Model(model.CoreDataSource{}).Select("source_id").Where("source =?", j1).First(&squery)
query = append(query, squery.SourceId)
}
px.QuerySource = query
px.DMLSource = dml
px.DDLSource = ddl
uj, _ := json.Marshal(px)
model.DB().Model(model.CoreRoleGroup{}).Where("id =?", i.ID).Updates(&model.CoreRoleGroup{GroupId: uuid.New().String(), Permissions: uj})
}
var p []model.CoreGrained
model.DB().Model(model.CoreGrained{}).Find(&p)
for _, i := range p {
var k []string
if err := json.Unmarshal(i.Group, &k); err != nil {
logger.DefaultLogger.Error(err)
}
var newGroup []string
for _, j := range k {
var s1 model.CoreRoleGroup
model.DB().Model(model.CoreRoleGroup{}).Where("name =?", j).First(&s1)
newGroup = append(newGroup, s1.GroupId)
}
if newGroup == nil {
newGroup = []string{}
}
n, _ := json.Marshal(newGroup)
model.DB().Model(model.CoreGrained{}).Where("id =?", i.ID).Updates(&model.CoreGrained{Group: n})
}
model.DB().AutoMigrate(model.CoreAccount{})
model.DB().Model(model.CoreAccount{}).Updates(&model.CoreAccount{IsRecorder: 2})
var conf model.CoreGlobalConfiguration
model.DB().Model(model.CoreGlobalConfiguration{}).First(&conf)
var o originOther
var l originLDAP
_ = json.Unmarshal(conf.Other, &o)
_ = json.Unmarshal(conf.Ldap, &l)
num, _ := strconv.Atoi(o.Limit)
other := model.Other{
Limit: uint64(num),
IDC: o.IDC,
ExQueryTime: o.ExQueryTime,
Register: o.Register,
Export: o.Export,
Query: o.Query,
}
ldap := model.Ldap{
Url: l.Url,
User: l.User,
Password: l.Password,
Type: "(&(objectClass=organizationalPerson)(sAMAccountName=%s))",
Sc: l.Sc,
Ldaps: l.Ldaps,
}
b, _ := json.Marshal(other)
ld, _ := json.Marshal(ldap)
model.DB().Model(model.CoreGlobalConfiguration{}).Where("1=1").Updates(&model.CoreGlobalConfiguration{Other: b, Ldap: ld})
fmt.Println("迁移完成!")
}