Merge pull request #14072 from zexi/webconsole-ssh-log

feat(webconsole): ssh command log
This commit is contained in:
Zexi Li
2022-04-22 14:33:35 +08:00
committed by GitHub
20 changed files with 551 additions and 23 deletions
+1
View File
@@ -36,6 +36,7 @@ import (
_ "yunion.io/x/onecloud/cmd/climc/shell/quota"
_ "yunion.io/x/onecloud/cmd/climc/shell/scheduledtask"
_ "yunion.io/x/onecloud/cmd/climc/shell/scheduler"
_ "yunion.io/x/onecloud/cmd/climc/shell/webconsole"
_ "yunion.io/x/onecloud/cmd/climc/shell/yunionconf"
)
+40
View File
@@ -0,0 +1,40 @@
// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package webconsole
import (
"fmt"
"yunion.io/x/jsonutils"
"yunion.io/x/onecloud/cmd/climc/shell"
"yunion.io/x/onecloud/cmd/climc/shell/events"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/mcclient/modules/webconsole"
)
var (
R = shell.R
)
func init() {
R(&events.EventListOptions{}, "webconsole-commandlog", "Show webconsole command logs", func(s *mcclient.ClientSession, args *events.EventListOptions) {
ret, err := webconsole.CommandLog.List(s, jsonutils.Marshal(args))
if err != nil {
fmt.Println(err)
}
shell.PrintList(ret, webconsole.CommandLog.GetColumns(s))
})
}
+24
View File
@@ -0,0 +1,24 @@
// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package webconsole
import (
"yunion.io/x/onecloud/pkg/apis"
)
type CommandLogListInput struct {
apis.Meta
apis.OpsLogListInput
}
@@ -0,0 +1,45 @@
// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package webconsole
import (
"yunion.io/x/onecloud/pkg/mcclient/modulebase"
)
var (
CommandLog *CommandLogManager
)
func init() {
CommandLog = NewCommandLogManager()
modulebase.Register("v1", CommandLog)
}
type CommandLogManager struct {
modulebase.ResourceManager
}
func NewCommandLogManager() *CommandLogManager {
return &CommandLogManager{
modulebase.ResourceManager{
BaseManager: *modulebase.NewBaseManager("webconsole", "", "webconsole", []string{
"id", "ops_time", "obj_id", "obj_type", "obj_name", "user", "user_id", "tenant", "tenant_id", "owner_tenant_id", "action", "notes",
"session_id", "accessed_at", "type", "start_time", "command",
}, nil),
Keyword: "commandlog", KeywordPlural: "commandlogs",
},
}
}
+16 -3
View File
@@ -18,6 +18,9 @@ import (
"os/exec"
"yunion.io/x/log"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/webconsole/recorder"
)
const (
@@ -33,20 +36,28 @@ type ICommand interface {
IsNeedShowInfo() bool
ShowInfo() string
Scan(d byte, send func(msg string))
GetClientSession() *mcclient.ClientSession
GetRecordObject() *recorder.Object
}
type BaseCommand struct {
s *mcclient.ClientSession
name string
args []string
}
func NewBaseCommand(name string, args ...string) *BaseCommand {
func NewBaseCommand(s *mcclient.ClientSession, name string, args ...string) *BaseCommand {
return &BaseCommand{
s: s,
name: name,
args: args,
}
}
func (c *BaseCommand) GetClientSession() *mcclient.ClientSession {
return c.s
}
func (c *BaseCommand) AppendArgs(args ...string) *BaseCommand {
for _, arg := range args {
c.args = append(c.args, arg)
@@ -59,7 +70,6 @@ func (c BaseCommand) GetCommand() *exec.Cmd {
}
func (c BaseCommand) Scan(byte, func(msg string)) {
return
}
func (c BaseCommand) IsNeedShowInfo() bool {
@@ -71,10 +81,13 @@ func (c BaseCommand) ShowInfo() string {
}
func (c BaseCommand) Reconnect() {
return
}
func (c BaseCommand) Cleanup() error {
log.Infof("BaseCommand Cleanup do nothing")
return nil
}
func (c BaseCommand) GetRecordObject() *recorder.Object {
return nil
}
+4 -2
View File
@@ -20,6 +20,7 @@ import (
"yunion.io/x/log"
"yunion.io/x/onecloud/pkg/mcclient"
o "yunion.io/x/onecloud/pkg/webconsole/options"
)
@@ -33,9 +34,10 @@ type IpmiInfo struct {
type IpmitoolSol struct {
*BaseCommand
Info *IpmiInfo
s *mcclient.ClientSession
}
func NewIpmitoolSolCommand(info *IpmiInfo) (*IpmitoolSol, error) {
func NewIpmitoolSolCommand(info *IpmiInfo, s *mcclient.ClientSession) (*IpmitoolSol, error) {
if info.IpAddr == "" {
return nil, fmt.Errorf("Empty host ip address")
}
@@ -53,7 +55,7 @@ func NewIpmitoolSolCommand(info *IpmiInfo) (*IpmitoolSol, error) {
"-P", info.Password,
"sol",
}
cmd := NewBaseCommand(name, solArgs...)
cmd := NewBaseCommand(s, name, solArgs...)
cmd.AppendArgs("activate")
tool := &IpmitoolSol{
BaseCommand: cmd,
+6 -4
View File
@@ -24,10 +24,12 @@ import (
"yunion.io/x/log"
webconsole_api "yunion.io/x/onecloud/pkg/apis/webconsole"
"yunion.io/x/onecloud/pkg/mcclient"
o "yunion.io/x/onecloud/pkg/webconsole/options"
)
type K8sEnv struct {
Session *mcclient.ClientSession
Cluster string
Namespace string
Pod string
@@ -41,12 +43,12 @@ type Kubectl struct {
kubeconfig string
}
func NewKubectlCommand(kubeconfig, namespace string) *Kubectl {
func NewKubectlCommand(s *mcclient.ClientSession, kubeconfig, namespace string) *Kubectl {
name := o.Options.KubectlPath
if len(namespace) == 0 {
namespace = "default"
}
cmd := NewBaseCommand(name, "--namespace", namespace)
cmd := NewBaseCommand(s, name, "--namespace", namespace)
return &Kubectl{
BaseCommand: cmd,
kubeconfig: kubeconfig,
@@ -132,7 +134,7 @@ func NewPodBashCommand(env *K8sEnv) ICommand {
shellRequest.Command = "env"
}
return NewKubectlCommand(env.Kubeconfig, env.Namespace).Exec().
return NewKubectlCommand(env.Session, env.Kubeconfig, env.Namespace).Exec().
Stdin().
TTY().
Pod(env.Pod).
@@ -189,7 +191,7 @@ func (c *KubectlLog) Since(data jsonutils.JSONObject) *KubectlLog {
}
func NewPodLogCommand(env *K8sEnv) ICommand {
return NewKubectlCommand(env.Kubeconfig, env.Namespace).Logs().
return NewKubectlCommand(env.Session, env.Kubeconfig, env.Namespace).Logs().
Follow().
Pod(env.Pod).
Since(env.Data).
+1 -1
View File
@@ -43,7 +43,7 @@ func TestKubectlExec_Command(t *testing.T) {
{
name: "bash command",
fields: fields{
Kubectl: NewKubectlCommand("/tmp/kubeconfig", "system"),
Kubectl: NewKubectlCommand(nil, "/tmp/kubeconfig", "system"),
},
args: args{
cmd: "bash",
+58 -8
View File
@@ -25,13 +25,16 @@ import (
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/mcclient/auth"
"yunion.io/x/onecloud/pkg/mcclient/modules/compute"
"yunion.io/x/onecloud/pkg/util/ansible"
"yunion.io/x/onecloud/pkg/util/ssh"
o "yunion.io/x/onecloud/pkg/webconsole/options"
"yunion.io/x/onecloud/pkg/webconsole/recorder"
)
type SSHtoolSol struct {
@@ -45,14 +48,28 @@ type SSHtoolSol struct {
keyFile string
buffer []byte
needShowInfo bool
objectType string
object jsonutils.JSONObject
}
func getCommand(ctx context.Context, userCred mcclient.TokenCredential, ip string, port int) (string, *BaseCommand, error) {
func getObjectFromRemote(us *mcclient.ClientSession, id string, objType string) (jsonutils.JSONObject, error) {
gFunc := func(_ *mcclient.ClientSession, _ string, _ jsonutils.JSONObject) (jsonutils.JSONObject, error) {
return nil, errors.Errorf("Can't get object %q by type %q", id, objType)
}
if objType == "server" {
gFunc = compute.Servers.GetById
} else if objType == "host" {
gFunc = compute.Hosts.GetById
}
return gFunc(us, id, jsonutils.NewDict())
}
func getCommand(ctx context.Context, us *mcclient.ClientSession, ip string, port int) (string, *BaseCommand, error) {
if !o.Options.EnableAutoLogin {
return "", nil, nil
}
s := auth.GetAdminSession(ctx, o.Options.Region, "v2")
key, err := compute.Sshkeypairs.GetById(s, userCred.GetProjectId(), jsonutils.Marshal(map[string]bool{"admin": true}))
key, err := compute.Sshkeypairs.GetById(s, us.GetProjectId(), jsonutils.Marshal(map[string]bool{"admin": true}))
if err != nil {
return "", nil, err
}
@@ -84,7 +101,7 @@ func getCommand(ctx context.Context, userCred mcclient.TokenCredential, ip strin
log.Warningf("try use %s without password login error: %v", user, err)
return "", nil, nil
} else {
cmd = NewBaseCommand(o.Options.SshToolPath)
cmd = NewBaseCommand(us, o.Options.SshToolPath)
cmd.AppendArgs("-i", filename)
cmd.AppendArgs("-q")
cmd.AppendArgs("-o", "StrictHostKeyChecking=no")
@@ -99,12 +116,29 @@ func getCommand(ctx context.Context, userCred mcclient.TokenCredential, ip strin
return filename, cmd, nil
}
func NewSSHtoolSolCommand(ctx context.Context, userCred mcclient.TokenCredential, ip string, query jsonutils.JSONObject) (*SSHtoolSol, error) {
port := 22
if query != nil {
if _port, _ := query.Int("webconsole", "port"); _port != 0 {
func NewSSHtoolSolCommand(ctx context.Context, us *mcclient.ClientSession, ip string, body jsonutils.JSONObject) (*SSHtoolSol, error) {
var (
port = 22
objId = ""
objType = ""
obj jsonutils.JSONObject = nil
)
if body != nil {
if _port, _ := body.Int("webconsole", "port"); _port != 0 {
port = int(_port)
}
objId, _ = body.GetString("webconsole", "id")
if objId != "" {
objType, _ = body.GetString("webconsole", "type")
if objType == "" {
return nil, httperrors.NewInputParameterError("type must provided")
}
var err error
obj, err = getObjectFromRemote(us, objId, objType)
if err != nil {
return nil, err
}
}
}
conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", ip, port), time.Second*2)
@@ -113,7 +147,7 @@ func NewSSHtoolSolCommand(ctx context.Context, userCred mcclient.TokenCredential
}
defer conn.Close()
keyFile, cmd, err := getCommand(ctx, userCred, ip, port)
keyFile, cmd, err := getCommand(ctx, us, ip, port)
if err != nil {
log.Errorf("getCommand error: %v", err)
}
@@ -128,6 +162,8 @@ func NewSSHtoolSolCommand(ctx context.Context, userCred mcclient.TokenCredential
keyFile: keyFile,
buffer: []byte{},
needShowInfo: true,
objectType: objType,
object: obj,
}, nil
}
@@ -225,3 +261,17 @@ func (c *SSHtoolSol) ShowInfo() string {
}
return ""
}
func (c *SSHtoolSol) GetRecordObject() *recorder.Object {
if c.object == nil {
return nil
}
id, _ := c.object.GetString("id")
name, _ := c.object.GetString("name")
notes := map[string]interface{}{
"user": c.username,
"ip": c.IP,
"port": c.Port,
}
return recorder.NewObject(id, name, c.objectType, jsonutils.Marshal(notes))
}
+14 -3
View File
@@ -27,6 +27,8 @@ import (
webconsole_api "yunion.io/x/onecloud/pkg/apis/webconsole"
"yunion.io/x/onecloud/pkg/appsrv"
"yunion.io/x/onecloud/pkg/appsrv/dispatcher"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/policy"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient"
@@ -34,6 +36,7 @@ import (
modules "yunion.io/x/onecloud/pkg/mcclient/modules/compute"
"yunion.io/x/onecloud/pkg/mcclient/modules/k8s"
"yunion.io/x/onecloud/pkg/webconsole/command"
"yunion.io/x/onecloud/pkg/webconsole/models"
o "yunion.io/x/onecloud/pkg/webconsole/options"
"yunion.io/x/onecloud/pkg/webconsole/session"
)
@@ -51,6 +54,14 @@ func InitHandlers(app *appsrv.Application) {
app.AddHandler("POST", ApiPathPrefix+"baremetal/<id>", auth.Authenticate(handleBaremetalShell))
app.AddHandler("POST", ApiPathPrefix+"ssh/<ip>", auth.Authenticate(handleSshShell))
app.AddHandler("POST", ApiPathPrefix+"server/<id>", auth.Authenticate(handleServerRemoteConsole))
for _, man := range []db.IModelManager{
models.GetCommandLogManager(),
} {
db.RegisterModelManager(man)
handler := db.NewModelHandler(man)
dispatcher.AddModelDispatcher(ApiPathPrefix, app, handler)
}
}
func fetchK8sEnv(ctx context.Context, w http.ResponseWriter, r *http.Request) (*command.K8sEnv, error) {
@@ -99,6 +110,7 @@ func fetchK8sEnv(ctx context.Context, w http.ResponseWriter, r *http.Request) (*
f.WriteString(conf)
return &command.K8sEnv{
Session: adminSession,
Cluster: k8sReq.Cluster,
Namespace: k8sReq.Namespace,
Pod: podName,
@@ -157,13 +169,12 @@ func handleK8sLog(ctx context.Context, w http.ResponseWriter, r *http.Request) {
}
func handleSshShell(ctx context.Context, w http.ResponseWriter, r *http.Request) {
userCred := auth.FetchUserCredential(ctx, policy.FilterPolicyCredential)
env, err := fetchCloudEnv(ctx, w, r)
if err != nil {
httperrors.GeneralServerError(ctx, w, err)
return
}
cmd, err := command.NewSSHtoolSolCommand(ctx, userCred, env.Params["<ip>"], env.Body)
cmd, err := command.NewSSHtoolSolCommand(ctx, env.ClientSessin, env.Params["<ip>"], env.Body)
if err != nil {
httperrors.GeneralServerError(ctx, w, err)
return
@@ -189,7 +200,7 @@ func handleBaremetalShell(ctx context.Context, w http.ResponseWriter, r *http.Re
httperrors.GeneralServerError(ctx, w, err)
return
}
cmd, err := command.NewIpmitoolSolCommand(&info)
cmd, err := command.NewIpmitoolSolCommand(&info, env.ClientSessin)
if err != nil {
httperrors.GeneralServerError(ctx, w, err)
return
+122
View File
@@ -0,0 +1,122 @@
// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package models
import (
"context"
"time"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/errors"
"yunion.io/x/sqlchemy"
api "yunion.io/x/onecloud/pkg/apis/webconsole"
"yunion.io/x/onecloud/pkg/cloudcommon/consts"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/mcclient"
)
var commandLogManager *SCommandLogManager
type CommandType string
const (
CommandTypeSSH = "ssh"
)
func InitCommandLog() {
commandLogManager = GetCommandLogManager()
}
func GetCommandLogManager() *SCommandLogManager {
if commandLogManager != nil {
return commandLogManager
}
commandLogManager = &SCommandLogManager{
SOpsLogManager: db.SOpsLogManager{
SModelBaseManager: db.NewModelBaseManagerWithSplitable(
SCommandLog{},
"command_log_tbl",
"commandlog",
"commandlogs",
"id",
"start_time",
consts.SplitableMaxDuration(),
consts.SplitableMaxKeepMonths(),
),
},
}
commandLogManager.SetVirtualObject(commandLogManager)
return commandLogManager
}
type SCommandLogManager struct {
db.SOpsLogManager
}
type SCommandLog struct {
db.SOpsLog
SessionId string `width:"128" charset:"ascii" list:"user"`
AccessedAt time.Time `nullable:"false" list:"user" create:"required"`
Type string `width:"32" charset:"utf8" nullable:"true" list:"user" create:"required"`
StartTime time.Time `list:"user" create:"required"`
Command CommandType `charset:"utf8" list:"user" create:"required"`
}
type CommandLogCreateInput struct {
ObjId string
ObjName string
ObjType string
Action string
UserId string
User string
TenantId string
Tenant string
DomainId string
Domain string
ProjectDomainId string
ProjectDomain string
Roles string
SessionId string
AccessedAt time.Time
Type CommandType
StartTime time.Time
Command string
Notes jsonutils.JSONObject
}
func (m *SCommandLogManager) Create(ctx context.Context, userCred mcclient.TokenCredential, input *CommandLogCreateInput) (*SCommandLog, error) {
data := jsonutils.Marshal(input)
obj, err := db.DoCreate(GetCommandLogManager(), ctx, userCred, jsonutils.NewDict(), data, userCred)
if err != nil {
return nil, errors.Wrap(err, "Create CommandLog")
}
return obj.(*SCommandLog), nil
}
func (m *SCommandLogManager) ListItemFilter(
ctx context.Context,
q *sqlchemy.SQuery,
userCred mcclient.TokenCredential,
input api.CommandLogListInput,
) (*sqlchemy.SQuery, error) {
q, err := m.SOpsLogManager.ListItemFilter(ctx, q, userCred, input.OpsLogListInput)
if err != nil {
return nil, errors.Wrap(err, "SOpsLogManager.ListItemFilter")
}
return q, nil
}
+1
View File
@@ -0,0 +1 @@
package models // import "yunion.io/x/onecloud/pkg/webconsole/models"
+37
View File
@@ -0,0 +1,37 @@
// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package models
import (
"yunion.io/x/log"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
)
func InitDB() error {
for _, manager := range []db.IModelManager{
/*
* Important!!!
* initialization order matters, do not change the order
*/
GetCommandLogManager(),
} {
err := manager.InitializeData()
if err != nil {
log.Errorf("Manager %s initializeData fail %s", manager.Keyword(), err)
}
}
return nil
}
+6
View File
@@ -23,6 +23,8 @@ var (
type WebConsoleOptions struct {
common_options.CommonOptions
common_options.DBOptions
//ApiServer string `help:"API server url to handle websocket connection, usually with public access" default:"http://webconsole.yunion.io"`
KubectlPath string `help:"kubectl binary path used to connect k8s cluster" default:"/usr/bin/kubectl"`
@@ -43,5 +45,9 @@ func OnOptionsChange(oldO, newO interface{}) bool {
changed = true
}
if common_options.OnDBOptionsChange(&oldOpts.DBOptions, &newOpts.DBOptions) {
changed = true
}
return changed
}
+1
View File
@@ -0,0 +1 @@
package recorder // import "yunion.io/x/onecloud/pkg/webconsole/recorder"
+137
View File
@@ -0,0 +1,137 @@
// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package recorder
import (
"strings"
"time"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/webconsole/models"
)
type Recoder interface {
Start()
Write(data string)
}
type Object struct {
Id string
Name string
Type string
Notes jsonutils.JSONObject
}
func NewObject(id, name, oType string, notes jsonutils.JSONObject) *Object {
return &Object{
Id: id,
Name: name,
Type: oType,
Notes: notes,
}
}
type cmdRecoder struct {
cs *mcclient.ClientSession
sessionId string
accessedAt time.Time
buffer []string
curCmd string
cmdCh chan string
object *Object
}
func NewCmdRecorder(s *mcclient.ClientSession, obj *Object, sessionId string, accessedAt time.Time) Recoder {
return &cmdRecoder{
cs: s,
sessionId: sessionId,
accessedAt: accessedAt,
buffer: make([]string, 0),
curCmd: "",
cmdCh: make(chan string),
object: obj,
}
}
func (r *cmdRecoder) Write(data string) {
if data == "\n" || data == "\r" {
r.sendMessage(r.curCmd)
return
}
r.curCmd += data
}
func (r *cmdRecoder) cleanCmd() *cmdRecoder {
r.curCmd = ""
return r
}
func (r *cmdRecoder) sendMessage(msg string) {
r.cmdCh <- msg
r.cleanCmd()
log.Infof("Message %q sended", msg)
}
func (r *cmdRecoder) Start() {
for {
select {
case cmd := <-r.cmdCh:
if err := r.save(cmd); err != nil {
log.Errorf("save comand %q error: %v", cmd, err)
}
}
}
}
func (r *cmdRecoder) save(command string) error {
if r.object == nil {
return nil
}
userCred := r.cs.GetToken()
input := r.newModelInput(userCred, command)
_, err := models.GetCommandLogManager().Create(r.cs.GetContext(), userCred, input)
if err != nil {
return errors.Wrapf(err, "Create command log by input: %s", jsonutils.Marshal(input))
}
return nil
}
func (r *cmdRecoder) newModelInput(userCred mcclient.TokenCredential, command string) *models.CommandLogCreateInput {
return &models.CommandLogCreateInput{
ObjId: r.object.Id,
ObjName: r.object.Name,
ObjType: r.object.Type,
Notes: r.object.Notes,
Action: "record",
UserId: userCred.GetUserId(),
User: userCred.GetUserName(),
TenantId: userCred.GetTenantId(),
Tenant: userCred.GetTenantName(),
DomainId: userCred.GetDomainId(),
Domain: userCred.GetDomainName(),
ProjectDomainId: userCred.GetProjectDomainId(),
ProjectDomain: userCred.GetProjectDomain(),
Roles: strings.Join(userCred.GetRoles(), ","),
SessionId: r.sessionId,
AccessedAt: r.accessedAt,
Type: models.CommandTypeSSH,
StartTime: time.Now(),
Command: command,
}
}
+1
View File
@@ -120,6 +120,7 @@ func initSocketHandler(so socketio.Socket, p *session.Pty) {
}
} else {
p.Pty.Write([]byte(data))
go p.Session.GetRecorder().Write(data)
}
})
+12 -1
View File
@@ -27,12 +27,16 @@ import (
"yunion.io/x/log"
"yunion.io/x/pkg/util/signalutils"
_ "yunion.io/x/sqlchemy/backends"
api "yunion.io/x/onecloud/pkg/apis/webconsole"
"yunion.io/x/onecloud/pkg/appsrv"
"yunion.io/x/onecloud/pkg/cloudcommon"
app_common "yunion.io/x/onecloud/pkg/cloudcommon/app"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
common_options "yunion.io/x/onecloud/pkg/cloudcommon/options"
"yunion.io/x/onecloud/pkg/webconsole"
"yunion.io/x/onecloud/pkg/webconsole/models"
o "yunion.io/x/onecloud/pkg/webconsole/options"
"yunion.io/x/onecloud/pkg/webconsole/server"
)
@@ -78,10 +82,17 @@ func registerSigTraps() {
func start() {
baseOpts := &o.Options.BaseOptions
// commonOpts := &o.Options.CommonOptions
app := app_common.InitApp(baseOpts, false)
app := app_common.InitApp(baseOpts, true)
dbOpts := &o.Options.DBOptions
cloudcommon.InitDB(dbOpts)
webconsole.InitHandlers(app)
db.EnsureAppSyncDB(app, dbOpts, models.InitDB)
root := mux.NewRouter()
root.UseEncodedPath()
+15 -1
View File
@@ -26,6 +26,7 @@ import (
"yunion.io/x/onecloud/pkg/mcclient"
modules "yunion.io/x/onecloud/pkg/mcclient/modules/compute"
"yunion.io/x/onecloud/pkg/webconsole/options"
"yunion.io/x/onecloud/pkg/webconsole/recorder"
)
const (
@@ -44,7 +45,11 @@ const (
CLOUDPODS = api.CLOUDPODS
)
type RemoteConsoleInfo cloudprovider.ServerVncOutput
type RemoteConsoleInfo struct {
cloudprovider.ServerVncOutput
s *mcclient.ClientSession
}
func NewRemoteConsoleInfoByCloud(s *mcclient.ClientSession, serverId string, query jsonutils.JSONObject) (*RemoteConsoleInfo, error) {
ret, err := modules.Servers.GetSpecific(s, serverId, "vnc", query)
@@ -56,6 +61,7 @@ func NewRemoteConsoleInfoByCloud(s *mcclient.ClientSession, serverId string, que
if err != nil {
return nil, err
}
vncInfo.s = s
if len(vncInfo.OsName) == 0 || len(vncInfo.VncPassword) == 0 {
metadata, err := modules.Servers.GetSpecific(s, serverId, "metadata", nil)
@@ -111,6 +117,10 @@ func (info *RemoteConsoleInfo) ShowInfo() string {
return ""
}
func (info *RemoteConsoleInfo) GetClientSession() *mcclient.ClientSession {
return info.s
}
func (info *RemoteConsoleInfo) GetConnectParams() (string, error) {
switch info.Protocol {
case ALIYUN:
@@ -191,3 +201,7 @@ func (info *RemoteConsoleInfo) getApsaraURL() (string, error) {
}
return info.getConnParamsURL(options.Options.ApsaraConsoleAddr, params), nil
}
func (info *RemoteConsoleInfo) GetRecordObject() *recorder.Object {
return nil
}
+10
View File
@@ -31,6 +31,7 @@ import (
"yunion.io/x/onecloud/pkg/webconsole/command"
o "yunion.io/x/onecloud/pkg/webconsole/options"
"yunion.io/x/onecloud/pkg/webconsole/recorder"
)
var (
@@ -124,6 +125,7 @@ type SSession struct {
AccessToken string
AccessedAt time.Time
duplicateHook func()
recorder recorder.Recoder
}
func (s SSession) GetConnectParams(params url.Values) (string, error) {
@@ -165,3 +167,11 @@ func (s *SSession) Close() error {
func (s *SSession) RegisterDuplicateHook(f func()) {
s.duplicateHook = f
}
func (s *SSession) GetRecorder() recorder.Recoder {
if s.recorder == nil {
s.recorder = recorder.NewCmdRecorder(s.GetClientSession(), s.GetRecordObject(), s.GetId(), s.AccessedAt)
go s.recorder.Start()
}
return s.recorder
}