diff --git a/bin/boot.sh b/bin/boot.sh index 8c8bba9a..b2595211 100755 --- a/bin/boot.sh +++ b/bin/boot.sh @@ -5,9 +5,6 @@ export ctx_root="/usr/local/context" export ctx_home=~/context export ctx_bin="bench" -export user_cert=etc/user/cert.pem -export user_key=etc/user/key.pem - log() { echo -e $* } diff --git a/bin/node.sh b/bin/node.sh index d19809a1..eb299ebb 100755 --- a/bin/node.sh +++ b/bin/node.sh @@ -5,9 +5,6 @@ export ctx_root="/usr/local/context" export ctx_home=~/context export ctx_bin="bench" -export user_cert=etc/user/cert.pem -export user_key=etc/user/key.pem - log() { echo -e $* } diff --git a/etc/init.shy b/etc/init.shy index bc1f539b..32aac901 100644 --- a/etc/init.shy +++ b/etc/init.shy @@ -1,3 +1,5 @@ source common.shy +~ssh + remote auto source local.shy diff --git a/src/contexts/aaa/aaa.go b/src/contexts/aaa/aaa.go index e98d7dc9..ff74aadb 100644 --- a/src/contexts/aaa/aaa.go +++ b/src/contexts/aaa/aaa.go @@ -126,6 +126,7 @@ var Index = &ctx.Context{Name: "aaa", Help: "认证中心", "auth_type": &ctx.Config{Name: "auth_type", Value: map[string]interface{}{ "session": map[string]interface{}{"unique": true}, "bench": map[string]interface{}{"unique": true}, + "cert": map[string]interface{}{"public": true}, "username": map[string]interface{}{"public": true}, "userrole": map[string]interface{}{"public": true}, "password": map[string]interface{}{"secrete": true, "single": true}, @@ -141,6 +142,8 @@ var Index = &ctx.Context{Name: "aaa", 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) { + m.Conf("runtime", "node.cert", m.Cmdx("nfs.load", os.Getenv("node_cert"))) + m.Conf("runtime", "node.key", m.Cmdx("nfs.load", os.Getenv("node_key"))) m.Conf("runtime", "user.cert", m.Cmdx("nfs.load", os.Getenv("user_cert"))) m.Conf("runtime", "user.key", m.Cmdx("nfs.load", os.Getenv("user_key"))) return @@ -508,7 +511,7 @@ var Index = &ctx.Context{Name: "aaa", Help: "认证中心", case 2: // 查看会话 m.Cmdy("aaa.auth", "ship", "username", arg[0], "session", arg[1]) case 3: // 用户认证 - if m.Cmds("aaa.auth", "ship", "username", arg[0]) && (arg[1] == "password" || arg[1] == "uuid") { + if (arg[1] == "password" || arg[1] == "uuid") && m.Cmds("aaa.auth", "ship", "username", arg[0]) { m.Cmdy("aaa.auth", "username", arg[0], arg[1], arg[2]) break } @@ -697,8 +700,9 @@ var Index = &ctx.Context{Name: "aaa", Help: "认证中心", template := x509.Certificate{ SerialNumber: big.NewInt(1), IsCA: true, - KeyUsage: x509.KeyUsageCertSign, - Subject: pkix.Name{CommonName: kit.Format(common)}, + BasicConstraintsValid: true, + KeyUsage: x509.KeyUsageCertSign, + Subject: pkix.Name{CommonName: kit.Format(common)}, } cert, e := x509.CreateCertificate(crand.Reader, &template, &template, &keys.PublicKey, keys) m.Assert(e) @@ -797,26 +801,36 @@ var Index = &ctx.Context{Name: "aaa", Help: "认证中心", var common interface{} json.Unmarshal([]byte(cert.Subject.CommonName), &common) m.Put("option", "common", common).Cmdy("ctx.trans", "common", "format", "object") + case "grant": + private, e := x509.ParsePKCS1PrivateKey(aaa.Decode(arg[1])) + m.Assert(e) + parent, e := x509.ParseCertificate(aaa.Decode(arg[2])) + m.Assert(e) + + for _, v := range arg[3:] { + template, e := x509.ParseCertificate(aaa.Decode(v)) + m.Assert(e) + + cert, e := x509.CreateCertificate(crand.Reader, template, parent, template.PublicKey, private) + m.Assert(e) + + certificate := string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert})) + m.Echo(certificate) + } case "check": - defer func() { - recover() - }() - - root, e := x509.ParseCertificate(aaa.Decode(arg[1])) + parent, e := x509.ParseCertificate(aaa.Decode(arg[1])) m.Assert(e) - cert, e := x509.ParseCertificate(aaa.Decode(arg[2])) - m.Assert(e) + for _, v := range arg[2:] { + template, e := x509.ParseCertificate(aaa.Decode(v)) + m.Assert(e) - // ee := cert.CheckSignatureFrom(root) - // m.Echo("%v", ee) - // - pool := &x509.CertPool{} - m.Echo("%c", pool) - pool.AddCert(root) - c, e := cert.Verify(x509.VerifyOptions{Roots: pool}) - m.Echo("%c", c) + if e = template.CheckSignatureFrom(parent); e != nil { + m.Echo("error: ").Echo("%v", e) + } + } + m.Echo("true") } } return diff --git a/src/contexts/cli/cli.go b/src/contexts/cli/cli.go index 0fda204d..82a96064 100644 --- a/src/contexts/cli/cli.go +++ b/src/contexts/cli/cli.go @@ -149,16 +149,6 @@ var Index = &ctx.Context{Name: "cli", Help: "管理中心", m.Confm("runtime", "init_env", func(index int, key string) { m.Conf("runtime", key, os.Getenv(key)) }) - - if m.Confs("runtime", "ctx_box") { - m.Conf("runtime", "node.type", "worker") - m.Conf("runtime", "node.name", m.Conf("runtime", "pathname")) - } else { - m.Conf("runtime", "node.type", "server") - m.Conf("runtime", "node.name", strings.Replace(m.Conf("runtime", "hostname"), ".", "_", -1)) - } - m.Conf("runtime", "node.route", m.Conf("runtime", "node.name")) - return }}, "source": &ctx.Command{Name: "source [script|stdio|snippet]", Help: "解析脚本, script: 脚本文件, stdio: 命令终端, snippet: 代码片段", Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) (e error) { diff --git a/src/contexts/ctx/ctx_init.go b/src/contexts/ctx/ctx_init.go index c81dbc24..37c4fa80 100644 --- a/src/contexts/ctx/ctx_init.go +++ b/src/contexts/ctx/ctx_init.go @@ -90,7 +90,7 @@ var Index = &Context{Name: "ctx", Help: "模块中心", Server: &CTX{}, }, Commands: map[string]*Command{ "init": &Command{Name: "init", Help: "启动", Hand: func(m *Message, c *Context, key string, arg ...string) (e error) { - for _, x := range []string{"cli", "yac", "nfs", "aaa", "log", "web", "gdb"} { + for _, x := range []string{"cli", "yac", "nfs", "aaa", "log", "ssh", "web", "gdb"} { m.Cmd(x + ".init") } return diff --git a/src/contexts/ctx/ctx_type.go b/src/contexts/ctx/ctx_type.go index 8cb5cfff..11038d8c 100644 --- a/src/contexts/ctx/ctx_type.go +++ b/src/contexts/ctx/ctx_type.go @@ -1323,7 +1323,12 @@ func (m *Message) CallBack(sync bool, cb func(msg *Message) (sub *Message), arg }) m.Log("sync", m.Format("wait", "result", "append")) - return <-wait + select { + case <-time.After(kit.Duration("30s")): + m.Log("sync", m.Format("timeout", "result", "append")) + case <-wait: + } + return m } func (m *Message) Free(cbs ...func(msg *Message) (done bool)) *Message { if len(cbs) == 0 { @@ -1364,6 +1369,7 @@ func (m *Message) Cmd(args ...interface{}) *Message { if strings.Contains(key, ".") { arg := strings.Split(key, ".") m, key = m.Sess(arg[0]), arg[1] + m.Option("remote_code", "") } if m == nil { return m @@ -1454,7 +1460,6 @@ func (m *Message) Confm(key string, args ...interface{}) map[string]interface{} if len(args) == 0 { return value } - switch fun := args[0].(type) { case func(int, string): for i, v := range table { @@ -1477,6 +1482,9 @@ func (m *Message) Confm(key string, args ...interface{}) map[string]interface{} } } case func(map[string]interface{}): + if len(value) == 0 { + return nil + } fun(value) case func(string, map[string]interface{}): for k, v := range value { diff --git a/src/contexts/log/log.go b/src/contexts/log/log.go index 4d2422f4..e3043b68 100644 --- a/src/contexts/log/log.go +++ b/src/contexts/log/log.go @@ -128,12 +128,13 @@ var Index = &ctx.Context{Name: "log", Help: "日志中心", "call": map[string]interface{}{"value": map[string]interface{}{"file": "debug.log", "meta": []interface{}{"time", "ship"}}}, "back": map[string]interface{}{"value": map[string]interface{}{"file": "debug.log", "meta": []interface{}{"time", "ship"}}}, - "right": map[string]interface{}{"value": map[string]interface{}{"file": "right.log", "meta": []interface{}{"time", "ship"}}}, - "bench": map[string]interface{}{"value": map[string]interface{}{"file": "bench.log", "meta": []interface{}{"time", "ship"}}}, "begin": map[string]interface{}{"value": map[string]interface{}{"file": "bench.log", "meta": []interface{}{"time", "ship"}, "color_begin": "\033[31m", "color_end": "\033[0m"}}, "start": map[string]interface{}{"value": map[string]interface{}{"file": "bench.log", "meta": []interface{}{"time", "ship"}, "color_begin": "\033[31m", "color_end": "\033[0m"}}, "close": map[string]interface{}{"value": map[string]interface{}{"file": "bench.log", "meta": []interface{}{"time", "ship"}, "color_begin": "\033[31m", "color_end": "\033[0m"}}, + "warn": map[string]interface{}{"value": map[string]interface{}{"file": "bench.log", "meta": []interface{}{"time", "ship"}, "color_begin": "\033[33m", "color_end": "\033[0m"}}, + + "right": map[string]interface{}{"value": map[string]interface{}{"file": "right.log", "meta": []interface{}{"time", "ship"}}}, "cmd": map[string]interface{}{"value": map[string]interface{}{"file": "bench.log", "meta": []interface{}{"time", "ship"}, "color_begin": "\033[32m", "color_end": "\033[0m"}, "lex": map[string]interface{}{"value": map[string]interface{}{"file": "debug.log", "meta": []interface{}{"time", "ship"}, "color_begin": "\033[32m", "color_end": "\033[0m"}}, diff --git a/src/contexts/nfs/nfs.go b/src/contexts/nfs/nfs.go index 35c09e9c..1878a688 100644 --- a/src/contexts/nfs/nfs.go +++ b/src/contexts/nfs/nfs.go @@ -150,8 +150,12 @@ func open(m *ctx.Message, name string, arg ...int) (string, *os.File, error) { flag = arg[0] } - m.Log("info", "open %s", name) f, e := os.OpenFile(name, flag, 0660) + if e == nil { + m.Log("info", "open %s", name) + return name, f, e + } + m.Log("warn", "%v", e) return name, f, e } func Format(args ...interface{}) string { @@ -929,14 +933,14 @@ func (nfs *NFS) Start(m *ctx.Message, arg ...string) bool { nfs.echo = make(chan *ctx.Message, 10) nfs.hand = map[int]*ctx.Message{} + // 消息发送队列 m.GoLoop(m, func(m *ctx.Message) { msg, code, meta, body := m, 0, "detail", "option" select { - case msg = <-nfs.send: + case msg = <-nfs.send: // 发送请求 code = msg.Code() nfs.hand[code] = msg - msg.Option("username", m.Conf("runtime", "USER")) - case msg = <-nfs.echo: + case msg = <-nfs.echo: // 发送响应 code, meta, body = msg.Optioni("remote_code"), "result", "append" } @@ -952,7 +956,7 @@ func (nfs *NFS) Start(m *ctx.Message, arg ...string) bool { nfs.Send("") }) - //接收消息队列 + // 消息接收队列 msg, code, head, body := m, "0", "result", "append" for bio := bufio.NewScanner(nfs.io); bio.Scan(); { @@ -971,16 +975,20 @@ func (nfs *NFS) Start(m *ctx.Message, arg ...string) bool { msg.Add(field, value) case "": + m.Log("recv", "time %v", time.Now().Format(m.Conf("time_format"))) if head == "detail" { // 接收请求 msg.Detail(-1, "remote") msg.Option("remote_code", code) - msg.Call(func(msg *ctx.Message) *ctx.Message { + go msg.Call(func(msg *ctx.Message) *ctx.Message { nfs.echo <- msg return nil }) } else { // 接收响应 h := nfs.hand[kit.Int(code)] - h.Copy(msg, "result").Copy(msg, "append").Back(h) + h.Copy(msg, "result").Copy(msg, "append") + go func() { + h.Back(h) + }() } msg, code, head, body = nil, "0", "result", "append" @@ -1269,7 +1277,7 @@ var Index = &ctx.Context{Name: "nfs", Help: "存储中心", return }}, "load": &ctx.Command{Name: "load file [buf_size [pos]]", Help: "加载文件, buf_size: 加载大小, pos: 加载位置", Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) (e error) { - if p, f, e := open(m, arg[0]); m.Assert(e) { + if p, f, e := open(m, arg[0]); e == nil { defer f.Close() pos := kit.Int(kit.Select("0", arg, 2)) @@ -1518,7 +1526,7 @@ var Index = &ctx.Context{Name: "nfs", Help: "存储中心", "remote": &ctx.Command{Name: "remote listen|dial args...", Help: "启动文件服务, args: 参考tcp模块, listen命令的参数", Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) (e error) { if _, ok := m.Target().Server.(*NFS); m.Assert(ok) { //{{{ m.Sess("tcp").Call(func(sub *ctx.Message) *ctx.Message { - if sub.Has("hostport") { + if sub.Has("node.port") { return sub } sub.Sess("ms_source", sub) diff --git a/src/contexts/ssh/ssh.go b/src/contexts/ssh/ssh.go index fc72d8c2..0c47a577 100644 --- a/src/contexts/ssh/ssh.go +++ b/src/contexts/ssh/ssh.go @@ -34,270 +34,448 @@ func (ssh *SSH) Close(m *ctx.Message, arg ...string) bool { var Index = &ctx.Context{Name: "ssh", Help: "集群中心", Caches: map[string]*ctx.Cache{ - "nnode": &ctx.Cache{Name: "nnode", Value: "0", Help: "节点数量"}, - "nodename": &ctx.Cache{Name: "nodename", Value: "dev", Help: "本机域名"}, + "nnode": &ctx.Cache{Name: "nnode", Value: "0", Help: "节点数量"}, }, Configs: map[string]*ctx.Config{ - "node": &ctx.Config{Name: "node", Value: map[string]interface{}{}, Help: "主机信息"}, - "hostport": &ctx.Config{Name: "hostport", Value: "", Help: "主机域名"}, - "current": &ctx.Config{Name: "current", Value: "", Help: "当前主机"}, - "timer": &ctx.Config{Name: "timer", Value: "", Help: "当前主机"}, + "node": &ctx.Config{Name: "node", 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{ - "remote": &ctx.Command{Name: "remote listen|dial args...", Help: "远程连接", Form: map[string]int{"right": 1, "nodename": 1, "nodetype": 1}, Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) (e error) { - if len(arg) == 0 { // 查看主机 + "init": &ctx.Command{Name: "init", Help: "启动", Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) (e error) { + if m.Confs("runtime", "ctx_box") { + m.Conf("runtime", "node.type", "worker") + m.Conf("runtime", "node.name", m.Conf("runtime", "pathname")) + } else { + m.Conf("runtime", "node.type", "server") + m.Conf("runtime", "node.name", strings.Replace(strings.TrimSuffix(m.Conf("runtime", "hostname"), ".local"), ".", "_", -1)) + } + m.Conf("runtime", "node.route", m.Conf("runtime", "node.name")) + m.Conf("runtime", "user.name", m.Conf("runtime", "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") return } - if !m.Confs("runtime", "node.cert") { // 设备证书 + // 设备证书 + 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")) } - if !m.Confs("runtime", "user.cert") { // 用户证书 - msg := m.Cmd("aaa.rsa", "gen", "common", m.Confv("runtime", "user")) - m.Conf("runtime", "user.cert", msg.Append("certificate")) - m.Conf("runtime", "user.key", msg.Append("private")) - } - switch arg[0] { - case "auto": + case "auto": // 自动连接 if m.Cmd("ssh.remote", "dial", "consul", "/shadow"); !m.Confs("runtime", "ctx_box") { m.Cmd("ssh.remote", "listen", m.Conf("runtime", "ssh_port")) m.Cmd("web.serve", "usr", m.Conf("runtime", "web_port")) } - case "listen": + case "listen": // 监听连接 m.Call(func(nfs *ctx.Message) *ctx.Message { - if nfs.Has("hostport") { - m.Log("info", "ssh_ports %v", nfs.Optionv("hostport")) - m.Conf("runtime", "ssh_ports", nfs.Optionv("hostport")) - - if !m.Confs("runtime", "node.sess") { // 注册设备 - m.Conf("runtime", "node.sess", m.Cmdx("web.get", "dev", "/login", "cert", m.Confv("runtime", "node.cert"), "temp", "sess.0")) - } + if nfs.Has("node.port") { + m.Log("info", "node.port %v", nfs.Optionv("node.port")) + m.Conf("runtime", "node.port", nfs.Optionv("node.port")) } - - // 创建会话 - sess := m.Cmd("aaa.auth", "nodes", m.Conf("runtime", "node.route"), "session", "nodes").Append("key") - if sess == "" { - sess = m.Cmdx("aaa.sess", "nodes", "nodes", m.Conf("runtime", "node.route")) - m.Cmd("aaa.auth", "nodes", m.Conf("runtime", "node.route"), "cert", m.Conf("runtime", "node.cert")) - } - - m.Cmd("aaa.auth", "username", m.Conf("runtime", "USER"), "userrole", "root") - m.Cmdx("aaa.sess", sess, m.Conf("runtime", "USER"), "cert", m.Conf("runtime", "user.cert")) - return nil }, "nfs.remote", arg) case "redial": // 断线重连 - if !m.Caps("nodename") { + if !m.Caps("stream") { m.Cmdx("remote", "dial", arg[1:]) } - case "dial": + + case "dial": // 连接主机 m.Call(func(nfs *ctx.Message) *ctx.Message { - if m.Confs("timer") { // 断线重连 + // 断线重连 + if m.Confs("timer") { m.Conf("timer", m.Cmdx("cli.timer", "delete", m.Conf("timer"))) } msg := m.Spawn(nfs.Target()) - msg.Option("node.cert", m.Conf("runtime", "node.cert")) - msg.Option("user.cert", m.Conf("runtime", "user.cert")) - + if m.Confs("runtime", "user.cert") { + msg.Option("user.route", kit.Select(m.Conf("runtime", "user.route"), m.Conf("runtime", "user.route"))) + } msg.Call(func(node *ctx.Message) *ctx.Message { - m.Confv("node", node.Result(1), map[string]interface{}{ // 添加主机 + // 添加主机 + m.Confv("node", node.Append("node.name"), map[string]interface{}{ + "module": m.Cap("stream", nfs.Format("target")), "create_time": m.Time(), - "access_time": m.Time(), - "nodename": node.Result(1), - "nodetype": "master", - "module": nfs.Format("target"), - - "username": m.Option("right"), - "cm_target": "ctx.web.code", + "node": map[string]interface{}{ + "name": node.Append("node.name"), + "type": "master", + }, }) - m.Conf("runtime", "node.route", node.Result(2)+"."+node.Result(0)) - if !m.Confs("runtime", "node.sess") { // 设备注册 - if !m.Confs("runtime", "node.cert") { - 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")) + m.Log("fuck", "what %v", node.Meta) + // 主机路由 + 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")) + m.Log("fuck", "what %v", 123) + m.Log("fuck", "what %v", node.Meta) + if !node.Appends("user.route") { + m.Log("fuck", "what %v", 123) + m.Cmd("ssh.share", node.Append("node.route"), "root", m.Conf("runtime", "node.route")) + } + + } else if node.Appends("user.route") { + m.Cmd("ssh.share", "root", node.Append("user.route")) } - m.Conf("runtime", "node.sess", m.Cmdx("web.get", "dev", "/login", - "cert", m.Confv("runtime", "node.cert"), "temp", "sess.0")) } - if !m.Confs("runtime", "user.name") && m.Confs("runtime", "user.key") { // 用户注册 - user := m.Cmd("web.get", "dev", "/login", "username", m.Conf("runtime", "USER"), - "user.cert", m.Conf("runtime", "user.cert"), "temp", "data", "format", "object") - m.Conf("runtime", "user.name", user.Append("username")) - } - - if m.Confs("runtime", "user.name") { // 绑定用户 - msg := m.Cmd("web.get", "dev", "/login", "username", m.Conf("runtime", "user.name"), - "bind", m.Conf("runtime", "node.route"), "code", m.Cmdx("aaa.rsa", "sign", m.Conf("runtime", "user.key"), m.Conf("runtime", "node.route")), "temp", "data", "format", "object") - m.Cmd("aaa.auth", "username", m.Conf("runtime", "user.name"), "userrole", msg.Append("userrole")) - } - - m.Cap("stream", nfs.Format("target")) + // 默认节点 if !m.Confs("current") { - m.Conf("current", node.Result(1)) + m.Conf("current", node.Append("node.name")) } - nfs.Free(func(nfs *ctx.Message) bool { // 连接中断 - m.Conf("timer", m.Cmdx("cli.timer", "repeat", "10s", "context", "ssh", "remote", "redial", arg[1:])) + // 清理主机 + 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.Result(1)) - delete(m.Confm("node"), node.Result(1)) - m.Cap("nodename", "") + 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")) + }, "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": - if node := m.Confm("node", arg[2]); node == nil { // 添加主机 - m.Confv("node", arg[2], map[string]interface{}{ - "create_time": m.Time(), - "access_time": m.Time(), - "nodename": arg[2], - "nodetype": arg[3], - "module": m.Format("source"), - - "username": m.Option("right"), - "cm_target": "ctx.web.code", - }) - } else if len(arg) > 3 && arg[3] == kit.Format(node["token"]) { // 断线重连 - node["access_time"] = m.Time() - node["module"] = m.Format("source") - } else { // 域名冲突 - arg[2] = fmt.Sprintf("%s_%d", arg[2], m.Capi("nnode", 1)) - m.Confv("node", arg[2], map[string]interface{}{ - "create_time": m.Time(), - "access_time": m.Time(), - "nodename": arg[2], - "nodetype": arg[3], - "module": m.Format("source"), - - "username": m.Option("right"), - "cm_target": "ctx.web.code", - }) + // 节点命名 + 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)) } + info := map[string]string{} + if len(arg) > 4 && m.Cmds("aaa.rsa", "check", m.Conf("runtime", "node.cert"), arg[4]) { + m.Cmd("aaa.rsa", "info", arg[4]).Table(func(line map[string]string) { + for k, v := range line { + info[k] = v + } + }) + } + m.Log("info", "info--- %v", info) + + // 添加节点 + m.Confv("node", name, map[string]interface{}{ + "module": m.Format("source"), + "create_time": m.Time(), + "node": map[string]interface{}{ + "name": name, + "type": arg[3], + }, + }) + + // 节点路由 + 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) + + // 默认节点 if !m.Confs("current") { - m.Conf("current", arg[2]) + m.Conf("current", name) } - m.Echo(arg[2]).Echo(m.Conf("runtime", "node.name")).Echo(m.Conf("runtime", "node.route")).Back(m) - m.Sess("ms_source", false).Free(func(msg *ctx.Message) bool { // 断线清理 - m.Log("info", "delete node %s", arg[2]) - delete(m.Confm("node"), arg[2]) + // 清理节点 + 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 !m.Options("sign_source") { // 数字签名 - hash, meta := kit.Hash("rand", - m.Option("sign_time", m.Time("stamp")), - m.Option("sign_username", m.Option("username")), - m.Option("sign_source", m.Conf("runtime", "node.route")), - m.Option("sign_target", arg[0]), - m.Option("sign_cmd", strings.Join(arg[1:], " ")), - ) - m.Option("sign_rand", meta[0]) - m.Option("sign_code", m.Cmdx("aaa.rsa", "sign", m.Conf("runtime", "node.key"), m.Option("sign_hash", hash))) + // 拆分路由 + 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] } - names, arg := strings.SplitN(arg[0], ".", 2), arg[1:] - if names[0] == "" { // 本地执行 - hash, _ := kit.Hash( - m.Option("sign_rand"), - m.Option("sign_time"), - m.Option("sign_username"), - m.Option("sign_source"), - m.Option("sign_target"), - m.Option("sign_cmd"), - ) - - // 创建会话 - if m.Option("sessid", m.Cmd("aaa.auth", "nodes", m.Option("sign_source"), "session").Append("key")); !m.Options("sessid") { - m.Option("sessid", m.Cmdx("aaa.sess", "nodes", "nodes", m.Option("sign_source"))) - } - - // 绑定设备 - m.Option("username", m.Cmd("aaa.sess", m.Option("sessid"), "username").Append("meta")) - if !m.Options("username") || !m.Cmds("aaa.auth", "nodes", m.Option("sign_source"), "cert") { - msg := m.Cmd("web.get", "dev", "/login", "pull", m.Option("sign_source"), "temp", "data", "format", "object") - if m.Cmds("aaa.auth", "nodes", m.Option("sign_source"), "cert", msg.Append("cert")); m.Appends("username") { - m.Cmds("aaa.auth", m.Option("sessid"), "username", m.Option("username", msg.Append("username"))) - } else { - m.Log("fuck", "no username") - } - } - - // 验证签名 - if !m.Cmds("aaa.rsa", "verify", m.Cmd("aaa.auth", "nodes", m.Option("sign_source"), "cert").Append("meta"), m.Option("sign_code"), hash) { - m.Log("fuck", "sign failure") - return - } - - // 创建空间 - 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")) - } - - m.Option("current_ctx", kit.Select("ssh", m.Cmdx("aaa.auth", m.Option("bench"), "data", "target"))) - if m.Cmds("aaa.work", m.Option("bench"), "right", m.Option("username"), "remote", arg[0]) { // 执行命令 - msg := m.Find(m.Option("current_ctx")).Cmd(arg).CopyTo(m) - m.Cmd("aaa.auth", m.Option("bench"), "data", "target", msg.Cap("module")) - } else { - m.Echo("no right %s %s", "remote", arg[0]) - } - - // 返回结果 - m.Back(m) - return - } - - //同步或异步 + // 同步异步 sync := !m.Options("remote_code") switch arg[0] { case "async", "sync": sync, arg = arg[0] == "sync", arg[1:] } - rest := kit.Select("", names, 1) - m.Option("username", m.Option("username")) - m.Option("nodename", m.Conf("runtime", "node.name")) + // 路由转发 + 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")) + }) - if names[0] == "*" { // 广播命令 - m.Confm("node", func(name string, node map[string]interface{}) { + // 数据哈希 + 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", "", arg) - }) + }, "send", rest, 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 + } - }) == 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) + // 返回结果 + 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]) { + 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 "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 + + 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 + } + + // 检查会话 + 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("remote_code") { + if !m.Options("sessid") { + // 用户签名 + 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") + return + } + // 创建会话 + 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.Options("remote_code") || m.Cmds("aaa.work", m.Option("bench"), "right", m.Option("user.name"), "remote", arg[0]) { + m.Option("username", m.Option("user.name")) + m.Option("current_ctx", kit.Select("ssh", m.Cmdx("aaa.auth", m.Option("bench"), "data", "target"))) + // 执行命令 + msg := m.Find(m.Option("current_ctx")).Cmd(arg).CopyTo(m) + m.Cmd("aaa.auth", m.Option("bench"), "data", "target", msg.Cap("module")) + } else { + m.Echo("no right %s %s", "remote", arg[0]) } } 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.Cmd("aaa.auth", "proxy") + return + } + m.Cmd("aaa.auth", "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 + }}, + "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 + }}, + "check": &ctx.Command{Name: "check proxy.route client.route", Help: "权限检查", Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) (e error) { + 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.Echo(m.Conf("current")) diff --git a/src/contexts/tcp/tcp.go b/src/contexts/tcp/tcp.go index a73e84f7..ec600dcf 100644 --- a/src/contexts/tcp/tcp.go +++ b/src/contexts/tcp/tcp.go @@ -18,6 +18,37 @@ type TCP struct { *ctx.Context } +func (tcp *TCP) Fuck(address []string, action func(address string) (net.Conn, error)) { + m := tcp.Message() + + fuck := make(chan bool, 3) + for _, p := range address { + m.Cap("address", p) + for i := 0; i < m.Confi("retry", "counts"); i++ { + go func() { + p := m.Cap("address") + if c, e := action(p); e == nil { + tcp.Conn = c + fuck <- true + } else { + m.Log("info", "dial %s:%s %s", m.Cap("protocol"), p, e) + fuck <- false + } + }() + + select { + case ok := <-fuck: + if ok { + return + } + case <-time.After(kit.Duration(m.Conf("retry", "interval"))): + m.Log("info", "dial %s:%s timeout", m.Cap("protocol"), p) + } + time.Sleep(kit.Duration(m.Conf("retry", "interval"))) + } + } +} + func (tcp *TCP) Read(b []byte) (n int, e error) { m := tcp.Context.Message() m.Assert(tcp.Conn != nil) @@ -59,9 +90,13 @@ func (tcp *TCP) Begin(m *ctx.Message, arg ...string) ctx.Server { func (tcp *TCP) Start(m *ctx.Message, arg ...string) bool { address := []string{} if arg[1] == "consul" { - m.Cmd("web.get", "dev", arg[2], "temp", "hostport").Table(func(line map[string]string) { + m.Cmd("web.get", "dev", arg[2], "temp", "ports", "format", "object").Table(func(line map[string]string) { address = append(address, line["value"]) }) + if len(address) == 0 { + m.Log("warn", "dial failure %v", arg) + return true + } for i := 2; i < len(arg)-1; i++ { arg[i] = arg[i+1] } @@ -78,33 +113,17 @@ func (tcp *TCP) Start(m *ctx.Message, arg ...string) bool { switch arg[0] { case "dial": if m.Caps("security") { - m.Sess("aaa", m.Sess("aaa").Cmd("login", "cert", m.Cap("certfile"), "key", m.Cap("keyfile"), "tcp")) cert, e := tls.LoadX509KeyPair(m.Cap("certfile"), m.Cap("keyfile")) m.Assert(e) conf := &tls.Config{Certificates: []tls.Certificate{cert}, InsecureSkipVerify: true} - for _, p := range address { - for i := 0; i < m.Confi("retry", "counts"); i++ { - if c, e := tls.Dial(m.Cap("protocol"), p, conf); e == nil { - m.Cap("address", p) - tcp.Conn = c - break - } else { - m.Log("info", "dial %s:%s %s", m.Cap("protocol"), m.Cap("address"), e) - } - time.Sleep(kit.Duration(m.Conf("retry", "interval"))) - } - } + tcp.Fuck(address, func(p string) (net.Conn, error) { + return tls.Dial(m.Cap("protocol"), p, conf) + }) } else { - for i := 0; i < m.Confi("retry", "counts"); i++ { - if c, e := net.Dial(m.Cap("protocol"), m.Cap("address")); e == nil { - tcp.Conn = c - break - } else { - m.Log("info", "dial %s:%s %s", m.Cap("protocol"), m.Cap("address"), e) - } - time.Sleep(kit.Duration(m.Conf("retry", "interval"))) - } + tcp.Fuck(address, func(p string) (net.Conn, error) { + return net.Dial(m.Cap("protocol"), p) + }) } m.Log("info", "%s dial %s", m.Cap("nclient"), @@ -153,7 +172,7 @@ func (tcp *TCP) Start(m *ctx.Message, arg ...string) bool { m.Cmd("tcp.ifconfig").Table(func(line map[string]string) { ports = append(ports, fmt.Sprintf("%s:%s", line["ip"], addr[len(addr)-1])) }) - m.Back(m.Spawn(m.Source()).Put("option", "hostport", ports)) + m.Back(m.Spawn(m.Source()).Put("option", "node.port", ports)) } for { @@ -195,7 +214,7 @@ var Index = &ctx.Context{Name: "tcp", Help: "网络中心", "security": &ctx.Config{Name: "security(true/false)", Value: "false", Help: "加密通信"}, "protocol": &ctx.Config{Name: "protocol(tcp/tcp4/tcp6)", Value: "tcp4", Help: "网络协议"}, "retry": &ctx.Config{Name: "retry", Value: map[string]interface{}{ - "interval": "3s", "counts": 5, + "interval": "3s", "counts": 3, }, Help: "网络协议"}, }, Commands: map[string]*ctx.Command{ @@ -231,20 +250,21 @@ var Index = &ctx.Context{Name: "tcp", Help: "网络中心", "ifconfig": &ctx.Command{Name: "ifconfig [name]", Help: "网络配置", Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) (e error) { if ifs, e := net.Interfaces(); m.Assert(e) { for _, v := range ifs { - + if len(arg) > 0 && !strings.Contains(v.Name, arg[0]) { + continue + } if ips, e := v.Addrs(); m.Assert(e) { for _, x := range ips { ip := strings.Split(x.String(), "/") - - if !strings.Contains(ip[0], ":") && len(ip) > 0 && len(v.HardwareAddr) > 0 { - if len(arg) > 0 && !strings.Contains(v.Name, arg[0]) { - continue - } - m.Add("append", "index", v.Index) - m.Add("append", "name", v.Name) - m.Add("append", "hard", v.HardwareAddr) - m.Add("append", "ip", ip[0]) + if strings.Contains(ip[0], ":") || len(ip) == 0 { + continue } + + m.Add("append", "index", v.Index) + m.Add("append", "name", v.Name) + m.Add("append", "ip", ip[0]) + m.Add("append", "mask", ip[1]) + m.Add("append", "hard", v.HardwareAddr.String()) } } } diff --git a/src/contexts/web/web.go b/src/contexts/web/web.go index 817bd159..21ff23f9 100644 --- a/src/contexts/web/web.go +++ b/src/contexts/web/web.go @@ -554,7 +554,11 @@ var Index = &ctx.Context{Name: "web", Help: "应用中心", } res, e := web.Client.Do(req) - if m.Assert(e); kit.Right(client["logheaders"]) { + if e != nil { + m.Log("warn", "%v", e) + return e + } + if kit.Right(client["logheaders"]) { for k, v := range res.Header { m.Log("info", "%s: %v", k, v) } @@ -1025,8 +1029,8 @@ var Index = &ctx.Context{Name: "web", Help: "应用中心", return }}, "/shadow": &ctx.Command{Name: "/shadow", Help: "暗网", Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) (e error) { - m.Confm("runtime", "ssh_ports", func(index int, value string) { - m.Add("append", "hostport", value) + m.Confm("runtime", "node.port", func(index int, value string) { + m.Add("append", "ports", value) }) return }},