// 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 fsdriver import ( "fmt" "path" "path/filepath" "strings" "syscall" "yunion.io/x/jsonutils" "yunion.io/x/log" "yunion.io/x/pkg/errors" "yunion.io/x/pkg/object" "yunion.io/x/onecloud/pkg/cloudcommon/types" deployapi "yunion.io/x/onecloud/pkg/hostman/hostdeployer/apis" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" modules "yunion.io/x/onecloud/pkg/mcclient/modules/compute" "yunion.io/x/onecloud/pkg/util/procutils" ) type sGuestRootFsDriver struct { object.SObject rootFs IDiskPartition } func newGuestRootFsDriver(rootFs IDiskPartition) *sGuestRootFsDriver { return &sGuestRootFsDriver{ rootFs: rootFs, } } func (d *sGuestRootFsDriver) GetIRootFsDriver() IRootFsDriver { return d.GetVirtualObject().(IRootFsDriver) } func (d *sGuestRootFsDriver) DeployFiles(deploys []*deployapi.DeployContent) error { caseInsensitive := d.IsFsCaseInsensitive() for _, deploy := range deploys { var modAppend = false if deploy.Action == "append" { modAppend = true } if len(deploy.Path) == 0 { return fmt.Errorf("Deploy file missing param path") } dirname := filepath.Dir(deploy.Path) if !d.GetPartition().Exists(dirname, caseInsensitive) { modeRWXOwner := syscall.S_IRWXU | syscall.S_IRGRP | syscall.S_IXGRP | syscall.S_IROTH | syscall.S_IXOTH err := d.GetPartition().Mkdir(dirname, modeRWXOwner, caseInsensitive) if err != nil { log.Errorf("Mkdir %s fail %s", dirname, err) return errors.Wrap(err, "Mkdir") } err = d.GetPartition().Chmod(dirname, uint32(modeRWXOwner), caseInsensitive) if err != nil { log.Errorf("Chmod %s fail %s", dirname, err) return errors.Wrap(err, "Chmod") } } if len(deploy.Content) > 0 { err := d.GetPartition().FilePutContents(deploy.Path, deploy.Content, modAppend, caseInsensitive) if err != nil { log.Errorln(err) return err } } } return nil } func (d *sGuestRootFsDriver) DeployTelegraf(string) (bool, error) { return false, nil } func (d *sGuestRootFsDriver) GetPartition() IDiskPartition { return d.rootFs } func (d *sGuestRootFsDriver) RootExcludeSignatures() []string { return []string{} } func (d *sGuestRootFsDriver) IsFsCaseInsensitive() bool { return false } func (d *sGuestRootFsDriver) DeployYunionroot(rootfs IDiskPartition, pubkeys *deployapi.SSHKeys, isInit, enableCloudInit bool) error { return nil } func (d *sGuestRootFsDriver) DeployUdevSubsystemScripts(rootfs IDiskPartition) error { return nil } func (d *sGuestRootFsDriver) DeployStandbyNetworkingScripts(part IDiskPartition, nics, nicsStandby []*types.SServerNic) error { return nil } func (d *sGuestRootFsDriver) DeployFstabScripts(_ IDiskPartition, _ []*deployapi.Disk) error { return nil } func (d *sGuestRootFsDriver) EnableSerialConsole(rootfs IDiskPartition, sysInfo *jsonutils.JSONDict) error { return nil } func (d *sGuestRootFsDriver) DisableSerialConsole(rootfs IDiskPartition) error { return nil } func (d *sGuestRootFsDriver) CommitChanges(rootfs IDiskPartition) error { return nil } func (d *sGuestRootFsDriver) DetectIsUEFISupport(IDiskPartition) bool { return false } func (l *sGuestRootFsDriver) IsCloudinitInstall() bool { return false } func (l *sGuestRootFsDriver) IsResizeFsPartitionSupport() bool { return true } func (r *sGuestRootFsDriver) CleanNetworkScripts(rootFs IDiskPartition) error { return nil } func (r *sGuestRootFsDriver) AllowAdminLogin() bool { return true } func (m *sGuestRootFsDriver) DeployQgaBlackList(part IDiskPartition) error { return nil } func (r *sGuestRootFsDriver) DeployQgaService(part IDiskPartition) error { return nil } const ( modeAuthorizedKeysRWX = syscall.S_IRUSR | syscall.S_IWUSR | syscall.S_IXUSR modeAuthorizedKeysRW = syscall.S_IRUSR | syscall.S_IWUSR ) func deployAuthorizedKeys(rootFs IDiskPartition, authFile string, uid, gid int, pubkeys *deployapi.SSHKeys, replace bool, admin bool) error { var oldKeys = "" if !replace { bOldKeys, _ := rootFs.FileGetContents(authFile, false) oldKeys = string(bOldKeys) } newKeys := MergeAuthorizedKeys(oldKeys, pubkeys, admin) if err := rootFs.FilePutContents(authFile, newKeys, false, false); err != nil { return fmt.Errorf("Put keys to %s: %v", authFile, err) } if err := rootFs.Chown(authFile, uid, gid, false); err != nil { return fmt.Errorf("Chown %s to uid: %d, gid: %d: %v", authFile, uid, gid, err) } if err := rootFs.Chmod(authFile, uint32(modeAuthorizedKeysRW), false); err != nil { return fmt.Errorf("Chmod %s to %d error: %v", authFile, uint32(modeAuthorizedKeysRW), err) } return nil } func DeployAuthorizedKeys(rootFs IDiskPartition, usrDir string, pubkeys *deployapi.SSHKeys, replace bool, admin bool) error { usrStat := rootFs.Stat(usrDir, false) if usrStat != nil { sshDir := path.Join(usrDir, ".ssh") authFile := path.Join(sshDir, "authorized_keys") fStat, _ := usrStat.Sys().(*syscall.Stat_t) uid := int(fStat.Uid) gid := int(fStat.Gid) if !rootFs.Exists(sshDir, false) { err := rootFs.Mkdir(sshDir, modeAuthorizedKeysRWX, false) if err != nil { log.Errorln(err) return err } err = rootFs.Chown(sshDir, uid, gid, false) if err != nil { log.Errorln(err) return err } } return deployAuthorizedKeys(rootFs, authFile, uid, gid, pubkeys, replace, admin) } return nil } const sshKeySignature = "@yunioncloudpods" func MergeAuthorizedKeys(oldKeys string, pubkeys *deployapi.SSHKeys, isAdmin bool) string { var allkeys = make(map[string]string) if len(oldKeys) > 0 { for _, line := range strings.Split(oldKeys, "\n") { line = strings.TrimSpace(line) dat := strings.Split(line, " ") if len(dat) > 1 { if len(dat) > 2 && dat[2] == sshKeySignature { // skip ssh keys with signature continue } if _, ok := allkeys[dat[1]]; !ok { allkeys[dat[1]] = line } } } } if len(pubkeys.DeletePublicKey) > 0 { dat := strings.Split(pubkeys.DeletePublicKey, " ") if len(dat) > 1 { if _, ok := allkeys[dat[1]]; ok { delete(allkeys, dat[1]) } } } var candiateKeys []string if isAdmin { candiateKeys = []string{pubkeys.AdminPublicKey, pubkeys.ProjectPublicKey} } else { candiateKeys = []string{pubkeys.PublicKey} } for _, k := range candiateKeys { if len(k) > 0 { k = strings.TrimSpace(k) dat := strings.Split(k, " ") if len(dat) > 1 { if _, ok := allkeys[dat[1]]; !ok { allkeys[dat[1]] = strings.Join([]string{dat[0], dat[1], sshKeySignature}, " ") } } } } var keys = make([]string, 0) for _, val := range allkeys { keys = append(keys, val) } return strings.Join(keys, "\n") + "\n" } func DeployAdminAuthorizedKeys(s *mcclient.ClientSession) error { sshDir := path.Join("/root", ".ssh") output, err := procutils.NewRemoteCommandAsFarAsPossible("mkdir", "-p", sshDir).Output() if err != nil { return errors.Wrapf(err, "mkdir .ssh %s", output) } query := jsonutils.NewDict() query.Set("admin", jsonutils.JSONTrue) ret, err := modules.Sshkeypairs.List(s, query) if err != nil { return errors.Wrap(err, "modules.Sshkeypairs.List") } if len(ret.Data) == 0 { return errors.Wrap(httperrors.ErrNotFound, "Not found admin sshkey") } keys := ret.Data[0] adminPublicKey, _ := keys.GetString("public_key") pubKeys := &deployapi.SSHKeys{AdminPublicKey: adminPublicKey} var oldKeys string authFile := path.Join(sshDir, "authorized_keys") if procutils.NewRemoteCommandAsFarAsPossible("test", "-f", authFile).Run() == nil { output, err := procutils.NewRemoteCommandAsFarAsPossible("cat", authFile).Output() if err != nil { return errors.Wrapf(err, "cat: %s", output) } oldKeys = string(output) } newKeys := MergeAuthorizedKeys(oldKeys, pubKeys, true) if output, err := procutils.NewRemoteCommandAsFarAsPossible( "sh", "-c", fmt.Sprintf("echo '%s' > %s", newKeys, authFile)).Output(); err != nil { return errors.Wrapf(err, "write public keys: %s", output) } if output, err := procutils.NewRemoteCommandAsFarAsPossible( "chmod", "0644", authFile).Output(); err != nil { return errors.Wrapf(err, "chmod failed %s", output) } return nil }