diff --git a/pkg/apis/compute/keypair.go b/pkg/apis/compute/keypair.go index a0c5596bd5..e82abe5c4d 100644 --- a/pkg/apis/compute/keypair.go +++ b/pkg/apis/compute/keypair.go @@ -18,6 +18,10 @@ import "yunion.io/x/onecloud/pkg/apis" var KEYPAIR_SCHEMAS = []string{ KEYPAIRE_SCHEME_RSA, + // OpenSSH deprecated DSA keys + //KEYPAIRE_SCHEME_DSA, + KEYPAIRE_SCHEME_ECDSA, + KEYPAIRE_SCHEME_ED25519, } type KeypairCreateInput struct { diff --git a/pkg/apis/compute/keypair_const.go b/pkg/apis/compute/keypair_const.go index 79e5a786f5..a917bbd858 100644 --- a/pkg/apis/compute/keypair_const.go +++ b/pkg/apis/compute/keypair_const.go @@ -15,6 +15,8 @@ package compute const ( - KEYPAIRE_SCHEME_RSA = "RSA" - KEYPAIRE_SCHEME_DSA = "DSA" + KEYPAIRE_SCHEME_RSA = "RSA" + KEYPAIRE_SCHEME_DSA = "DSA" + KEYPAIRE_SCHEME_ECDSA = "ECDSA" + KEYPAIRE_SCHEME_ED25519 = "ED25519" ) diff --git a/pkg/compute/models/keypairs.go b/pkg/compute/models/keypairs.go index 18ae61c6af..657294768a 100644 --- a/pkg/compute/models/keypairs.go +++ b/pkg/compute/models/keypairs.go @@ -24,7 +24,6 @@ import ( "yunion.io/x/pkg/errors" "yunion.io/x/pkg/gotypes" "yunion.io/x/pkg/util/rbacscope" - "yunion.io/x/pkg/utils" "yunion.io/x/sqlchemy" "yunion.io/x/onecloud/pkg/apis" @@ -230,24 +229,34 @@ func (self *SKeypair) GetLinkedGuestsCount() (int, error) { return GuestManager.Query().Equals("keypair_id", self.Id).CountWithError() } -func (manager *SKeypairManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.KeypairCreateInput) (api.KeypairCreateInput, error) { +func (manager *SKeypairManager) ValidateCreateData( + ctx context.Context, + userCred mcclient.TokenCredential, + ownerId mcclient.IIdentityProvider, + query jsonutils.JSONObject, + input *api.KeypairCreateInput, +) (*api.KeypairCreateInput, error) { input.PublicKey = strings.TrimSpace(input.PublicKey) if len(input.PublicKey) == 0 { if len(input.Scheme) == 0 { input.Scheme = api.KEYPAIRE_SCHEME_RSA } - if !utils.IsInStringArray(input.Scheme, api.KEYPAIR_SCHEMAS) { - return input, httperrors.NewInputParameterError("Unsupported scheme %s", input.Scheme) - } var err error - if input.Scheme == api.KEYPAIRE_SCHEME_RSA { + switch input.Scheme { + case api.KEYPAIRE_SCHEME_RSA: input.PrivateKey, input.PublicKey, err = seclib2.GenerateRSASSHKeypair() - } else { + case api.KEYPAIRE_SCHEME_DSA: input.PrivateKey, input.PublicKey, err = seclib2.GenerateDSASSHKeypair() + case api.KEYPAIRE_SCHEME_ECDSA: + input.PrivateKey, input.PublicKey, err = seclib2.GenerateECDSASHAP521SSHKeypair() + case api.KEYPAIRE_SCHEME_ED25519: + input.PrivateKey, input.PublicKey, err = seclib2.GenerateED25519SSHKeypair() + default: + return nil, httperrors.NewInputParameterError("Unsupported scheme %s", input.Scheme) } if err != nil { - return input, httperrors.NewGeneralError(errors.Wrapf(err, "Generate%sSSHKeypair", input.Scheme)) + return nil, httperrors.NewGeneralError(errors.Wrapf(err, "Generate%sSSHKeypair", input.Scheme)) } } pubKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(input.PublicKey)) @@ -255,11 +264,7 @@ func (manager *SKeypairManager) ValidateCreateData(ctx context.Context, userCred return input, httperrors.NewInputParameterError("invalid public error: %v", err) } - // 只允许上传RSA格式密钥。PS: AWS只支持RSA格式。 input.Scheme = seclib2.GetPublicKeyScheme(pubKey) - if input.Scheme != api.KEYPAIRE_SCHEME_RSA { - return input, httperrors.NewInputParameterError("Unsupported scheme %s", input.Scheme) - } input.Fingerprint = ssh.FingerprintLegacyMD5(pubKey) input.UserResourceCreateInput, err = manager.SUserResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.UserResourceCreateInput) diff --git a/pkg/mcclient/options/compute/keypairs.go b/pkg/mcclient/options/compute/keypairs.go index d0c2ccbc12..335b0bcb63 100644 --- a/pkg/mcclient/options/compute/keypairs.go +++ b/pkg/mcclient/options/compute/keypairs.go @@ -22,6 +22,7 @@ import ( type KeypairList struct { options.BaseListOptions + Scheme string `help:"Scheme of keypair, default is RSA" choices:"RSA|DSA|ECDSA|ED25519"` } func (self *KeypairList) Params() (jsonutils.JSONObject, error) { @@ -30,7 +31,7 @@ func (self *KeypairList) Params() (jsonutils.JSONObject, error) { type KeypairCreate struct { NAME string `help:"Name of keypair to be created"` - Scheme string `help:"Scheme of keypair, default is RSA" choices:"RSA" default:"RSA"` + Scheme string `help:"Scheme of keypair, default is RSA" choices:"RSA|DSA|ECDSA|ED25519" default:"RSA"` PublicKey string `help:"Publickey of keypair"` Desc string `help:"Short description of keypair"` } diff --git a/pkg/util/seclib2/ssh.go b/pkg/util/seclib2/ssh.go index 41a21269b6..d96c47512a 100644 --- a/pkg/util/seclib2/ssh.go +++ b/pkg/util/seclib2/ssh.go @@ -15,7 +15,11 @@ package seclib2 import ( + "crypto" "crypto/dsa" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/elliptic" "crypto/rand" "crypto/rsa" "crypto/x509" @@ -25,14 +29,13 @@ import ( "golang.org/x/crypto/ssh" - "yunion.io/x/log" + "yunion.io/x/pkg/errors" ) func GenerateRSASSHKeypair() (string, string, error) { privateKey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { - log.Errorf("generate rsa key error %s", err) - return "", "", err + return "", "", errors.Wrapf(err, "generate rsa key") } privateKeyPEM := &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)} @@ -40,7 +43,51 @@ func GenerateRSASSHKeypair() (string, string, error) { pub, err := exportSshPublicKey(&privateKey.PublicKey) if err != nil { - return "", "", err + return "", "", errors.Wrapf(err, "export ssh public key") + } + publicStr := string(pub) + + return privateStr, publicStr, nil +} + +func GenerateED25519SSHKeypair() (string, string, error) { + publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + return "", "", errors.Wrapf(err, "generate ed25519 key") + } + + pemBlock, err := ssh.MarshalPrivateKey(crypto.PrivateKey(privateKey), "") + if err != nil { + return "", "", errors.Wrapf(err, "marshal pkix private key") + } + + privateStr := string(pem.EncodeToMemory(pemBlock)) + + pub, err := exportSshPublicKey(publicKey) + if err != nil { + return "", "", errors.Wrapf(err, "export ssh public key") + } + publicStr := string(pub) + + return privateStr, publicStr, nil +} + +func GenerateECDSASHAP521SSHKeypair() (string, string, error) { + privateKey, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader) + if err != nil { + return "", "", errors.Wrapf(err, "generate ecdsa key") + } + + pemBlock, err := ssh.MarshalPrivateKey(crypto.PrivateKey(privateKey), "") + if err != nil { + return "", "", errors.Wrapf(err, "marshal pkix private key") + } + + privateStr := string(pem.EncodeToMemory(pemBlock)) + + pub, err := exportSshPublicKey(&privateKey.PublicKey) + if err != nil { + return "", "", errors.Wrapf(err, "export ssh public key") } publicStr := string(pub) @@ -53,13 +100,11 @@ func GenerateDSASSHKeypair() (string, string, error) { params := &privateKey.Parameters err := dsa.GenerateParameters(params, rand.Reader, dsa.L1024N160) if err != nil { - log.Errorf("generateParameter error %s", err) - return "", "", err + return "", "", errors.Wrapf(err, "generate dsa key") } err = dsa.GenerateKey(&privateKey, rand.Reader) if err != nil { - log.Errorf("generate key error %s", err) - return "", "", err + return "", "", errors.Wrapf(err, "generate dsa key") } type DsaASN1 struct { @@ -80,8 +125,7 @@ func GenerateDSASSHKeypair() (string, string, error) { privBytes, err := asn1.Marshal(k) if err != nil { - log.Errorf("asn1 marshal error %s", err) - return "", "", err + return "", "", errors.Wrapf(err, "asn1 marshal") } privateKeyPEM := &pem.Block{Type: "DSA PRIVATE KEY", Bytes: privBytes} @@ -89,7 +133,7 @@ func GenerateDSASSHKeypair() (string, string, error) { pub, err := exportSshPublicKey(&privateKey.PublicKey) if err != nil { - return "", "", err + return "", "", errors.Wrapf(err, "export ssh public key") } publicStr := string(pub) @@ -104,8 +148,8 @@ func GetPublicKeyScheme(pubkey ssh.PublicKey) string { return "DSA" case ssh.KeyAlgoECDSA256, ssh.KeyAlgoECDSA384, ssh.KeyAlgoECDSA521: return "ECDSA" - // case ssh.KeyAlgoED25519: - // return "ED" + case ssh.KeyAlgoED25519: + return "ED25519" } return "UNKNOWN" } diff --git a/pkg/util/seclib2/ssh_test.go b/pkg/util/seclib2/ssh_test.go index 815177acba..7d26484b20 100644 --- a/pkg/util/seclib2/ssh_test.go +++ b/pkg/util/seclib2/ssh_test.go @@ -35,6 +35,18 @@ func TestGenerateDSASSHKeypair(t *testing.T) { t.Logf("%s", pub) } +func TestGenerateECDSASHAP521SSHKeypair(t *testing.T) { + priv, pub, _ := GenerateECDSASHAP521SSHKeypair() + t.Logf("%s", priv) + t.Logf("%s", pub) +} + +func TestGenerateED25519SSHKeypair(t *testing.T) { + priv, pub, _ := GenerateED25519SSHKeypair() + t.Logf("%s", priv) + t.Logf("%s", pub) +} + func getPublicKeyPem(privateKey string) ([]byte, error) { block, _ := pem.Decode([]byte(privateKey)) if block == nil {