diff --git a/pkg/apis/compute/baremetal.go b/pkg/apis/compute/baremetal.go index 179a8062c5..d2e9479841 100644 --- a/pkg/apis/compute/baremetal.go +++ b/pkg/apis/compute/baremetal.go @@ -23,3 +23,17 @@ type SGMapItem struct { Type int LinuxDeviceName string } + +const ( + IPMIUserPrivUser = "USER" + IPMIUSERPrivAdmin = "ADMINISTRATOR" +) + +type IPMIUser struct { + Id int + Name string + Callin bool + LinkAuth bool + IPMIMsg bool + Priv string +} diff --git a/pkg/baremetal/manager.go b/pkg/baremetal/manager.go index 81c239f5f3..53a465aeba 100644 --- a/pkg/baremetal/manager.go +++ b/pkg/baremetal/manager.go @@ -1056,7 +1056,7 @@ func (b *SBaremetalInstance) enableWire(mac net.HardwareAddr, ipAddr string, nic if len(netType) > 0 { params.Add(jsonutils.NewString(netType), "net_type") } - log.Errorf("enable net if params: %s", params.String()) + log.Infof("enable net if params: %s", params.String()) return modules.Hosts.PerformAction(session, b.GetId(), "enable-netif", params) } diff --git a/pkg/baremetal/tasks/baseprepare.go b/pkg/baremetal/tasks/baseprepare.go index 4112291161..b10ce29ceb 100644 --- a/pkg/baremetal/tasks/baseprepare.go +++ b/pkg/baremetal/tasks/baseprepare.go @@ -113,11 +113,18 @@ func (task *sBaremetalPrepareTask) prepareBaremetalInfo(cli *ssh.Client) (*barem } return &baremetalPrepareInfo{ - sysInfo, cpuInfo, dmiCPUInfo, memInfo, nicsInfo, diskInfo, storageDriver, ipmiInfo, + sysInfo, + cpuInfo, + dmiCPUInfo, + memInfo, + nicsInfo, + diskInfo, + storageDriver, + ipmiInfo, }, nil } -func (task *sBaremetalPrepareTask) ipmiNicDHCP(cli *ssh.Client, i *baremetalPrepareInfo) error { +func (task *sBaremetalPrepareTask) configIPMISetting(cli *ssh.Client, i *baremetalPrepareInfo) error { if !i.ipmiInfo.Present { return nil } @@ -153,9 +160,11 @@ func (task *sBaremetalPrepareTask) ipmiNicDHCP(cli *ssh.Client, i *baremetalPrep Speed: 100, Mtu: 1500, } - task.sendNicInfo(ipmiNic, -1, types.NIC_TYPE_IPMI, true, "") + if err := task.sendNicInfo(ipmiNic, -1, types.NIC_TYPE_IPMI, true, ""); err != nil { + log.Errorf("Send IPMI nic %#v info: %v", ipmiNic, err) + } rootId := ipmitool.GetRootId(ipmiSysInfo) - err = ipmitool.SetLanUserPasswd(sshIPMI, lanChannel, rootId, ipmiUser, ipmiPasswd) + err = ipmitool.CreateOrSetAdminUser(sshIPMI, lanChannel, rootId, ipmiUser, ipmiPasswd) if err != nil { // ignore the error log.Errorf("Lan channel %d set user password error: %v", lanChannel, err) @@ -197,7 +206,7 @@ func (task *sBaremetalPrepareTask) ipmiNicDHCP(cli *ssh.Client, i *baremetalPrep if err != nil { log.Errorf("Set lan channel %d dhcp error: %v", lanChannel, err) } - time.Sleep(1 * time.Second) + time.Sleep(2 * time.Second) nic := task.baremetal.GetIPMINic(conf.Mac) maxTries := 180 // wait 3 minutes for tried := 0; nic != nil && nic.IpAddr == "" && tried < maxTries; tried++ { @@ -208,9 +217,11 @@ func (task *sBaremetalPrepareTask) ipmiNicDHCP(cli *ssh.Client, i *baremetalPrep if err != nil { log.Errorf("Do BMC reset error: %v", err) } + time.Sleep(1 * time.Second) } for tried := 0; nic != nil && nic.IpAddr == "" && tried < maxTries; tried++ { nic = task.baremetal.GetIPMINic(conf.Mac) + time.Sleep(1 * time.Second) } if nic != nil && len(nic.IpAddr) == 0 { log.Errorf("DHCP wait IPMI address fail, retry ...") @@ -262,8 +273,8 @@ func (task *sBaremetalPrepareTask) DoPrepare(cli *ssh.Client) error { return err } - // set ipmi nic DHCP - if err = task.ipmiNicDHCP(cli, infos); err != nil { + // set ipmi nic address and user password + if err = task.configIPMISetting(cli, infos); err != nil { return err } @@ -550,8 +561,7 @@ func (task *sBaremetalPrepareTask) sendNicInfo(nic *types.SNicDevInfo, idx int, if err != nil { return err } - task.baremetal.SaveDesc(resp) - return nil + return task.baremetal.SaveDesc(resp) } func (task *sBaremetalPrepareTask) sendStorageInfo(size int64) error { diff --git a/pkg/baremetal/utils/ipmitool/ipmitool.go b/pkg/baremetal/utils/ipmitool/ipmitool.go index 22f124bac7..1c9cb51897 100644 --- a/pkg/baremetal/utils/ipmitool/ipmitool.go +++ b/pkg/baremetal/utils/ipmitool/ipmitool.go @@ -28,6 +28,7 @@ import ( "yunion.io/x/pkg/util/stringutils" "yunion.io/x/pkg/utils" + "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/baremetal/profiles" "yunion.io/x/onecloud/pkg/cloudcommon/types" "yunion.io/x/onecloud/pkg/util/procutils" @@ -307,17 +308,58 @@ func EnableLanAccess(exector IPMIExecutor, channel int) error { return setLanAccess(exector, channel, "on") } -func SetLanUserPasswd(exector IPMIExecutor, channel int, rootId int, user string, password string) error { +func ListLanUsers(exector IPMIExecutor, channel int) ([]compute.IPMIUser, error) { + args := newArgs("user", "list", channel) + ret, err := ExecuteCommands(exector, args) + if err != nil { + return nil, errors.Wrapf(err, "list user at channel %d", channel) + } + return sysutils.ParseIPMIUser(ret), nil +} + +func CreateOrSetAdminUser(exector IPMIExecutor, channel int, rootId int, username string, password string) error { + users, err := ListLanUsers(exector, channel) + if err != nil { + return errors.Wrap(err, "List users") + } + if len(users) == 0 { + return errors.Errorf("Empty users at channel %d", channel) + } + var foundUser *compute.IPMIUser = nil + for _, user := range users { + tmp := user + if user.Name == username { + foundUser = &tmp + break + } + } + if foundUser == nil { + minEmptyUserId := -1 + for _, user := range users { + if user.Name == "" { + minEmptyUserId = user.Id + break + } + } + if minEmptyUserId == -1 { + log.Warningf("Not found min empty user id, use root id %d to set", rootId) + minEmptyUserId = rootId + } + return SetIdUserPasswd(exector, channel, minEmptyUserId, username, password) + } + return SetLanUserAdminPasswd(exector, channel, foundUser.Id, password) +} + +func SetLanUserAdminPasswd(exector IPMIExecutor, channel int, id int, password string) error { var err error password, err = stage_stringutils.EscapeEchoString(password) if err != nil { return fmt.Errorf("EscapeEchoString for password: %s, error: %v", password, err) } args := []Args{ - newArgs("user", "enable", rootId), - newArgs("user", "set", "name", rootId, user), - newArgs("user", "set", "password", rootId, fmt.Sprintf("\"%s\"", password)), - newArgs("user", "priv", rootId, 4, channel), + newArgs("user", "enable", id), + newArgs("user", "set", "password", id, fmt.Sprintf("\"%s\"", password)), + newArgs("user", "priv", id, 4, channel), } err = doActions(exector, "set_lan_user_password", args...) if err != nil { @@ -326,19 +368,27 @@ func SetLanUserPasswd(exector IPMIExecutor, channel int, rootId int, user string args = []Args{newArgs( "raw", "0x06", "0x43", fmt.Sprintf("0x%02x", 0xb0+channel), - fmt.Sprintf("0x%02x", rootId), "0x04", "0x00")} + fmt.Sprintf("0x%02x", id), "0x04", "0x00")} err = doActions(exector, "set_lan_user_password2", args...) if err == nil { return nil } args = []Args{newArgs( "channel", "setaccess", channel, - rootId, "link=on", "ipmi=on", + id, "link=on", "ipmi=on", "callin=on", "privilege=4", )} return doActions(exector, "set_lan_user_password3", args...) } +func SetIdUserPasswd(exector IPMIExecutor, channel int, id int, user string, password string) error { + args := newArgs("user", "set", "name", id, user) + if err := doActions(exector, fmt.Sprintf("set_id%d_name", id), args); err != nil { + return errors.Wrapf(err, "change root id %d to name %s", id, user) + } + return SetLanUserAdminPasswd(exector, channel, id, password) +} + func SetLanPasswd(exector IPMIExecutor, rootId int, password string) error { password, err := stringutils2.EscapeEchoString(password) if err != nil { diff --git a/pkg/compute/models/hosts.go b/pkg/compute/models/hosts.go index 83fcfe7000..810dc72b2e 100644 --- a/pkg/compute/models/hosts.go +++ b/pkg/compute/models/hosts.go @@ -50,6 +50,7 @@ import ( "yunion.io/x/onecloud/pkg/mcclient/modules" "yunion.io/x/onecloud/pkg/util/httputils" "yunion.io/x/onecloud/pkg/util/logclient" + "yunion.io/x/onecloud/pkg/util/seclib2" ) /* @@ -2594,6 +2595,10 @@ func (manager *SHostManager) ValidateCreateData(ctx context.Context, userCred mc if len(ipmiIpAddr) > 0 && !NetworkManager.IsValidOnPremiseNetworkIP(ipmiIpAddr) { return nil, httperrors.NewInputParameterError("%s is out of network IP ranges", ipmiIpAddr) } + ipmiPasswd, _ := ipmiInfo.GetString("password") + if len(ipmiPasswd) > 0 && !seclib2.MeetComplxity(ipmiPasswd) { + return nil, httperrors.NewWeakPasswordError() + } return manager.SEnabledStatusStandaloneResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, data) } diff --git a/pkg/util/sysutils/sysutils.go b/pkg/util/sysutils/sysutils.go index 13a02b3869..6934ea1ccb 100644 --- a/pkg/util/sysutils/sysutils.go +++ b/pkg/util/sysutils/sysutils.go @@ -311,3 +311,34 @@ func Start(closeFd bool, args ...string) (p *os.Process, err error) { } return nil, err } + +func ParseIPMIUser(lines []string) []compute.IPMIUser { + ret := make([]compute.IPMIUser, 0) + for _, l := range lines { + if strings.HasPrefix(l, "ID") { + continue + } + fields := strings.Fields(l) + if strings.Contains(l, "Empty User") { + id, err := strconv.Atoi(fields[0]) + if err != nil { + continue + } + ret = append(ret, compute.IPMIUser{Id: id}) + continue + } + if len(fields) != 6 { + continue + } + id, err := strconv.Atoi(fields[0]) + if err != nil { + continue + } + ret = append(ret, compute.IPMIUser{ + Id: id, + Name: fields[1], + Priv: fields[5], + }) + } + return ret +} diff --git a/pkg/util/sysutils/sysutils_test.go b/pkg/util/sysutils/sysutils_test.go index 28e5412a2b..435f69d215 100644 --- a/pkg/util/sysutils/sysutils_test.go +++ b/pkg/util/sysutils/sysutils_test.go @@ -401,3 +401,57 @@ func TestParseSGMap(t *testing.T) { }) } } + +func TestParseIPMIUser(t *testing.T) { + tests := []struct { + name string + args []string + want []compute.IPMIUser + }{ + { + name: "empty line", + args: []string{"ID Name Callin Link Auth IPMI Msg Channel Priv Limit"}, + want: []compute.IPMIUser{}, + }, + { + name: "users", + args: []string{ + "ID Name Callin Link Auth IPMI Msg Channel Priv Limit", + "1 root true false true ADMINISTRATOR", + "2 admin true false true ADMINISTRATOR", + "3 true false true USER", + "4 (Empty User) true false false NO ACCESS", + "5 (Empty User) true false false NO ACCESS", + }, + want: []compute.IPMIUser{ + { + Id: 1, + Name: "root", + Priv: "ADMINISTRATOR", + }, + { + Id: 2, + Name: "admin", + Priv: "ADMINISTRATOR", + }, + { + Id: 4, + Name: "", + Priv: "", + }, + { + Id: 5, + Name: "", + Priv: "", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := ParseIPMIUser(tt.args); !reflect.DeepEqual(got, tt.want) { + t.Errorf("ParseIPMIUser() = %v, want %v", got, tt.want) + } + }) + } +}