1
0
mirror of https://shylinux.com/x/ContextOS synced 2025-04-25 16:58:06 +08:00
shaoying 0bc0e17674 opt ssh
Change-Id: I86d3d3a981795d3180ffd6f1a7468fcefbca8030
2019-03-17 11:40:23 +08:00

567 lines
19 KiB
Go

package ssh
import (
"contexts/ctx"
"encoding/base64"
"fmt"
"os"
"path"
"strings"
"toolkit"
)
type SSH struct {
*ctx.Context
}
func (ssh *SSH) Spawn(m *ctx.Message, c *ctx.Context, arg ...string) ctx.Server {
c.Caches = map[string]*ctx.Cache{}
c.Configs = map[string]*ctx.Config{}
s := new(SSH)
s.Context = c
return s
}
func (ssh *SSH) Begin(m *ctx.Message, arg ...string) ctx.Server {
return ssh
}
func (ssh *SSH) Start(m *ctx.Message, arg ...string) bool {
return true
}
func (ssh *SSH) Close(m *ctx.Message, arg ...string) bool {
return true
}
var Index = &ctx.Context{Name: "ssh", Help: "集群中心",
Caches: map[string]*ctx.Cache{
"nnode": &ctx.Cache{Name: "nnode", Value: "0", Help: "节点数量"},
},
Configs: map[string]*ctx.Config{
"node": &ctx.Config{Name: "node", Value: map[string]interface{}{}, Help: "主机信息"},
"trust": &ctx.Config{Name: "trust", Value: map[string]interface{}{}, Help: "主机信息"},
"current": &ctx.Config{Name: "current", Value: "", Help: "当前主机"},
"timer": &ctx.Config{Name: "timer", Value: "", Help: "断线重连"},
"timer_interval": &ctx.Config{Name: "timer_interval", Value: "10s", Help: "断线重连"},
},
Commands: map[string]*ctx.Command{
"init": &ctx.Command{Name: "init", Help: "启动", Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) (e error) {
if m.Confs("runtime", "boot.ctx_box") {
m.Conf("runtime", "node.type", "worker")
m.Conf("runtime", "node.name", m.Conf("runtime", "boot.pathname"))
} else {
m.Conf("runtime", "node.type", "server")
m.Conf("runtime", "node.name", strings.Replace(strings.TrimSuffix(m.Conf("runtime", "boot.hostname"), ".local"), ".", "_", -1))
}
m.Conf("runtime", "node.route", m.Conf("runtime", "node.name"))
m.Conf("runtime", "user.name", m.Conf("runtime", "boot.USER"))
return
}},
"remote": &ctx.Command{Name: "remote auto|dial|listen args...", Help: "远程连接", Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) (e error) {
if len(arg) == 0 {
m.Cmdy("ctx.config", "node", "format", "table")
m.Meta["append"] = []string{"key", "type", "create_time"}
return
}
// 设备证书
if !m.Confs("runtime", "node.cert") || !m.Confs("runtime", "node.key") {
msg := m.Cmd("aaa.rsa", "gen", "common", m.Confv("runtime", "node"))
m.Conf("runtime", "node.cert", msg.Append("certificate"))
m.Conf("runtime", "node.key", msg.Append("private"))
}
switch arg[0] {
case "auto": // 自动连接
if m.Cmd("ssh.remote", "dial", "consul", "/shadow"); !m.Confs("runtime", "boot.ctx_box") {
m.Cmd("ssh.remote", "listen", m.Conf("runtime", "boot.ssh_port"))
m.Cmd("web.serve", "usr", m.Conf("runtime", "boot.web_port"))
}
case "listen": // 监听连接
m.Call(func(nfs *ctx.Message) *ctx.Message {
if nfs.Has("node.port") {
m.Log("info", "node.port %v", nfs.Optionv("node.port"))
m.Conf("runtime", "node.port", nfs.Optionv("node.port"))
}
return nil
}, "nfs.remote", arg)
case "redial": // 断线重连
if !m.Caps("stream") {
m.Cmdx("remote", "dial", arg[1:])
}
case "dial": // 连接主机
m.Call(func(nfs *ctx.Message) *ctx.Message {
// 断线重连
if m.Confs("timer") {
m.Conf("timer", m.Cmdx("cli.timer", "delete", m.Conf("timer")))
}
m.Spawn(nfs.Target()).Call(func(node *ctx.Message) *ctx.Message {
// 添加网关
m.Confv("node", node.Append("node.name"), map[string]interface{}{
"module": m.Cap("stream", nfs.Format("target")),
"create_time": m.Time(),
"type": "master",
"name": node.Append("node.name"),
})
// 本机路由
m.Conf("runtime", "node.route", node.Append("node.route")+"."+node.Result(0))
// 本机用户
if !m.Confs("runtime", "user.route") {
if m.Confs("runtime", "user.cert") && m.Confs("runtime", "user.key") {
m.Cmd("ssh.share", "root", m.Conf("runtime", "node.route"))
} else if node.Appends("user.route") {
m.Cmd("ssh.share", "root", node.Append("user.route"))
}
}
// 网关用户
if !node.Appends("user.route") {
m.Cmd("ssh.share", node.Append("node.route"), "root", m.Conf("runtime", "node.route"))
}
// 清理主机
nfs.Free(func(nfs *ctx.Message) bool {
m.Conf("timer", m.Cmdx("cli.timer", "repeat", m.Conf("timer_interval"), "context", "ssh", "remote", "redial", arg[1:]))
m.Log("info", "delete node %s", node.Append("node.name"))
delete(m.Confm("node"), node.Append("node.name"))
m.Cap("stream", "")
return true
})
return nil
}, "send", "recv", "add", m.Conf("runtime", "node.name"), m.Conf("runtime", "node.type"), m.Conf("runtime", "node.cert"))
return nil
}, "nfs.remote", arg)
case "recv":
switch arg[1] {
case "add":
// 节点命名
name := arg[2]
for node := m.Confm("node", name); node != nil; node = m.Confm("node", name) {
name = fmt.Sprintf("%s_%d", arg[2], m.Capi("nnode", 1))
}
// 添加节点
m.Confv("node", name, map[string]interface{}{
"module": m.Format("source"),
"create_time": m.Time(),
"type": arg[3],
"name": name,
})
// 节点路由
m.Append("user.name", m.Conf("runtime", "user.name"))
m.Append("user.route", m.Conf("runtime", "user.route"))
m.Append("node.route", m.Conf("runtime", "node.route"))
m.Append("node.name", m.Conf("runtime", "node.name"))
m.Echo(name).Back(m)
// 清理节点
m.Sess("ms_source", false).Free(func(msg *ctx.Message) bool {
m.Log("info", "delete node %s", name)
delete(m.Confm("node"), name)
return true
})
}
default:
// 拆分路由
if arg[0] == m.Conf("runtime", "node.name") || arg[0] == m.Conf("runtime", "node.route") {
arg[0] = ""
}
arg[0] = strings.TrimPrefix(arg[0], m.Conf("runtime", "node.route")+".")
route, names, arg := arg[0], strings.SplitN(arg[0], ".", 2), arg[1:]
if len(names) > 1 && names[0] == "" && names[1] != "" {
names[0], names[1] = names[1], names[0]
}
// 同步异步
sync := !m.Options("remote_code")
switch arg[0] {
case "async", "sync":
sync, arg = arg[0] == "sync", arg[1:]
}
// 路由转发
if rest := kit.Select("", names, 1); names[0] != "" {
// 数字签名
if !m.Options("remote_code") {
// 用户路由
m.Option("user.route", kit.Select(m.Conf("runtime", "node.route"), m.Conf("runtime", "user.route")))
m.Cmd("aaa.auth", "username", m.Option("username"), "session", "login").Table(func(line map[string]string) {
m.Option("user.route", m.Cmd("aaa.auth", line["key"], "login").Append("meta"))
})
// 数据哈希
hash, meta := kit.Hash("rand",
m.Option("text.time", m.Time("stamp")),
m.Option("text.cmd", strings.Join(arg, " ")),
m.Option("text.route", route),
m.Option("node.route", m.Conf("runtime", "node.route")),
m.Option("user.route"),
m.Option("user.name", m.Option("username")),
)
m.Option("text.rand", meta[0])
// 设备签名
m.Option("node.sign", m.Cmdx("aaa.rsa", "sign", m.Conf("runtime", "node.key"), m.Option("text.hash", hash)))
// 用户签名
if m.Options("user.sign") && m.Confs("runtime", "user.key") {
m.Option("user.sign", m.Cmdx("aaa.rsa", "sign", m.Conf("runtime", "user.key"), m.Option("text.hash", hash)))
}
}
if names[0] == "*" { // 广播命令
m.Confm("node", func(name string, node map[string]interface{}) {
m.Find(kit.Format(node["module"]), true).Copy(m, "option").CallBack(sync, func(sub *ctx.Message) *ctx.Message {
return m.Copy(sub, "append").Copy(sub, "result")
}, "send", "", arg)
})
} else if m.Confm("node", names[0], func(node map[string]interface{}) { // 单播命令
m.Find(kit.Format(node["module"]), true).Copy(m, "option").CallBack(sync, func(sub *ctx.Message) *ctx.Message {
return m.Copy(sub, "append").Copy(sub, "result")
}, "send", rest, arg)
}) == nil { // 回溯命令
m.Find(m.Cap("stream"), true).Copy(m, "option").CallBack(sync, func(sub *ctx.Message) *ctx.Message {
return m.Copy(sub, "append").Copy(sub, "result")
}, "send", strings.Join(names, "."), arg)
}
return
}
// 返回结果
defer func() { m.Back(m) }()
// 查看证书
switch arg[0] {
case "check":
switch arg[1] {
case "node": // 设备证书
m.Echo(m.Conf("runtime", "node.cert"))
case "user":
if len(arg) == 2 { // 用户证书
m.Append("user.cert", m.Conf("runtime", "user.cert"))
m.Append("user.name", m.Conf("runtime", "user.name"))
m.Append("user.route", kit.Select(m.Conf("runtime", "node.route"), m.Conf("runtime", "user.route")))
} else { // 代理验证
if arg[2] == m.Conf("runtime", "node.route") || m.Cmds("aaa.auth", "proxy", arg[2], "session") {
m.Echo(m.Cmdx("aaa.rsa", "sign", m.Conf("runtime", "user.key"), arg[3]))
}
}
}
return
}
if m.Options("remote_code") {
// 检查数据
hash, _ := kit.Hash(
m.Option("text.rand"),
m.Option("text.time"),
m.Option("text.cmd"),
m.Option("text.route"),
m.Option("node.route"),
m.Option("user.route"),
m.Option("user.name"),
)
if m.Option("text.hash") != hash {
m.Log("warning", "text error")
return
}
// 设备证书
m.Option("node.cert", m.Cmd("aaa.auth", "nodes", m.Option("node.route"), "cert").Append("meta"))
if !m.Options("node.cert") {
m.Option("node.cert", m.Spawn().Cmdx("ssh.remote", m.Option("node.route"), "sync", "check", "node"))
m.Cmd("aaa.auth", "nodes", m.Option("node.route"), "cert", m.Option("node.cert"))
}
// 设备验签
if !m.Cmds("aaa.rsa", "verify", m.Option("node.cert"), m.Option("node.sign"), m.Option("text.hash", hash)) {
m.Log("warning", "node error")
return
}
} else {
m.Option("user.name", m.Conf("runtime", "user.name"))
}
switch arg[0] {
case "share": // 设备权限
// 默认用户
if !m.Confs("runtime", "user.route") {
user := m.Spawn().Cmd("ssh.remote", m.Option("user.route"), "sync", "check", "user")
m.Conf("runtime", "user.route", user.Append("user.route"))
m.Conf("runtime", "user.name", user.Append("user.name"))
m.Conf("runtime", "user.cert", user.Append("user.cert"))
m.Cmd("aaa.auth", "username", user.Append("user.name"), "cert", user.Append("user.cert"))
m.Cmd("aaa.user", "root", user.Append("user.name"), "what")
return
}
// 共享用户
if !m.Options("remote_code") || (m.Options("user.sign") && m.Conf("runtime", "user.name") == m.Option("user.name")) {
if !m.Options("remote_code") || m.Cmds("aaa.rsa", "verify", m.Conf("runtime", "user.cert"), m.Option("user.sign"), m.Option("text.hash")) {
for _, v := range arg[2:] {
user := m.Spawn().Cmd("ssh.remote", v, "sync", "check", "user")
m.Cmd("aaa.auth", "username", user.Append("user.name"), "cert", user.Append("user.cert"))
m.Cmd("aaa.user", arg[1], user.Append("user.name"), "what")
}
return
}
}
// 申请权限
m.Spawn().Set("option", "remote_code", "").Cmds("ssh.remote", m.Conf("runtime", "user.route"), "sync", "apply", arg[1:])
return
case "apply": // 权限申请
for _, v := range arg[2:] {
user := m.Spawn().Cmd("ssh.remote", v, "sync", "check", "user")
m.Cmd("aaa.auth", "username", user.Append("user.name"), "cert", user.Append("user.cert"))
sess := m.Cmd("aaa.auth", "username", user.Append("user.name"), "session", "apply").Append("key")
if sess == "" {
sess = m.Cmdx("aaa.sess", "apply", "username", arg[2])
}
m.Cmd("aaa.auth", sess, "apply", m.Option("node.route"))
m.Cmd("aaa.auth", sess, "share", user.Append("user.route"))
}
return
case "login": // 用户代理
if !m.Cmds("aaa.auth", "proxy", m.Option("node.route")) {
return
}
sess := m.Cmd("aaa.auth", "username", m.Option("user.name"), "session", "proxy").Append("key")
if sess == "" {
sess = m.Cmdx("aaa.sess", "proxy", "username", m.Option("user.name"))
}
m.Cmd("aaa.auth", sess, "proxy", m.Option("node.route"))
m.Echo(sess)
return
}
if m.Options("remote_code") {
m.Option("username", m.Option("user.name"))
// 检查会话
m.Option("sessid", "")
m.Cmd("aaa.auth", "nodes", m.Option("node.route"), "session").Table(func(line map[string]string) {
if m.Cmds("aaa.auth", line["key"], "username", m.Option("user.name")) {
m.Option("sessid", line["key"])
}
})
if !m.Options("sessid") {
if !m.Confs("trust", m.Option("node.route")) {
// 用户签名
hash, _ := kit.Hash("rand", m.Option("text.time", m.Time("stamp")), m.Option("node.route"))
m.Option("user.cert", m.Cmd("aaa.auth", "username", m.Option("user.name"), "cert").Append("meta"))
m.Option("user.sign", m.Spawn().Cmdx("ssh.remote", m.Option("user.route"), "sync", "check", "user", m.Option("node.route"), hash))
// 代理验签
if !m.Options("user.cert") || !m.Options("user.sign") || !m.Cmds("aaa.rsa", "verify", m.Option("user.cert"), m.Option("user.sign"), hash) {
m.Log("warn", "user error")
m.Echo("no right of %s", m.Option("text.route"))
return
}
} else {
m.Log("info", "skip verify user %s", m.Option("user.name"))
}
// 创建会话
m.Option("sessid", m.Cmdx("aaa.sess", "nodes", "username", m.Option("user.name")))
m.Cmd("aaa.auth", m.Option("sessid"), "nodes", m.Option("node.route"))
}
// 创建空间
if m.Option("bench", m.Cmd("aaa.sess", m.Option("sessid"), "bench").Append("key")); !m.Options("bench") {
m.Option("bench", m.Cmdx("aaa.work", m.Option("sessid"), "nodes"))
}
// 权限检查
if !m.Cmds("aaa.work", m.Option("bench"), "right", m.Option("user.name"), "remote", arg[0]) {
m.Echo("no right %s %s", "remote", arg[0])
return
}
}
// 执行命令
m.Cmdm(arg)
}
return
}},
"share": &ctx.Command{Name: "share [serve.route] role client.route...", Help: "共享权限", Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) (e error) {
if len(arg) == 0 {
m.Cmd("aaa.auth", "apply").Table(func(node map[string]string) {
m.Cmd("aaa.auth", node["key"], "session", "apply").Table(func(sess map[string]string) {
m.Cmd("aaa.auth", sess["key"], "username").Table(func(user map[string]string) {
m.Add("append", "time", sess["create_time"])
m.Add("append", "user", user["meta"])
m.Add("append", "node", node["meta"])
})
})
})
m.Table()
return
}
// 本地用户
if len(arg) == 2 {
m.Option("user.route", arg[1])
m.Cmd("ssh.remote", "", "share", arg[1:])
return
}
// 远程用户
m.Option("user.sign", "yes")
m.Cmd("ssh.remote", arg[0], "sync", "share", arg[1:])
return
}},
"proxy": &ctx.Command{Name: "proxy [proxy.route]", Help: "代理节点", Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) (e error) {
if len(arg) == 0 {
m.Cmdy("aaa.auth", "proxy")
return
}
if !m.Cmds("aaa.auth", "proxy", arg[0], "session") {
m.Cmdy("aaa.sess", "proxy", "proxy", arg[0])
}
return
}},
"login": &ctx.Command{Name: "login client.route", Help: "用户节点", Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) (e error) {
if len(arg) == 0 {
m.Cmd("aaa.auth", "login")
return
}
if !m.Cmds("ssh.remote", arg[0], "login") {
m.Echo("error: ").Echo("login failure")
return
}
sess := m.Cmd("aaa.auth", "username", m.Option("username"), "session", "login").Append("key")
if sess == "" {
sess = m.Cmdx("aaa.sess", "login", "username", m.Option("username"))
}
m.Cmd("aaa.auth", sess, "login", arg[0])
m.Echo(sess)
return
}},
"sh": &ctx.Command{Name: "sh [[node] name] cmd...", Help: "发送命令", Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) (e error) {
if len(arg) == 0 {
m.Cmdy("ssh.remote")
return
}
if arg[0] == "sub" {
m.Confm("node", func(name string, node map[string]interface{}) {
if node["type"] == "master" {
return
}
msg := m.Cmd("ssh.remote", name, arg[1:])
if len(msg.Meta["append"]) > 0 && !msg.Has("node") {
line := len(msg.Meta[msg.Meta["append"][0]])
for i := 0; i < line; i++ {
msg.Add("append", "node", m.Conf("runtime", "node.route")+"."+name)
}
msg.Set("result").Table()
}
m.CopyFuck(msg, "append")
m.CopyFuck(msg, "result")
return
})
return
}
if arg[0] == "node" {
m.Conf("current", arg[1])
arg = arg[2:]
} else if m.Confm("node", arg[0]) != nil {
m.Conf("current", arg[0])
arg = arg[1:]
} else {
m.Confm("node", func(name string, node map[string]interface{}) bool {
if strings.Contains(name, arg[0]) {
m.Conf("current", name)
arg = arg[1:]
return true
}
return false
})
}
msg := m.Cmd("ssh.remote", m.Conf("current"), arg)
m.Copy(msg, "append")
m.Copy(msg, "result")
return
}},
"cp": &ctx.Command{Name: "cp [[node] name] filename", Help: "发送文件", Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) (e error) {
if len(arg) == 0 {
m.Echo(m.Conf("current"))
return
}
if arg[0] == "node" {
m.Conf("current", arg[1])
arg = arg[2:]
} else if m.Confm("node", arg[0]) != nil {
m.Conf("current", arg[0])
arg = arg[1:]
}
if arg[0] == "save" {
buf, e := base64.StdEncoding.DecodeString(m.Option("filebuf"))
m.Assert(e)
f, e := os.OpenFile(path.Join("tmp", m.Option("filename")), os.O_RDWR|os.O_CREATE, 0666)
f.WriteAt(buf, int64(m.Optioni("filepos")))
return e
}
p := m.Cmdx("nfs.path", arg[0])
f, e := os.Open(p)
m.Assert(e)
s, e := f.Stat()
m.Assert(e)
buf := make([]byte, 1024)
for i := int64(0); i < s.Size(); i += 1024 {
n, _ := f.ReadAt(buf, i)
if n == 0 {
break
}
buf = buf[:n]
msg := m.Spawn()
msg.Option("filename", arg[0])
msg.Option("filesize", s.Size())
msg.Option("filepos", i)
msg.Option("filebuf", base64.StdEncoding.EncodeToString(buf))
msg.Cmd("remote", m.Conf("current"), "cp", "save", arg[0])
}
return
}},
},
}
func init() {
ssh := &SSH{}
ssh.Context = Index
ctx.Index.Register(Index, ssh)
}