mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-09-24 16:03:43 +08:00
Merge pull request #18998 from swordqiu/automated-cherry-pick-of-#18997-upstream-release-3.11
Automated cherry pick of #18997: fix: refine webconsole ssh api
This commit is contained in:
@@ -117,7 +117,7 @@ func init() {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ret, err := webconsole.WebConsole.DoSshConnect(s, args.IP, params)
|
||||
ret, err := webconsole.WebConsole.DoSshConnect(s, args.ID, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -406,7 +406,11 @@ func FetchCheckQueryOwnerScope(
|
||||
ownerId = userCred
|
||||
reqScopeStr, _ := data.GetString("scope")
|
||||
if len(reqScopeStr) > 0 {
|
||||
queryScope = rbacscope.String2Scope(reqScopeStr)
|
||||
if reqScopeStr == "max" || reqScopeStr == "maxallowed" {
|
||||
queryScope = allowScope
|
||||
} else {
|
||||
queryScope = rbacscope.String2Scope(reqScopeStr)
|
||||
}
|
||||
} else if data.Contains("admin") {
|
||||
isAdmin := jsonutils.QueryBoolean(data, "admin", false)
|
||||
if isAdmin && allowScope.HigherThan(rbacscope.ScopeProject) {
|
||||
|
||||
@@ -71,7 +71,8 @@ func (opt *WebConsoleBaremetalOptions) Params() (*jsonutils.JSONDict, error) {
|
||||
|
||||
type WebConsoleSshOptions struct {
|
||||
WebConsoleOptions
|
||||
IP string `help:"IP to connect" json:"-"`
|
||||
ID string `help:"ID of server" json:"-"`
|
||||
Ip string `help:"IP to connect if multiple IPs on server"`
|
||||
Port int `help:"Remote server port"`
|
||||
Username string `help:"Remote server username"`
|
||||
KeepUsername bool `help:"Keep remove username`
|
||||
@@ -97,7 +98,7 @@ type WebConsoleServerRdpOptions struct {
|
||||
WebConsoleOptions
|
||||
ID string `help:"Server id or name"`
|
||||
|
||||
HOST string
|
||||
Host *string
|
||||
Port *int
|
||||
Username *string
|
||||
Password *string
|
||||
|
||||
@@ -30,7 +30,7 @@ import (
|
||||
o "yunion.io/x/onecloud/pkg/webconsole/options"
|
||||
)
|
||||
|
||||
func GetValidPrivateKey(host string, port int64, username string, projectId string) (string, error) {
|
||||
func GetValidPrivateKey(host string, port int, username string, projectId string) (string, error) {
|
||||
errs := []error{}
|
||||
ctx := context.Background()
|
||||
admin := auth.GetAdminSession(ctx, o.Options.Region)
|
||||
|
||||
@@ -37,7 +37,7 @@ import (
|
||||
type WebsocketServer struct {
|
||||
Session *session.SSession
|
||||
Host string
|
||||
Port int64
|
||||
Port int
|
||||
Username string
|
||||
Password string
|
||||
PrivateKey string
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package webconsole
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/pkg/errors"
|
||||
"yunion.io/x/pkg/gotypes"
|
||||
"yunion.io/x/pkg/util/regutils"
|
||||
|
||||
webconsole_api "yunion.io/x/onecloud/pkg/apis/webconsole"
|
||||
"yunion.io/x/onecloud/pkg/appsrv"
|
||||
@@ -50,7 +51,7 @@ const (
|
||||
WebsocketProxyPathPrefix = "/wsproxy/"
|
||||
)
|
||||
|
||||
func InitHandlers(app *appsrv.Application) {
|
||||
func initHandlers(app *appsrv.Application) {
|
||||
app.AddHandler("POST", ApiPathPrefix+"k8s/<podName>/shell", auth.Authenticate(handleK8sShell))
|
||||
app.AddHandler("POST", ApiPathPrefix+"climc/shell", auth.Authenticate(handleClimcShell))
|
||||
app.AddHandler("POST", ApiPathPrefix+"k8s/<podName>/log", auth.Authenticate(handleK8sLog))
|
||||
@@ -173,13 +174,29 @@ func handleSshShell(ctx context.Context, w http.ResponseWriter, r *http.Request)
|
||||
httperrors.GeneralServerError(ctx, w, err)
|
||||
return
|
||||
}
|
||||
ip := env.Params["<ip>"]
|
||||
port, _ := env.Body.Int("port")
|
||||
username, _ := env.Body.GetString("username")
|
||||
keepusername, _ := env.Body.Bool("keep_username")
|
||||
password, _ := env.Body.GetString("password")
|
||||
name, _ := env.Body.GetString("name")
|
||||
s := session.NewSshSession(ctx, env.ClientSessin, name, ip, port, username, password, keepusername)
|
||||
|
||||
sshConnInfo := session.SSshConnectionInfo{}
|
||||
if !gotypes.IsNil(env.Body) {
|
||||
err = env.Body.Unmarshal(&sshConnInfo)
|
||||
if err != nil {
|
||||
httperrors.InputParameterError(ctx, w, "unmarshal error: %s", err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
idStr := env.Params["<ip>"]
|
||||
if !regutils.MatchIPAddr(idStr) {
|
||||
ip, port, err := session.ResolveServerSSHIPPortById(ctx, env.ClientSessin, idStr, sshConnInfo.IP, sshConnInfo.Port)
|
||||
if err != nil {
|
||||
httperrors.GeneralServerError(ctx, w, err)
|
||||
return
|
||||
}
|
||||
sshConnInfo.IP = ip
|
||||
sshConnInfo.Port = port
|
||||
} else {
|
||||
// directly ssh IP should be deprecated gradually
|
||||
sshConnInfo.IP = idStr
|
||||
}
|
||||
s := session.NewSshSession(ctx, env.ClientSessin, sshConnInfo)
|
||||
handleSshSession(ctx, s, w)
|
||||
}
|
||||
|
||||
@@ -267,7 +284,7 @@ func handleServerRemoteRDPConsole(ctx context.Context, w http.ResponseWriter, r
|
||||
}
|
||||
query := env.Body
|
||||
srvId := env.Params["<id>"]
|
||||
info, err := session.NewRemoteRDPConsoleInfoByCloud(env.ClientSessin, srvId, query)
|
||||
info, err := session.NewRemoteRDPConsoleInfoByCloud(ctx, env.ClientSessin, srvId, query)
|
||||
if err != nil {
|
||||
httperrors.GeneralServerError(ctx, w, err)
|
||||
return
|
||||
@@ -36,7 +36,6 @@ import (
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/cronman"
|
||||
"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"
|
||||
@@ -90,7 +89,7 @@ func start() {
|
||||
|
||||
cloudcommon.InitDB(dbOpts)
|
||||
|
||||
webconsole.InitHandlers(app)
|
||||
initHandlers(app)
|
||||
|
||||
db.EnsureAppSyncDB(app, dbOpts, models.InitDB)
|
||||
|
||||
@@ -98,17 +97,17 @@ func start() {
|
||||
root.UseEncodedPath()
|
||||
|
||||
// api handler
|
||||
root.PathPrefix(webconsole.ApiPathPrefix).Handler(app)
|
||||
root.PathPrefix(ApiPathPrefix).Handler(app)
|
||||
|
||||
srv := server.NewConnectionServer()
|
||||
// websocket command text console handler
|
||||
root.Handle(webconsole.ConnectPathPrefix, srv)
|
||||
root.Handle(ConnectPathPrefix, srv)
|
||||
|
||||
// websockify graphic console handler
|
||||
root.Handle(webconsole.WebsockifyPathPrefix, srv)
|
||||
root.Handle(WebsockifyPathPrefix, srv)
|
||||
|
||||
// websocketproxy handler
|
||||
root.Handle(webconsole.WebsocketProxyPathPrefix, srv)
|
||||
root.Handle(WebsocketProxyPathPrefix, srv)
|
||||
|
||||
// misc handler
|
||||
addMiscHandlers(app, root)
|
||||
|
||||
@@ -15,9 +15,11 @@
|
||||
package session
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os/exec"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/pkg/errors"
|
||||
"yunion.io/x/pkg/gotypes"
|
||||
|
||||
api "yunion.io/x/onecloud/pkg/apis/webconsole"
|
||||
@@ -40,10 +42,21 @@ type RemoteRDPConsoleInfo struct {
|
||||
s *mcclient.ClientSession
|
||||
}
|
||||
|
||||
func NewRemoteRDPConsoleInfoByCloud(s *mcclient.ClientSession, serverId string, query jsonutils.JSONObject) (*RemoteRDPConsoleInfo, error) {
|
||||
func NewRemoteRDPConsoleInfoByCloud(ctx context.Context, s *mcclient.ClientSession, serverId string, query jsonutils.JSONObject) (*RemoteRDPConsoleInfo, error) {
|
||||
info := &RemoteRDPConsoleInfo{s: s}
|
||||
if !gotypes.IsNil(query) {
|
||||
query.Unmarshal(&info)
|
||||
err := query.Unmarshal(&info)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Unmarshal")
|
||||
}
|
||||
}
|
||||
if info.Port <= 0 {
|
||||
info.Port = 3389
|
||||
}
|
||||
var err error
|
||||
info.Host, info.Port, err = resolveServerIPPortById(ctx, s, serverId, info.Host, info.Port)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "resolveServerIPPortById")
|
||||
}
|
||||
if len(info.Host) == 0 {
|
||||
return nil, httperrors.NewMissingParameterError("host")
|
||||
@@ -59,9 +72,6 @@ func NewRemoteRDPConsoleInfoByCloud(s *mcclient.ClientSession, serverId string,
|
||||
info.Password, _ = ret.GetString("password")
|
||||
info.Username, _ = ret.GetString("username")
|
||||
}
|
||||
if info.Port == 0 {
|
||||
info.Port = 3389
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
// 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 session
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/pkg/errors"
|
||||
|
||||
compute_api "yunion.io/x/onecloud/pkg/apis/compute"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db/lockman"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modules/compute"
|
||||
modules "yunion.io/x/onecloud/pkg/mcclient/modules/compute"
|
||||
options "yunion.io/x/onecloud/pkg/mcclient/options/compute"
|
||||
)
|
||||
|
||||
type SSshConnectionInfo struct {
|
||||
IP string `json:"ip"`
|
||||
Port int `json:"port"`
|
||||
Username string `json:"username"`
|
||||
KeepUsername bool `json:"keep_username"`
|
||||
Password string `json:"password"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func ResolveServerSSHIPPortById(ctx context.Context, s *mcclient.ClientSession, id string, ip string, port int) (string, int, error) {
|
||||
if port <= 0 {
|
||||
port = 22
|
||||
}
|
||||
return resolveServerIPPortById(ctx, s, id, ip, port)
|
||||
}
|
||||
|
||||
func resolveServerIPPortById(ctx context.Context, s *mcclient.ClientSession, id string, ip string, port int) (string, int, error) {
|
||||
guestInfo, err := compute.Servers.Get(s, id, nil)
|
||||
if err != nil {
|
||||
return "", 0, errors.Wrapf(err, "GetById %s", id)
|
||||
}
|
||||
guestDetails := compute_api.SGuest{}
|
||||
err = guestInfo.Unmarshal(&guestDetails)
|
||||
if err != nil {
|
||||
return "", 0, errors.Wrap(err, "Unmarshal guest info")
|
||||
}
|
||||
// list all nic of a server
|
||||
input := compute_api.GuestnetworkListInput{}
|
||||
input.ServerId = guestDetails.Id
|
||||
True := true
|
||||
input.Details = &True
|
||||
input.ServerFilterListInput.Scope = "max"
|
||||
result, err := compute.Servernetworks.List(s, jsonutils.Marshal(input))
|
||||
if err != nil {
|
||||
return "", 0, errors.Wrap(err, "Servernetworks.List")
|
||||
}
|
||||
if result.Total == 0 {
|
||||
// not nic found!!!
|
||||
return "", 0, errors.Wrap(httperrors.ErrNotFound, "no nic on server")
|
||||
}
|
||||
|
||||
// find nics
|
||||
var guestNicDetails compute_api.GuestnetworkDetails
|
||||
if result.Total == 1 {
|
||||
err := result.Data[0].Unmarshal(&guestNicDetails)
|
||||
if err != nil {
|
||||
return "", 0, errors.Wrap(err, "Unmarshal guest network info")
|
||||
}
|
||||
if len(ip) > 0 && ip != guestNicDetails.EipAddr && ip != guestNicDetails.IpAddr && ip != guestNicDetails.Ip6Addr {
|
||||
return "", 0, errors.Wrapf(httperrors.ErrInputParameter, "ip %s not match with server", ip)
|
||||
}
|
||||
} else {
|
||||
if len(ip) == 0 {
|
||||
return "", 0, errors.Wrap(httperrors.ErrInputParameter, "must specify ip")
|
||||
}
|
||||
find := false
|
||||
for _, gnJson := range result.Data {
|
||||
err := gnJson.Unmarshal(&guestNicDetails)
|
||||
if err != nil {
|
||||
return "", 0, errors.Wrap(err, "Unmarshal guest network info")
|
||||
}
|
||||
if ip == guestNicDetails.EipAddr || ip == guestNicDetails.IpAddr || ip == guestNicDetails.Ip6Addr {
|
||||
find = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !find {
|
||||
return "", 0, errors.Wrap(httperrors.ErrInputParameter, "ip specified not match with server")
|
||||
}
|
||||
}
|
||||
|
||||
if len(ip) == 0 {
|
||||
// guest ip
|
||||
if len(guestNicDetails.EipAddr) > 0 {
|
||||
ip = guestNicDetails.EipAddr
|
||||
} else if len(guestNicDetails.IpAddr) > 0 {
|
||||
ip = guestNicDetails.IpAddr
|
||||
} else {
|
||||
return "", 0, errors.Wrap(httperrors.ErrNotSupported, "no valid ipv4 addr")
|
||||
}
|
||||
}
|
||||
|
||||
if ip == guestNicDetails.Ip6Addr {
|
||||
return "", 0, errors.Wrap(httperrors.ErrNotSupported, "ipv6 not supported")
|
||||
}
|
||||
|
||||
if ip == guestNicDetails.IpAddr && len(guestNicDetails.MappedIpAddr) > 0 {
|
||||
// need to do open forward
|
||||
return acquireForward(ctx, s, guestDetails.Id, ip, "tcp", port)
|
||||
}
|
||||
|
||||
return ip, port, nil
|
||||
}
|
||||
|
||||
type sForwardInfo struct {
|
||||
ProxyAddr string `json:"proxy_addr"`
|
||||
ProxyPort int `json:"proxy_port"`
|
||||
}
|
||||
|
||||
func acquireForward(ctx context.Context, session *mcclient.ClientSession, srvid string, ip string, proto string, port int) (string, int, error) {
|
||||
lockman.LockRawObject(ctx, "server", srvid)
|
||||
defer lockman.ReleaseRawObject(ctx, "server", srvid)
|
||||
|
||||
addr, port, err := listForward(session, srvid, ip, proto, port)
|
||||
if err == nil {
|
||||
return addr, port, nil
|
||||
}
|
||||
if errors.Cause(err) == httperrors.ErrNotFound {
|
||||
return openForward(session, srvid, ip, proto, port)
|
||||
} else {
|
||||
return "", 0, errors.Wrap(err, "listForward")
|
||||
}
|
||||
}
|
||||
|
||||
func listForward(session *mcclient.ClientSession, srvid string, ip string, proto string, port int) (string, int, error) {
|
||||
opt := &options.ServerListForwardOptions{
|
||||
ServerIdOptions: options.ServerIdOptions{
|
||||
ID: srvid,
|
||||
},
|
||||
Proto: &proto,
|
||||
Port: &port,
|
||||
Addr: &ip,
|
||||
}
|
||||
|
||||
params, err := opt.Params()
|
||||
if err != nil {
|
||||
return "", 0, errors.Wrap(err, "get list forward params")
|
||||
}
|
||||
jsonItem, err := modules.Servers.PerformAction(session, opt.ID, "list-forward", params)
|
||||
if err != nil {
|
||||
return "", 0, errors.Wrap(err, "list-forward")
|
||||
}
|
||||
|
||||
infoList := make([]sForwardInfo, 0)
|
||||
err = jsonItem.Unmarshal(&infoList, "forwards")
|
||||
if err != nil {
|
||||
return "", 0, errors.Wrap(err, "Unmarshal forwards")
|
||||
}
|
||||
|
||||
if len(infoList) > 0 {
|
||||
return infoList[0].ProxyAddr, infoList[0].ProxyPort, nil
|
||||
}
|
||||
|
||||
return "", 0, errors.Wrap(httperrors.ErrNotFound, "no forwards")
|
||||
}
|
||||
|
||||
func openForward(session *mcclient.ClientSession, srvid string, ip string, proto string, port int) (string, int, error) {
|
||||
opt := &options.ServerOpenForwardOptions{
|
||||
ServerIdOptions: options.ServerIdOptions{
|
||||
ID: srvid,
|
||||
},
|
||||
Proto: proto,
|
||||
Port: port,
|
||||
Addr: ip,
|
||||
}
|
||||
|
||||
params, err := opt.Params()
|
||||
if err != nil {
|
||||
return "", 0, errors.Wrap(err, "get open forward params")
|
||||
}
|
||||
|
||||
jsonItem, err := modules.Servers.PerformAction(session, opt.ID, "open-forward", params)
|
||||
if err != nil {
|
||||
return "", 0, errors.Wrap(err, "open-forward")
|
||||
}
|
||||
|
||||
info := sForwardInfo{}
|
||||
err = jsonItem.Unmarshal(&info)
|
||||
if err != nil {
|
||||
return "", 0, errors.Wrap(err, "Unmarshal")
|
||||
}
|
||||
|
||||
return info.ProxyAddr, info.ProxyPort, nil
|
||||
}
|
||||
@@ -42,7 +42,7 @@ type SSshSession struct {
|
||||
name string
|
||||
Host string
|
||||
|
||||
Port int64
|
||||
Port int
|
||||
PrivateKey string
|
||||
Username string
|
||||
// 保持原有 Username ,不实用 cloudroot 的同时使用 PrivateKey
|
||||
@@ -50,19 +50,18 @@ type SSshSession struct {
|
||||
Password string
|
||||
}
|
||||
|
||||
func NewSshSession(ctx context.Context, us *mcclient.ClientSession,
|
||||
name, ip string, port int64, username, password string, KeepUsername bool) *SSshSession {
|
||||
func NewSshSession(ctx context.Context, us *mcclient.ClientSession, conn SSshConnectionInfo) *SSshSession {
|
||||
ret := &SSshSession{
|
||||
us: us,
|
||||
id: stringutils.UUID4(),
|
||||
Port: port,
|
||||
Host: ip,
|
||||
name: name,
|
||||
Username: username,
|
||||
KeepUsername: KeepUsername,
|
||||
Password: password,
|
||||
Port: conn.Port,
|
||||
Host: conn.IP,
|
||||
name: conn.Name,
|
||||
Username: conn.Username,
|
||||
KeepUsername: conn.KeepUsername,
|
||||
Password: conn.Password,
|
||||
}
|
||||
if port <= 0 {
|
||||
if conn.Port <= 0 {
|
||||
ret.Port = 22
|
||||
}
|
||||
return ret
|
||||
|
||||
Reference in New Issue
Block a user