Commit 747900d3 authored by ibuler's avatar ibuler

[Update] 提交1

parent 17a481d4
package main package main
import ( import (
"cocogo/pkg/auth"
"cocogo/pkg/config" "cocogo/pkg/config"
"cocogo/pkg/sshd" "cocogo/pkg/sshd"
) )
func init() { func init() {
config.Initial() config.Initial()
auth.Initial()
sshd.Initial()
} }
func main() { func main() {
......
package auth package auth
import ( import (
gossh "golang.org/x/crypto/ssh"
"github.com/gliderlabs/ssh" "github.com/gliderlabs/ssh"
"cocogo/pkg/common" "cocogo/pkg/common"
"cocogo/pkg/service" "cocogo/pkg/service"
) )
func CheckUserPassword(ctx ssh.Context, password string) bool {
return true
}
func CheckUserPublicKey(ctx ssh.Context, key ssh.PublicKey) bool { func CheckUserPublicKey(ctx ssh.Context, key ssh.PublicKey) bool {
username := ctx.User() username := ctx.User()
b := key.Marshal() b := key.Marshal()
...@@ -18,5 +24,8 @@ func CheckUserPublicKey(ctx ssh.Context, key ssh.PublicKey) bool { ...@@ -18,5 +24,8 @@ func CheckUserPublicKey(ctx ssh.Context, key ssh.PublicKey) bool {
} }
ctx.SetValue("LoginUser", authUser) ctx.SetValue("LoginUser", authUser)
return true return true
}
func CheckMFA(ctx ssh.Context, challenger gossh.KeyboardInteractiveChallenge) bool {
return true
} }
...@@ -26,6 +26,7 @@ type Config struct { ...@@ -26,6 +26,7 @@ type Config struct {
TelnetRegex string `json:"TERMINAL_TELNET_REGEX"` TelnetRegex string `json:"TERMINAL_TELNET_REGEX"`
MaxIdleTime time.Duration `json:"SECURITY_MAX_IDLE_TIME"` MaxIdleTime time.Duration `json:"SECURITY_MAX_IDLE_TIME"`
Name string `yaml:"NAME"` Name string `yaml:"NAME"`
HostKeyFile string `yaml:"HOST_KEY_FILE"`
CoreHost string `yaml:"CORE_HOST"` CoreHost string `yaml:"CORE_HOST"`
BootstrapToken string `yaml:"BOOTSTRAP_TOKEN"` BootstrapToken string `yaml:"BOOTSTRAP_TOKEN"`
BindHost string `yaml:"BIND_HOST"` BindHost string `yaml:"BIND_HOST"`
......
...@@ -40,3 +40,7 @@ func Error(args ...interface{}) { ...@@ -40,3 +40,7 @@ func Error(args ...interface{}) {
func Panic(args ...interface{}) { func Panic(args ...interface{}) {
logrus.Panic(args...) logrus.Panic(args...)
} }
func Fatal(args ...interface{}) {
logrus.Fatal(args...)
}
package record package record
import ( import (
"cocogo/pkg/auth"
"cocogo/pkg/config"
"cocogo/pkg/storage"
"compress/gzip" "compress/gzip"
"context" "context"
"encoding/json" "encoding/json"
...@@ -13,10 +10,15 @@ import ( ...@@ -13,10 +10,15 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"time" "time"
"cocogo/pkg/config"
"cocogo/pkg/storage"
) )
var conf = config.Conf
func NewReplyRecord(sessionID string) *Reply { func NewReplyRecord(sessionID string) *Reply {
rootPath := config.GetGlobalConfig().RootPath rootPath := conf.RootPath
currentData := time.Now().UTC().Format("2006-01-02") currentData := time.Now().UTC().Format("2006-01-02")
gzFileName := sessionID + ".replay.gz" gzFileName := sessionID + ".replay.gz"
absFilePath := filepath.Join(rootPath, "data", "replays", currentData, sessionID) absFilePath := filepath.Join(rootPath, "data", "replays", currentData, sessionID)
...@@ -52,9 +54,9 @@ func (r *Reply) Record(b []byte) { ...@@ -52,9 +54,9 @@ func (r *Reply) Record(b []byte) {
} }
func (r *Reply) StartRecord() { func (r *Reply) StartRecord() {
auth.MakeSureDirExit(r.absFilePath) //auth.MakeSureDirExit(r.absFilePath)
r.WriteF, _ = os.Create(r.absFilePath) //r.WriteF, _ = os.Create(r.absFilePath)
_, _ = r.WriteF.Write([]byte("{")) //_, _ = r.WriteF.Write([]byte("{"))
} }
func (r *Reply) EndRecord(ctx context.Context) { func (r *Reply) EndRecord(ctx context.Context) {
......
package service package service
import ( import (
"fmt"
"cocogo/pkg/logger" "cocogo/pkg/logger"
"cocogo/pkg/model" "cocogo/pkg/model"
) )
...@@ -20,12 +18,12 @@ import ( ...@@ -20,12 +18,12 @@ import (
// //
//} //}
// //
func GetSystemUserAuthInfo(systemUserID string) { func GetSystemUserAssetAuthInfo(systemUserID, assetID string) (info model.SystemUserAuthInfo, err error) {
var authInfo model.SystemUserAuthInfo var authInfo model.SystemUserAuthInfo
err := client.Get("systemUserAuthInfo", nil, &authInfo) err = Client.Get("systemUserAuthInfo", nil, &authInfo)
if err != nil { if err != nil {
logger.Info("get User Assets Groups err:", err) logger.Info("get User Assets Groups err:", err)
return return
} }
fmt.Println(authInfo) return
} }
package service package service
import ( import (
"cocogo/pkg/model"
"net/http" "net/http"
"path" "path"
"path/filepath" "path/filepath"
...@@ -61,10 +60,10 @@ func (c *WrapperClient) LoadAuth() error { ...@@ -61,10 +60,10 @@ func (c *WrapperClient) LoadAuth() error {
} }
func (c *WrapperClient) CheckAuth() error { func (c *WrapperClient) CheckAuth() error {
var user model.User //var user model.User
err := c.Get("UserProfileUrl", &user) //err := c.Get("UserProfileUrl", &user)
if err != nil { //if err != nil {
return err // return err
} //}
return nil return nil
} }
...@@ -6,15 +6,15 @@ import ( ...@@ -6,15 +6,15 @@ import (
"cocogo/pkg/logger" "cocogo/pkg/logger"
) )
var client = WrapperClient{} var Client = WrapperClient{}
func init() { func init() {
err := client.LoadAuth() err := Client.LoadAuth()
if err != nil { if err != nil {
logger.Error("Load client access key error: %s", err) logger.Error("Load client access key error: %s", err)
os.Exit(10) os.Exit(10)
} }
err = client.CheckAuth() err = Client.CheckAuth()
if err != nil { if err != nil {
logger.Error("Check client auth error: %s", err) logger.Error("Check client auth error: %s", err)
os.Exit(11) os.Exit(11)
......
package service package service
import "cocogo/pkg/model"
func CheckAuth(username, password, publicKey, remoteAddr, loginType string) (user model.User, err error) {
return user, nil
}
// //
//func (s *Service) CheckAuth(username, password, publicKey, remoteAddr, loginType string) (model.User, error) { //func (s *Service) CheckAuth(username, password, publicKey, remoteAddr, loginType string) (model.User, error) {
// /* // /*
......
package sshd package handlers
const welcomeTemplate = ` import (
"text/template"
"github.com/gliderlabs/ssh"
"cocogo/pkg/logger"
)
const bannerTemplate = `
{{.UserName}} Welcome to use Jumpserver open source fortress system{{.EndLine}} {{.UserName}} Welcome to use Jumpserver open source fortress system{{.EndLine}}
{{.Tab}}1) Enter {{.ColorCode}}ID{{.ColorEnd}} directly login or enter {{.ColorCode}}part IP, Hostname, Comment{{.ColorEnd}} to search login(if unique). {{.EndLine}} {{.Tab}}1) Enter {{.ColorCode}}ID{{.ColorEnd}} directly login or enter {{.ColorCode}}part IP, Hostname, Comment{{.ColorEnd}} to search login(if unique). {{.EndLine}}
{{.Tab}}2) Enter {{.ColorCode}}/{{.ColorEnd}} + {{.ColorCode}}IP, Hostname{{.ColorEnd}} or {{.ColorCode}}Comment{{.ColorEnd}} search, such as: /ip. {{.EndLine}} {{.Tab}}2) Enter {{.ColorCode}}/{{.ColorEnd}} + {{.ColorCode}}IP, Hostname{{.ColorEnd}} or {{.ColorCode}}Comment{{.ColorEnd}} search, such as: /ip. {{.EndLine}}
...@@ -12,3 +20,30 @@ const welcomeTemplate = ` ...@@ -12,3 +20,30 @@ const welcomeTemplate = `
{{.Tab}}8) Enter {{.ColorCode}}r{{.ColorEnd}} to refresh your assets and nodes.{{.EndLine}} {{.Tab}}8) Enter {{.ColorCode}}r{{.ColorEnd}} to refresh your assets and nodes.{{.EndLine}}
{{.Tab}}0) Enter {{.ColorCode}}q{{.ColorEnd}} exit.{{.EndLine}} {{.Tab}}0) Enter {{.ColorCode}}q{{.ColorEnd}} exit.{{.EndLine}}
` `
var displayTemplate = template.Must(template.New("display").Parse(bannerTemplate))
type Banner struct {
UserName string
ColorCode string
ColorEnd string
Tab string
EndLine string
}
func (h *Banner) display(sess ssh.Session) {
e := displayTemplate.Execute(sess, h)
if e != nil {
logger.Warn("Display help info failed")
}
}
func NewBanner(userName string) *Banner {
return &Banner{
UserName: userName,
ColorCode: GreenColorCode,
ColorEnd: ColorEnd,
Tab: Tab,
EndLine: EndLine,
}
}
package sshd package handlers
const ( const (
GreenColorCode = "\033[32m" GreenColorCode = "\033[32m"
......
package handlers
import (
"context"
"encoding/json"
"fmt"
"io"
"strconv"
"strings"
"sync"
"time"
"github.com/gliderlabs/ssh"
"github.com/olekukonko/tablewriter"
"github.com/satori/go.uuid"
"github.com/xlab/treeprint"
"golang.org/x/crypto/ssh/terminal"
"cocogo/pkg/logger"
"cocogo/pkg/model"
"cocogo/pkg/proxy"
"cocogo/pkg/service"
"cocogo/pkg/transport"
"cocogo/pkg/userhome"
)
type InteractiveHandler struct {
sess ssh.Session
term *terminal.Terminal
assetData *sync.Map
user model.User
banner *Banner
currentSearchAssets []model.Asset
onceLoad sync.Once
sync.RWMutex
}
func (i *InteractiveHandler) displayBanner() {
i.banner.display(i.sess)
}
//func (i *InteractiveHandler) Dispatch() {
// _, winCh, _ := i.sess.Pty()
// for {
// ctx, cancelFunc := context.WithCancel(i.sess.Context())
// go func() {
// for {
// select {
// case <-ctx.Done():
// logger.Info("ctx done")
// return
// case win, ok := <-winCh:
// if !ok {
// return
// }
// logger.Info("SessionHandler term change:", win)
// _ = i.term.SetSize(win.Width, win.Height)
// }
// }
// }()
// line, err := i.term.ReadLine()
// cancelFunc()
// if err != nil {
// logger.Error("ReadLine done", err)
// break
// }
// if line == "" {
// continue
// }
//
// i.onceLoad.Do(func() {
// if _, ok := Cached.Load(i.user.Id); !ok {
// logger.Info("first load this user asset data ")
// i.loadUserAssets()
// i.loadUserAssetNodes()
//
// } else {
// go func() {
// i.loadUserAssets()
// i.loadUserAssetNodes()
// }()
// }
// })
//
// if len(line) == 1 {
// switch line {
// case "p", "P":
// if assets, ok := i.assetData.Load(AssetsMapKey); ok {
// i.displayAssets(assets.([]model.Asset))
// i.currentSearchAssets = assets.([]model.Asset)
// } else if assets, ok := Cached.Load(i.user.Id); ok {
// i.displayAssets(assets.([]model.Asset))
// i.currentSearchAssets = assets.([]model.Asset)
// }
//
// case "g", "G":
// if assetNodes, ok := i.assetData.Load(AssetNodesMapKey); ok {
// i.displayAssetNodes(assetNodes.([]model.AssetNode))
// } else {
// i.displayAssetNodes([]model.AssetNode{})
// }
// case "s", "S":
// i.changeLanguage()
// case "h", "H":
// i.displayBanner()
// case "r", "R":
// i.refreshAssetsAndNodesData()
// case "q", "Q":
// logger.Info("exit session")
// return
// default:
// assets := i.searchAsset(line)
// i.currentSearchAssets = assets
// i.displayAssetsOrProxy(assets)
// }
// continue
// }
// if strings.Index(line, "/") == 0 {
// searchWord := strings.TrimSpace(strings.TrimPrefix(line, "/"))
// assets := i.searchAsset(searchWord)
// i.currentSearchAssets = assets
// i.displayAssets(assets)
// continue
// }
//
// if strings.Index(line, "g") == 0 {
// searchWord := strings.TrimSpace(strings.TrimPrefix(line, "g"))
// if num, err := strconv.Atoi(searchWord); err == nil {
// if num >= 0 {
// assets := i.searchNodeAssets(num)
// i.displayAssets(assets)
// i.currentSearchAssets = assets
// continue
// }
// }
// }
//
// if strings.Index(line, "join") == 0 {
// roomID := strings.TrimSpace(strings.TrimPrefix(line, "join"))
// i.JoinShareRoom(roomID)
// continue
// }
//
// assets := i.searchAsset(line)
// i.currentSearchAssets = assets
// i.displayAssetsOrProxy(assets)
//
// }
//}
//
//func (i *InteractiveHandler) chooseSystemUser(systemUsers []model.SystemUser) model.SystemUser {
// table := tablewriter.NewWriter(i.sess)
// table.SetHeader([]string{"ID", "UserName"})
// for i := 0; i < len(systemUsers); i++ {
// table.Append([]string{strconv.Itoa(i + 1), systemUsers[i].UserName})
// }
// table.SetBorder(false)
// count := 0
// term := terminal.NewTerminal(i.sess, "num:")
// for count < 3 {
// table.Render()
// line, err := term.ReadLine()
// if err != nil {
// continue
// }
// if num, err := strconv.Atoi(line); err == nil {
// if num > 0 && num <= len(systemUsers) {
// return systemUsers[num-1]
// }
// }
// count++
// }
// return systemUsers[0]
//}
//
//// 当资产的数量为1的时候,就进行代理转化
//func (i *InteractiveHandler) displayAssetsOrProxy(assets []model.Asset) {
// if len(assets) == 1 {
// var systemUser model.SystemUser
// switch len(assets[0].SystemUsers) {
// case 0:
// // 有授权的资产,但是资产用户信息,无法登陆
// i.displayAssets(assets)
// return
// case 1:
// systemUser = assets[0].SystemUsers[0]
// default:
// systemUser = i.chooseSystemUser(assets[0].SystemUsers)
// }
//
// authInfo, err := service.GetSystemUserAssetAuthInfo(systemUser.Id, assets[0].Id)
// if err != nil {
// return
// }
// if ok := appService.ValidateUserAssetPermission(i.user.Id, systemUser.Id, assets[0].Id); !ok {
// // 检查user 是否对该资产有权限
// return
// }
//
// err = i.Proxy(assets[0], authInfo)
// if err != nil {
// logger.Info(err)
// }
// return
// } else {
// i.displayAssets(assets)
// }
//}
//
//func (i *InteractiveHandler) displayAssets(assets []model.Asset) {
// if len(assets) == 0 {
// _, _ = io.WriteString(i.sess, "\r\n No Assets\r\n\r")
// } else {
// table := tablewriter.NewWriter(i.sess)
// table.SetHeader([]string{"ID", "Hostname", "IP", "LoginAs", "Comment"})
// for index, assetItem := range assets {
// sysUserArray := make([]string, len(assetItem.SystemUsers))
// for index, sysUser := range assetItem.SystemUsers {
// sysUserArray[index] = sysUser.Name
// }
// sysUsers := "[" + strings.Join(sysUserArray, " ") + "]"
// table.Append([]string{strconv.Itoa(index + 1), assetItem.Hostname, assetItem.Ip, sysUsers, assetItem.Comment})
// }
//
// table.SetBorder(false)
// table.Render()
// }
//
//}
//
//func (i *InteractiveHandler) displayAssetNodes(nodes []model.AssetNode) {
// tree := ConstructAssetNodeTree(nodes)
// tipHeaderMsg := "\r\nNode: [ ID.Name(Asset amount) ]"
// tipEndMsg := "Tips: Enter g+NodeID to display the host under the node, such as g1\r\n\r"
//
// _, err := io.WriteString(i.sess, tipHeaderMsg)
// _, err = io.WriteString(i.sess, tree.String())
// _, err = io.WriteString(i.sess, tipEndMsg)
// if err != nil {
// logger.Info("displayAssetNodes err:", err)
// }
//
//}
//
//func (i *InteractiveHandler) refreshAssetsAndNodesData() {
// i.loadUserAssets()
// i.loadUserAssetNodes()
// _, err := io.WriteString(i.sess, "Refresh done\r\n")
// if err != nil {
// logger.Error("refresh Assets Nodes err:", err)
// }
//
//}
//
//func (i *InteractiveHandler) loadUserAssets() {
// assets, err := appService.GetUserAssets(i.user.Id)
// if err != nil {
// logger.Error("load Assets failed")
// return
// }
// logger.Info("load Assets success")
// Cached.Store(i.user.Id, assets)
// i.assetData.Store(AssetsMapKey, assets)
//}
//
//func (i *InteractiveHandler) loadUserAssetNodes() {
// assetNodes, err := appService.GetUserAssetNodes(i.user.Id)
// if err != nil {
// logger.Error("load Asset Nodes failed")
// return
// }
// logger.Info("load Asset Nodes success")
// i.assetData.Store(AssetNodesMapKey, assetNodes)
//}
//
//func (i *InteractiveHandler) changeLanguage() {
//
//}
//
//func (i *InteractiveHandler) JoinShareRoom(roomID string) {
// sshConn := userhome.NewSSHConn(i.sess)
// ctx, cancelFuc := context.WithCancel(i.sess.Context())
//
// _, winCh, _ := i.sess.Pty()
// go func() {
// for {
// select {
// case <-ctx.Done():
// return
// case win, ok := <-winCh:
// if !ok {
// return
// }
// fmt.Println("join term change:", win)
// }
// }
// }()
// proxy.Manager.JoinShareRoom(roomID, sshConn)
// logger.Info("exit room id:", roomID)
// cancelFuc()
//
//}
//
//func (i *InteractiveHandler) searchAsset(key string) (assets []model.Asset) {
// if indexNum, err := strconv.Atoi(key); err == nil {
// if indexNum > 0 && indexNum <= len(i.currentSearchAssets) {
// return []model.Asset{i.currentSearchAssets[indexNum-1]}
// }
// }
//
// if assetsData, ok := i.assetData.Load(AssetsMapKey); ok {
// for _, assetValue := range assetsData.([]model.Asset) {
// if isSubstring([]string{assetValue.Ip, assetValue.Hostname, assetValue.Comment}, key) {
// assets = append(assets, assetValue)
// }
// }
// } else {
// assetsData, _ := Cached.Load(i.user.Id)
// for _, assetValue := range assetsData.([]model.Asset) {
// if isSubstring([]string{assetValue.Ip, assetValue.Hostname, assetValue.Comment}, key) {
// assets = append(assets, assetValue)
// }
// }
// }
//
// return assets
//}
//
//func (i *InteractiveHandler) searchNodeAssets(num int) (assets []model.Asset) {
// var assetNodesData []model.AssetNode
// if assetNodes, ok := i.assetData.Load(AssetNodesMapKey); ok {
// assetNodesData = assetNodes.([]model.AssetNode)
// if num > len(assetNodesData) || num == 0 {
// return assets
// }
// return assetNodesData[num-1].AssetsGranted
// }
// return assets
//
//}
//
//func (i *InteractiveHandler) Proxy(asset model.Asset, systemUser model.SystemUserAuthInfo) error {
// /*
// 1. 创建SSHConn,符合core.Conn接口
// 2. 创建一个session Home
// 3. 创建一个NodeConn,及相关的channel 可以是MemoryChannel 或者是redisChannel
// 4. session Home 与 proxy channel 交换数据
// */
// ptyReq, winChan, _ := i.sess.Pty()
// sshConn := userhome.NewSSHConn(i.sess)
// serverAuth := transport.ServerAuth{
// SessionID: uuid.NewV4().String(),
// IP: asset.Ip,
// Port: asset.Port,
// UserName: systemUser.UserName,
// Password: systemUser.Password,
// PublicKey: parsePrivateKey(systemUser.PrivateKey)}
//
// nodeConn, err := transport.NewNodeConn(i.sess.Context(), serverAuth, ptyReq, winChan)
// if err != nil {
// logger.Error(err)
// return err
// }
// defer func() {
// nodeConn.Close()
// data := map[string]interface{}{
// "id": nodeConn.SessionID,
// "user": i.user.UserName,
// "asset": asset.Hostname,
// "org_id": asset.OrgID,
// "system_user": systemUser.UserName,
// "login_from": "ST",
// "remote_addr": i.sess.RemoteAddr().String(),
// "is_finished": true,
// "date_start": nodeConn.StartTime.Format("2006-01-02 15:04:05 +0000"),
// "date_end": time.Now().UTC().Format("2006-01-02 15:04:05 +0000"),
// }
// postData, _ := json.Marshal(data)
// appService.FinishSession(nodeConn.SessionID, postData)
// appService.FinishReply(nodeConn.SessionID)
// }()
// data := map[string]interface{}{
// "id": nodeConn.SessionID,
// "user": i.user.UserName,
// "asset": asset.Hostname,
// "org_id": asset.OrgID,
// "system_user": systemUser.UserName,
// "login_from": "ST",
// "remote_addr": i.sess.RemoteAddr().String(),
// "is_finished": false,
// "date_start": nodeConn.StartTime.Format("2006-01-02 15:04:05 +0000"),
// "date_end": nil,
// }
// postData, err := json.Marshal(data)
//
// if !appService.CreateSession(postData) {
// return err
// }
//
// memChan := transport.NewMemoryAgent(nodeConn)
//
// Home := userhome.NewUserSessionHome(sshConn)
// logger.Info("session Home ID: ", Home.SessionID())
//
// err = proxy.Manager.Switch(i.sess.Context(), Home, memChan)
// if err != nil {
// logger.Error(err)
// }
// return err
//}
//
//func isSubstring(sArray []string, substr string) bool {
// for _, s := range sArray {
// if strings.Contains(s, substr) {
// return true
// }
// }
// return false
//}
//
//func ConstructAssetNodeTree(assetNodes []model.AssetNode) treeprint.Tree {
// model.SortAssetNodesByKey(assetNodes)
// var treeMap = map[string]treeprint.Tree{}
// tree := treeprint.New()
// for i := 0; i < len(assetNodes); i++ {
// r := strings.LastIndex(assetNodes[i].Key, ":")
// if r < 0 {
// subtree := tree.AddBranch(fmt.Sprintf("%s.%s(%s)",
// strconv.Itoa(i+1), assetNodes[i].Name,
// strconv.Itoa(assetNodes[i].AssetsAmount)))
// treeMap[assetNodes[i].Key] = subtree
// continue
// }
// if subtree, ok := treeMap[assetNodes[i].Key[:r]]; ok {
// nodeTree := subtree.AddBranch(fmt.Sprintf("%s.%s(%s)",
// strconv.Itoa(i+1), assetNodes[i].Name,
// strconv.Itoa(assetNodes[i].AssetsAmount)))
// treeMap[assetNodes[i].Key] = nodeTree
// }
//
// }
// return tree
//}
func SessionHandler(sess ssh.Session) {
_, _, ptyOk := sess.Pty()
if ptyOk {
user, ok := sess.Context().Value("LoginUser").(model.User)
if !ok {
logger.Info("Get current User failed")
return
}
banner := NewBanner(sess.User())
handler := &InteractiveHandler{
sess: sess,
term: terminal.NewTerminal(sess, "Opt> "),
user: user,
assetData: new(sync.Map),
banner: banner,
}
logger.Info("accept one session")
handler.displayBanner()
handler.Dispatch()
} else {
_, err := io.WriteString(sess, "No PTY requested.\n")
if err != nil {
return
}
}
}
package sshd
import (
"golang.org/x/crypto/ssh"
"io/ioutil"
"os"
)
type HostKey struct {
Value string
Path string
}
func (hk *HostKey) loadHostKeyFromFile(keyPath string) (signer ssh.Signer, err error) {
_, err = os.Stat(conf.HostKeyFile)
if err != nil {
return
}
buf, err := ioutil.ReadFile(conf.HostKeyFile)
if err != nil {
return
}
return hk.loadHostKeyFromString(string(buf))
}
func (hk *HostKey) loadHostKeyFromString(value string) (signer ssh.Signer, err error) {
signer, err = ssh.ParsePrivateKey([]byte(value))
return
}
func (hk *HostKey) Gen() (signer ssh.Signer, err error) {
return
}
func (hk *HostKey) SaveToFile(signer ssh.Signer) (err error) {
return
}
func (hk *HostKey) Load() (signer ssh.Signer, err error) {
if hk.Value != "" {
signer, err = hk.loadHostKeyFromString(hk.Value)
if err == nil {
return
}
}
if hk.Path != "" {
signer, err = hk.loadHostKeyFromFile(hk.Path)
if err == nil {
return
}
}
signer, err = hk.Gen()
if err != nil {
return
}
err = hk.SaveToFile(signer)
if err != nil {
return
}
return
}
package sshd
import (
"cocogo/pkg/model"
"cocogo/pkg/proxy"
"cocogo/pkg/transport"
"cocogo/pkg/userhome"
"context"
"encoding/json"
"fmt"
"io"
"strconv"
"strings"
"sync"
"time"
uuid "github.com/satori/go.uuid"
"github.com/olekukonko/tablewriter"
"github.com/xlab/treeprint"
"github.com/gliderlabs/ssh"
"golang.org/x/crypto/ssh/terminal"
)
type HelpInfo struct {
UserName string
ColorCode string
ColorEnd string
Tab string
EndLine string
}
func (d HelpInfo) displayHelpInfo(sess ssh.Session) {
e := displayTemplate.Execute(sess, d)
if e != nil {
log.Warn("display help info failed")
}
}
type sshInteractive struct {
sess ssh.Session
term *terminal.Terminal
assetData *sync.Map
user model.User
helpInfo HelpInfo
currentSearchAssets []model.Asset
onceLoad sync.Once
sync.RWMutex
}
func (s *sshInteractive) displayHelpInfo() {
s.helpInfo.displayHelpInfo(s.sess)
}
func (s *sshInteractive) chooseSystemUser(systemUsers []model.SystemUser) model.SystemUser {
table := tablewriter.NewWriter(s.sess)
table.SetHeader([]string{"ID", "UserName"})
for i := 0; i < len(systemUsers); i++ {
table.Append([]string{strconv.Itoa(i + 1), systemUsers[i].UserName})
}
table.SetBorder(false)
count := 0
term := terminal.NewTerminal(s.sess, "num:")
for count < 3 {
table.Render()
line, err := term.ReadLine()
if err != nil {
continue
}
if num, err := strconv.Atoi(line); err == nil {
if num > 0 && num <= len(systemUsers) {
return systemUsers[num-1]
}
}
count++
}
return systemUsers[0]
}
// 当资产的数量为1的时候,就进行代理转化
func (s *sshInteractive) displayAssetsOrProxy(assets []model.Asset) {
if len(assets) == 1 {
var systemUser model.SystemUser
switch len(assets[0].SystemUsers) {
case 0:
// 有授权的资产,但是资产用户信息,无法登陆
s.displayAssets(assets)
return
case 1:
systemUser = assets[0].SystemUsers[0]
default:
systemUser = s.chooseSystemUser(assets[0].SystemUsers)
}
authInfo, err := appService.GetSystemUserAssetAuthInfo(systemUser.Id, assets[0].Id)
if err != nil {
return
}
if ok := appService.ValidateUserAssetPermission(s.user.Id, systemUser.Id, assets[0].Id); !ok {
// 检查user 是否对该资产有权限
return
}
err = s.Proxy(assets[0], authInfo)
if err != nil {
log.Info(err)
}
return
} else {
s.displayAssets(assets)
}
}
func (s *sshInteractive) displayAssets(assets []model.Asset) {
if len(assets) == 0 {
_, _ = io.WriteString(s.sess, "\r\n No Assets\r\n\r")
} else {
table := tablewriter.NewWriter(s.sess)
table.SetHeader([]string{"ID", "Hostname", "IP", "LoginAs", "Comment"})
for index, assetItem := range assets {
sysUserArray := make([]string, len(assetItem.SystemUsers))
for index, sysUser := range assetItem.SystemUsers {
sysUserArray[index] = sysUser.Name
}
sysUsers := "[" + strings.Join(sysUserArray, " ") + "]"
table.Append([]string{strconv.Itoa(index + 1), assetItem.Hostname, assetItem.Ip, sysUsers, assetItem.Comment})
}
table.SetBorder(false)
table.Render()
}
}
func (s *sshInteractive) displayAssetNodes(nodes []model.AssetNode) {
tree := ConstructAssetNodeTree(nodes)
tipHeaderMsg := "\r\nNode: [ ID.Name(Asset amount) ]"
tipEndMsg := "Tips: Enter g+NodeID to display the host under the node, such as g1\r\n\r"
_, err := io.WriteString(s.sess, tipHeaderMsg)
_, err = io.WriteString(s.sess, tree.String())
_, err = io.WriteString(s.sess, tipEndMsg)
if err != nil {
log.Info("displayAssetNodes err:", err)
}
}
func (s *sshInteractive) refreshAssetsAndNodesData() {
s.loadUserAssets()
s.loadUserAssetNodes()
_, err := io.WriteString(s.sess, "Refresh done\r\n")
if err != nil {
log.Error("refresh Assets Nodes err:", err)
}
}
func (s *sshInteractive) loadUserAssets() {
assets, err := appService.GetUserAssets(s.user.Id)
if err != nil {
log.Error("load Assets failed")
return
}
log.Info("load Assets success")
Cached.Store(s.user.Id, assets)
s.assetData.Store(AssetsMapKey, assets)
}
func (s *sshInteractive) loadUserAssetNodes() {
assetNodes, err := appService.GetUserAssetNodes(s.user.Id)
if err != nil {
log.Error("load Asset Nodes failed")
return
}
log.Info("load Asset Nodes success")
s.assetData.Store(AssetNodesMapKey, assetNodes)
}
func (s *sshInteractive) changeLanguage() {
}
func (s *sshInteractive) JoinShareRoom(roomID string) {
sshConn := userhome.NewSSHConn(s.sess)
ctx, cancelFuc := context.WithCancel(s.sess.Context())
_, winCh, _ := s.sess.Pty()
go func() {
for {
select {
case <-ctx.Done():
return
case win, ok := <-winCh:
if !ok {
return
}
fmt.Println("join term change:", win)
}
}
}()
proxy.Manager.JoinShareRoom(roomID, sshConn)
log.Info("exit room id:", roomID)
cancelFuc()
}
func (s *sshInteractive) StartDispatch() {
_, winCh, _ := s.sess.Pty()
for {
ctx, cancelFuc := context.WithCancel(s.sess.Context())
go func() {
for {
select {
case <-ctx.Done():
log.Info("ctx done")
return
case win, ok := <-winCh:
if !ok {
return
}
log.Info("InteractiveHandler term change:", win)
_ = s.term.SetSize(win.Width, win.Height)
}
}
}()
line, err := s.term.ReadLine()
cancelFuc()
if err != nil {
log.Error("ReadLine done", err)
break
}
if line == "" {
continue
}
s.onceLoad.Do(func() {
if _, ok := Cached.Load(s.user.Id); !ok {
log.Info("first load this user asset data ")
s.loadUserAssets()
s.loadUserAssetNodes()
} else {
go func() {
s.loadUserAssets()
s.loadUserAssetNodes()
}()
}
})
if len(line) == 1 {
switch line {
case "p", "P":
if assets, ok := s.assetData.Load(AssetsMapKey); ok {
s.displayAssets(assets.([]model.Asset))
s.currentSearchAssets = assets.([]model.Asset)
} else if assets, ok := Cached.Load(s.user.Id); ok {
s.displayAssets(assets.([]model.Asset))
s.currentSearchAssets = assets.([]model.Asset)
}
case "g", "G":
if assetNodes, ok := s.assetData.Load(AssetNodesMapKey); ok {
s.displayAssetNodes(assetNodes.([]model.AssetNode))
} else {
s.displayAssetNodes([]model.AssetNode{})
}
case "s", "S":
s.changeLanguage()
case "h", "H":
s.displayHelpInfo()
case "r", "R":
s.refreshAssetsAndNodesData()
case "q", "Q":
log.Info("exit session")
return
default:
assets := s.searchAsset(line)
s.currentSearchAssets = assets
s.displayAssetsOrProxy(assets)
}
continue
}
if strings.Index(line, "/") == 0 {
searchWord := strings.TrimSpace(strings.TrimPrefix(line, "/"))
assets := s.searchAsset(searchWord)
s.currentSearchAssets = assets
s.displayAssets(assets)
continue
}
if strings.Index(line, "g") == 0 {
searchWord := strings.TrimSpace(strings.TrimPrefix(line, "g"))
if num, err := strconv.Atoi(searchWord); err == nil {
if num >= 0 {
assets := s.searchNodeAssets(num)
s.displayAssets(assets)
s.currentSearchAssets = assets
continue
}
}
}
if strings.Index(line, "join") == 0 {
roomID := strings.TrimSpace(strings.TrimPrefix(line, "join"))
s.JoinShareRoom(roomID)
continue
}
assets := s.searchAsset(line)
s.currentSearchAssets = assets
s.displayAssetsOrProxy(assets)
}
}
func (s *sshInteractive) searchAsset(key string) (assets []model.Asset) {
if indexNum, err := strconv.Atoi(key); err == nil {
if indexNum > 0 && indexNum <= len(s.currentSearchAssets) {
return []model.Asset{s.currentSearchAssets[indexNum-1]}
}
}
if assetsData, ok := s.assetData.Load(AssetsMapKey); ok {
for _, assetValue := range assetsData.([]model.Asset) {
if isSubstring([]string{assetValue.Ip, assetValue.Hostname, assetValue.Comment}, key) {
assets = append(assets, assetValue)
}
}
} else {
assetsData, _ := Cached.Load(s.user.Id)
for _, assetValue := range assetsData.([]model.Asset) {
if isSubstring([]string{assetValue.Ip, assetValue.Hostname, assetValue.Comment}, key) {
assets = append(assets, assetValue)
}
}
}
return assets
}
func (s *sshInteractive) searchNodeAssets(num int) (assets []model.Asset) {
var assetNodesData []model.AssetNode
if assetNodes, ok := s.assetData.Load(AssetNodesMapKey); ok {
assetNodesData = assetNodes.([]model.AssetNode)
if num > len(assetNodesData) || num == 0 {
return assets
}
return assetNodesData[num-1].AssetsGranted
}
return assets
}
func (s *sshInteractive) Proxy(asset model.Asset, systemUser model.SystemUserAuthInfo) error {
/*
1. 创建SSHConn,符合core.Conn接口
2. 创建一个session Home
3. 创建一个NodeConn,及相关的channel 可以是MemoryChannel 或者是redisChannel
4. session Home 与 proxy channel 交换数据
*/
ptyReq, winChan, _ := s.sess.Pty()
sshConn := userhome.NewSSHConn(s.sess)
serverAuth := transport.ServerAuth{
SessionID: uuid.NewV4().String(),
IP: asset.Ip,
Port: asset.Port,
UserName: systemUser.UserName,
Password: systemUser.Password,
PublicKey: parsePrivateKey(systemUser.PrivateKey)}
nodeConn, err := transport.NewNodeConn(s.sess.Context(), serverAuth, ptyReq, winChan)
if err != nil {
log.Error(err)
return err
}
defer func() {
nodeConn.Close()
data := map[string]interface{}{
"id": nodeConn.SessionID,
"user": s.user.UserName,
"asset": asset.Hostname,
"org_id": asset.OrgID,
"system_user": systemUser.UserName,
"login_from": "ST",
"remote_addr": s.sess.RemoteAddr().String(),
"is_finished": true,
"date_start": nodeConn.StartTime.Format("2006-01-02 15:04:05 +0000"),
"date_end": time.Now().UTC().Format("2006-01-02 15:04:05 +0000"),
}
postData, _ := json.Marshal(data)
appService.FinishSession(nodeConn.SessionID, postData)
appService.FinishReply(nodeConn.SessionID)
}()
data := map[string]interface{}{
"id": nodeConn.SessionID,
"user": s.user.UserName,
"asset": asset.Hostname,
"org_id": asset.OrgID,
"system_user": systemUser.UserName,
"login_from": "ST",
"remote_addr": s.sess.RemoteAddr().String(),
"is_finished": false,
"date_start": nodeConn.StartTime.Format("2006-01-02 15:04:05 +0000"),
"date_end": nil,
}
postData, err := json.Marshal(data)
if !appService.CreateSession(postData) {
return err
}
memChan := transport.NewMemoryAgent(nodeConn)
Home := userhome.NewUserSessionHome(sshConn)
log.Info("session Home ID: ", Home.SessionID())
err = proxy.Manager.Switch(s.sess.Context(), Home, memChan)
if err != nil {
log.Error(err)
}
return err
}
func isSubstring(sArray []string, substr string) bool {
for _, s := range sArray {
if strings.Contains(s, substr) {
return true
}
}
return false
}
func ConstructAssetNodeTree(assetNodes []model.AssetNode) treeprint.Tree {
model.SortAssetNodesByKey(assetNodes)
var treeMap = map[string]treeprint.Tree{}
tree := treeprint.New()
for i := 0; i < len(assetNodes); i++ {
r := strings.LastIndex(assetNodes[i].Key, ":")
if r < 0 {
subtree := tree.AddBranch(fmt.Sprintf("%s.%s(%s)",
strconv.Itoa(i+1), assetNodes[i].Name,
strconv.Itoa(assetNodes[i].AssetsAmount)))
treeMap[assetNodes[i].Key] = subtree
continue
}
if subtree, ok := treeMap[assetNodes[i].Key[:r]]; ok {
nodeTree := subtree.AddBranch(fmt.Sprintf("%s.%s(%s)",
strconv.Itoa(i+1), assetNodes[i].Name,
strconv.Itoa(assetNodes[i].AssetsAmount)))
treeMap[assetNodes[i].Key] = nodeTree
}
}
return tree
}
package sshd package sshd
import ( import (
"cocogo/pkg/auth"
"cocogo/pkg/config"
"cocogo/pkg/model"
"io"
"strconv" "strconv"
"sync"
"text/template"
"golang.org/x/crypto/ssh/terminal"
"github.com/gliderlabs/ssh" "github.com/gliderlabs/ssh"
"github.com/sirupsen/logrus"
"cocogo/pkg/auth"
"cocogo/pkg/config"
"cocogo/pkg/logger"
"cocogo/pkg/sshd/handlers"
) )
var ( var (
conf *config.Config conf = config.Conf
appService *auth.Service
serverSig ssh.Signer
displayTemplate *template.Template
log *logrus.Logger
Cached *sync.Map
) )
func Initial() {
displayTemplate = template.Must(template.New("display").Parse(welcomeTemplate))
Cached = new(sync.Map)
conf = config.GetGlobalConfig()
appService = auth.GetGlobalService()
serverSig = parsePrivateKey(conf.TermConfig.HostKey)
log = logrus.New()
if level, err := logrus.ParseLevel(conf.LogLevel); err != nil {
log.SetLevel(logrus.InfoLevel)
} else {
log.SetLevel(level)
}
}
func StartServer() { func StartServer() {
srv := ssh.Server{ hostKey := HostKey{Value: conf.HostKey, Path: conf.HostKeyFile}
Addr: conf.BindHost + ":" + strconv.Itoa(conf.SSHPort), signer, err := hostKey.Load()
PasswordHandler: appService.CheckSSHPassword, if err != nil {
PublicKeyHandler: appService.CheckSSHPublicKey, logger.Fatal("Load access key error: %s", err)
HostSigners: []ssh.Signer{serverSig},
Version: "coco-v1.4",
Handler: connectHandler,
} }
log.Fatal(srv.ListenAndServe())
}
func connectHandler(sess ssh.Session) { srv := ssh.Server{
_, _, ptyOk := sess.Pty() Addr: conf.BindHost + ":" + strconv.Itoa(conf.SSHPort),
if ptyOk { PasswordHandler: auth.CheckUserPassword,
user, ok := sess.Context().Value("LoginUser").(model.User) PublicKeyHandler: auth.CheckUserPublicKey,
if !ok { KeyboardInteractiveHandler: auth.CheckMFA,
log.Info("Get current User failed") HostSigners: []ssh.Signer{signer},
return Version: "coco-v1.4",
} Handler: handlers.SessionHandler,
userInteractive := &sshInteractive{
sess: sess,
term: terminal.NewTerminal(sess, "Opt>"),
user: user,
assetData: new(sync.Map),
helpInfo: HelpInfo{UserName: sess.User(),
ColorCode: GreenColorCode,
ColorEnd: ColorEnd,
Tab: Tab,
EndLine: EndLine}}
log.Info("accept one session")
userInteractive.displayHelpInfo()
userInteractive.StartDispatch()
} else {
_, err := io.WriteString(sess, "No PTY requested.\n")
if err != nil {
return
}
} }
logger.Fatal(srv.ListenAndServe())
} }
package sshd
import (
gossh "golang.org/x/crypto/ssh"
)
func parsePrivateKey(privateKey string) gossh.Signer {
private, err := gossh.ParsePrivateKey([]byte(privateKey))
if err != nil {
log.Error("Failed to parse private key: ", err)
}
return private
}
package storage package storage
import "cocogo/pkg/config"
type Storage interface { type Storage interface {
Upload(gZipFile, target string) Upload(gZipFile, target string)
} }
func NewStorageServer() Storage { func NewStorageServer() Storage {
conf := config.GetGlobalConfig() //conf := config.GetGlobalConfig()
//
switch conf.TermConfig.RePlayStorage["TYPE"] { //switch conf.TermConfig.RePlayStorage["TYPE"] {
case "server": //case "server":
return NewJmsStorage() // return NewJmsStorage()
} //}
return nil return nil
} }
package storage package storage
import ( //var client = service.Client
"cocogo/pkg/auth"
"path/filepath"
"strings"
)
func NewJmsStorage() Storage { func NewJmsStorage() Storage {
appService := auth.GetGlobalService() //appService := auth.GetGlobalService()
return &Server{ //return &Server{
StorageType: "jms", // StorageType: "jms",
service: appService, // service: appService,
} //}
return &Server{}
} }
type Server struct { type Server struct {
StorageType string StorageType string
service *auth.Service
} }
func (s *Server) Upload(gZipFilePath, target string) { func (s *Server) Upload(gZipFilePath, target string) {
sessionID := strings.Split(filepath.Base(gZipFilePath), ".")[0] //sessionID := strings.Split(filepath.Base(gZipFilePath), ".")[0]
_ = s.service.PushSessionReplay(gZipFilePath, sessionID) //_ = client.PushSessionReplay(gZipFilePath, sessionID)
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment