From a6dc2f471f537d9239f96d35cd178b9861403a75 Mon Sep 17 00:00:00 2001 From: Jian Qiu Date: Sun, 27 Sep 2026 20:59:49 +0800 Subject: [PATCH] fix: upgrade ssh keypair to ed25519 (#25738) Co-authored-by: Qiu Jian --- cmd/climc/shell/compute/hosts.go | 27 ++++-- cmd/climc/shell/compute/servers.go | 45 +++++----- cmd/climc/shell/compute/sshkeypairs.go | 77 ++++++++-------- .../models/ansibleplaybook_instance.go | 10 +-- pkg/ansibleserver/models/ansibleplaybooks.go | 8 +- .../models/ansibleplaybooks_v2.go | 10 +-- .../models/ansibleplaybooks_validator.go | 6 +- pkg/baremetal/manager.go | 16 ++-- pkg/cloudproxy/models/proxy_endpoints.go | 56 ++++++++---- .../proxy_endpoints_remote_check_make.go | 2 +- pkg/cloudproxy/models/server_util.go | 8 +- pkg/compute/models/guest_sshable.go | 53 ++++++----- pkg/compute/models/guests.go | 4 +- pkg/compute/models/kube_node_pools.go | 3 +- pkg/compute/regiondrivers/managedvirtual.go | 6 +- pkg/compute/sshkeys/handler.go | 23 ++++- pkg/compute/sshkeys/sshkeypairs.go | 53 ++++++++--- pkg/hostman/guestfs/fsdriver/base.go | 10 +-- pkg/mcclient/models/sshkeypairs.go | 4 +- .../modules/compute/mod_sshkeypairs.go | 88 ++++++++++++++----- pkg/util/ansible/playbook.go | 77 ++++++++-------- pkg/util/ansiblev2/offline_session.go | 33 ++++++- pkg/util/ansiblev2/playbook_session.go | 6 +- pkg/util/ansiblev2/session.go | 33 ++++++- pkg/webconsole/command/climc_ssh_command.go | 2 +- pkg/webconsole/helper/helper.go | 45 ++-------- pkg/webconsole/session/ssh_session.go | 2 +- 27 files changed, 427 insertions(+), 280 deletions(-) diff --git a/cmd/climc/shell/compute/hosts.go b/cmd/climc/shell/compute/hosts.go index 891d082f20..c776cfaa2f 100644 --- a/cmd/climc/shell/compute/hosts.go +++ b/cmd/climc/shell/compute/hosts.go @@ -578,11 +578,11 @@ func init() { } R(&HostSSHLoginOptions{}, "host-ssh", "SSH login of a host", func(s *mcclient.ClientSession, args *HostSSHLoginOptions) error { i, e := modules.Hosts.PerformAction(s, args.ID, "login-info", nil) - privateKey := "" + var privateKeys []string if e != nil { if httputils.ErrorCode(e) == 404 || strings.Contains(e.Error(), "ciphertext too short") { var err error - privateKey, err = modules.Sshkeypairs.FetchPrivateKeyBySession(context.Background(), s) + privateKeys, err = modules.Sshkeypairs.FetchAdminPrivateKeysBySession(context.Background(), s) if err != nil { return errors.Wrap(err, "fetch private key") } @@ -624,10 +624,27 @@ func init() { if args.Port != 22 { port = args.Port } - sshCli, err := ssh.NewClient(host, port, user, passwd, privateKey) - if err != nil { - return err + + if len(privateKeys) == 0 { + privateKeys = append(privateKeys, "") } + + var sshCli *ssh.Client + var errs []error + for _, privateKey := range privateKeys { + cli, err := ssh.NewClient(host, port, user, passwd, privateKey) + if err == nil { + sshCli = cli + break + } else { + errs = append(errs, err) + } + } + + if sshCli == nil { + return errors.NewAggregate(errs) + } + log.Infof("ssh %s:%d", host, port) if err := sshCli.RunTerminal(); err != nil { return err diff --git a/cmd/climc/shell/compute/servers.go b/cmd/climc/shell/compute/servers.go index db8cd53fbc..e41b98e3b1 100644 --- a/cmd/climc/shell/compute/servers.go +++ b/cmd/climc/shell/compute/servers.go @@ -24,6 +24,7 @@ import ( "path/filepath" "gopkg.in/yaml.v2" + "k8s.io/apimachinery/pkg/util/errors" "yunion.io/x/jsonutils" "yunion.io/x/log" @@ -826,15 +827,15 @@ func init() { return fmt.Errorf("Not found ip address from server %s", opts.ID) } - privateKey := "" + var privateKeys []string params := jsonutils.NewDict() if len(opts.Key) > 0 { - key, e := ioutil.ReadFile(opts.Key) + key, e := os.ReadFile(opts.Key) if e != nil { return e } params.Add(jsonutils.NewString(string(key)), "private_key") - privateKey = string(key) + privateKeys = append(privateKeys, string(key)) } i, e := modules.Servers.PerformAction(s, srvid, "login-info", params) @@ -873,6 +874,7 @@ func init() { if err != nil { return err } + defer closeForward(s, srvid, forwardItem) host = forwardItem.ProxyAddr port = forwardItem.ProxyPort } @@ -881,7 +883,7 @@ func init() { if opts.UseCloudroot { var err error - privateKey, err = modules.Sshkeypairs.FetchPrivateKeyBySession(context.Background(), s) + privateKeys, err = modules.Sshkeypairs.FetchProjectPrivateKeysBySession(context.Background(), s) if err != nil { return err } @@ -889,30 +891,26 @@ func init() { user = "cloudroot" } + if len(privateKeys) == 0 { + privateKeys = append(privateKeys, "") + } + var sshCli *ssh.Client - err = nil - for ; sshCli == nil; sshCli, err = ssh.NewClient(host, port, user, passwd, privateKey) { + var errs []error + for _, privateKey := range privateKeys { + cli, err := ssh.NewClient(host, port, user, passwd, privateKey) if err == nil { - continue - } - if opts.Host != "" { - return err - } - if forwardItem != nil { - closeForward(s, srvid, forwardItem) - return err + sshCli = cli + break } else { - if vpcid != "default" { - forwardItem, e = openForward(s, srvid) - if e != nil { - return e - } - host = forwardItem.ProxyAddr - port = forwardItem.ProxyPort - } + errs = append(errs, err) } } + if sshCli == nil { + return errors.NewAggregate(errs) + } + log.Infof("ssh %s:%d", host, port) if err := sshCli.RunTerminal(); err != nil { if forwardItem != nil { @@ -921,9 +919,6 @@ func init() { return err } - if forwardItem != nil { - closeForward(s, srvid, forwardItem) - } return nil }) } diff --git a/cmd/climc/shell/compute/sshkeypairs.go b/cmd/climc/shell/compute/sshkeypairs.go index 8dc86bf985..dfd8bade8d 100644 --- a/cmd/climc/shell/compute/sshkeypairs.go +++ b/cmd/climc/shell/compute/sshkeypairs.go @@ -15,58 +15,49 @@ package compute import ( + "context" "fmt" "os" "path" "strings" - "yunion.io/x/jsonutils" "yunion.io/x/pkg/errors" "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/models" modules "yunion.io/x/onecloud/pkg/mcclient/modules/compute" "yunion.io/x/onecloud/pkg/util/procutils" ) +type SshkeypairQueryOptions struct { + Project string `help:"get keypair for specific project"` + Admin bool `help:"get admin keypair, sysadmin ONLY option"` +} + +func getSshKeypair(s *mcclient.ClientSession, args *SshkeypairQueryOptions) ([]models.SshKeypair, error) { + ctx := context.Background() + if len(args.Project) > 0 { + return modules.Sshkeypairs.FetchKeypairsByProject(ctx, s, args.Project) + } else if args.Admin { + return modules.Sshkeypairs.FetchAdminKeypairsBySession(ctx, s) + } else { + return modules.Sshkeypairs.FetchProjectKeypairsBySession(ctx, s) + } +} + func init() { - type SshkeypairQueryOptions struct { - Project string `help:"get keypair for specific project"` - Admin bool `help:"get admin keypair, sysadmin ONLY option"` - } - - getSshKeypair := func(s *mcclient.ClientSession, args *SshkeypairQueryOptions) (string, string, error) { - query := jsonutils.NewDict() - if args.Admin { - query.Add(jsonutils.JSONTrue, "admin") - } - var keys jsonutils.JSONObject - if len(args.Project) == 0 { - listResult, err := modules.Sshkeypairs.List(s, query) - if err != nil { - return "", "", err - } - keys = listResult.Data[0] - } else { - result, err := modules.Sshkeypairs.GetById(s, args.Project, query) - if err != nil { - 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) + keypairs, err := getSshKeypair(s, args) if err != nil { return err } - - fmt.Print(privKey) - fmt.Print(pubKey) - + if len(keypairs) == 0 { + return errors.Wrap(errors.ErrNotFound, "no ssh key found") + } + for _, keypair := range keypairs { + fmt.Print(keypair.PrivateKey) + fmt.Print(keypair.PublicKey) + } return nil }) @@ -75,10 +66,13 @@ func init() { 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) + keypairs, err := getSshKeypair(s, &args.SshkeypairQueryOptions) if err != nil { return err } + if len(keypairs) == 0 { + return errors.Wrap(errors.ErrNotFound, "no ssh key found") + } targetDir := args.TargetDir if targetDir == "" { homeDir, err := os.UserHomeDir() @@ -100,7 +94,7 @@ func init() { } oldKeys = string(output) } - var MergeAuthorizedKeys = func(oldKeys string, pubKey string) string { + var MergeAuthorizedKeys = func(oldKeys string, keyPairs []models.SshKeypair) string { const sshKeySignature = "@yunioncloudpods" var allkeys = make(map[string]string) if len(oldKeys) > 0 { @@ -118,10 +112,9 @@ func init() { } } } - candiateKeys := []string{pubKey} - for _, k := range candiateKeys { - if len(k) > 0 { - k = strings.TrimSpace(k) + for _, k := range keyPairs { + if len(k.PublicKey) > 0 { + k := strings.TrimSpace(k.PublicKey) dat := strings.Split(k, " ") if len(dat) > 1 { if _, ok := allkeys[dat[1]]; !ok { @@ -137,7 +130,7 @@ func init() { return strings.Join(keys, "\n") + "\n" } - newKeys := MergeAuthorizedKeys(oldKeys, pubKey) + newKeys := MergeAuthorizedKeys(oldKeys, keypairs) 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) diff --git a/pkg/ansibleserver/models/ansibleplaybook_instance.go b/pkg/ansibleserver/models/ansibleplaybook_instance.go index 44e908dced..5c38db3098 100644 --- a/pkg/ansibleserver/models/ansibleplaybook_instance.go +++ b/pkg/ansibleserver/models/ansibleplaybook_instance.go @@ -125,11 +125,11 @@ func (ai *SAnsiblePlaybookInstance) runPlaybook(ctx context.Context, userCred mc ar = obj.(*SAnsiblePlaybookReference) } var ( - privateKey string - err error + privateKeys []string + err error ) - if privateKey, err = compute.Sshkeypairs.FetchPrivateKey(ctx, userCred); err != nil { - return err + if privateKeys, err = compute.Sshkeypairs.FetchProjectPrivateKeys(ctx, userCred); err != nil { + return errors.Wrap(err, "unable to fetch private keys") } _, err = db.Update(ai, func() error { ai.StartTime = time.Now() @@ -157,7 +157,7 @@ func (ai *SAnsiblePlaybookInstance) runPlaybook(ctx context.Context, userCred mc } sess := ansiblev2.NewOfflineSession(). Inventory(ai.Inventory). - PrivateKey(privateKey). + PrivateKeys(privateKeys). ConfigYaml(params.YAMLString()). PlaybookPath(ar.PlaybookPath). OutputWriter(&ansiblePlaybookOutputWriter{ai}). diff --git a/pkg/ansibleserver/models/ansibleplaybooks.go b/pkg/ansibleserver/models/ansibleplaybooks.go index 03d268237f..1529dc515a 100644 --- a/pkg/ansibleserver/models/ansibleplaybooks.go +++ b/pkg/ansibleserver/models/ansibleplaybooks.go @@ -276,11 +276,11 @@ func (apb *SAnsiblePlaybook) runPlaybook(ctx context.Context, userCred mcclient. if err := ansible.ValidatePlaybook(pb); err != nil { return err } - if len(pb.PrivateKey) == 0 { - if k, err := compute.Sshkeypairs.FetchPrivateKey(ctx, userCred); err != nil { - return err + if len(pb.PrivateKeys) == 0 { + if keys, err := compute.Sshkeypairs.FetchProjectPrivateKeys(ctx, userCred); err != nil { + return errors.Wrap(err, "unable to fetch private keys") } else { - pb.PrivateKey = []byte(k) + pb.PrivateKeys = keys } } // init tmpdir clean policy diff --git a/pkg/ansibleserver/models/ansibleplaybooks_v2.go b/pkg/ansibleserver/models/ansibleplaybooks_v2.go index 0fcec3b712..7a61ee48f0 100644 --- a/pkg/ansibleserver/models/ansibleplaybooks_v2.go +++ b/pkg/ansibleserver/models/ansibleplaybooks_v2.go @@ -252,9 +252,9 @@ func (apb *SAnsiblePlaybookV2) runPlaybook(ctx context.Context, userCred mcclien // time.Sleep(50 * time.Second) var ( - privateKey string - err error - files = map[string][]byte{} + privateKeys []string + err error + files = map[string][]byte{} ) if apb.Files != "" { obj, err := jsonutils.ParseString(apb.Files) @@ -274,7 +274,7 @@ func (apb *SAnsiblePlaybookV2) runPlaybook(ctx context.Context, userCred mcclien } } // init private key - if privateKey, err = compute.Sshkeypairs.FetchPrivateKey(ctx, userCred); err != nil { + if privateKeys, err = compute.Sshkeypairs.FetchProjectPrivateKeys(ctx, userCred); err != nil { return errors.Wrap(err, "fetch private key") } @@ -306,7 +306,7 @@ func (apb *SAnsiblePlaybookV2) runPlaybook(ctx context.Context, userCred mcclien sess := ansiblev2.NewSession(). Inventory(apb.Inventory). Playbook(apb.Playbook). - PrivateKey(privateKey). + PrivateKeys(privateKeys). Requirements(requirements). Files(files). OutputWriter(&ansiblePlaybookOutputWriter{apb}). diff --git a/pkg/ansibleserver/models/ansibleplaybooks_validator.go b/pkg/ansibleserver/models/ansibleplaybooks_validator.go index 86377006a2..2219e548b6 100644 --- a/pkg/ansibleserver/models/ansibleplaybooks_validator.go +++ b/pkg/ansibleserver/models/ansibleplaybooks_validator.go @@ -91,9 +91,9 @@ func (v *ValidatorAnsiblePlaybook) Validate(ctx context.Context, data *jsonutils return httperrors.NewInputParameterError("%s", err.Error()) } // add LF for privateKey - if len(pb.PrivateKey) > 0 && pb.PrivateKey[len(pb.PrivateKey)-1] != 10 { - pb.PrivateKey = append(pb.PrivateKey, 10) - } + // if len(pb.PrivateKey) > 0 && pb.PrivateKey[len(pb.PrivateKey)-1] != 10 { + // pb.PrivateKey = append(pb.PrivateKey, 10) + //} pbJson := jsonutils.Marshal(pb) if serialized := pbJson.String(); len(serialized) > PlaybookMaxBytes { return httperrors.NewBadRequestError("playbook too big, got %d bytes, exceeding %d", diff --git a/pkg/baremetal/manager.go b/pkg/baremetal/manager.go index df3e3877b3..4d036937b2 100644 --- a/pkg/baremetal/manager.go +++ b/pkg/baremetal/manager.go @@ -1636,7 +1636,7 @@ func (b *SBaremetalInstance) GetServerSSHClient() (*ssh.Client, error) { return nil, errors.Error("No server") } - privateKey, err := modules.Sshkeypairs.FetchPrivateKey(context.TODO(), auth.AdminCredential()) + privateKeys, err := modules.Sshkeypairs.FetchProjectPrivateKeys(context.TODO(), auth.AdminCredential()) if err != nil { return nil, errors.Wrapf(err, "Get server %s login info", s.GetId()) } @@ -1645,12 +1645,14 @@ func (b *SBaremetalInstance) GetServerSSHClient() (*ssh.Client, error) { for idx, nic := range nics { if nic.Ip != "" { for _, user := range []string{"cloudroot", "root"} { - sshCli, err := ssh.NewClient(nic.Ip, 22, user, "", privateKey) - if err != nil { - err = errors.Wrapf(err, "New server %s ssh client %s@%s", s.GetName(), user, nic.Ip) - errs = append(errs, err) - } else { - return sshCli, nil + for _, privateKey := range privateKeys { + sshCli, err := ssh.NewClient(nic.Ip, 22, user, "", privateKey) + if err != nil { + err = errors.Wrapf(err, "New server %s ssh client %s@%s", s.GetName(), user, nic.Ip) + errs = append(errs, err) + } else { + return sshCli, nil + } } } } else { diff --git a/pkg/cloudproxy/models/proxy_endpoints.go b/pkg/cloudproxy/models/proxy_endpoints.go index 3a382f62b1..cdc0b9f1a2 100644 --- a/pkg/cloudproxy/models/proxy_endpoints.go +++ b/pkg/cloudproxy/models/proxy_endpoints.go @@ -35,9 +35,10 @@ import ( type SProxyEndpoint struct { db.SVirtualResourceBase - User string `nullable:"false" list:"user" update:"user" create:"optional"` - Host string `nullable:"false" list:"user" update:"user" create:"required"` - Port int `nullable:"false" list:"user" update:"user" create:"optional"` + User string `nullable:"false" list:"user" update:"user" create:"optional"` + Host string `nullable:"false" list:"user" update:"user" create:"required"` + Port int `nullable:"false" list:"user" update:"user" create:"optional"` + PrivateKey string `nullable:"false" update:"user" list:"admin" get:"admin" create:"required"` // do not allow get, list IntranetIpAddr string `width:"16" charset:"ascii" nullable:"true" list:"user" create:"required"` @@ -73,7 +74,7 @@ func (man *SProxyEndpointManager) PerformCreateFromServer(ctx context.Context, u if err != nil { return nil, err } - if serverInfo.PrivateKey == "" { + if len(serverInfo.PrivateKeys) == 0 { return nil, httperrors.NewBadRequestError("cannot find ssh private key for this server") } @@ -111,21 +112,42 @@ func (man *SProxyEndpointManager) PerformCreateFromServer(ctx context.Context, u if portStr, ok := serverInfo.Server.Metadata[compute_apis.SSH_PORT]; ok { port, _ = strconv.Atoi(portStr) } - proxyendpoint := &SProxyEndpoint{ - User: "cloudroot", - Host: host, - Port: port, - PrivateKey: serverInfo.PrivateKey, + createAndVerifyPE := func(privateKey string) (*SProxyEndpoint, error) { + proxyendpoint := &SProxyEndpoint{ + User: "cloudroot", + Host: host, + Port: port, + PrivateKey: privateKey, - IntranetIpAddr: nic.IpAddr, + IntranetIpAddr: nic.IpAddr, + } + proxyendpoint.SetModelManager(man, proxyendpoint) + proxyendpoint.Name = name + proxyendpoint.DomainId = serverInfo.Server.DomainId + proxyendpoint.ProjectId = serverInfo.Server.ProjectId + + if err := proxyendpoint.remoteCheckMake(ctx, userCred); err != nil { + return nil, errors.Wrap(err, "remoteCheckMake") + } + return proxyendpoint, nil } - proxyendpoint.SetModelManager(man, proxyendpoint) - proxyendpoint.Name = name - proxyendpoint.DomainId = serverInfo.Server.DomainId - proxyendpoint.ProjectId = serverInfo.Server.ProjectId - - if err := proxyendpoint.remoteCheckMake(ctx, userCred); err != nil { - return nil, err + var proxyendpoint *SProxyEndpoint + var errs []error + for _, privateKey := range serverInfo.PrivateKeys { + pe, err := createAndVerifyPE(privateKey) + if err == nil { + proxyendpoint = pe + break + } else { + errs = append(errs, err) + } + } + if proxyendpoint == nil { + if len(errs) > 0 { + return nil, errors.NewAggregate(errs) + } else { + return nil, errors.Wrap(httperrors.ErrInvalidStatus, "server has no usable ssh private key") + } } if err := man.TableSpec().Insert(ctx, proxyendpoint); err != nil { diff --git a/pkg/cloudproxy/models/proxy_endpoints_remote_check_make.go b/pkg/cloudproxy/models/proxy_endpoints_remote_check_make.go index 8553481ebb..af9fbe2555 100644 --- a/pkg/cloudproxy/models/proxy_endpoints_remote_check_make.go +++ b/pkg/cloudproxy/models/proxy_endpoints_remote_check_make.go @@ -123,7 +123,7 @@ func (proxyendpoint *SProxyEndpoint) remoteConfigure(ctx context.Context, userCr host.SetVar("ansible_port", fmt.Sprintf("%d", proxyendpoint.Port)) host.SetVar("ansible_become", "yes") pb := &ansible.Playbook{ - PrivateKey: []byte(proxyendpoint.PrivateKey), + PrivateKeys: []string{proxyendpoint.PrivateKey}, Inventory: ansible.Inventory{ Hosts: []ansible.Host{host}, }, diff --git a/pkg/cloudproxy/models/server_util.go b/pkg/cloudproxy/models/server_util.go index 5e9aed9195..25a8418136 100644 --- a/pkg/cloudproxy/models/server_util.go +++ b/pkg/cloudproxy/models/server_util.go @@ -29,7 +29,7 @@ type serverInfo struct { // PrivateKey is the one corresponds to userCred when getting this // serverInfo instance. It can be empty - PrivateKey string + PrivateKeys []string } func (si *serverInfo) GetNic() *compute_apis.GuestnetworkShortDesc { @@ -71,11 +71,11 @@ func getServerInfo( return nil, httperrors.NewServerError("unmarshal server %s: %v", serverId, err) } - privateKey, _ := compute_modules.Sshkeypairs.FetchPrivateKey(ctx, userCred) + privateKeys, _ := compute_modules.Sshkeypairs.FetchProjectPrivateKeys(ctx, userCred) serverInfo := &serverInfo{ - Server: server, - PrivateKey: privateKey, + Server: server, + PrivateKeys: privateKeys, } return serverInfo, nil } diff --git a/pkg/compute/models/guest_sshable.go b/pkg/compute/models/guest_sshable.go index 2f99878a80..a88fa72b01 100644 --- a/pkg/compute/models/guest_sshable.go +++ b/pkg/compute/models/guest_sshable.go @@ -87,11 +87,20 @@ func (guest *SGuest) GetDetailsSshable( if err != nil { return nil, httperrors.NewInternalServerError("fetch ssh private key: %v", err) } - tryData.PrivateKey = privateKey - tryData.PublicKey = publicKey + var sshTryErrs []error + for i := range privateKey { + tryData.PrivateKey = privateKey[i] + tryData.PublicKey = publicKey[i] - if err := guest.sshableTryEach(ctx, userCred, tryData); err != nil { - return nil, err + if err := guest.sshableTryEach(ctx, userCred, tryData); err != nil { + sshTryErrs = append(sshTryErrs, err) + } else { + sshTryErrs = nil + break + } + } + if len(sshTryErrs) > 0 { + return nil, errors.NewAggregate(sshTryErrs) } { @@ -497,7 +506,7 @@ func (guest *SGuest) PerformMakeSshable( }, } if input.PrivateKey != "" { - pb.PrivateKey = []byte(input.PrivateKey) + pb.PrivateKeys = []string{input.PrivateKey} } else if input.Password != "" { host.SetVar("ansible_password", input.Password) } @@ -537,8 +546,8 @@ func (guest *SGuest) GetDetailsMakeSshableCmd( varVals := [][2]string{ {"user", "cloudroot"}, - {"adminpub", strings.TrimSpace(adminPublicKey)}, - {"projpub", strings.TrimSpace(projectPublicKey)}, + {"adminpub", strings.TrimSpace(adminPublicKey[0])}, + {"projpub", strings.TrimSpace(projectPublicKey[0])}, } shellCmd := "" for i := range varVals { @@ -547,23 +556,18 @@ func (guest *SGuest) GetDetailsMakeSshableCmd( } shellCmd += ` -group="$user" -sshdir="/home/$user/.ssh" -keyfile="$sshdir/authorized_keys" -` - shellCmd += ` -id -g "$group" &>/dev/null || groupadd "$group" -id -u "$user" &>/dev/null || useradd --create-home --gid "$group" "$user" -mkdir -p "$sshdir" -grep -q -F "$adminpub" "$keyfile" &>/dev/null || echo "$adminpub" >>"$keyfile" -grep -q -F "$projpub" "$keyfile" &>/dev/null || echo "$projpub" >>"$keyfile" -chown -R "$user:$group" "$sshdir" -chmod -R 700 "$sshdir" -chmod -R 600 "$keyfile" - -if ! grep -q "^$user " /etc/sudoers; then - echo "$user ALL=(ALL) NOPASSWD: ALL" | EDITOR='tee -a' visudo -fi +sshdir=/opt/$user/.ssh +keyfile=$sshdir/authorized_keys +grep -q "^$user:" /etc/group || groupadd "$user" +shell=$(command -v bash || command -v zsh || true) +id -u "$user" >/dev/null 2>&1 || useradd ${shell:+--shell "$shell"} -d $(dirname $sshdir) --create-home -g "$user" "$user" +install -d -m 700 -o "$user" -g "$user" "$sshdir" +for k in "$adminpub" "$projpub"; do + grep -qF "$k" "$keyfile" 2>/dev/null || echo "$k" >>"$keyfile" +done +chown "$user:$user" "$keyfile" +chmod 600 "$keyfile" +grep -q "^$user " /etc/sudoers || echo "$user ALL=(ALL) NOPASSWD: ALL" | EDITOR='tee -a' visudo ` output = compute_api.GuestMakeSshableCmdOutput{ ShellCmd: shellCmd, @@ -590,6 +594,7 @@ func (guest *SGuest) PerformSetSshport(ctx context.Context, userCred mcclient.To } return nil, guest.SetSshPort(ctx, userCred, input.Port) } + func (guest *SGuest) GetDetailsSshport( ctx context.Context, userCred mcclient.TokenCredential, diff --git a/pkg/compute/models/guests.go b/pkg/compute/models/guests.go index 6b68ca1178..7795821d42 100644 --- a/pkg/compute/models/guests.go +++ b/pkg/compute/models/guests.go @@ -5707,8 +5707,8 @@ func (self *SGuest) GetDeployConfigOnHost(ctx context.Context, userCred mcclient log.Errorf("fail to get ssh project public key %s", err) } - config.Add(jsonutils.NewString(adminPubKey), "admin_public_key") - config.Add(jsonutils.NewString(projPubKey), "project_public_key") + config.Add(jsonutils.NewString(adminPubKey[0]), "admin_public_key") + config.Add(jsonutils.NewString(projPubKey[0]), "project_public_key") config.Add(jsonutils.NewString(deployAction), "action") diff --git a/pkg/compute/models/kube_node_pools.go b/pkg/compute/models/kube_node_pools.go index 15d0627757..8f9cc35a91 100644 --- a/pkg/compute/models/kube_node_pools.go +++ b/pkg/compute/models/kube_node_pools.go @@ -329,10 +329,11 @@ func (manager *SKubeNodePoolManager) ValidateCreateData(ctx context.Context, use keypair := keypairObj.(*SKeypair) input.PublicKey = keypair.PublicKey } else { - _, input.PublicKey, err = sshkeys.GetSshAdminKeypair(ctx) + _, pubKeys, err := sshkeys.GetSshAdminKeypair(ctx) if err != nil { return nil, httperrors.NewGeneralError(errors.Wrapf(err, "GetSshAdminKeypair")) } + input.PublicKey = pubKeys[0] } if input.DesiredInstanceCount > 0 { diff --git a/pkg/compute/regiondrivers/managedvirtual.go b/pkg/compute/regiondrivers/managedvirtual.go index a2ac10cf71..5453b8acf8 100644 --- a/pkg/compute/regiondrivers/managedvirtual.go +++ b/pkg/compute/regiondrivers/managedvirtual.go @@ -3247,7 +3247,11 @@ func (self *SManagedVirtualizationRegionDriver) RequestCreateKubeCluster(ctx con opts.RoleName, _ = params.GetString("role_name") opts.PrivateAccess, _ = params.Bool("private_access") opts.PublicAccess, _ = params.Bool("public_access") - _, opts.PublicKey, _ = sshkeys.GetSshAdminKeypair(ctx) + _, pubKeys, err := sshkeys.GetSshAdminKeypair(ctx) + if err != nil { + return nil, errors.Wrapf(err, "GetSshAdminKeypair") + } + opts.PublicKey = pubKeys[0] iregion, err := cluster.GetIRegion(ctx) if err != nil { diff --git a/pkg/compute/sshkeys/handler.go b/pkg/compute/sshkeys/handler.go index 64c9dd3eab..0898ab9253 100644 --- a/pkg/compute/sshkeys/handler.go +++ b/pkg/compute/sshkeys/handler.go @@ -31,10 +31,15 @@ import ( "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/models" ) func AddSshKeysHandler(prefix string, app *appsrv.Application) { + // 1. 获取用户自己项目的密钥对,总是返回公钥+私钥 + // 2. 如果是regionadmin所在系统项目(system),指定admin=true,则获取全局管理密钥 app.AddHandler2("GET", fmt.Sprintf("%s/sshkeypairs", prefix), auth.Authenticate(sshKeysHandler), nil, "get_sshkeys", nil) + // 1. 获取指定项目(tenant_id)的项目密钥对,只有域管理员或者系统管理员级别才能获得私钥 + // 2. 无法获取全局管理密钥 app.AddHandler2("GET", fmt.Sprintf("%s/sshkeypairs/", prefix), auth.Authenticate(adminSshKeysHandler), nil, "get_sshkeys", nil) } @@ -80,7 +85,7 @@ func sshKeysHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) } func sendSshKey(ctx context.Context, w http.ResponseWriter, userCred mcclient.TokenCredential, projectId string, isAdmin bool, publicOnly bool) { - var privKey, pubKey string + var privKey, pubKey []string if isAdmin { if policy.PolicyManager.Allow(rbacscope.ScopeSystem, userCred, consts.GetServiceType(), "sshkeypairs", policy.PolicyActionGet).Result.IsAllow() { @@ -93,12 +98,24 @@ func sendSshKey(ctx context.Context, w http.ResponseWriter, userCred mcclient.To privKey, pubKey, _ = GetSshProjectKeypair(ctx, projectId) } + keypairs := make([]models.SshKeypair, 0, len(privKey)) + for i := range pubKey { + keypair := models.SshKeypair{ + PublicKey: pubKey[i], + } + if !publicOnly { + keypair.PrivateKey = privKey[i] + } + keypairs = append(keypairs, keypair) + } + ret := jsonutils.NewDict() if !publicOnly { - ret.Add(jsonutils.NewString(privKey), "private_key") + ret.Add(jsonutils.NewString(privKey[0]), "private_key") } - ret.Add(jsonutils.NewString(pubKey), "public_key") + ret.Add(jsonutils.NewString(pubKey[0]), "public_key") + ret.Add(jsonutils.Marshal(keypairs), "keypairs") body := jsonutils.NewDict() body.Add(ret, "sshkeypair") appsrv.SendJSON(w, body) diff --git a/pkg/compute/sshkeys/sshkeypairs.go b/pkg/compute/sshkeys/sshkeypairs.go index f818c6079b..957a5c0c14 100644 --- a/pkg/compute/sshkeys/sshkeypairs.go +++ b/pkg/compute/sshkeys/sshkeypairs.go @@ -16,6 +16,7 @@ package sshkeys import ( "context" + "strings" "yunion.io/x/pkg/utils" @@ -25,14 +26,20 @@ import ( ) const ( - sshAdminPrivateKey = "admin-ssh-private-key" - sshAdminPublicKey = "admin-ssh-public-key" + sshAdminRsaPrivateKey = "admin-ssh-private-key" + sshAdminRsaPublicKey = "admin-ssh-public-key" - sshPrivateKey = "project-ssh-private-key" - sshPublicKey = "project-ssh-public-key" + sshAdminEd25519PrivateKey = "admin-ssh-ed25519-private-key" + sshAdminEd25519PublicKey = "admin-ssh-ed25519-public-key" + + sshRsaPrivateKey = "project-ssh-private-key" + sshRsaPublicKey = "project-ssh-public-key" + + sshEd25519PrivateKey = "project-ssh-ed25519-private-key" + sshEd25519PublicKey = "project-ssh-ed25519-public-key" ) -func _getKeys(ctx context.Context, tenantId string, privateKey, publicKey string) (string, string, error) { +func _getKeys(ctx context.Context, tenantId string, privateKey, publicKey string, autocreate bool) (string, string, error) { tenant, err := db.TenantCacheManager.FetchTenantById(ctx, tenantId) if err != nil { return "", "", err @@ -40,21 +47,43 @@ func _getKeys(ctx context.Context, tenantId string, privateKey, publicKey string private := tenant.GetMetadata(ctx, privateKey, nil) public := tenant.GetMetadata(ctx, publicKey, nil) userCred := auth.AdminCredential() - if len(private) == 0 || len(public) == 0 { - private, public, _ = seclib2.GenerateRSASSHKeypair() + if (len(private) == 0 || len(public) == 0) && autocreate { + if strings.Contains(privateKey, "ed25519") { + private, public, _ = seclib2.GenerateED25519SSHKeypair() + } else { + private, public, _ = seclib2.GenerateRSASSHKeypair() + } private, _ = utils.EncryptAESBase64(tenantId, private) tenant.SetMetadata(ctx, privateKey, private, userCred) tenant.SetMetadata(ctx, publicKey, public, userCred) + } else if len(private) > 0 { + private, _ = utils.DescryptAESBase64(tenantId, private) } - private, _ = utils.DescryptAESBase64(tenantId, private) return private, public, nil } -func GetSshProjectKeypair(ctx context.Context, tenantId string) (string, string, error) { - return _getKeys(ctx, tenantId, sshPrivateKey, sshPublicKey) +func GetSshProjectKeypair(ctx context.Context, tenantId string) ([]string, []string, error) { + return getSshProjectKeypair(ctx, tenantId, sshEd25519PrivateKey, sshEd25519PublicKey, sshRsaPrivateKey, sshRsaPublicKey) } -func GetSshAdminKeypair(ctx context.Context) (string, string, error) { +func getSshProjectKeypair(ctx context.Context, tenantId string, ed25519PrivKey, ed25519PubKey, rsaPrivKey, rsaPubKey string) ([]string, []string, error) { + ed25519Priv, ed25519Pub, err := _getKeys(ctx, tenantId, ed25519PrivKey, ed25519PubKey, true) + if err != nil { + return nil, nil, err + } + rsaPriv, rsaPub, _ := _getKeys(ctx, tenantId, rsaPrivKey, rsaPubKey, false) + private := []string{ed25519Priv} + if len(rsaPriv) > 0 { + private = append(private, rsaPriv) + } + public := []string{ed25519Pub} + if len(rsaPub) > 0 { + public = append(public, rsaPub) + } + return private, public, nil +} + +func GetSshAdminKeypair(ctx context.Context) ([]string, []string, error) { userCred := auth.AdminCredential() - return _getKeys(ctx, userCred.GetProjectId(), sshAdminPrivateKey, sshAdminPublicKey) + return getSshProjectKeypair(ctx, userCred.GetProjectId(), sshAdminEd25519PrivateKey, sshAdminEd25519PublicKey, sshAdminRsaPrivateKey, sshAdminRsaPublicKey) } diff --git a/pkg/hostman/guestfs/fsdriver/base.go b/pkg/hostman/guestfs/fsdriver/base.go index ac1056a224..e9cc2b6979 100644 --- a/pkg/hostman/guestfs/fsdriver/base.go +++ b/pkg/hostman/guestfs/fsdriver/base.go @@ -277,18 +277,14 @@ func DeployAdminAuthorizedKeys(s *mcclient.ClientSession) error { return errors.Wrapf(err, "mkdir .ssh %s", output) } - query := jsonutils.NewDict() - query.Set("admin", jsonutils.JSONTrue) - ret, err := modules.Sshkeypairs.List(s, query) + keypairs, err := modules.Sshkeypairs.FetchAdminKeypairsBySession(s.GetContext(), s) if err != nil { return errors.Wrap(err, "modules.Sshkeypairs.List") } - if len(ret.Data) == 0 { + if len(keypairs) == 0 { return errors.Wrap(httperrors.ErrNotFound, "Not found admin sshkey") } - keys := ret.Data[0] - adminPublicKey, _ := keys.GetString("public_key") - pubKeys := &deployapi.SSHKeys{AdminPublicKey: adminPublicKey} + pubKeys := &deployapi.SSHKeys{AdminPublicKey: keypairs[0].PublicKey} var oldKeys string authFile := path.Join(sshDir, "authorized_keys") diff --git a/pkg/mcclient/models/sshkeypairs.go b/pkg/mcclient/models/sshkeypairs.go index 7da3aa7896..2809cc0ded 100644 --- a/pkg/mcclient/models/sshkeypairs.go +++ b/pkg/mcclient/models/sshkeypairs.go @@ -15,6 +15,6 @@ package models type SshKeypair struct { - PrivateKey string - PublicKey string + PrivateKey string `json:"private_key"` + PublicKey string `json:"public_key"` } diff --git a/pkg/mcclient/modules/compute/mod_sshkeypairs.go b/pkg/mcclient/modules/compute/mod_sshkeypairs.go index 54236a514b..1f96801644 100644 --- a/pkg/mcclient/modules/compute/mod_sshkeypairs.go +++ b/pkg/mcclient/modules/compute/mod_sshkeypairs.go @@ -48,42 +48,84 @@ func (this *SSshkeypairManager) List(s *mcclient.ClientSession, params jsonutils return &result, nil } -func (this *SSshkeypairManager) FetchPrivateKey(ctx context.Context, userCred mcclient.TokenCredential) (string, error) { +func (this *SSshkeypairManager) FetchProjectPrivateKeys(ctx context.Context, userCred mcclient.TokenCredential) ([]string, error) { s := auth.GetSession(ctx, userCred, "") - return this.FetchPrivateKeyBySession(ctx, s) + return this.FetchProjectPrivateKeysBySession(ctx, s) } -func (this *SSshkeypairManager) FetchPrivateKeyBySession(ctx context.Context, s *mcclient.ClientSession) (string, error) { - kp, err := this.FetchKeypairBySession(ctx, s) +func (this *SSshkeypairManager) FetchProjectPrivateKeysBySession(ctx context.Context, s *mcclient.ClientSession) ([]string, error) { + kp, err := this.FetchProjectKeypairsBySession(ctx, s) if err != nil { - return "", errors.Wrap(err, "FetchKeypairBySession") + return nil, errors.Wrap(err, "FetchProjectKeypairsBySession") } - return kp.PrivateKey, nil + keys := make([]string, 0, len(kp)) + for _, kp := range kp { + keys = append(keys, kp.PrivateKey) + } + return keys, nil } -func (this *SSshkeypairManager) FetchKeypairBySession(ctx context.Context, s *mcclient.ClientSession) (*models.SshKeypair, error) { - userCred := s.GetToken() +func (this *SSshkeypairManager) FetchAdminPrivateKeysBySession(ctx context.Context, s *mcclient.ClientSession) ([]string, error) { + kp, err := this.FetchAdminKeypairsBySession(ctx, s) + if err != nil { + return nil, errors.Wrap(err, "FetchAdminKeypairsBySession") + } + keys := make([]string, 0, len(kp)) + for _, kp := range kp { + keys = append(keys, kp.PrivateKey) + } + return keys, nil +} + +func (this *SSshkeypairManager) FetchAdminKeypairsBySession(ctx context.Context, s *mcclient.ClientSession) ([]models.SshKeypair, error) { + return this.fetchKeypairsBySession(ctx, s, true) +} + +func (this *SSshkeypairManager) FetchProjectKeypairsBySession(ctx context.Context, s *mcclient.ClientSession) ([]models.SshKeypair, error) { + return this.fetchKeypairsBySession(ctx, s, false) +} + +func (this *SSshkeypairManager) fetchKeypairsBySession(ctx context.Context, s *mcclient.ClientSession, isAdmin bool) ([]models.SshKeypair, error) { jd := jsonutils.NewDict() - var jr jsonutils.JSONObject - if userCred.HasSystemAdminPrivilege() { + if isAdmin { jd.Set("admin", jsonutils.JSONTrue) - r, err := Sshkeypairs.List(s, jd) - if err != nil { - return nil, errors.Wrap(err, "get admin ssh key") + } + r, err := this.List(s, jd) + if err != nil { + return nil, errors.Wrap(err, "get admin ssh key") + } + if len(r.Data) == 0 { + return nil, errors.Wrap(errors.ErrNotFound, "no ssh key found") + } + return unmarshalSshKeypairs(r.Data[0]) +} + +func (this *SSshkeypairManager) FetchKeypairsByProject(ctx context.Context, s *mcclient.ClientSession, projectId string) ([]models.SshKeypair, error) { + jd := jsonutils.NewDict() + if len(projectId) == 0 { + projectId = s.GetProjectId() + } + r, err := Sshkeypairs.GetById(s, projectId, jd) + if err != nil { + return nil, errors.Wrap(err, "get project ssh key") + } + return unmarshalSshKeypairs(r) +} + +func unmarshalSshKeypairs(jr jsonutils.JSONObject) ([]models.SshKeypair, error) { + kps := make([]models.SshKeypair, 0, 2) + if jr.Contains("keypairs") { + if err := jr.Unmarshal(&kps, "keypairs"); err != nil { + return nil, errors.Wrap(err, "unmarshal ssh key") } - jr = r.Data[0] } else { - r, err := Sshkeypairs.GetById(s, userCred.GetProjectId(), jd) - if err != nil { - return nil, errors.Wrap(err, "get project ssh key") + kp := models.SshKeypair{} + if err := jr.Unmarshal(&kp); err != nil { + return nil, errors.Wrap(err, "unmarshal ssh key") } - jr = r + kps = append(kps, kp) } - kp := &models.SshKeypair{} - if err := jr.Unmarshal(kp); err != nil { - return nil, errors.Wrap(err, "unmarshal ssh key") - } - return kp, nil + return kps, nil } var ( diff --git a/pkg/util/ansible/playbook.go b/pkg/util/ansible/playbook.go index 65ee6952c1..6cd0b4f47d 100644 --- a/pkg/util/ansible/playbook.go +++ b/pkg/util/ansible/playbook.go @@ -17,7 +17,6 @@ package ansible import ( "context" "io" - "io/ioutil" "os" "os/exec" "path/filepath" @@ -51,10 +50,10 @@ func (pbs pbState) String() string { } type Playbook struct { - Inventory Inventory - Modules []Module - PrivateKey []byte - Files map[string][]byte + Inventory Inventory + Modules []Module + PrivateKeys []string + Files map[string][]byte tmpdir string noCleanOnExit bool @@ -75,7 +74,7 @@ func (pb *Playbook) Copy() *Playbook { pb1 := NewPlaybook() pb1.Inventory = gotypes.DeepCopy(pb.Inventory).(Inventory) pb1.Modules = gotypes.DeepCopy(pb.Modules).([]Module) - pb1.PrivateKey = gotypes.DeepCopy(pb.PrivateKey).([]byte) + pb1.PrivateKeys = gotypes.DeepCopy(pb.PrivateKeys).([]string) pb1.Files = gotypes.DeepCopy(pb.Files).(map[string][]byte) return pb1 } @@ -102,8 +101,21 @@ func (pb *Playbook) Running() bool { return pb.state == pbStateRunning } +func (pb *Playbook) Run(ctx context.Context) error { + errs := make([]error, 0, len(pb.PrivateKeys)) + for _, privateKey := range pb.PrivateKeys { + err := pb.runOnce(ctx, privateKey) + if err != nil { + errs = append(errs, err) + } else { + return nil + } + } + return errors.NewAggregate(errs) +} + // Run runs the playbook -func (pb *Playbook) Run(ctx context.Context) (err error) { +func (pb *Playbook) runOnce(ctx context.Context, privateKey string) error { var ( tmpdir string ) @@ -126,10 +138,9 @@ func (pb *Playbook) Run(ctx context.Context) (err error) { } // make tmpdir - tmpdir, err = os.MkdirTemp("", "onecloud-ansible") + tmpdir, err := os.MkdirTemp("", "onecloud-ansible") if err != nil { - err = errors.Wrap(err, "making tmp dir") - return + return errors.Wrap(err, "making tmp dir") } pb.tmpdir = tmpdir defer func() { @@ -143,55 +154,45 @@ func (pb *Playbook) Run(ctx context.Context) (err error) { // write out inventory inventory := filepath.Join(tmpdir, "inventory") - err = ioutil.WriteFile(inventory, pb.Inventory.Data(), os.FileMode(0600)) + err = os.WriteFile(inventory, pb.Inventory.Data(), os.FileMode(0600)) if err != nil { - err = errors.Wrapf(err, "writing inventory %s", inventory) - return + return errors.Wrapf(err, "writing inventory %s", inventory) } // write out private key - var privateKey string - if len(pb.PrivateKey) > 0 { - privateKey = filepath.Join(tmpdir, "private_key") - err = ioutil.WriteFile(privateKey, pb.PrivateKey, os.FileMode(0600)) + var privateKeyFile string + if len(privateKey) > 0 { + privateKeyFile = filepath.Join(tmpdir, "private_key") + err = os.WriteFile(privateKeyFile, []byte(privateKey), os.FileMode(0600)) if err != nil { - err = errors.Wrapf(err, "writing private key %s", privateKey) - return + return errors.Wrapf(err, "writing private key %s", privateKeyFile) } } // write out files for name, content := range pb.Files { - path, err2 := fileutils2.JoinInside(tmpdir, name) - if err2 != nil { - err = errors.Wrapf(err2, "playbook file %s", name) - return + path, err := fileutils2.JoinInside(tmpdir, name) + if err != nil { + return errors.Wrapf(err, "playbook file %s", name) } dir := filepath.Dir(path) err = os.MkdirAll(dir, os.FileMode(0700)) if err != nil { - err = errors.Wrapf(err, "mkdir -p %s", dir) - return + return errors.Wrapf(err, "mkdir -p %s", dir) } - err = ioutil.WriteFile(path, content, os.FileMode(0600)) + err = os.WriteFile(path, content, os.FileMode(0600)) if err != nil { - err = errors.Wrapf(err, "writing file %s", name) - return + return errors.Wrapf(err, "writing file %s", name) } } // run modules one by one var errs []error - defer func() { - if len(errs) > 0 { - err = errors.NewAggregate(errs) - } - }() for _, m := range pb.Modules { select { case <-ctx.Done(): - err = ctx.Err() - return + errs = append(errs, errors.Wrap(ctx.Err(), "context done")) + return errors.NewAggregate(errs) default: } modArgs := strings.Join(m.Args, " ") @@ -212,7 +213,7 @@ func (pb *Playbook) Run(ctx context.Context) (err error) { stderr, _ := cmd.StderrPipe() if err1 := cmd.Start(); err1 != nil { errs = append(errs, errors.Wrapf(err1, "run module %q, args %q", m.Name, modArgs)) - return + continue } // Mix stdout, stderr if pb.outputWriter != nil { @@ -221,10 +222,10 @@ func (pb *Playbook) Run(ctx context.Context) (err error) { } if err1 := cmd.Wait(); err1 != nil { errs = append(errs, errors.Wrapf(err1, "wait module %q, args %q", m.Name, modArgs)) - // continue to next + continue } } - return nil + return errors.NewAggregate(errs) } func (pb *Playbook) OutputWriter(w io.Writer) { diff --git a/pkg/util/ansiblev2/offline_session.go b/pkg/util/ansiblev2/offline_session.go index 1657f5d314..f84d04f20a 100644 --- a/pkg/util/ansiblev2/offline_session.go +++ b/pkg/util/ansiblev2/offline_session.go @@ -17,6 +17,8 @@ package ansiblev2 import ( "context" "io" + + "yunion.io/x/pkg/errors" ) type OfflineSession struct { @@ -31,6 +33,16 @@ type OfflineSession struct { configYaml string } +type tryOfflineSession struct { + *OfflineSession + + privateKeyIndex int +} + +func (trySession *tryOfflineSession) GetPrivateKey() string { + return trySession.GetPrivateKeys()[trySession.privateKeyIndex] +} + func NewOfflineSession() *OfflineSession { sess := &OfflineSession{ PlaybookSessionBase: NewPlaybookSessionBase(), @@ -38,8 +50,8 @@ func NewOfflineSession() *OfflineSession { return sess } -func (sess *OfflineSession) PrivateKey(s string) *OfflineSession { - sess.privateKey = s +func (sess *OfflineSession) PrivateKeys(s []string) *OfflineSession { + sess.privateKeys = s return sess } @@ -85,6 +97,19 @@ func (sess *OfflineSession) GetConfigYaml() string { return sess.configYaml } -func (sess *OfflineSession) Run(ctx context.Context) (err error) { - return runnable{sess}.Run(ctx) +func (sess *OfflineSession) Run(ctx context.Context) error { + privateKeys := sess.GetPrivateKeys() + errs := make([]error, 0, len(privateKeys)) + for i := range privateKeys { + trySession := &tryOfflineSession{ + OfflineSession: sess, + privateKeyIndex: i, + } + err := runnable{trySession}.Run(ctx) + if err == nil { + return nil + } + errs = append(errs, err) + } + return errors.NewAggregate(errs) } diff --git a/pkg/util/ansiblev2/playbook_session.go b/pkg/util/ansiblev2/playbook_session.go index 8b140c51f0..6f607b6e83 100644 --- a/pkg/util/ansiblev2/playbook_session.go +++ b/pkg/util/ansiblev2/playbook_session.go @@ -262,7 +262,7 @@ func (r runnable) Run(ctx context.Context) (err error) { } type PlaybookSessionBase struct { - privateKey string + privateKeys []string inventory string outputWriter io.Writer @@ -280,8 +280,8 @@ func NewPlaybookSessionBase() PlaybookSessionBase { } } -func (pb *PlaybookSessionBase) GetPrivateKey() string { - return pb.privateKey +func (pb *PlaybookSessionBase) GetPrivateKeys() []string { + return pb.privateKeys } func (pb *PlaybookSessionBase) IsKeepTmpdir() bool { diff --git a/pkg/util/ansiblev2/session.go b/pkg/util/ansiblev2/session.go index 8a1c0d1aad..142adf2d91 100644 --- a/pkg/util/ansiblev2/session.go +++ b/pkg/util/ansiblev2/session.go @@ -17,6 +17,8 @@ package ansiblev2 import ( "context" "io" + + "yunion.io/x/pkg/errors" ) type Session struct { @@ -27,6 +29,16 @@ type Session struct { files map[string][]byte } +type trySession struct { + *Session + + privateKeyIndex int +} + +func (trySession *trySession) GetPrivateKey() string { + return trySession.GetPrivateKeys()[trySession.privateKeyIndex] +} + func NewSession() *Session { sess := &Session{ PlaybookSessionBase: NewPlaybookSessionBase(), @@ -35,8 +47,8 @@ func NewSession() *Session { return sess } -func (sess *Session) PrivateKey(s string) *Session { - sess.privateKey = s +func (sess *Session) PrivateKeys(s []string) *Session { + sess.privateKeys = s return sess } @@ -103,6 +115,19 @@ func (sess *Session) GetFile() map[string][]byte { return sess.files } -func (sess *Session) Run(ctx context.Context) (err error) { - return runnable{sess}.Run(ctx) +func (sess *Session) Run(ctx context.Context) error { + privateKeys := sess.GetPrivateKeys() + errs := make([]error, 0, len(privateKeys)) + for i := range privateKeys { + trySession := &trySession{ + Session: sess, + privateKeyIndex: i, + } + err := runnable{trySession}.Run(ctx) + if err == nil { + return nil + } + errs = append(errs, err) + } + return errors.NewAggregate(errs) } diff --git a/pkg/webconsole/command/climc_ssh_command.go b/pkg/webconsole/command/climc_ssh_command.go index 76385cb4b6..0162860027 100644 --- a/pkg/webconsole/command/climc_ssh_command.go +++ b/pkg/webconsole/command/climc_ssh_command.go @@ -82,7 +82,7 @@ func NewClimcSshCommand(info *webconsole.ClimcSshInfo, s *mcclient.ClientSession return nil, fmt.Errorf("Invalid username %q", info.Username) } targetIp := helper.FetchClimcTargetIp() - privateKey, err := helper.GetValidPrivateKey(targetIp, 22, info.Username, "") + privateKey, err := helper.GetValidPrivateKey(targetIp, 22, info.Username, s) if err != nil { return nil, errors.Wrap(err, "get cloud admin private key") } diff --git a/pkg/webconsole/helper/helper.go b/pkg/webconsole/helper/helper.go index d795e4effb..705eaa642a 100644 --- a/pkg/webconsole/helper/helper.go +++ b/pkg/webconsole/helper/helper.go @@ -27,6 +27,7 @@ import ( "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/mcclient/modules/k8s" @@ -78,45 +79,17 @@ func FetchClimcTargetIp() string { return podIp } -func GetValidPrivateKey(host string, port int, username string, projectId string) (string, error) { +func GetValidPrivateKey(host string, port int, username string, cliSession *mcclient.ClientSession) (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 - } + var privateKeys []string + projectPrivateKeys, _ := compute.Sshkeypairs.FetchProjectPrivateKeysBySession(ctx, cliSession) + privateKeys = append(privateKeys, projectPrivateKeys...) + adminPrivateKeys, _ := compute.Sshkeypairs.FetchAdminPrivateKeysBySession(ctx, admin) + privateKeys = append(privateKeys, adminPrivateKeys...) + + for _, privKey := range privateKeys { signer, err := ssh.ParsePrivateKey([]byte(privKey)) if err != nil { errs = append(errs, errors.Wrapf(err, "ParsePrivateKey")) diff --git a/pkg/webconsole/session/ssh_session.go b/pkg/webconsole/session/ssh_session.go index c7cd9f4998..5586d9efc1 100644 --- a/pkg/webconsole/session/ssh_session.go +++ b/pkg/webconsole/session/ssh_session.go @@ -134,7 +134,7 @@ func (s *SSshSession) IsNeedLogin() (bool, error) { return true, errors.Error("username is empty") } } - privateKey, err := helper.GetValidPrivateKey(s.Host, s.Port, s.Username, s.us.GetProjectId()) + privateKey, err := helper.GetValidPrivateKey(s.Host, s.Port, s.Username, s.us) if err != nil { return true, errors.Wrap(err, "try to use cloud admin private_key for ssh login") }