basic function comparing uids of host/container
This commit is contained in:
committed by
Christian Goll
parent
95050a44ce
commit
8c7fd7d140
@@ -128,7 +128,13 @@ func CobraRunE(cmd *cobra.Command, args []string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
wwlog.Printf(wwlog.WARN, "Could not copy /etc/resolv.conf into container: %s\n", err)
|
wwlog.Printf(wwlog.WARN, "Could not copy /etc/resolv.conf into container: %s\n", err)
|
||||||
}
|
}
|
||||||
|
if !NoSyncUser {
|
||||||
|
err = container.SyncUids(name)
|
||||||
|
if err != nil {
|
||||||
|
wwlog.Printf(wwlog.ERROR, "Error in user sync, fix error and run 'syncuser' manually: %s\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
fmt.Printf("Building container: %s\n", name)
|
fmt.Printf("Building container: %s\n", name)
|
||||||
err = container.Build(name, true)
|
err = container.Build(name, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -5,20 +5,20 @@ import "github.com/spf13/cobra"
|
|||||||
var (
|
var (
|
||||||
baseCmd = &cobra.Command{
|
baseCmd = &cobra.Command{
|
||||||
DisableFlagsInUseLine: true,
|
DisableFlagsInUseLine: true,
|
||||||
Use: "import [OPTIONS] SOURCE [NAME]",
|
Use: "import [OPTIONS] SOURCE [NAME]",
|
||||||
Short: "Import a container into Warewulf",
|
Short: "Import a container into Warewulf",
|
||||||
Long:
|
Long: `This command will pull and import a container into Warewulf from SOURCE,
|
||||||
`This command will pull and import a container into Warewulf from SOURCE,
|
|
||||||
optionally renaming it to NAME. The SOURCE must be in a supported URI format.
|
optionally renaming it to NAME. The SOURCE must be in a supported URI format.
|
||||||
Imported containers are used to create bootable VNFS images.`,
|
Imported containers are used to create bootable VNFS images.`,
|
||||||
Example: "wwctl container import docker://warewulf/centos-8 my_container",
|
Example: "wwctl container import docker://warewulf/centos-8 my_container",
|
||||||
RunE: CobraRunE,
|
RunE: CobraRunE,
|
||||||
Args: cobra.MinimumNArgs(1),
|
Args: cobra.MinimumNArgs(1),
|
||||||
}
|
}
|
||||||
SetForce bool
|
SetForce bool
|
||||||
SetUpdate bool
|
SetUpdate bool
|
||||||
SetBuild bool
|
SetBuild bool
|
||||||
SetDefault bool
|
SetDefault bool
|
||||||
|
NoSyncUser bool
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -26,6 +26,7 @@ func init() {
|
|||||||
baseCmd.PersistentFlags().BoolVarP(&SetUpdate, "update", "u", false, "Update and overwrite an existing container")
|
baseCmd.PersistentFlags().BoolVarP(&SetUpdate, "update", "u", false, "Update and overwrite an existing container")
|
||||||
baseCmd.PersistentFlags().BoolVarP(&SetBuild, "build", "b", false, "Build container when after pulling")
|
baseCmd.PersistentFlags().BoolVarP(&SetBuild, "build", "b", false, "Build container when after pulling")
|
||||||
baseCmd.PersistentFlags().BoolVar(&SetDefault, "setdefault", false, "Set this container for the default profile")
|
baseCmd.PersistentFlags().BoolVar(&SetDefault, "setdefault", false, "Set this container for the default profile")
|
||||||
|
baseCmd.PersistentFlags().BoolVar(&NoSyncUser, "nosyncuser", false, "Don't synchronize uis/gods from host to container")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRootCommand returns the root cobra.Command for the application.
|
// GetRootCommand returns the root cobra.Command for the application.
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/hpcng/warewulf/internal/app/wwctl/container/list"
|
"github.com/hpcng/warewulf/internal/app/wwctl/container/list"
|
||||||
"github.com/hpcng/warewulf/internal/app/wwctl/container/shell"
|
"github.com/hpcng/warewulf/internal/app/wwctl/container/shell"
|
||||||
"github.com/hpcng/warewulf/internal/app/wwctl/container/show"
|
"github.com/hpcng/warewulf/internal/app/wwctl/container/show"
|
||||||
|
"github.com/hpcng/warewulf/internal/app/wwctl/container/syncuser"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -31,6 +32,7 @@ func init() {
|
|||||||
baseCmd.AddCommand(shell.GetCommand())
|
baseCmd.AddCommand(shell.GetCommand())
|
||||||
baseCmd.AddCommand(delete.GetCommand())
|
baseCmd.AddCommand(delete.GetCommand())
|
||||||
baseCmd.AddCommand(show.GetCommand())
|
baseCmd.AddCommand(show.GetCommand())
|
||||||
|
baseCmd.AddCommand(syncuser.GetCommand())
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,8 +21,7 @@ More information about the conainer can be shown with the '-a' option.`,
|
|||||||
return list, cobra.ShellCompDirectiveNoFileComp
|
return list, cobra.ShellCompDirectiveNoFileComp
|
||||||
},
|
},
|
||||||
|
|
||||||
Aliases: []string{"sh", "chroot"},
|
Args: cobra.MinimumNArgs(1),
|
||||||
Args: cobra.MinimumNArgs(1),
|
|
||||||
}
|
}
|
||||||
ShowAll bool
|
ShowAll bool
|
||||||
)
|
)
|
||||||
|
|||||||
23
internal/app/wwctl/container/syncuser/main.go
Normal file
23
internal/app/wwctl/container/syncuser/main.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package syncuser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/hpcng/warewulf/internal/pkg/container"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CobraRunE(cmd *cobra.Command, args []string) error {
|
||||||
|
containerName := args[0]
|
||||||
|
if !container.ValidName(containerName) {
|
||||||
|
return fmt.Errorf("%s is not a valid container", containerName)
|
||||||
|
}
|
||||||
|
err := container.SyncUids(containerName)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Sprint(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
35
internal/app/wwctl/container/syncuser/root.go
Normal file
35
internal/app/wwctl/container/syncuser/root.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package syncuser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hpcng/warewulf/internal/pkg/container"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
baseCmd = &cobra.Command{
|
||||||
|
DisableFlagsInUseLine: true,
|
||||||
|
Use: "syncuser [OPTIONS] CONTAINER",
|
||||||
|
Short: "Synchronizes user in container",
|
||||||
|
Long: `Synchronize the uids and gids from the host to the container.
|
||||||
|
Users/groups which are only present in the container will be preserved if no
|
||||||
|
uid/gid collision is detected. File ownerships are also changed.`,
|
||||||
|
RunE: CobraRunE,
|
||||||
|
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
if len(args) != 0 {
|
||||||
|
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||||
|
}
|
||||||
|
list, _ := container.ListSources()
|
||||||
|
return list, cobra.ShellCompDirectiveNoFileComp
|
||||||
|
},
|
||||||
|
|
||||||
|
Args: cobra.MinimumNArgs(1),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRootCommand returns the root cobra.Command for the application.
|
||||||
|
func GetCommand() *cobra.Command {
|
||||||
|
return baseCmd
|
||||||
|
}
|
||||||
@@ -1,8 +1,16 @@
|
|||||||
package container
|
package container
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
@@ -70,3 +78,250 @@ func DeleteSource(name string) error {
|
|||||||
wwlog.Printf(wwlog.VERBOSE, "Removing path: %s\n", fullPath)
|
wwlog.Printf(wwlog.VERBOSE, "Removing path: %s\n", fullPath)
|
||||||
return os.RemoveAll(fullPath)
|
return os.RemoveAll(fullPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type completeUserInfo struct {
|
||||||
|
Name string
|
||||||
|
UidHost int `access:"r,w"`
|
||||||
|
GidHost int `access:"r,w"`
|
||||||
|
UidCont int `access:"r,w"`
|
||||||
|
GidCont int `access:"r,w"`
|
||||||
|
FileListUid []string `access:"r,w"`
|
||||||
|
FileListGid []string `access:"r,w"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type simpleUserInfo struct {
|
||||||
|
name string
|
||||||
|
uid int
|
||||||
|
gid int
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
sync the uids,gids from the host to the container
|
||||||
|
*/
|
||||||
|
func SyncUids(name string) error {
|
||||||
|
var userDb []completeUserInfo
|
||||||
|
passwdName := "/etc/passwd"
|
||||||
|
groupName := "/etc/group"
|
||||||
|
fullPath := RootFsDir(name)
|
||||||
|
hostName, err := createPasswdMap(passwdName)
|
||||||
|
if err != nil {
|
||||||
|
wwlog.Printf(wwlog.ERROR, "Could not open "+passwdName)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// populate db with the user of the
|
||||||
|
for _, user := range hostName {
|
||||||
|
userDb = append(userDb, completeUserInfo{Name: user.name,
|
||||||
|
UidHost: user.uid, GidHost: user.gid, UidCont: -1, GidCont: -1})
|
||||||
|
}
|
||||||
|
|
||||||
|
contName, err := createPasswdMap(path.Join(fullPath, passwdName))
|
||||||
|
if err != nil {
|
||||||
|
wwlog.Printf(wwlog.ERROR, "Could not open "+path.Join(fullPath, passwdName))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, userCont := range contName {
|
||||||
|
foundUser := false
|
||||||
|
for idxHost, userHost := range userDb {
|
||||||
|
if userCont.name == userHost.Name {
|
||||||
|
foundUser = true
|
||||||
|
(&userDb[idxHost]).UidCont = userCont.uid
|
||||||
|
(&userDb[idxHost]).GidCont = userCont.gid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !foundUser {
|
||||||
|
userDb = append(userDb, completeUserInfo{Name: userCont.name,
|
||||||
|
UidHost: -1, GidHost: -1, UidCont: userCont.uid, GidCont: userCont.gid})
|
||||||
|
wwlog.Printf(wwlog.WARN, "user: %s:%v:%v not present on host", userCont.name, userCont.uid, userCont.gid)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
// find out which user/goup are only in the container
|
||||||
|
var userOnlyCont []string
|
||||||
|
for _, user := range userDb {
|
||||||
|
if user.UidHost == -1 {
|
||||||
|
userOnlyCont = append(userOnlyCont, user.Name)
|
||||||
|
for _, userCheck := range userDb {
|
||||||
|
if userCheck.UidHost == user.UidCont {
|
||||||
|
wwlog.Printf(wwlog.WARN, fmt.Sprintf("uid(%v) collision for host: %s and container: %s\n",
|
||||||
|
user.UidCont, user.Name, userCheck.Name))
|
||||||
|
return errors.New(fmt.Sprintf("user %s only present in container has same uid(%v) as user %s on host,\n"+
|
||||||
|
"add this user to /etc/passwd on host", user.Name, user.UidCont, userCheck.Name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if user.GidHost == -1 {
|
||||||
|
for _, userCheck := range userDb {
|
||||||
|
if userCheck.GidHost == user.GidCont {
|
||||||
|
wwlog.Printf(wwlog.WARN, fmt.Sprintf("gid(%v) collision for host: %s and container: %s\n",
|
||||||
|
user.GidCont, user.Name, userCheck.Name))
|
||||||
|
return errors.New(fmt.Sprintf("user %s only present in container has same gid(%v) as user %s on host,\n"+
|
||||||
|
" add this group to /etc/group on host", user.Name, user.GidCont, userCheck.Name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
// create list of files which need changed ownerships in order to change them later what
|
||||||
|
// avoid uid/gid collisions
|
||||||
|
for idx, user := range userDb {
|
||||||
|
if (user.UidHost != user.UidCont && user.UidCont != -1) || (user.GidHost != user.GidCont && user.GidCont != -1) {
|
||||||
|
wwlog.Printf(wwlog.VERBOSE, fmt.Sprintf("host %s:%v:%v <-> container %s:%v:%v\n",
|
||||||
|
user.Name, user.UidHost, user.GidHost, user.Name, user.UidCont, user.GidCont))
|
||||||
|
err = filepath.Walk(fullPath, func(filePath string, info fs.FileInfo, err error) error {
|
||||||
|
// root is always good, if we failt to get UID/GID of a file
|
||||||
|
var uid, gid int
|
||||||
|
if stat, ok := info.Sys().(*syscall.Stat_t); ok {
|
||||||
|
uid = int(stat.Uid)
|
||||||
|
gid = int(stat.Gid)
|
||||||
|
}
|
||||||
|
if uid == user.UidCont {
|
||||||
|
(&userDb[idx]).FileListUid = append((&userDb[idx]).FileListUid, filePath)
|
||||||
|
}
|
||||||
|
if gid == user.GidCont {
|
||||||
|
(&userDb[idx]).FileListGid = append((&userDb[idx]).FileListGid, filePath)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// change uids and gid of file
|
||||||
|
for _, user := range userDb {
|
||||||
|
if len(user.FileListUid) != 0 {
|
||||||
|
fmt.Printf("uidList(%s): %v\n", user.Name, user.FileListUid)
|
||||||
|
for _, file := range user.FileListUid {
|
||||||
|
fsInfo, err := os.Stat(file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var gid int
|
||||||
|
if stat, ok := fsInfo.Sys().(*syscall.Stat_t); ok {
|
||||||
|
gid = int(stat.Gid)
|
||||||
|
}
|
||||||
|
wwlog.Printf(wwlog.DEBUG, "%s chown(%v,%v)\n", file, user.UidHost, gid)
|
||||||
|
err = os.Chown(file, user.UidHost, gid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(user.FileListGid) != 0 {
|
||||||
|
fmt.Printf("gidList(%s): %v\n", user.Name, user.FileListGid)
|
||||||
|
for _, file := range user.FileListGid {
|
||||||
|
fsInfo, err := os.Stat(file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var uid int
|
||||||
|
if stat, ok := fsInfo.Sys().(*syscall.Stat_t); ok {
|
||||||
|
uid = int(stat.Uid)
|
||||||
|
}
|
||||||
|
wwlog.Printf(wwlog.DEBUG, "%s chown(%v,%v)\n", file, user.UidHost, uid)
|
||||||
|
err = os.Chown(file, uid, user.GidHost)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
// get the entries for the passwd/group file before copy over
|
||||||
|
passwdEntries, err := getEntires(path.Join(fullPath, passwdName), userOnlyCont)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// implicitly assuming that users/groups which only exists on the host have the same name
|
||||||
|
groupEntries, err := getEntires(path.Join(fullPath, groupName), userOnlyCont)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = util.CopyFile(passwdName, path.Join(fullPath, passwdName)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = util.CopyFile(groupName, path.Join(fullPath, groupName)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = appendLines(passwdName, passwdEntries); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = appendLines(groupName, groupEntries); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
creates simple user db []simpleUserInfo for a /etc/{passwd|group} file
|
||||||
|
*/
|
||||||
|
func createPasswdMap(fileName string) ([]simpleUserInfo, error) {
|
||||||
|
var nameDb []simpleUserInfo
|
||||||
|
file, err := os.Open(fileName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
fileScanner := bufio.NewScanner(file)
|
||||||
|
for fileScanner.Scan() {
|
||||||
|
line := fileScanner.Text()
|
||||||
|
entries := strings.Split(line, ":")
|
||||||
|
name := entries[0]
|
||||||
|
uid, err := strconv.Atoi(entries[2])
|
||||||
|
if err != nil {
|
||||||
|
wwlog.Printf(wwlog.WARN, "could not parse uid(%s) for %s\n", entries[2], name)
|
||||||
|
}
|
||||||
|
gid, err := strconv.Atoi(entries[3])
|
||||||
|
if err != nil {
|
||||||
|
wwlog.Printf(wwlog.WARN, "could not parse gid(%s) for %s\n", entries[2], name)
|
||||||
|
}
|
||||||
|
if name != "" {
|
||||||
|
nameDb = append(nameDb, simpleUserInfo{name: name, uid: uid, gid: gid})
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wwlog.Printf(wwlog.DEBUG, fmt.Sprintf("created uid/gid map with %v entries from %s\n", len(nameDb), fileName))
|
||||||
|
return nameDb, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Creates a slice with the entries of of passwd for the given slice of user names
|
||||||
|
*/
|
||||||
|
func getEntires(fileName string, names []string) ([]string, error) {
|
||||||
|
file, err := os.Open(fileName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
var entries []string
|
||||||
|
fileScanner := bufio.NewScanner(file)
|
||||||
|
for fileScanner.Scan() {
|
||||||
|
line := fileScanner.Text()
|
||||||
|
entries := strings.Split(line, ":")
|
||||||
|
for _, name := range names {
|
||||||
|
if entries[0] == name {
|
||||||
|
entries = append(entries, line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return entries, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Appending the lines to the given file
|
||||||
|
*/
|
||||||
|
func appendLines(fileName string, lines []string) error {
|
||||||
|
wwlog.Printf(wwlog.VERBOSE, "appending %v lines to %s\n", len(lines), fileName)
|
||||||
|
file, err := os.OpenFile(fileName, os.O_APPEND, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
for _, line := range lines {
|
||||||
|
if _, err := file.WriteString(line); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user