mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-09-24 16:03:43 +08:00
Merge pull request #10600 from yousong/automated-cherry-pick-of-#10599-upstream-release-3.7
Automated cherry pick of #10599: Feature/yousong sshable
This commit is contained in:
@@ -91,6 +91,7 @@ func init() {
|
||||
cmd.Get("status", new(options.ServerIdOptions))
|
||||
cmd.Get("iso", new(options.ServerIdOptions))
|
||||
cmd.Get("create-params", new(options.ServerIdOptions))
|
||||
cmd.Get("sshable", new(options.ServerIdOptions))
|
||||
cmd.Get("change-owner-candidate-domains", new(options.ServerChangeOwnerCandidateDomainsOptions))
|
||||
|
||||
type ServerTaskShowOptions struct {
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
package cloudproxy
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis"
|
||||
)
|
||||
|
||||
@@ -38,7 +40,7 @@ type ForwardCreateFromServerInput struct {
|
||||
|
||||
Type string
|
||||
BindPortReq int `json:",omitzero"`
|
||||
RemotePort string
|
||||
RemotePort int `json:",omitzero"`
|
||||
|
||||
LastSeenTimeout int `json:",omitzero"`
|
||||
}
|
||||
@@ -49,7 +51,30 @@ type ForwardListInput struct {
|
||||
ProxyAgentId string
|
||||
ProxyEndpointId string
|
||||
|
||||
Type string
|
||||
Type string
|
||||
RemoteAddr string
|
||||
RemotePort *int
|
||||
BindPortReq *int
|
||||
|
||||
Opaque string
|
||||
}
|
||||
|
||||
type ForwardDetails struct {
|
||||
ProxyEndpoint string
|
||||
ProxyEndpointId string
|
||||
ProxyAgent string
|
||||
ProxyAgentId string
|
||||
|
||||
Type string
|
||||
BindPortReq int
|
||||
BindPort int
|
||||
RemoteAddr string
|
||||
RemotePort int
|
||||
|
||||
LastSeen time.Time
|
||||
LastSeenTimeout int
|
||||
|
||||
Opaque string
|
||||
|
||||
BindAddr string
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
// 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 compute
|
||||
|
||||
const (
|
||||
MethodDirect = "direct"
|
||||
MethodEIP = "eip"
|
||||
MethodDNAT = "dnat"
|
||||
MethodProxyForward = "proxy_forward"
|
||||
)
|
||||
|
||||
type GuestSshableMethodData struct {
|
||||
Method string
|
||||
Host string
|
||||
Port int
|
||||
|
||||
Sshable bool
|
||||
Reason string
|
||||
}
|
||||
|
||||
type GuestSshableOutput struct {
|
||||
User string
|
||||
PublicKey string
|
||||
|
||||
MethodTried []GuestSshableMethodData
|
||||
}
|
||||
@@ -26,6 +26,8 @@ import (
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/errors"
|
||||
"yunion.io/x/pkg/util/sets"
|
||||
|
||||
ssh_util "yunion.io/x/onecloud/pkg/util/ssh"
|
||||
)
|
||||
|
||||
type addrMap map[string]interface{}
|
||||
@@ -93,7 +95,7 @@ func (am addrMap) delete(addr string) {
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
cc *ClientConfig
|
||||
cc *ssh_util.ClientConfig
|
||||
c *ssh.Client
|
||||
|
||||
stopc chan sets.Empty
|
||||
@@ -111,7 +113,7 @@ type Client struct {
|
||||
remoteForwards portMap
|
||||
}
|
||||
|
||||
func NewClient(cc *ClientConfig) *Client {
|
||||
func NewClient(cc *ssh_util.ClientConfig) *Client {
|
||||
c := &Client{
|
||||
cc: cc,
|
||||
|
||||
@@ -141,6 +143,9 @@ func (c *Client) Stop(ctx context.Context) {
|
||||
}
|
||||
|
||||
func (c *Client) Start(ctx context.Context) {
|
||||
ctx, cancelFunc := context.WithCancel(ctx)
|
||||
defer cancelFunc()
|
||||
|
||||
pingT := time.NewTimer(17 * time.Second)
|
||||
pingFailCount := 0
|
||||
const pingMaxFail = 3
|
||||
@@ -214,7 +219,7 @@ func (c *Client) Start(ctx context.Context) {
|
||||
}
|
||||
|
||||
func (c *Client) connect(ctx context.Context) (*ssh.Client, error) {
|
||||
sshc, err := c.cc.NewClient(ctx)
|
||||
sshc, err := c.cc.ConnectContext(ctx)
|
||||
return sshc, err
|
||||
}
|
||||
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
// 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 ssh
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
"yunion.io/x/pkg/errors"
|
||||
)
|
||||
|
||||
type ClientConfig struct {
|
||||
User string
|
||||
Host string
|
||||
Port int
|
||||
Key string
|
||||
}
|
||||
|
||||
func (cc *ClientConfig) NewClient(ctx context.Context) (*ssh.Client, error) {
|
||||
signer, err := ssh.ParsePrivateKey([]byte(cc.Key))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "parse ssh key")
|
||||
}
|
||||
sshcc := &ssh.ClientConfig{
|
||||
User: cc.User,
|
||||
Auth: []ssh.AuthMethod{
|
||||
ssh.PublicKeys(signer),
|
||||
},
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
addr := net.JoinHostPort(cc.Host, fmt.Sprintf("%d", cc.Port))
|
||||
d := &net.Dialer{}
|
||||
netconn, err := d.DialContext(ctx, "tcp", addr)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "net dial")
|
||||
}
|
||||
|
||||
sshconn, chans, reqs, err := ssh.NewClientConn(netconn, addr, sshcc)
|
||||
if err != nil {
|
||||
netconn.Close()
|
||||
return nil, errors.Wrap(err, "ssh new client conn")
|
||||
}
|
||||
|
||||
sshc := ssh.NewClient(sshconn, chans, reqs)
|
||||
return sshc, nil
|
||||
}
|
||||
@@ -16,10 +16,12 @@ package ssh
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
ssh_util "yunion.io/x/onecloud/pkg/util/ssh"
|
||||
)
|
||||
|
||||
type epClientSet struct {
|
||||
cc ClientConfig
|
||||
cc ssh_util.ClientConfig
|
||||
clients []*Client
|
||||
|
||||
mark bool
|
||||
@@ -62,12 +64,13 @@ func (cs *ClientSet) ClearAllMark() {
|
||||
}
|
||||
}
|
||||
|
||||
func (cs *ClientSet) ResetIfChanged(ctx context.Context, epKey string, cc ClientConfig) bool {
|
||||
func (cs *ClientSet) ResetIfChanged(ctx context.Context, epKey string, cc ssh_util.ClientConfig) bool {
|
||||
epcs, ok := cs.epClients[epKey]
|
||||
if ok {
|
||||
if epcs.cc != cc {
|
||||
epcs.stop(ctx)
|
||||
delete(cs.epClients, epKey)
|
||||
cs.AddIfNotExist(ctx, epKey, cc)
|
||||
return true
|
||||
}
|
||||
epcs.setMark()
|
||||
@@ -75,7 +78,7 @@ func (cs *ClientSet) ResetIfChanged(ctx context.Context, epKey string, cc Client
|
||||
return false
|
||||
}
|
||||
|
||||
func (cs *ClientSet) AddIfNotExist(ctx context.Context, epKey string, cc ClientConfig) bool {
|
||||
func (cs *ClientSet) AddIfNotExist(ctx context.Context, epKey string, cc ssh_util.ClientConfig) bool {
|
||||
epcs, ok := cs.epClients[epKey]
|
||||
if !ok {
|
||||
epcs := &epClientSet{
|
||||
@@ -170,7 +173,7 @@ func (cs *ClientSet) getClient_(epKey string, typ string, create bool) (*Client,
|
||||
|
||||
clients, ok := cs.epClients[epKey]
|
||||
if !ok || len(clients.clients) == 0 {
|
||||
if !create {
|
||||
if !ok || !create {
|
||||
return nil, false
|
||||
}
|
||||
client = NewClient(&clients.cc)
|
||||
|
||||
@@ -36,6 +36,7 @@ import (
|
||||
"yunion.io/x/onecloud/pkg/mcclient/auth"
|
||||
cloudproxy_modules "yunion.io/x/onecloud/pkg/mcclient/modules/cloudproxy"
|
||||
"yunion.io/x/onecloud/pkg/util/netutils2"
|
||||
ssh_util "yunion.io/x/onecloud/pkg/util/ssh"
|
||||
)
|
||||
|
||||
type Worker struct {
|
||||
@@ -209,11 +210,11 @@ func (w *Worker) run(ctx context.Context, mss *agentmodels.ModelSets) (err error
|
||||
|
||||
w.clientSet.ClearAllMark()
|
||||
for _, pep := range mss.ProxyEndpoints {
|
||||
cc := agentssh.ClientConfig{
|
||||
User: pep.User,
|
||||
Host: pep.Host,
|
||||
Port: pep.Port,
|
||||
Key: pep.PrivateKey,
|
||||
cc := ssh_util.ClientConfig{
|
||||
Username: pep.User,
|
||||
Host: pep.Host,
|
||||
Port: pep.Port,
|
||||
PrivateKey: pep.PrivateKey,
|
||||
}
|
||||
if reset := w.clientSet.ResetIfChanged(ctx, pep.Id, cc); reset {
|
||||
log.Warningf("proxy endpoint %s changed, connections reset", pep.Id)
|
||||
|
||||
@@ -358,6 +358,7 @@ func (man *SForwardManager) ListItemFilter(
|
||||
) (*sqlchemy.SQuery, error) {
|
||||
filters := [][2]string{
|
||||
[2]string{"type", input.Type},
|
||||
[2]string{"remote_addr", input.RemoteAddr},
|
||||
[2]string{"proxy_endpoint_id", input.ProxyEndpointId},
|
||||
[2]string{"proxy_agent_id", input.ProxyAgentId},
|
||||
[2]string{"opaque", input.Opaque},
|
||||
@@ -367,6 +368,18 @@ func (man *SForwardManager) ListItemFilter(
|
||||
q = q.Equals(filter[0], v)
|
||||
}
|
||||
}
|
||||
intFilters := []struct {
|
||||
name string
|
||||
val *int
|
||||
}{
|
||||
{"remote_port", input.RemotePort},
|
||||
{"bind_port_req", input.BindPortReq},
|
||||
}
|
||||
for _, filter := range intFilters {
|
||||
if v := filter.val; v != nil {
|
||||
q = q.Equals(filter.name, *v)
|
||||
}
|
||||
}
|
||||
return q, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ func InitHandlers(app *appsrv.Application) {
|
||||
db.InitAllManagers()
|
||||
|
||||
db.RegisterModelManager(db.OpsLog)
|
||||
db.RegisterModelManager(db.Metadata)
|
||||
db.RegisterModelManager(db.TenantCacheManager)
|
||||
db.RegisterModelManager(db.UserCacheManager)
|
||||
for _, manager := range []db.IModelManager{
|
||||
|
||||
@@ -0,0 +1,299 @@
|
||||
// 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/log"
|
||||
"yunion.io/x/sqlchemy"
|
||||
|
||||
cloudproxy_api "yunion.io/x/onecloud/pkg/apis/cloudproxy"
|
||||
compute_api "yunion.io/x/onecloud/pkg/apis/compute"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/compute/sshkeys"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/auth"
|
||||
cloudproxy_module "yunion.io/x/onecloud/pkg/mcclient/modules/cloudproxy"
|
||||
ssh_util "yunion.io/x/onecloud/pkg/util/ssh"
|
||||
)
|
||||
|
||||
type GuestSshableTryData struct {
|
||||
User string
|
||||
Host string
|
||||
Port int
|
||||
PrivateKey string
|
||||
PublicKey string
|
||||
|
||||
MethodTried []compute_api.GuestSshableMethodData
|
||||
}
|
||||
|
||||
func (tryData *GuestSshableTryData) AddMethodTried(tryMethodData compute_api.GuestSshableMethodData) {
|
||||
tryData.MethodTried = append(tryData.MethodTried, tryMethodData)
|
||||
}
|
||||
|
||||
func (tryData *GuestSshableTryData) outputJSON() jsonutils.JSONObject {
|
||||
out := compute_api.GuestSshableOutput{
|
||||
User: tryData.User,
|
||||
PublicKey: tryData.PublicKey,
|
||||
|
||||
MethodTried: tryData.MethodTried,
|
||||
}
|
||||
outJSON := jsonutils.Marshal(out)
|
||||
return outJSON
|
||||
}
|
||||
|
||||
func (guest *SGuest) AllowGetDetailsSshable(
|
||||
ctx context.Context,
|
||||
userCred mcclient.TokenCredential,
|
||||
query jsonutils.JSONObject,
|
||||
) bool {
|
||||
return db.IsProjectAllowGetSpec(userCred, guest, "sshable")
|
||||
}
|
||||
|
||||
func (guest *SGuest) GetDetailsSshable(
|
||||
ctx context.Context,
|
||||
userCred mcclient.TokenCredential,
|
||||
query jsonutils.JSONObject,
|
||||
) (jsonutils.JSONObject, error) {
|
||||
tryData := &GuestSshableTryData{
|
||||
User: "cloudroot",
|
||||
}
|
||||
|
||||
// - get admin key
|
||||
privateKey, publicKey, err := sshkeys.GetSshAdminKeypair(ctx)
|
||||
if err != nil {
|
||||
return nil, httperrors.NewInternalServerError("fetch ssh private key: %v", err)
|
||||
}
|
||||
tryData.PrivateKey = privateKey
|
||||
tryData.PublicKey = publicKey
|
||||
|
||||
gns, err := guest.GetNetworks("")
|
||||
if err != nil {
|
||||
return nil, httperrors.NewInternalServerError("fetch network interface information: %v", err)
|
||||
}
|
||||
type gnInfo struct {
|
||||
guestNetwork *SGuestnetwork
|
||||
network *SNetwork
|
||||
vpc *SVpc
|
||||
}
|
||||
var gnInfos []gnInfo
|
||||
for i := range gns {
|
||||
gn := &gns[i]
|
||||
network := gn.GetNetwork()
|
||||
if network == nil {
|
||||
continue
|
||||
}
|
||||
vpc := network.GetVpc()
|
||||
if vpc == nil {
|
||||
continue
|
||||
}
|
||||
if vpc.Id == compute_api.DEFAULT_VPC_ID {
|
||||
// - vpc_id == "default"
|
||||
if ok := guest.sshableTryDefaultVPC(ctx, tryData, gn); ok {
|
||||
return tryData.outputJSON(), nil
|
||||
}
|
||||
} else {
|
||||
gnInfos = append(gnInfos, gnInfo{
|
||||
guestNetwork: gn,
|
||||
network: network,
|
||||
vpc: vpc,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// - check eip
|
||||
if eip, err := guest.GetEipOrPublicIp(); err == nil && eip != nil {
|
||||
if ok := guest.sshableTryEip(ctx, tryData, eip); ok {
|
||||
return tryData.outputJSON(), nil
|
||||
}
|
||||
}
|
||||
|
||||
sess := auth.GetSession(ctx, userCred, "", "")
|
||||
// - check existing proxy forward
|
||||
proxyforwardTried := false
|
||||
for i := range gnInfos {
|
||||
gnInfo := &gnInfos[i]
|
||||
gn := gnInfo.guestNetwork
|
||||
port := 22
|
||||
input := &cloudproxy_api.ForwardListInput{
|
||||
Type: cloudproxy_api.FORWARD_TYPE_LOCAL,
|
||||
RemoteAddr: gn.IpAddr,
|
||||
RemotePort: &port,
|
||||
Opaque: guest.Id,
|
||||
}
|
||||
params := jsonutils.Marshal(input).(*jsonutils.JSONDict)
|
||||
params.Set("details", jsonutils.JSONTrue)
|
||||
res, err := cloudproxy_module.Forwards.List(sess, params)
|
||||
if err != nil {
|
||||
log.Warningf("list cloudproxy forwards: %v", err)
|
||||
continue
|
||||
}
|
||||
proxyforwardTried = len(res.Data) != 0
|
||||
for _, data := range res.Data {
|
||||
var fwd cloudproxy_api.ForwardDetails
|
||||
if err := data.Unmarshal(&fwd); err != nil {
|
||||
log.Warningf("unmarshal cloudproxy forward list data: %v", err)
|
||||
continue
|
||||
}
|
||||
if ok := guest.sshableTryForward(ctx, tryData, &fwd); ok {
|
||||
return tryData.outputJSON(), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if !proxyforwardTried {
|
||||
// - create and use new proxy forward
|
||||
fwdCreateInput := cloudproxy_api.ForwardCreateFromServerInput{
|
||||
ServerId: guest.Id,
|
||||
Type: cloudproxy_api.FORWARD_TYPE_LOCAL,
|
||||
RemotePort: 22,
|
||||
}
|
||||
fwdCreateParams := jsonutils.Marshal(fwdCreateInput)
|
||||
res, err := cloudproxy_module.Forwards.PerformClassAction(sess, "create-from-server", fwdCreateParams)
|
||||
if err == nil {
|
||||
var fwd cloudproxy_api.ForwardDetails
|
||||
if err := res.Unmarshal(&fwd); err == nil {
|
||||
if ok := guest.sshableTryForward(ctx, tryData, &fwd); ok {
|
||||
return tryData.outputJSON(), nil
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tryData.AddMethodTried(compute_api.GuestSshableMethodData{
|
||||
Method: compute_api.MethodProxyForward,
|
||||
Reason: err.Error(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// - existing dnat rule
|
||||
for i := range gnInfos {
|
||||
gnInfo := &gnInfos[i]
|
||||
gn := gnInfo.guestNetwork
|
||||
vpc := gnInfo.vpc
|
||||
|
||||
natgwq := NatGatewayManager.Query().SubQuery()
|
||||
q := NatDEntryManager.Query().
|
||||
Equals("internal_ip", gn.IpAddr).
|
||||
Equals("internal_port", 22).
|
||||
Equals("ip_protocol", "tcp")
|
||||
q = q.Join(natgwq, sqlchemy.AND(
|
||||
sqlchemy.In(natgwq.Field("vpc_id"), vpc.Id),
|
||||
sqlchemy.Equals(natgwq.Field("id"), q.Field("natgateway_id")),
|
||||
))
|
||||
|
||||
var dnats []SNatDEntry
|
||||
if err := db.FetchModelObjects(NatDEntryManager, q, &dnats); err != nil {
|
||||
log.Warningf("query dnat to ssh service: %v", err)
|
||||
continue
|
||||
}
|
||||
for j := range dnats {
|
||||
dnat := &dnats[j]
|
||||
if ok := guest.sshableTryDnat(ctx, tryData, dnat); ok {
|
||||
return tryData.outputJSON(), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tryData.outputJSON(), nil
|
||||
}
|
||||
|
||||
func (guest *SGuest) sshableTryDnat(
|
||||
ctx context.Context,
|
||||
tryData *GuestSshableTryData,
|
||||
dnat *SNatDEntry,
|
||||
) bool {
|
||||
methodData := compute_api.GuestSshableMethodData{
|
||||
Method: compute_api.MethodDNAT,
|
||||
Host: dnat.ExternalIP,
|
||||
Port: dnat.ExternalPort,
|
||||
}
|
||||
return guest.sshableTry(
|
||||
ctx, tryData, methodData,
|
||||
)
|
||||
}
|
||||
|
||||
func (guest *SGuest) sshableTryForward(
|
||||
ctx context.Context,
|
||||
tryData *GuestSshableTryData,
|
||||
fwd *cloudproxy_api.ForwardDetails,
|
||||
) bool {
|
||||
if fwd.BindAddr != "" && fwd.BindPort > 0 {
|
||||
methodData := compute_api.GuestSshableMethodData{
|
||||
Method: compute_api.MethodProxyForward,
|
||||
Host: fwd.BindAddr,
|
||||
Port: fwd.BindPort,
|
||||
}
|
||||
return guest.sshableTry(
|
||||
ctx, tryData, methodData,
|
||||
)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (guest *SGuest) sshableTryEip(
|
||||
ctx context.Context,
|
||||
tryData *GuestSshableTryData,
|
||||
eip *SElasticip,
|
||||
) bool {
|
||||
methodData := compute_api.GuestSshableMethodData{
|
||||
Method: compute_api.MethodEIP,
|
||||
Host: eip.IpAddr,
|
||||
Port: 22,
|
||||
}
|
||||
return guest.sshableTry(
|
||||
ctx, tryData, methodData,
|
||||
)
|
||||
}
|
||||
|
||||
func (guest *SGuest) sshableTryDefaultVPC(
|
||||
ctx context.Context,
|
||||
tryData *GuestSshableTryData,
|
||||
gn *SGuestnetwork,
|
||||
) bool {
|
||||
methodData := compute_api.GuestSshableMethodData{
|
||||
Method: compute_api.MethodDirect,
|
||||
Host: gn.IpAddr,
|
||||
Port: 22,
|
||||
}
|
||||
return guest.sshableTry(
|
||||
ctx, tryData, methodData,
|
||||
)
|
||||
}
|
||||
|
||||
func (guest *SGuest) sshableTry(
|
||||
ctx context.Context,
|
||||
tryData *GuestSshableTryData,
|
||||
methodData compute_api.GuestSshableMethodData,
|
||||
) bool {
|
||||
ctx, _ = context.WithTimeout(ctx, 7*time.Second)
|
||||
conf := ssh_util.ClientConfig{
|
||||
Username: tryData.User,
|
||||
Host: methodData.Host,
|
||||
Port: methodData.Port,
|
||||
PrivateKey: tryData.PrivateKey,
|
||||
}
|
||||
ok := false
|
||||
if client, err := conf.ConnectContext(ctx); err == nil {
|
||||
defer client.Close()
|
||||
methodData.Sshable = true
|
||||
} else {
|
||||
methodData.Reason = err.Error()
|
||||
}
|
||||
tryData.AddMethodTried(methodData)
|
||||
return ok
|
||||
}
|
||||
+32
-1
@@ -16,8 +16,10 @@ package ssh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -29,6 +31,12 @@ import (
|
||||
"yunion.io/x/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
ErrBadConfig = errors.Error("bad config")
|
||||
ErrNetwork = errors.Error("network error")
|
||||
ErrProtocol = errors.Error("ssh protocol error")
|
||||
)
|
||||
|
||||
type ClientConfig struct {
|
||||
Username string
|
||||
Password string
|
||||
@@ -54,7 +62,7 @@ func (conf ClientConfig) ToSshConfig() (*ssh.ClientConfig, error) {
|
||||
if conf.PrivateKey != "" {
|
||||
signer, err := parsePrivateKey(conf.PrivateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, errors.Wrapf(ErrBadConfig, "parse private key: %v", err)
|
||||
}
|
||||
auths = append(auths, ssh.PublicKeys(signer))
|
||||
}
|
||||
@@ -75,6 +83,29 @@ func (conf ClientConfig) Connect() (*ssh.Client, error) {
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (conf ClientConfig) ConnectContext(ctx context.Context) (*ssh.Client, error) {
|
||||
cliConfig, err := conf.ToSshConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addr := fmt.Sprintf("%s:%d", conf.Host, conf.Port)
|
||||
d := &net.Dialer{}
|
||||
netconn, err := d.DialContext(ctx, "tcp", addr)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(ErrNetwork, "tcp dial: %v", err)
|
||||
}
|
||||
|
||||
sshconn, chans, reqs, err := ssh.NewClientConn(netconn, addr, cliConfig)
|
||||
if err != nil {
|
||||
netconn.Close()
|
||||
return nil, errors.Wrap(ErrProtocol, err.Error())
|
||||
}
|
||||
|
||||
sshc := ssh.NewClient(sshconn, chans, reqs)
|
||||
return sshc, nil
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
config ClientConfig
|
||||
client *ssh.Client
|
||||
|
||||
Reference in New Issue
Block a user