mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-09-24 16:03:43 +08:00
Merge pull request #17682 from zexi/automated-cherry-pick-of-#17681-upstream-master
Automated cherry pick of #17681: fix(webconsole,climc): cloud shell within docker compose environment
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
BASHRC=/etc/profile.d/climc.sh
|
||||
|
||||
grep -q rcadmin $BASHRC || echo 'source /etc/yunion/rcadmin' >> $BASHRC
|
||||
|
||||
source $BASHRC
|
||||
|
||||
climc sshkeypair-inject --admin --target-dir /root/
|
||||
|
||||
mkdir -p /etc/dropbear
|
||||
|
||||
mkdir -p /var/run/dropbear
|
||||
|
||||
test -f /etc/dropbear/dropbear_rsa_host_key || dropbearkey -t rsa -f /etc/dropbear/dropbear_rsa_host_key
|
||||
|
||||
dropbear -RFEjk -G root -p 22
|
||||
@@ -1,3 +1,9 @@
|
||||
export PATH=/opt/yunion/bin:$PATH
|
||||
|
||||
test -f /etc/yunion/rcadmin && source /etc/yunion/rcadmin
|
||||
|
||||
source <(climc --completion bash)
|
||||
|
||||
echo "Welcome to Cloud Shell :-) You may execute climc and other command tools in this shell."
|
||||
echo "Please exec 'climc' to get started"
|
||||
echo ""
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
FROM registry.cn-beijing.aliyuncs.com/yunionio/climc-base:20210901
|
||||
FROM registry.cn-beijing.aliyuncs.com/yunionio/climc-base:20230731.5
|
||||
|
||||
ADD ./build/climc/root/opt /opt
|
||||
|
||||
RUN cat /opt/yunion/scripts/motd/climc.sh >> /root/.bashrc
|
||||
RUN cp /opt/yunion/scripts/motd/climc.sh /etc/profile.d/climc.sh && ln -sf /etc/profile.d/climc.sh /root/.bashrc
|
||||
|
||||
ADD ./_output/alpine-build/bin/climc ./_output/alpine-build/bin/*cli /opt/yunion/bin/
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
FROM registry.cn-beijing.aliyuncs.com/yunionio/onecloud-base:v0.3.5
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
ARG BUILDPLATFORM
|
||||
ARG TARGETARCH
|
||||
|
||||
MAINTAINER "Zexi Li <lizexi@yunionyun.com>"
|
||||
|
||||
ENV TZ Asia/Shanghai
|
||||
@@ -8,11 +12,13 @@ RUN mkdir -p /opt/yunion/bin
|
||||
|
||||
RUN echo http://dl-cdn.alpinelinux.org/alpine/edge/testing >>/etc/apk/repositories
|
||||
|
||||
RUN apk add --no-cache bash bash-completion tzdata ca-certificates vim ipmitool kubectl ceph-common && \
|
||||
RUN apk add --no-cache bash bash-completion tzdata ca-certificates vim ipmitool ceph-common dropbear shadow && \
|
||||
rm -rf /var/cache/apk/*
|
||||
|
||||
RUN usermod --shell /bin/bash root
|
||||
|
||||
RUN curl https://iso.yunion.cn/binaries/kubernetes-release/release/v1.22.9/bin/linux/${TARGETARCH}/kubectl -o /usr/bin/kubectl && chmod a+x /usr/bin/kubectl
|
||||
|
||||
RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
|
||||
|
||||
ENV PATH="/opt/yunion/bin:${PATH}"
|
||||
|
||||
RUN mkdir -p /opt/yunion/bin
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
FROM registry.cn-beijing.aliyuncs.com/yunionio/webconsole-base:v0.2-1
|
||||
FROM registry.cn-beijing.aliyuncs.com/yunionio/webconsole-base:20230731.2
|
||||
|
||||
ADD ./_output/alpine-build/bin/webconsole /opt/yunion/bin/webconsole
|
||||
|
||||
@@ -20,13 +20,13 @@ ANSIBLESERVER_BASE = v1.1.1
|
||||
ansibleserver-base:
|
||||
$(DOCKER_BUILDX)/ansibleserver-base:$(ANSIBLESERVER_BASE) -f ./Dockerfile.ansibleserver-base .
|
||||
|
||||
CLIMC_BASE_VERSION = 20210901
|
||||
CLIMC_BASE_VERSION = 20230731.5
|
||||
|
||||
climc-base:
|
||||
docker buildx build --platform linux/arm64,linux/amd64 --push \
|
||||
-t registry.cn-beijing.aliyuncs.com/yunionio/climc-base:$(CLIMC_BASE_VERSION) -f ./Dockerfile.climc-base .
|
||||
|
||||
WEBCONSOLE_BASE_VERSION = v0.2-1
|
||||
WEBCONSOLE_BASE_VERSION = 20230731.2
|
||||
webconsole-base:
|
||||
$(DOCKER_BUILDX)/webconsole-base:$(WEBCONSOLE_BASE_VERSION) -f ./Dockerfile.webconsole-base .
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
BASHRC=/root/.bashrc
|
||||
|
||||
source $BASHRC
|
||||
|
||||
climc sshkeypair-inject --admin
|
||||
|
||||
mkdir -p /etc/dropbear
|
||||
|
||||
mkdir -p /var/run/dropbear
|
||||
|
||||
dropbearkey -t rsa -f /etc/dropbear/dropbear_rsa_host_key
|
||||
|
||||
dropbear -RFEjk -G root -p 22
|
||||
@@ -16,11 +16,17 @@ package compute
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/pkg/errors"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/hostman/guestfs/fsdriver"
|
||||
deployapi "yunion.io/x/onecloud/pkg/hostman/hostdeployer/apis"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
modules "yunion.io/x/onecloud/pkg/mcclient/modules/compute"
|
||||
"yunion.io/x/onecloud/pkg/util/procutils"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -28,7 +34,8 @@ func init() {
|
||||
Project string `help:"get keypair for specific project"`
|
||||
Admin bool `help:"get admin keypair, sysadmin ONLY option"`
|
||||
}
|
||||
R(&SshkeypairQueryOptions{}, "sshkeypair-show", "Get ssh keypairs", func(s *mcclient.ClientSession, args *SshkeypairQueryOptions) error {
|
||||
|
||||
getSshKeypair := func(s *mcclient.ClientSession, args *SshkeypairQueryOptions) (string, string, error) {
|
||||
query := jsonutils.NewDict()
|
||||
if args.Admin {
|
||||
query.Add(jsonutils.JSONTrue, "admin")
|
||||
@@ -37,22 +44,73 @@ func init() {
|
||||
if len(args.Project) == 0 {
|
||||
listResult, err := modules.Sshkeypairs.List(s, query)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", "", err
|
||||
}
|
||||
keys = listResult.Data[0]
|
||||
} else {
|
||||
result, err := modules.Sshkeypairs.GetById(s, args.Project, query)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", "", err
|
||||
}
|
||||
keys = result
|
||||
}
|
||||
privKey, _ := keys.GetString("private_key")
|
||||
pubKey, _ := keys.GetString("public_key")
|
||||
return privKey, pubKey, nil
|
||||
}
|
||||
|
||||
R(&SshkeypairQueryOptions{}, "sshkeypair-show", "Get ssh keypairs", func(s *mcclient.ClientSession, args *SshkeypairQueryOptions) error {
|
||||
privKey, pubKey, err := getSshKeypair(s, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Print(privKey)
|
||||
fmt.Print(pubKey)
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
type SshkeypairInjectOptions struct {
|
||||
SshkeypairQueryOptions
|
||||
TargetDir string `help:"Target directory to save cloud ssh keypair"`
|
||||
}
|
||||
R(&SshkeypairInjectOptions{}, "sshkeypair-inject", "Inject ssh keypairs to local path", func(s *mcclient.ClientSession, args *SshkeypairInjectOptions) error {
|
||||
_, pubKey, err := getSshKeypair(s, &args.SshkeypairQueryOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
targetDir := args.TargetDir
|
||||
if targetDir == "" {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "get current user's home dir")
|
||||
}
|
||||
targetDir = homeDir
|
||||
}
|
||||
sshDir := path.Join(targetDir, ".ssh")
|
||||
// MkdirAll anyways
|
||||
os.MkdirAll(sshDir, 0700)
|
||||
authFile := path.Join(sshDir, "authorized_keys")
|
||||
var oldKeys string
|
||||
|
||||
if procutils.NewCommand("test", "-f", authFile).Run() == nil {
|
||||
output, err := procutils.NewCommand("cat", authFile).Output()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "cat: %s", output)
|
||||
}
|
||||
oldKeys = string(output)
|
||||
}
|
||||
pubKeys := &deployapi.SSHKeys{AdminPublicKey: pubKey}
|
||||
newKeys := fsdriver.MergeAuthorizedKeys(oldKeys, pubKeys)
|
||||
if output, err := procutils.NewCommand(
|
||||
"sh", "-c", fmt.Sprintf("echo '%s' > %s", newKeys, authFile)).Output(); err != nil {
|
||||
return errors.Wrapf(err, "write public keys: %s", output)
|
||||
}
|
||||
if output, err := procutils.NewCommand(
|
||||
"chmod", "0644", authFile).Output(); err != nil {
|
||||
return errors.Wrapf(err, "chmod failed %s", output)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -81,7 +81,12 @@ func (m WebConsoleManager) DoCloudShell(s *mcclient.ClientSession, _ jsonutils.J
|
||||
return nil, errors.Wrap(err, "KubeClusters")
|
||||
}
|
||||
if len(clusters.Data) == 0 {
|
||||
return nil, httperrors.NewNotFoundError("cluster system-default not found")
|
||||
// maybe running in docker compose environment, so try to use ssh way
|
||||
if data, err := m.DoClimcSshConnect(s, "climc", 22); err != nil {
|
||||
return nil, httperrors.NewNotFoundError(errors.Wrap(err, "cluster system-default not found, try to use ssh way").Error())
|
||||
} else {
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
clusterId, _ := clusters.Data[0].GetString("id")
|
||||
if len(clusterId) == 0 {
|
||||
@@ -147,3 +152,16 @@ func (m WebConsoleManager) DoSshConnect(s *mcclient.ClientSession, id string, pa
|
||||
func (m WebConsoleManager) DoServerConnect(s *mcclient.ClientSession, id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
||||
return m.DoConnect(s, "server", id, "", params)
|
||||
}
|
||||
|
||||
func (m WebConsoleManager) DoClimcSshConnect(s *mcclient.ClientSession, ip string, port int) (jsonutils.JSONObject, error) {
|
||||
data := jsonutils.Marshal(map[string]interface{}{
|
||||
"username": "root",
|
||||
"keep_username": true,
|
||||
"ip_addr": ip,
|
||||
"port": port,
|
||||
"name": "climc",
|
||||
})
|
||||
body := jsonutils.NewDict()
|
||||
body.Set("webconsole", data)
|
||||
return m.DoConnect(s, "climc", "shell", "", body)
|
||||
}
|
||||
|
||||
@@ -71,10 +71,11 @@ func (opt *WebConsoleBaremetalOptions) Params() (*jsonutils.JSONDict, error) {
|
||||
|
||||
type WebConsoleSshOptions struct {
|
||||
WebConsoleOptions
|
||||
IP string `help:"IP to connect" json:"-"`
|
||||
Port int `help:"Remote server port"`
|
||||
Username string `help:"Remote server username"`
|
||||
Password string `help:"Remote server password"`
|
||||
IP string `help:"IP to connect" json:"-"`
|
||||
Port int `help:"Remote server port"`
|
||||
Username string `help:"Remote server username"`
|
||||
KeepUsername bool `help:"Keep remove username`
|
||||
Password string `help:"Remote server password"`
|
||||
}
|
||||
|
||||
func (opt *WebConsoleSshOptions) Params() (*jsonutils.JSONDict, error) {
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
// 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 command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"yunion.io/x/pkg/errors"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/webconsole/helper"
|
||||
)
|
||||
|
||||
type ClimcSshInfo struct {
|
||||
IpAddr string `json:"ip_addr"`
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
type ClimcSshCommand struct {
|
||||
*BaseCommand
|
||||
Info *ClimcSshInfo
|
||||
s *mcclient.ClientSession
|
||||
keyFile string
|
||||
buffer []byte
|
||||
}
|
||||
|
||||
func NewClimcSshCommand(info *ClimcSshInfo, s *mcclient.ClientSession) (*ClimcSshCommand, error) {
|
||||
if info.IpAddr == "" {
|
||||
return nil, fmt.Errorf("Empty host ip address")
|
||||
}
|
||||
if info.Username == "" {
|
||||
return nil, fmt.Errorf("Empty username")
|
||||
}
|
||||
privateKey, err := helper.GetValidPrivateKey(info.IpAddr, 22, info.Username, "")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "get cloud admin private key")
|
||||
}
|
||||
file, err := ioutil.TempFile("", fmt.Sprintf("id_rsa.%s.", info.IpAddr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
filename := file.Name()
|
||||
{
|
||||
err = os.Chmod(filename, 0600)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = file.Write([]byte(privateKey))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
name := "bash"
|
||||
args := []string{
|
||||
"-c",
|
||||
fmt.Sprintf("ssh -o StrictHostKeyChecking=no -i %s %s@%s", filename, info.Username, info.IpAddr),
|
||||
}
|
||||
bCmd := NewBaseCommand(s, name, args...)
|
||||
cmd := &ClimcSshCommand{
|
||||
BaseCommand: bCmd,
|
||||
Info: info,
|
||||
s: s,
|
||||
keyFile: filename,
|
||||
buffer: []byte{},
|
||||
}
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
func (c ClimcSshCommand) GetCommand() *exec.Cmd {
|
||||
cmd := c.BaseCommand.GetCommand()
|
||||
cmd.Env = append(cmd.Env, "TERM=xterm-256color")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c ClimcSshCommand) GetProtocol() string {
|
||||
return PROTOCOL_TTY
|
||||
}
|
||||
|
||||
func (c ClimcSshCommand) Cleanup() error {
|
||||
if len(c.keyFile) > 0 {
|
||||
os.Remove(c.keyFile)
|
||||
c.keyFile = ""
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ClimcSshCommand) Scan(d byte, send func(msg string)) {
|
||||
switch d {
|
||||
case '\r': // 换行
|
||||
send("\r\n")
|
||||
c.buffer = []byte{}
|
||||
case '\u007f': // 退格
|
||||
if len(c.buffer) > 0 {
|
||||
c.buffer = c.buffer[:len(c.buffer)-1]
|
||||
send("\b \b")
|
||||
}
|
||||
default:
|
||||
c.buffer = append(c.buffer, d)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// 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 command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
)
|
||||
|
||||
type SocatInfo struct {
|
||||
IP string `json:"ip"`
|
||||
Port int `json:"port"`
|
||||
}
|
||||
|
||||
type SocatCmd struct {
|
||||
*BaseCommand
|
||||
Info *SocatInfo
|
||||
s *mcclient.ClientSession
|
||||
}
|
||||
|
||||
func NewSocatCommand(info *SocatInfo, s *mcclient.ClientSession) (*SocatCmd, error) {
|
||||
if info.IP == "" {
|
||||
return nil, fmt.Errorf("Empty remote ip address")
|
||||
}
|
||||
if info.Port <= 0 {
|
||||
return nil, fmt.Errorf("Invalid port %d", info.Port)
|
||||
}
|
||||
|
||||
name := "bash"
|
||||
args := []string{
|
||||
"-c",
|
||||
fmt.Sprintf("socat FILE:`tty`,raw,echo=0 TCP:%s:%d", info.IP, info.Port),
|
||||
}
|
||||
cmd := NewBaseCommand(s, name, args...)
|
||||
scmd := &SocatCmd{
|
||||
BaseCommand: cmd,
|
||||
Info: info,
|
||||
}
|
||||
|
||||
return scmd, nil
|
||||
}
|
||||
|
||||
func (c *SocatCmd) GetCommand() *exec.Cmd {
|
||||
return c.BaseCommand.GetCommand()
|
||||
}
|
||||
|
||||
func (c *SocatCmd) GetProtocol() string {
|
||||
return PROTOCOL_TTY
|
||||
}
|
||||
@@ -51,6 +51,7 @@ const (
|
||||
|
||||
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))
|
||||
app.AddHandler("POST", ApiPathPrefix+"baremetal/<id>", auth.Authenticate(handleBaremetalShell))
|
||||
app.AddHandler("POST", ApiPathPrefix+"ssh/<ip>", auth.Authenticate(handleSshShell))
|
||||
@@ -170,9 +171,10 @@ func handleSshShell(ctx context.Context, w http.ResponseWriter, r *http.Request)
|
||||
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)
|
||||
s := session.NewSshSession(ctx, env.ClientSessin, name, ip, port, username, password, keepusername)
|
||||
handleSshSession(ctx, s, w)
|
||||
}
|
||||
|
||||
@@ -206,6 +208,26 @@ func handleBaremetalShell(ctx context.Context, w http.ResponseWriter, r *http.Re
|
||||
handleCommandSession(ctx, cmd, w)
|
||||
}
|
||||
|
||||
func handleClimcShell(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
env, err := fetchCloudEnv(ctx, w, r)
|
||||
if err != nil {
|
||||
httperrors.GeneralServerError(ctx, w, err)
|
||||
return
|
||||
}
|
||||
info := command.ClimcSshInfo{}
|
||||
err = env.Body.Unmarshal(&info)
|
||||
if err != nil {
|
||||
httperrors.GeneralServerError(ctx, w, err)
|
||||
return
|
||||
}
|
||||
cmd, err := command.NewClimcSshCommand(&info, env.ClientSessin)
|
||||
if err != nil {
|
||||
httperrors.GeneralServerError(ctx, w, err)
|
||||
return
|
||||
}
|
||||
handleCommandSession(ctx, cmd, w)
|
||||
}
|
||||
|
||||
func handleServerRemoteConsole(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
env, err := fetchCloudEnv(ctx, w, r)
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
package helper // import "yunion.io/x/onecloud/pkg/webconsole/helper"
|
||||
@@ -0,0 +1,95 @@
|
||||
// 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 helper
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/pkg/errors"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/auth"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modules/compute"
|
||||
o "yunion.io/x/onecloud/pkg/webconsole/options"
|
||||
)
|
||||
|
||||
func GetValidPrivateKey(host string, port int64, username string, projectId string) (string, error) {
|
||||
errs := []error{}
|
||||
ctx := context.Background()
|
||||
admin := auth.GetAdminSession(ctx, o.Options.Region)
|
||||
for _, gf := range []func() (jsonutils.JSONObject, error){
|
||||
func() (jsonutils.JSONObject, error) {
|
||||
if projectId == "" {
|
||||
return nil, errors.Error("project_id is empty")
|
||||
}
|
||||
key, err := compute.Sshkeypairs.GetById(admin, projectId, jsonutils.Marshal(map[string]bool{"admin": true}))
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Sshkeypairs.GetById(%s)", projectId)
|
||||
}
|
||||
return key, nil
|
||||
},
|
||||
func() (jsonutils.JSONObject, error) {
|
||||
query := jsonutils.NewDict()
|
||||
query.Set("admin", jsonutils.JSONTrue)
|
||||
ret, err := compute.Sshkeypairs.List(admin, query)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "modules.Sshkeypairs.List")
|
||||
}
|
||||
if len(ret.Data) == 0 {
|
||||
return nil, errors.Wrap(httperrors.ErrNotFound, "Not found admin sshkey")
|
||||
}
|
||||
keys := ret.Data[0]
|
||||
return keys, nil
|
||||
},
|
||||
} {
|
||||
key, err := gf()
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
privKey, err := key.GetString("private_key")
|
||||
if err != nil {
|
||||
errs = append(errs, errors.Wrapf(err, "get private_key"))
|
||||
continue
|
||||
}
|
||||
signer, err := ssh.ParsePrivateKey([]byte(privKey))
|
||||
if err != nil {
|
||||
errs = append(errs, errors.Wrapf(err, "ParsePrivateKey"))
|
||||
continue
|
||||
}
|
||||
config := &ssh.ClientConfig{
|
||||
Timeout: time.Second,
|
||||
User: username,
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
Auth: []ssh.AuthMethod{
|
||||
ssh.PublicKeys(signer),
|
||||
},
|
||||
}
|
||||
addr := fmt.Sprintf("%s:%d", host, port)
|
||||
client, err := ssh.Dial("tcp", addr, config)
|
||||
if err != nil {
|
||||
errs = append(errs, errors.Wrapf(err, "dial %s", addr))
|
||||
continue
|
||||
}
|
||||
defer client.Close()
|
||||
return privKey, nil
|
||||
}
|
||||
return "", errors.NewAggregate(errs)
|
||||
}
|
||||
@@ -133,7 +133,7 @@ type SSession struct {
|
||||
recorder recorder.Recoder
|
||||
}
|
||||
|
||||
func (s SSession) GetConnectParams(params url.Values) (string, error) {
|
||||
func (s *SSession) GetConnectParams(params url.Values) (string, error) {
|
||||
if params == nil {
|
||||
params = url.Values(make(map[string][]string))
|
||||
}
|
||||
|
||||
@@ -29,8 +29,7 @@ import (
|
||||
|
||||
api "yunion.io/x/onecloud/pkg/apis/webconsole"
|
||||
"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/webconsole/helper"
|
||||
o "yunion.io/x/onecloud/pkg/webconsole/options"
|
||||
"yunion.io/x/onecloud/pkg/webconsole/recorder"
|
||||
)
|
||||
@@ -46,18 +45,22 @@ type SSshSession struct {
|
||||
Port int64
|
||||
PrivateKey string
|
||||
Username string
|
||||
Password string
|
||||
// 保持原有 Username ,不实用 cloudroot 的同时使用 PrivateKey
|
||||
KeepUsername bool
|
||||
Password string
|
||||
}
|
||||
|
||||
func NewSshSession(ctx context.Context, us *mcclient.ClientSession, name, ip string, port int64, username, password string) *SSshSession {
|
||||
func NewSshSession(ctx context.Context, us *mcclient.ClientSession,
|
||||
name, ip string, port int64, username, password string, KeepUsername bool) *SSshSession {
|
||||
ret := &SSshSession{
|
||||
us: us,
|
||||
id: stringutils.UUID4(),
|
||||
Port: port,
|
||||
Host: ip,
|
||||
name: name,
|
||||
Username: username,
|
||||
Password: password,
|
||||
us: us,
|
||||
id: stringutils.UUID4(),
|
||||
Port: port,
|
||||
Host: ip,
|
||||
name: name,
|
||||
Username: username,
|
||||
KeepUsername: KeepUsername,
|
||||
Password: password,
|
||||
}
|
||||
if port <= 0 {
|
||||
ret.Port = 22
|
||||
@@ -113,41 +116,17 @@ func (s *SSshSession) IsNeedLogin() (bool, error) {
|
||||
if !o.Options.EnableAutoLogin {
|
||||
return true, nil
|
||||
}
|
||||
privateKey, err := func() (string, error) {
|
||||
ctx := context.Background()
|
||||
admin := auth.GetAdminSession(ctx, o.Options.Region)
|
||||
key, err := compute.Sshkeypairs.GetById(admin, s.us.GetProjectId(), jsonutils.Marshal(map[string]bool{"admin": true}))
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "Sshkeypairs.GetById(%s)", s.us.GetProjectId())
|
||||
if !s.KeepUsername {
|
||||
s.Username = "cloudroot"
|
||||
} else {
|
||||
if s.Username == "" {
|
||||
return true, errors.Error("username is empty")
|
||||
}
|
||||
privKey, err := key.GetString("private_key")
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "get private_key")
|
||||
}
|
||||
signer, err := ssh.ParsePrivateKey([]byte(privKey))
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "ParsePrivateKey")
|
||||
}
|
||||
config := &ssh.ClientConfig{
|
||||
Timeout: time.Second,
|
||||
User: "cloudroot",
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
Auth: []ssh.AuthMethod{
|
||||
ssh.PublicKeys(signer),
|
||||
},
|
||||
}
|
||||
addr := fmt.Sprintf("%s:%d", s.Host, s.Port)
|
||||
client, err := ssh.Dial("tcp", addr, config)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "dial %s", addr)
|
||||
}
|
||||
defer client.Close()
|
||||
return privKey, nil
|
||||
}()
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
s.Username = "cloudroot"
|
||||
privateKey, err := helper.GetValidPrivateKey(s.Host, s.Port, s.Username, s.us.GetProjectId())
|
||||
if err != nil {
|
||||
return true, errors.Wrap(err, "try to use cloud admin private_key for ssh login")
|
||||
}
|
||||
s.PrivateKey = privateKey
|
||||
return false, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user