From 8c2250517fe19d2a101cba5e9b3ae63c4ecbf558 Mon Sep 17 00:00:00 2001 From: Qiu Jian Date: Sat, 15 Sep 2018 00:31:58 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=EF=BC=9A1.=20=E4=B8=BB?= =?UTF-8?q?=E6=9C=BA=E5=88=9B=E5=BB=BA=EF=BC=8Frebuild=20root=E5=AF=86?= =?UTF-8?q?=E7=A0=81=E8=AE=BE=E7=BD=AE=E9=97=AE=E9=A2=98=202.=20climc=20?= =?UTF-8?q?=E5=AF=86=E7=A0=81=E8=AE=BE=E7=BD=AE=E9=97=AE=E9=A2=98=203.=20b?= =?UTF-8?q?illing=5Ftype=E5=AD=97=E6=AE=B5=E4=B8=BA=E7=A9=BA=E5=AF=BC?= =?UTF-8?q?=E8=87=B4=E5=88=9B=E5=BB=BA=E4=B8=BB=E6=9C=BA=E5=A4=B1=E8=B4=A5?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/climc/shell/servers.go | 17 +++ pkg/compute/guestdrivers/aliyun.go | 3 + pkg/compute/models/billingresource.go | 2 +- pkg/compute/models/guests.go | 150 +++++++++++++------ pkg/compute/tasks/guest_rebuild_root_task.go | 14 +- pkg/httperrors/errors.go | 5 + pkg/mcclient/options/servers.go | 13 +- pkg/util/aliyun/storagecache.go | 5 +- pkg/util/seclib2/seclib.go | 19 ++- pkg/util/seclib2/seclib_test.go | 15 ++ 10 files changed, 189 insertions(+), 54 deletions(-) diff --git a/cmd/climc/shell/servers.go b/cmd/climc/shell/servers.go index a30a759422..0c4c4cb857 100644 --- a/cmd/climc/shell/servers.go +++ b/cmd/climc/shell/servers.go @@ -50,6 +50,11 @@ func init() { if err != nil { return err } + + if opts.NoAccountInit != nil && *opts.NoAccountInit { + params.Add(jsonutils.JSONFalse, "reset_password") + } + count := options.IntV(opts.Count) if options.BoolV(opts.DryRun) { results, err := modules.SchedManager.DoScheduleListResult(s, params, count) @@ -328,6 +333,11 @@ func init() { if err != nil { return err } + + if opts.NoAccountInit != nil && *opts.NoAccountInit { + params.Add(jsonutils.JSONFalse, "reset_password") + } + srv, err := modules.Servers.PerformAction(s, opts.ID, "rebuild-root", params) if err != nil { return err @@ -338,6 +348,13 @@ func init() { R(&options.ServerChangeConfigOptions{}, "server-change-config", "Change configuration of VM", func(s *mcclient.ClientSession, opts *options.ServerChangeConfigOptions) error { params, err := options.StructToParams(opts) + if len(opts.Disk) > 0 { + params.Remove("disk.0") + for i, d := range opts.Disk { + params.Set(fmt.Sprintf("disk.%d", i+1), jsonutils.NewString(d)) + } + } + if err != nil { return err } diff --git a/pkg/compute/guestdrivers/aliyun.go b/pkg/compute/guestdrivers/aliyun.go index 908bad6ff4..8b724ff283 100644 --- a/pkg/compute/guestdrivers/aliyun.go +++ b/pkg/compute/guestdrivers/aliyun.go @@ -150,6 +150,9 @@ func fetchIVMinfo(desc SAliyunVMCreateConfig, iVM cloudprovider.ICloudVM, guestI } data.Add(jsonutils.NewString("root"), "account") data.Add(jsonutils.NewString(encpasswd), "key") + } else { + data.Add(jsonutils.NewString(""), "account") + data.Add(jsonutils.NewString(""), "key") } if len(desc.OsDistribution) > 0 { diff --git a/pkg/compute/models/billingresource.go b/pkg/compute/models/billingresource.go index 3bb4303bfb..612919afdc 100644 --- a/pkg/compute/models/billingresource.go +++ b/pkg/compute/models/billingresource.go @@ -8,7 +8,7 @@ const ( ) type SBillingResourceBase struct { - BillingType string `width:"36" charset:"ascii" nullable:"true" default:"postpaid" list:"user" create:"required"` + BillingType string `width:"36" charset:"ascii" nullable:"true" default:"postpaid" list:"user" create:"optional"` ExpiredAt time.Time `nullable:"true" list:"user" create:"optional"` } diff --git a/pkg/compute/models/guests.go b/pkg/compute/models/guests.go index e12443265d..560c3c22b6 100644 --- a/pkg/compute/models/guests.go +++ b/pkg/compute/models/guests.go @@ -36,6 +36,7 @@ import ( "yunion.io/x/onecloud/pkg/mcclient/auth" "yunion.io/x/onecloud/pkg/util/httputils" "yunion.io/x/onecloud/pkg/util/logclient" + "yunion.io/x/onecloud/pkg/util/seclib2" ) const ( @@ -272,8 +273,8 @@ func (manager *SGuestManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQ if count > 0 { sgq := guestdisks.Query(guestdisks.Field("guest_id")). Filter(sqlchemy.AND( - sqlchemy.Equals(guestdisks.Field("disk_id"), disk.Id), - sqlchemy.IsFalse(guestdisks.Field("deleted")))) + sqlchemy.Equals(guestdisks.Field("disk_id"), disk.Id), + sqlchemy.IsFalse(guestdisks.Field("deleted")))) q = q.Filter(sqlchemy.In(q.Field("id"), sgq)) } else { hosts := HostManager.Query().SubQuery() @@ -281,11 +282,11 @@ func (manager *SGuestManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQ storages := StorageManager.Query().SubQuery() sq := hosts.Query(hosts.Field("id")). Join(hoststorages, sqlchemy.AND( - sqlchemy.Equals(hoststorages.Field("host_id"), hosts.Field("id")), - sqlchemy.IsFalse(hoststorages.Field("deleted")))). + sqlchemy.Equals(hoststorages.Field("host_id"), hosts.Field("id")), + sqlchemy.IsFalse(hoststorages.Field("deleted")))). Join(storages, sqlchemy.AND( - sqlchemy.Equals(storages.Field("id"), hoststorages.Field("storage_id")), - sqlchemy.IsFalse(storages.Field("deleted")))). + sqlchemy.Equals(storages.Field("id"), hoststorages.Field("storage_id")), + sqlchemy.IsFalse(storages.Field("deleted")))). Filter(sqlchemy.Equals(storages.Field("id"), disk.StorageId)).SubQuery() q = q.In("host_id", sq) } @@ -339,8 +340,8 @@ func (manager *SGuestManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQ isodev := IsolatedDeviceManager.Query().SubQuery() sgq := isodev.Query(isodev.Field("guest_id")). Filter(sqlchemy.AND( - sqlchemy.IsNotNull(isodev.Field("guest_id")), - sqlchemy.Startswith(isodev.Field("dev_type"), "GPU"))) + sqlchemy.IsNotNull(isodev.Field("guest_id")), + sqlchemy.Startswith(isodev.Field("dev_type"), "GPU"))) showGpu := utils.ToBool(gpu) cond := sqlchemy.NotIn if showGpu { @@ -561,6 +562,14 @@ func (manager *SGuestManager) ValidateCreateData(ctx context.Context, userCred m data.Add(jsonutils.NewInt(int64(vmemSize)), "vmem_size") data.Add(jsonutils.NewInt(int64(vcpuCount)), "vcpu_count") + resetPassword := jsonutils.QueryBoolean(data, "reset_password", true) + passwd, _ := data.GetString("password") + if resetPassword && len(passwd) > 0 { + if ! seclib2.MeetComplxity(passwd) { + return nil, httperrors.NewWeakPasswordError() + } + } + disk0Json, _ := data.Get("disk.0") if disk0Json == nil { return nil, httperrors.NewInputParameterError("No disk information provided") @@ -2411,41 +2420,81 @@ func (self *SGuest) PerformPurge(ctx context.Context, userCred mcclient.TokenCre return nil, err } +func (self *SGuest) setKeypairId(userCred mcclient.TokenCredential, keypairId string) error { + diff, err := self.GetModelManager().TableSpec().Update(self, func() error { + self.KeypairId = keypairId + return nil + }) + if err != nil { + db.OpsLog.LogEvent(self, db.ACT_UPDATE, diff, userCred) + } + return err +} + func (self *SGuest) AllowPerformRebuildRoot(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { return self.IsOwner(userCred) } func (self *SGuest) PerformRebuildRoot(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { imageId, _ := data.GetString("image_id") - if utils.IsInStringArray(self.Status, []string{VM_READY, VM_RUNNING, VM_ADMIN}) { - if !data.Contains("image_id") { - gdc := self.CategorizeDisks() - imageId = gdc.Root.GetTemplateId() - if len(imageId) == 0 { - return nil, httperrors.NewBadRequestError("No template for root disk") - } - img, err := CachedimageManager.getImageInfo(ctx, userCred, imageId, false) - if err != nil { - return nil, httperrors.NewBadRequestError("Template %s not accessible: %s", imageId, err.Error()) - } - osType, _ := img.Properties["os_type"] - osName := self.GetMetadata("os_name", userCred) - if len(osName) == 0 && len(osType) == 0 && strings.ToLower(osType) != strings.ToLower(osName) { - return nil, httperrors.NewBadRequestError("Cannot switch OS between %s-%s", osName, osType) - } - } - autoStart := jsonutils.QueryBoolean(data, "auto_start", false) - var needStop = false - if self.Status == VM_RUNNING { - needStop = true - } - err := self.StartRebuildRootTask(ctx, userCred, imageId, needStop, autoStart) - return nil, err + if ! utils.IsInStringArray(self.Status, []string{VM_READY, VM_RUNNING, VM_ADMIN}) { + return nil, httperrors.NewInvalidStatusError("Cannot reset root in status %s", self.Status) } - return nil, httperrors.NewInvalidStatusError("Cannot reset root in status %s", self.Status) + + if !data.Contains("image_id") { + gdc := self.CategorizeDisks() + imageId = gdc.Root.GetTemplateId() + if len(imageId) == 0 { + return nil, httperrors.NewBadRequestError("No template for root disk") + } + img, err := CachedimageManager.getImageInfo(ctx, userCred, imageId, false) + if err != nil { + return nil, httperrors.NewBadRequestError("Template %s not accessible: %s", imageId, err.Error()) + } + osType, _ := img.Properties["os_type"] + osName := self.GetMetadata("os_name", userCred) + if len(osName) == 0 && len(osType) == 0 && strings.ToLower(osType) != strings.ToLower(osName) { + return nil, httperrors.NewBadRequestError("Cannot switch OS between %s-%s", osName, osType) + } + } + + autoStart := jsonutils.QueryBoolean(data, "auto_start", false) + var needStop = false + if self.Status == VM_RUNNING { + needStop = true + } + resetPasswd := jsonutils.QueryBoolean(data, "reset_password", true) + passwd, _ := data.GetString("password") + if len(passwd) > 0 { + if ! seclib2.MeetComplxity(passwd) { + return nil, httperrors.NewWeakPasswordError() + } + } + + keypairStr := jsonutils.GetAnyString(data, []string{"keypair", "keypair_id"}) + if len(keypairStr) > 0 { + keypairObj, err := KeypairManager.FetchByIdOrName(userCred.GetUserId(), keypairStr) + if err != nil { + if err == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError("keypair %s not found", keypairStr) + } else { + return nil, httperrors.NewGeneralError(err) + } + } + if self.KeypairId != keypairObj.GetId() { + err = self.setKeypairId(userCred, keypairObj.GetId()) + if err != nil { + return nil, httperrors.NewGeneralError(err) + } + } + } + + err := self.StartRebuildRootTask(ctx, userCred, imageId, needStop, autoStart, passwd, resetPasswd) + return nil, err + } -func (self *SGuest) StartRebuildRootTask(ctx context.Context, userCred mcclient.TokenCredential, imageId string, needStop, autoStart bool) error { +func (self *SGuest) StartRebuildRootTask(ctx context.Context, userCred mcclient.TokenCredential, imageId string, needStop, autoStart bool, passwd string, resetPasswd bool) error { data := jsonutils.NewDict() data.Set("image_id", jsonutils.NewString(imageId)) if needStop { @@ -2454,6 +2503,14 @@ func (self *SGuest) StartRebuildRootTask(ctx context.Context, userCred mcclient. if autoStart { data.Set("auto_start", jsonutils.JSONTrue) } + if resetPasswd { + data.Set("reset_password", jsonutils.JSONTrue) + } else { + data.Set("reset_password", jsonutils.JSONFalse) + } + if len(passwd) > 0 { + data.Set("password", jsonutils.NewString(passwd)) + } if self.GetHypervisor() == HYPERVISOR_BAREMETAL { task, err := taskman.TaskManager.NewTask(ctx, "BaremetalServerRebuildRootTask", self, userCred, data, "", "", nil) if err != nil { @@ -2875,12 +2932,12 @@ func (self *SGuest) PerformChangeConfig(ctx context.Context, userCred mcclient.T } provider, e := self.GetHost().GetDriver() - if e != nil { + /*if e != nil { log.Errorf("Get Provider Error: %s", e) return nil, httperrors.NewInsufficientResourceError("Provider Not Found") - } + }*/ - if !provider.IsPublicCloud() { + if e != nil || !provider.IsPublicCloud() { for storageId, needSize := range diskSizes { iStorage, err := StorageManager.FetchById(storageId) if err != nil { @@ -2904,6 +2961,9 @@ func (self *SGuest) PerformChangeConfig(ctx context.Context, userCred mcclient.T if jsonutils.QueryBoolean(data, "auto_start", false) { confs.Add(jsonutils.NewBool(true), "auto_start") } + + log.Debugf("%s", confs.String()) + pendingUsage := &SQuota{} if addCpu > 0 { pendingUsage.Cpu = addCpu @@ -3140,17 +3200,23 @@ func (self *SGuest) GetDeployConfigOnHost(ctx context.Context, host *SHost, para deployAction = "deploy" } - resetPasswd := true - if deployAction == "deploy" { - resetPasswd = jsonutils.QueryBoolean(params, "reset_password", false) - } + // resetPasswd := true + // if deployAction == "deploy" { + resetPasswd := jsonutils.QueryBoolean(params, "reset_password", true) + //} if resetPasswd { config.Add(jsonutils.JSONTrue, "reset_password") + passwd, _ := params.GetString("password") + if len(passwd) > 0 { + config.Add(jsonutils.NewString(passwd), "password") + } keypair := self.getKeypair() if keypair != nil { config.Add(jsonutils.NewString(keypair.PublicKey), "public_key") } + } else { + config.Add(jsonutils.JSONFalse, "reset_password") } config.Add(jsonutils.NewString(deployAction), "action") @@ -3883,7 +3949,7 @@ func (manager *SGuestManager) GetIpInProjectWithName(projectId, name string, isE sqlchemy.IsFalse(guests.Field("pending_deleted"))), sqlchemy.IsFalse(guests.Field("deleted")))). Join(networks, sqlchemy.AND(sqlchemy.Equals(networks.Field("id"), guestnics.Field("network_id")), - sqlchemy.IsFalse(networks.Field("deleted")))). + sqlchemy.IsFalse(networks.Field("deleted")))). Filter(sqlchemy.Equals(guests.Field("name"), name)). Filter(sqlchemy.NotEquals(guestnics.Field("ip_addr"), "")). Filter(sqlchemy.IsNotNull(guestnics.Field("ip_addr"))). diff --git a/pkg/compute/tasks/guest_rebuild_root_task.go b/pkg/compute/tasks/guest_rebuild_root_task.go index 4202c450df..ebceff6cd4 100644 --- a/pkg/compute/tasks/guest_rebuild_root_task.go +++ b/pkg/compute/tasks/guest_rebuild_root_task.go @@ -139,9 +139,17 @@ func (self *KVMGuestRebuildRootTask) OnRebuildRootDiskComplete(ctx context.Conte self.SetStage("OnGuestDeployComplete", nil) guest.SetStatus(self.UserCred, models.VM_DEPLOYING, "") - params := jsonutils.NewDict() - params.Set("reset_password", jsonutils.JSONTrue) - guest.StartGuestDeployTask(ctx, self.UserCred, params, "deploy", self.GetTaskId()) + // params := jsonutils.NewDict() + // params.Set("reset_password", jsonutils.JSONTrue) + + // clear logininfo + loginParams := make(map[string]interface{}) + loginParams["login_account"] = "none" + loginParams["login_key"] = "none" + loginParams["login_key_timestamp"] = "none" + guest.SetAllMetadata(ctx, loginParams, self.UserCred) + + guest.StartGuestDeployTask(ctx, self.UserCred, self.GetParams(), "deploy", self.GetTaskId()) } func (self *KVMGuestRebuildRootTask) OnRebuildRootDiskCompleteFailed(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { diff --git a/pkg/httperrors/errors.go b/pkg/httperrors/errors.go index 4df2957513..662b59ec0e 100644 --- a/pkg/httperrors/errors.go +++ b/pkg/httperrors/errors.go @@ -100,6 +100,11 @@ func NewInputParameterError(msg string, params ...interface{}) *httputils.JSONCl return NewJsonClientError(400, "InputParameterError", msg, err) } +func NewWeakPasswordError() *httputils.JSONClientError { + msg, err := errorMessage("password must be 12 chars of at least one digit, letter, uppercase letter and punctuate", nil) + return NewJsonClientError(400, "WeakPasswordError", msg, err) +} + func NewInsufficientResourceError(msg string, params ...interface{}) *httputils.JSONClientError { msg, err := errorMessage(msg, params) return NewJsonClientError(400, "InsufficientResourceError", msg, err) diff --git a/pkg/mcclient/options/servers.go b/pkg/mcclient/options/servers.go index ed7e731b02..cd0a866205 100644 --- a/pkg/mcclient/options/servers.go +++ b/pkg/mcclient/options/servers.go @@ -98,7 +98,7 @@ type ServerCreateOptions struct { Bios string `help:"BIOS" choices:"BIOS|UEFI"` Desc string `help:"Description" metavar:"" json:"description"` Boot string `help:"Boot device" metavar:"" choices:"disk|cdrom" json:"-"` - NoAccountInit *bool `help:"Not reset account password" json:"reset_password"` + NoAccountInit *bool `help:"Not reset account password"` AllowDelete *bool `help:"Unlock server to allow deleting" json:"-"` ShutdownBehavior string `help:"Behavior after VM server shutdown, stop or terminate server" metavar:"" choices:"stop|terminate"` AutoStart *bool `help:"Auto start server after it is created"` @@ -264,11 +264,12 @@ type ServerSaveImageOptions struct { } type ServerRebuildRootOptions struct { - ID string `help:"Server to rebuild root" json:"-"` - Image string `help:"New root Image template ID" json:"image_id"` - Keypair string `help:"ssh Keypair used for login"` - Password string `help:"Default user password"` - AutoStart *bool `help:"Auto start server after it is created"` + ID string `help:"Server to rebuild root" json:"-"` + Image string `help:"New root Image template ID" json:"image_id"` + Keypair string `help:"ssh Keypair used for login"` + Password string `help:"Default user password"` + NoAccountInit *bool `help:"Not reset account password"` + AutoStart *bool `help:"Auto start server after it is created"` } type ServerChangeConfigOptions struct { diff --git a/pkg/util/aliyun/storagecache.go b/pkg/util/aliyun/storagecache.go index d945c8d054..aed2d7c32b 100644 --- a/pkg/util/aliyun/storagecache.go +++ b/pkg/util/aliyun/storagecache.go @@ -88,7 +88,10 @@ func (self *SStoragecache) GetIImages() ([]cloudprovider.ICloudImage, error) { func (self *SStoragecache) UploadImage(userCred mcclient.TokenCredential, imageId string, osArch, osType, osDist string, extId string, isForce bool) (string, error) { if len(extId) > 0 { - status, _ := self.region.GetImageStatus(extId) + status, err := self.region.GetImageStatus(extId) + if err != nil { + log.Errorf("GetImageStatus error %s", err) + } if status == ImageStatusAvailable && !isForce { return extId, nil } diff --git a/pkg/util/seclib2/seclib.go b/pkg/util/seclib2/seclib.go index af4609363b..ef1c0bb3cc 100644 --- a/pkg/util/seclib2/seclib.go +++ b/pkg/util/seclib2/seclib.go @@ -16,7 +16,7 @@ const ( ALL_DIGITS = "0123456789" ALL_LETTERS = "abcdefghijklmnopqrstuvwxyz" ALL_UPPERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - ALL_PUNC = PUNC + ALL_PUNC = "~`!@#$%^&*()-_=+[]{}|:':\",./<>?" ) type PasswordStrength struct { @@ -72,3 +72,20 @@ func AnalyzePasswordStrenth(passwd string) PasswordStrength { } return ps } + +func (ps PasswordStrength) Len() int { + return ps.Punctuats + ps.Uppercases + ps.Lowercases + ps.Digits +} + +func (ps PasswordStrength) MeetComplexity() bool { + if ps.Punctuats > 0 && ps.Digits > 0 && ps.Lowercases > 0 && ps.Uppercases > 0 && ps.Len() >= 12 { + return true + } else { + return false + } +} + +func MeetComplxity(passwd string) bool { + ps := AnalyzePasswordStrenth(passwd) + return ps.MeetComplexity() +} diff --git a/pkg/util/seclib2/seclib_test.go b/pkg/util/seclib2/seclib_test.go index c869234eba..9921aa09fb 100644 --- a/pkg/util/seclib2/seclib_test.go +++ b/pkg/util/seclib2/seclib_test.go @@ -10,3 +10,18 @@ func TestRandomPassword2(t *testing.T) { rand.Seed(time.Now().Unix()) t.Logf("%s", RandomPassword2(12)) } + +func TestMeetComplxity(t *testing.T) { + cases := [] struct { + in string + want bool + } { + {"123456", false}, + {"123abcABC!@#", true}, + } + for _, c := range cases { + if c.want != MeetComplxity(c.in) { + t.Errorf("%s != %v", c.in, c.want) + } + } +}