Merge pull request #19013 from swordqiu/automated-cherry-pick-of-#19012-upstream-release-3.11

Automated cherry pick of #19012: fix: webconsole display information control
This commit is contained in:
Zexi Li
2023-12-16 10:10:39 +08:00
committed by GitHub
11 changed files with 264 additions and 39 deletions
+5
View File
@@ -32,6 +32,11 @@ import (
func init() {
handleResult := func(s *mcclient.ClientSession, opt o.WebConsoleOptions, obj jsonutils.JSONObject) error {
if obj.Contains("access_url") {
accessUrl, _ := obj.GetString("access_url")
fmt.Println("AccessURL:", accessUrl)
return nil
}
if opt.WebconsoleUrl == "" {
resp, err := identity.ServicesV3.GetSpecific(s, "common", "config", nil)
if err != nil {
-1
View File
@@ -116,7 +116,6 @@ var (
"ignore_nonrunning_guests",
"platform_name",
"enable_cloud_shell",
"enable_watermark",
"platform_names",
},
}
+1
View File
@@ -22,6 +22,7 @@ import (
)
type ServerRemoteConsoleResponse struct {
AccessUrl string `json:"access_url"`
ConnectParams string `json:"connect_params"`
Session string `json:"session,omitempty"`
+10 -7
View File
@@ -25,13 +25,16 @@ type WebConsoleOptions struct {
common_options.DBOptions
KubectlPath string `help:"kubectl binary path used to connect k8s cluster" default:"/usr/bin/kubectl"`
IpmitoolPath string `help:"ipmitool binary path used to connect baremetal sol" default:"/usr/bin/ipmitool"`
EnableAutoLogin bool `help:"allow webconsole to log in directly with the cloudroot public key" default:"false"`
ApsaraConsoleAddr string `help:"Apsara console addr" default:"https://xxxx.com.cn/module/ecs/vnc/index.html"`
AliyunConsoleAddr string `help:"Aliyun vnc addr" default:"https://ecs.console.aliyun.com/vnc/index.htm"`
SshSessionTimeoutMinutes int `help:"ssh timeout session" default:"-1"`
RdpSessionTimeoutMinutes int `help:"rdp timeout session" default:"-1"`
KubectlPath string `help:"kubectl binary path used to connect k8s cluster" default:"/usr/bin/kubectl"`
IpmitoolPath string `help:"ipmitool binary path used to connect baremetal sol" default:"/usr/bin/ipmitool"`
EnableAutoLogin bool `help:"allow webconsole to log in directly with the cloudroot public key" default:"false"`
ApsaraConsoleAddr string `help:"Apsara console addr" default:"https://xxxx.com.cn/module/ecs/vnc/index.html"`
AliyunConsoleAddr string `help:"Aliyun vnc addr" default:"https://ecs.console.aliyun.com/vnc/index.htm"`
SshSessionTimeoutMinutes int `help:"ssh timeout session" default:"-1"`
RdpSessionTimeoutMinutes int `help:"rdp timeout session" default:"-1"`
EnableWatermark bool `help:"enable water mark" default:"true"`
}
func OnOptionsChange(oldO, newO interface{}) bool {
+29 -8
View File
@@ -25,6 +25,7 @@ import (
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/gotypes"
"yunion.io/x/pkg/util/httputils"
"yunion.io/x/pkg/util/regutils"
webconsole_api "yunion.io/x/onecloud/pkg/apis/webconsole"
@@ -185,13 +186,14 @@ func handleSshShell(ctx context.Context, w http.ResponseWriter, r *http.Request)
}
idStr := env.Params["<ip>"]
if !regutils.MatchIPAddr(idStr) {
ip, port, err := session.ResolveServerSSHIPPortById(ctx, env.ClientSessin, idStr, sshConnInfo.IP, sshConnInfo.Port)
ip, port, guestDetails, 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
sshConnInfo.GuestDetails = guestDetails
} else {
// directly ssh IP should be deprecated gradually
sshConnInfo.IP = idStr
@@ -201,7 +203,7 @@ func handleSshShell(ctx context.Context, w http.ResponseWriter, r *http.Request)
}
func handleSshSession(ctx context.Context, session *session.SSshSession, w http.ResponseWriter) {
handleDataSession(ctx, session, w, nil, false)
handleDataSession(ctx, session, w, "ws", nil, false)
}
func handleBaremetalShell(ctx context.Context, w http.ResponseWriter, r *http.Request) {
@@ -269,8 +271,12 @@ func handleServerRemoteConsole(ctx context.Context, w http.ResponseWriter, r *ht
session.HUAWEI, session.HCS, session.APSARA,
session.JDCLOUD, session.CLOUDPODS, session.PROXMOX:
responsePublicCloudConsole(ctx, info, w)
case session.VNC, session.SPICE, session.WMKS:
handleDataSession(ctx, info, w, url.Values{"password": {info.GetPassword()}}, true)
case session.VNC:
handleDataSession(ctx, info, w, "no-vnc", url.Values{"password": {info.GetPassword()}}, true)
case session.SPICE:
handleDataSession(ctx, info, w, "spice", url.Values{"password": {info.GetPassword()}}, true)
case session.WMKS:
handleDataSession(ctx, info, w, "wmks", url.Values{"password": {info.GetPassword()}}, true)
default:
httperrors.NotAcceptableError(ctx, w, "Unspported remote console protocol: %s", info.Protocol)
}
@@ -289,7 +295,7 @@ func handleServerRemoteRDPConsole(ctx context.Context, w http.ResponseWriter, r
httperrors.GeneralServerError(ctx, w, err)
return
}
handleDataSession(ctx, info, w, url.Values{"password": {info.GetPassword()}}, true)
handleDataSession(ctx, info, w, "rdp", url.Values{"password": {info.GetPassword()}}, true)
}
func responsePublicCloudConsole(ctx context.Context, info *session.RemoteConsoleInfo, w http.ResponseWriter) {
@@ -304,21 +310,36 @@ func responsePublicCloudConsole(ctx context.Context, info *session.RemoteConsole
sendJSON(w, resp.JSON(resp))
}
func handleDataSession(ctx context.Context, sData session.ISessionData, w http.ResponseWriter, connParams url.Values, b64Encode bool) {
func handleDataSession(ctx context.Context, sData session.ISessionData, w http.ResponseWriter, base string, connParams url.Values, b64Encode bool) {
s, err := session.Manager.Save(sData)
if err != nil {
httperrors.GeneralServerError(ctx, w, err)
return
}
params, err := s.GetConnectParams(connParams)
dispInfo, err := sData.GetDisplayInfo(ctx)
if err != nil {
httperrors.GeneralServerError(ctx, w, err)
return
}
params, err := s.GetConnectParams(connParams, dispInfo)
if err != nil {
httperrors.GeneralServerError(ctx, w, err)
return
}
var accessUrl string
{
dataVal := url.Values{}
dataVal.Add("data", base64.StdEncoding.EncodeToString([]byte(params)))
accessUrl = httputils.JoinPath(o.Options.ApiServer, fmt.Sprintf("web-console/%s?%s", base, dataVal.Encode()))
}
if b64Encode {
params = base64.StdEncoding.EncodeToString([]byte(params))
}
resp := webconsole_api.ServerRemoteConsoleResponse{
AccessUrl: accessUrl,
ConnectParams: params,
Session: s.Id,
}
@@ -326,7 +347,7 @@ func handleDataSession(ctx context.Context, sData session.ISessionData, w http.R
}
func handleCommandSession(ctx context.Context, cmd command.ICommand, w http.ResponseWriter) {
handleDataSession(ctx, session.WrapCommandSession(cmd), w, nil, false)
handleDataSession(ctx, session.WrapCommandSession(cmd), w, "tty", nil, false)
}
func sendJSON(w http.ResponseWriter, body jsonutils.JSONObject) {
+125
View File
@@ -0,0 +1,125 @@
// 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"
"net/url"
"yunion.io/x/pkg/errors"
compute_api "yunion.io/x/onecloud/pkg/apis/compute"
identity_api "yunion.io/x/onecloud/pkg/apis/identity"
"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/mcclient/modules/identity"
"yunion.io/x/onecloud/pkg/webconsole/options"
)
type SDisplayInfo struct {
WaterMark string `json:"water_mark"`
InstanceName string `json:"instance_name"`
Ips string `json:"ips"`
Hypervisor string `json:"hypervisor"`
OsType string `json:"os_type"`
OsName string `json:"os_name"`
OsArch string `json:"os_arch"`
OsDistribution string `json:"os_distribution"`
}
func (dispInfo *SDisplayInfo) fetchGuestInfo(guestDetails *compute_api.ServerDetails) {
dispInfo.Hypervisor = guestDetails.Hypervisor
dispInfo.OsName = guestDetails.OsName
dispInfo.OsType = guestDetails.OsType
dispInfo.OsArch = guestDetails.OsArch
dispInfo.OsDistribution = guestDetails.Metadata[compute_api.VM_METADATA_OS_DISTRO]
dispInfo.InstanceName = guestDetails.Name
dispInfo.Ips = guestDetails.IPs
}
func (dispInfo *SDisplayInfo) populateParams(params url.Values) url.Values {
if options.Options.EnableWatermark && len(dispInfo.WaterMark) > 0 {
params["water_mark"] = []string{dispInfo.WaterMark}
}
if len(dispInfo.InstanceName) > 0 {
params["instance_name"] = []string{dispInfo.InstanceName}
}
if len(dispInfo.Ips) > 0 {
params["ips"] = []string{dispInfo.Ips}
}
if len(dispInfo.Hypervisor) > 0 {
params["hypervisor"] = []string{dispInfo.Hypervisor}
}
if len(dispInfo.OsType) > 0 {
params["os_type"] = []string{dispInfo.OsType}
}
if len(dispInfo.OsName) > 0 {
params["os_name"] = []string{dispInfo.OsName}
}
if len(dispInfo.OsArch) > 0 {
params["os_arch"] = []string{dispInfo.OsArch}
}
if len(dispInfo.OsDistribution) > 0 {
params["os_distribution"] = []string{dispInfo.OsDistribution}
}
return params
}
func fetchWaterMark(userInfo *identity_api.UserDetails) string {
info := userInfo.Name
if len(userInfo.Displayname) > 0 {
info += " (" + userInfo.Displayname + ")"
}
info += "<br/>"
if len(userInfo.Mobile) > 0 {
info += userInfo.Mobile
} else if len(userInfo.Email) > 0 {
info += userInfo.Email
} else {
info += userInfo.Id
}
return info
}
func fetchUserInfo(ctx context.Context, s *mcclient.ClientSession) (*identity_api.UserDetails, error) {
usrObj, err := identity.UsersV3.GetById(auth.GetAdminSession(ctx, s.GetRegion()), s.GetUserId(), nil)
if err != nil {
return nil, errors.Wrap(err, "GetById")
}
usr := identity_api.UserDetails{}
err = usrObj.Unmarshal(&usr)
if err != nil {
return nil, errors.Wrap(err, "Unmarshal")
}
return &usr, nil
}
func FetchServerInfo(ctx context.Context, s *mcclient.ClientSession, sid string) (*compute_api.ServerDetails, error) {
guestInfo, err := compute.Servers.Get(s, sid, nil)
if err != nil {
return nil, errors.Wrapf(err, "GetById %s", sid)
}
guestDetails := compute_api.ServerDetails{}
err = guestInfo.Unmarshal(&guestDetails)
if err != nil {
return nil, errors.Wrap(err, "Unmarshal guest info")
}
return &guestDetails, nil
}
+18
View File
@@ -15,12 +15,14 @@
package session
import (
"context"
"fmt"
"net/url"
"os/exec"
"yunion.io/x/cloudmux/pkg/cloudprovider"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/errors"
api "yunion.io/x/onecloud/pkg/apis/webconsole"
"yunion.io/x/onecloud/pkg/mcclient"
@@ -211,3 +213,19 @@ func (info *RemoteConsoleInfo) getApsaraURL() (string, error) {
func (info *RemoteConsoleInfo) GetRecordObject() *recorder.Object {
return nil
}
func (info *RemoteConsoleInfo) GetDisplayInfo(ctx context.Context) (*SDisplayInfo, error) {
userInfo, err := fetchUserInfo(ctx, info.GetClientSession())
if err != nil {
return nil, errors.Wrap(err, "fetchUserInfo")
}
guestDetails, err := FetchServerInfo(ctx, info.GetClientSession(), info.Id)
if err != nil {
return nil, errors.Wrap(err, "FetchServerInfo")
}
dispInfo := SDisplayInfo{}
dispInfo.WaterMark = fetchWaterMark(userInfo)
dispInfo.fetchGuestInfo(guestDetails)
return &dispInfo, nil
}
+15 -1
View File
@@ -22,6 +22,7 @@ import (
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/gotypes"
compute_api "yunion.io/x/onecloud/pkg/apis/compute"
api "yunion.io/x/onecloud/pkg/apis/webconsole"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient"
@@ -36,6 +37,8 @@ type RemoteRDPConsoleInfo struct {
Password string
ConnectionId string
guestDetails *compute_api.ServerDetails
Width int
Height int
Dpi int
@@ -54,7 +57,7 @@ func NewRemoteRDPConsoleInfoByCloud(ctx context.Context, s *mcclient.ClientSessi
info.Port = 3389
}
var err error
info.Host, info.Port, err = resolveServerIPPortById(ctx, s, serverId, info.Host, info.Port)
info.Host, info.Port, info.guestDetails, err = resolveServerIPPortById(ctx, s, serverId, info.Host, info.Port)
if err != nil {
return nil, errors.Wrap(err, "resolveServerIPPortById")
}
@@ -118,3 +121,14 @@ func (info *RemoteRDPConsoleInfo) GetId() string {
func (info *RemoteRDPConsoleInfo) GetRecordObject() *recorder.Object {
return nil
}
func (info *RemoteRDPConsoleInfo) GetDisplayInfo(ctx context.Context) (*SDisplayInfo, error) {
userInfo, err := fetchUserInfo(ctx, info.GetClientSession())
if err != nil {
return nil, errors.Wrap(err, "fetchUserInfo")
}
dispInfo := SDisplayInfo{}
dispInfo.WaterMark = fetchWaterMark(userInfo)
dispInfo.fetchGuestInfo(info.guestDetails)
return &dispInfo, nil
}
+20 -20
View File
@@ -36,24 +36,21 @@ type SSshConnectionInfo struct {
KeepUsername bool `json:"keep_username"`
Password string `json:"password"`
Name string `json:"name"`
GuestDetails *compute_api.ServerDetails
}
func ResolveServerSSHIPPortById(ctx context.Context, s *mcclient.ClientSession, id string, ip string, port int) (string, int, error) {
func ResolveServerSSHIPPortById(ctx context.Context, s *mcclient.ClientSession, id string, ip string, port int) (string, int, *compute_api.ServerDetails, 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)
func resolveServerIPPortById(ctx context.Context, s *mcclient.ClientSession, id string, ip string, port int) (string, int, *compute_api.ServerDetails, error) {
guestDetails, err := FetchServerInfo(ctx, s, id)
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")
return "", 0, nil, errors.Wrap(err, "fetchServerInfo")
}
// list all nic of a server
input := compute_api.GuestnetworkListInput{}
@@ -63,11 +60,11 @@ func resolveServerIPPortById(ctx context.Context, s *mcclient.ClientSession, id
input.ServerFilterListInput.Scope = "max"
result, err := compute.Servernetworks.List(s, jsonutils.Marshal(input))
if err != nil {
return "", 0, errors.Wrap(err, "Servernetworks.List")
return "", 0, nil, errors.Wrap(err, "Servernetworks.List")
}
if result.Total == 0 {
// not nic found!!!
return "", 0, errors.Wrap(httperrors.ErrNotFound, "no nic on server")
return "", 0, nil, errors.Wrap(httperrors.ErrNotFound, "no nic on server")
}
// find nics
@@ -75,20 +72,20 @@ func resolveServerIPPortById(ctx context.Context, s *mcclient.ClientSession, id
if result.Total == 1 {
err := result.Data[0].Unmarshal(&guestNicDetails)
if err != nil {
return "", 0, errors.Wrap(err, "Unmarshal guest network info")
return "", 0, nil, 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)
return "", 0, nil, 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")
return "", 0, nil, 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")
return "", 0, nil, errors.Wrap(err, "Unmarshal guest network info")
}
if ip == guestNicDetails.EipAddr || ip == guestNicDetails.IpAddr || ip == guestNicDetails.Ip6Addr {
find = true
@@ -96,7 +93,7 @@ func resolveServerIPPortById(ctx context.Context, s *mcclient.ClientSession, id
}
}
if !find {
return "", 0, errors.Wrap(httperrors.ErrInputParameter, "ip specified not match with server")
return "", 0, nil, errors.Wrap(httperrors.ErrInputParameter, "ip specified not match with server")
}
}
@@ -107,20 +104,23 @@ func resolveServerIPPortById(ctx context.Context, s *mcclient.ClientSession, id
} else if len(guestNicDetails.IpAddr) > 0 {
ip = guestNicDetails.IpAddr
} else {
return "", 0, errors.Wrap(httperrors.ErrNotSupported, "no valid ipv4 addr")
return "", 0, nil, errors.Wrap(httperrors.ErrNotSupported, "no valid ipv4 addr")
}
}
if ip == guestNicDetails.Ip6Addr {
return "", 0, errors.Wrap(httperrors.ErrNotSupported, "ipv6 not supported")
return "", 0, nil, 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)
ip, port, err = acquireForward(ctx, s, guestDetails.Id, ip, "tcp", port)
if err != nil {
return "", 0, nil, errors.Wrap(err, "acquireForward")
}
}
return ip, port, nil
return ip, port, guestDetails, nil
}
type sForwardInfo struct {
+17 -2
View File
@@ -15,6 +15,7 @@
package session
import (
"context"
"fmt"
"math/rand"
"net/url"
@@ -102,6 +103,7 @@ type ISessionData interface {
command.ICommand
IsNeedLogin() (bool, error)
GetId() string
GetDisplayInfo(ctx context.Context) (*SDisplayInfo, error)
}
type RandomSessionData struct {
@@ -124,6 +126,17 @@ func (s *RandomSessionData) IsNeedLogin() (bool, error) {
return false, nil
}
func (s *RandomSessionData) GetDisplayInfo(ctx context.Context) (*SDisplayInfo, error) {
userInfo, err := fetchUserInfo(ctx, s.GetClientSession())
if err != nil {
return nil, errors.Wrap(err, "fetchUserInfo")
}
dispInfo := SDisplayInfo{}
dispInfo.WaterMark = fetchWaterMark(userInfo)
dispInfo.InstanceName = s.GetCommand().String()
return &dispInfo, nil
}
type SSession struct {
ISessionData
Id string
@@ -133,11 +146,13 @@ type SSession struct {
recorder recorder.Recoder
}
func (s *SSession) GetConnectParams(params url.Values) (string, error) {
func (s *SSession) GetConnectParams(params url.Values, dispInfo *SDisplayInfo) (string, error) {
if params == nil {
params = url.Values(make(map[string][]string))
params = url.Values{}
}
params = dispInfo.populateParams(params)
apiUrl, err := url.Parse(o.Options.ApiServer)
if err != nil {
return "", errors.Errorf("invalid api_server url: %s", o.Options.ApiServer)
+24
View File
@@ -27,6 +27,7 @@ import (
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/util/stringutils"
compute_api "yunion.io/x/onecloud/pkg/apis/compute"
api "yunion.io/x/onecloud/pkg/apis/webconsole"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/webconsole/helper"
@@ -48,6 +49,8 @@ type SSshSession struct {
// 保持原有 Username ,不实用 cloudroot 的同时使用 PrivateKey
KeepUsername bool
Password string
guestDetails *compute_api.ServerDetails
}
func NewSshSession(ctx context.Context, us *mcclient.ClientSession, conn SSshConnectionInfo) *SSshSession {
@@ -60,6 +63,8 @@ func NewSshSession(ctx context.Context, us *mcclient.ClientSession, conn SSshCon
Username: conn.Username,
KeepUsername: conn.KeepUsername,
Password: conn.Password,
guestDetails: conn.GuestDetails,
}
if conn.Port <= 0 {
ret.Port = 22
@@ -132,3 +137,22 @@ func (s *SSshSession) IsNeedLogin() (bool, error) {
func (s *SSshSession) Scan(d byte, send func(msg string)) {
}
func (s *SSshSession) GetDisplayInfo(ctx context.Context) (*SDisplayInfo, error) {
userInfo, err := fetchUserInfo(ctx, s.GetClientSession())
if err != nil {
return nil, errors.Wrap(err, "fetchUserInfo")
}
dispInfo := SDisplayInfo{}
dispInfo.WaterMark = fetchWaterMark(userInfo)
if s.guestDetails != nil {
dispInfo.fetchGuestInfo(s.guestDetails)
} else {
dispInfo.Ips = s.Host
if len(s.name) > 0 {
dispInfo.InstanceName = s.name
}
}
return &dispInfo, nil
}