From bedbbac318b26b21bc5bc0f6a2b2c32a399c21a254da3294f129488051811521 Mon Sep 17 00:00:00 2001 From: Christian Goll Date: Thu, 12 Oct 2023 07:44:53 +0000 Subject: [PATCH 01/13] - check if automatic configured ip range doesn'y contain ip address of ww4 host OBS-URL: https://build.opensuse.org/package/show/network:cluster/warewulf4?expand=0&rev=42 --- config-ww4.sh | 30 +++++++++++++++++++++++++++++- warewulf4.changes | 6 ++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/config-ww4.sh b/config-ww4.sh index eda006b..1a33d30 100644 --- a/config-ww4.sh +++ b/config-ww4.sh @@ -27,6 +27,21 @@ network_address() { echo $( IFS=.; echo "${octets[*]}" ) } +# Check if last Octed is in range +function is_ip_in_range() { + # split the ip addresses into their octets. + local ip_start_octets=($(echo $DYNSTART | tr "." " ")) + local ip_end_octets=($(echo $DYNEND | tr "." " ")) + local ip_address_octets=($(echo $1 | tr "." " ")) + + # compare the octets one at a time to see if the ip address is within the range. + if [[ ${ip_address_octets[3]} -lt ${ip_start_octets[3]} || ${ip_address_octets[3]} -gt ${ip_end_octets[3]} ]]; then + return 1 + fi + # if we reach this point, the ip address is in the range. + return 0 +} + echo "-- WW4 CONFIGURAION $* --" # Make sure that a ip address was defined for out network so that @@ -39,13 +54,21 @@ IP4NET=$(network_address "$IP4/$IP4PREFIX") if [ "$IP4PREFIX" -gt 25 ] ; then echo "ERROR: warewulf does at least a /25 network for dynamic addresses" + cat << EOF +ipaddr: $IP4 +netmask: $IP4MASK +network: $IP4NET + range start: $DYNSTART + range end: $DYNEND +EOF exit 1 fi DYNSIZE=20 DYNSTART=${IP4#*.*.*.} +DYNSTART=$(( $DYNSTART + 2)) DYNPRE=${IP4%.*} -DYNEND=$(( $DYNSTART + $DYNSIZE )) +DYNEND=$(( $DYNSTART + $DYNSIZE + 1 )) if [ $DYNEND -gt 254 ] ; then DYNEND=$(( $IPNET + 2 + $DYNSIZE )) DYNSTART=$(( $IPNET + 2 )) @@ -53,6 +76,11 @@ fi DYNSTART="${DYNPRE}.${DYNSTART}" DYNEND="${DYNPRE}.${DYNEND}" +if is_ip_in_range $IP4 ; then + echo "ERROR: ip address is in range for dynamic address, please set this manually" + exit 1 +fi + if [ -e $WW4CONF ] ; then test -n $IP4 && sed -i 's/^ipaddr:.*/ipaddr: '$IP4'/' $WW4CONF diff --git a/warewulf4.changes b/warewulf4.changes index 954c44e..f4429a7 100644 --- a/warewulf4.changes +++ b/warewulf4.changes @@ -1,3 +1,9 @@ +------------------------------------------------------------------- +Thu Oct 12 07:43:32 UTC 2023 - Christian Goll + +- check if automatic configured ip range doesn'y contain ip address + of ww4 host + ------------------------------------------------------------------- Thu Sep 21 09:23:31 UTC 2023 - Christian Goll From 45e0aded2ecc4475a103d8fa0ae49583ac1ebf2caa9c36f458a1cd4d75b33494 Mon Sep 17 00:00:00 2001 From: Christian Goll Date: Thu, 12 Oct 2023 07:45:29 +0000 Subject: [PATCH 02/13] of ww4 host (bsc#1215583) OBS-URL: https://build.opensuse.org/package/show/network:cluster/warewulf4?expand=0&rev=43 --- warewulf4.changes | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/warewulf4.changes b/warewulf4.changes index f4429a7..fa21c8b 100644 --- a/warewulf4.changes +++ b/warewulf4.changes @@ -2,7 +2,8 @@ Thu Oct 12 07:43:32 UTC 2023 - Christian Goll - check if automatic configured ip range doesn'y contain ip address - of ww4 host + of ww4 host (bsc#1215583) + ------------------------------------------------------------------- Thu Sep 21 09:23:31 UTC 2023 - Christian Goll From a1e977c8a438f778e0060a1ca20731046ae48dc60748acd1c26fcfe0e345e3de Mon Sep 17 00:00:00 2001 From: Christian Goll Date: Thu, 21 Dec 2023 15:03:59 +0000 Subject: [PATCH 03/13] Accepting request 1134480 from home:mslacken:pr - updated to v4.5.0rc0 pre release with following new features: * disks, partitions and file systems can set in the configration and if ignition is present in the container, changes to the disks will be made * grub can be used as alternative boot method instead of iPXE. The grub binairy is extracted from the container and shim is executed before the grub. This enables secure boot * wwctl has now the genconfig subcommand which will print/create valid configurations * all paths e.g the overlay dir, can now be configured in warewulf.conf - notable bug fixes are: * Fixed a bug where profile tags were erroneously overridden by empty node values. * Fixed bug where tags from profiles weren't rendered - added grub-boot.patch which includes the not merged grub support OBS-URL: https://build.opensuse.org/request/show/1134480 OBS-URL: https://build.opensuse.org/package/show/network:cluster/warewulf4?expand=0&rev=44 --- CreateMt-Targets.patch | 1004 --------------------- Fix-for-CVE-2022-41723.patch | 370 -------- config-ww4.sh | 4 +- grub-boot.patch | 1647 ++++++++++++++++++++++++++++++++++ vendor.tar.gz | 4 +- warewulf4-v4.5.0rc0.tar.gz | 3 + warewulf4.changes | 20 + warewulf4.spec | 156 ++-- 8 files changed, 1767 insertions(+), 1441 deletions(-) delete mode 100644 CreateMt-Targets.patch delete mode 100644 Fix-for-CVE-2022-41723.patch create mode 100644 grub-boot.patch create mode 100644 warewulf4-v4.5.0rc0.tar.gz diff --git a/CreateMt-Targets.patch b/CreateMt-Targets.patch deleted file mode 100644 index 10ac360..0000000 --- a/CreateMt-Targets.patch +++ /dev/null @@ -1,1004 +0,0 @@ -diff --git a/CHANGELOG.md b/CHANGELOG.md -index 04a40983..a36b77a3 100644 ---- a/CHANGELOG.md -+++ b/CHANGELOG.md -@@ -56,7 +56,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - The environment variable `WW_CONTAINER_SHELL` is defined in a `wwctl - container shell` environment to indicate the container in use. #579 - - Network interface configuration (`ifcfg`) files now include the -- interface name and type. #574 -+ interface name and type. #457 - - ### Fixed - -diff --git a/Makefile b/Makefile -index b494dd69..6363b653 100644 ---- a/Makefile -+++ b/Makefile -@@ -96,7 +96,7 @@ export GOPROXY - WW_GO_BUILD_TAGS := containers_image_openpgp containers_image_ostree - - # Default target --all: config vendor wwctl wwclient bash_completion.d man_pages config_defaults print_defaults wwapid wwapic wwapird -+all: config vendor wwctl wwclient bash_completion.d man_pages config_defaults print_defaults wwapid wwapic wwapird print_mnts - - # Validate source and build all packages - build: lint test-it vet all -@@ -247,6 +247,7 @@ config_defaults: vendor cmd/config_defaults/config_defaults.go - print_defaults: vendor cmd/print_defaults/print_defaults.go - cd cmd/print_defaults && go build -ldflags="-X 'github.com/hpcng/warewulf/internal/pkg/warewulfconf.ConfigFile=./etc/warewulf.conf'" -o ../../print_defaults - -+ - update_configuration: vendor cmd/update_configuration/update_configuration.go - cd cmd/update_configuration && go build -ldflags="-X 'github.com/hpcng/warewulf/internal/pkg/warewulfconf.ConfigFile=./etc/warewulf.conf'\ - -X 'github.com/hpcng/warewulf/internal/pkg/node.ConfigFile=./etc/nodes.conf'"\ -diff --git a/etc/warewulf.conf b/etc/warewulf.conf -index d0a0c4db..a5b9a346 100644 ---- a/etc/warewulf.conf -+++ b/etc/warewulf.conf -@@ -29,3 +29,7 @@ nfs: - mount options: defaults - mount: false - systemd name: nfs-server -+container mounts: -+ - source: /etc/resolv.conf -+ dest: /etc/resolv.conf -+ readonly: true -diff --git a/internal/app/wwctl/container/exec/child/main.go b/internal/app/wwctl/container/exec/child/main.go -index dda8b757..bf32ad17 100644 ---- a/internal/app/wwctl/container/exec/child/main.go -+++ b/internal/app/wwctl/container/exec/child/main.go -@@ -7,11 +7,15 @@ import ( - "fmt" - "os" - "path" -+ "path/filepath" -+ "strings" - "syscall" -- "time" - - "github.com/hpcng/warewulf/internal/pkg/container" -+ "github.com/hpcng/warewulf/internal/pkg/node" -+ "github.com/hpcng/warewulf/internal/pkg/overlay" - "github.com/hpcng/warewulf/internal/pkg/util" -+ "github.com/hpcng/warewulf/internal/pkg/warewulfconf" - "github.com/hpcng/warewulf/internal/pkg/wwlog" - "github.com/pkg/errors" - "github.com/spf13/cobra" -@@ -29,43 +33,130 @@ func CobraRunE(cmd *cobra.Command, args []string) error { - wwlog.Error("Unknown Warewulf container: %s", containerName) - os.Exit(1) - } -+ conf, err := warewulfconf.New() -+ if err != nil { -+ wwlog.Verbose("Couldn't get warewulf ocnfiguration: %s", err) -+ } -+ mountPts := conf.MountsContainer -+ mountPts = append(container.InitMountPnts(binds), mountPts...) -+ // check for valid mount points -+ lowerObjects := checkMountPoints(containerName, mountPts) -+ if len(lowerObjects) != 0 { -+ if tempDir == "" { -+ tempDir, err = os.MkdirTemp(os.TempDir(), "overlay") -+ if err != nil { -+ wwlog.Warn("couldn't create temp dir for overlay", err) -+ lowerObjects = []string{} -+ tempDir = "" -+ } -+ } -+ // need to create a overlay, where the lower layer contains -+ // the missing mount points -+ if tempDir != "" { -+ wwlog.Verbose("for ephermal mount use tempdir %s", tempDir) -+ // ignore errors as we are doomed if a tmp dir couldn't be written -+ _ = os.Mkdir(path.Join(tempDir, "work"), os.ModePerm) -+ _ = os.Mkdir(path.Join(tempDir, "lower"), os.ModePerm) -+ _ = os.Mkdir(path.Join(tempDir, "nodeoverlay"), os.ModePerm) -+ for _, obj := range lowerObjects { -+ newFile := "" -+ if !strings.HasSuffix(obj, "/") { -+ newFile = filepath.Base(obj) -+ obj = filepath.Dir(obj) -+ } -+ err = os.MkdirAll(filepath.Join(tempDir, "lower", obj), os.ModePerm) -+ if err != nil { -+ wwlog.Warn("couldn't create directory for mounts: %s", err) -+ } -+ if newFile != "" { -+ desc, err := os.Create(filepath.Join(tempDir, "lower", obj, newFile)) -+ if err != nil { -+ wwlog.Warn("couldn't create directory for mounts: %s", err) -+ } -+ defer desc.Close() -+ } -+ } -+ } -+ } - containerPath := container.RootFsDir(containerName) -- fileStat, _ := os.Stat(path.Join(containerPath, "/etc/passwd")) -- unixStat := fileStat.Sys().(*syscall.Stat_t) -- passwdTime := time.Unix(int64(unixStat.Ctim.Sec), int64(unixStat.Ctim.Nsec)) -- fileStat, _ = os.Stat(path.Join(containerPath, "/etc/group")) -- unixStat = fileStat.Sys().(*syscall.Stat_t) -- groupTime := time.Unix(int64(unixStat.Ctim.Sec), int64(unixStat.Ctim.Nsec)) -- wwlog.Debug("passwd: %v", passwdTime) -- wwlog.Debug("group: %v", groupTime) -- -- err := syscall.Mount("", "/", "", syscall.MS_PRIVATE|syscall.MS_REC, "") -+ err = syscall.Mount("", "/", "", syscall.MS_PRIVATE|syscall.MS_REC, "") - if err != nil { - return errors.Wrap(err, "failed to mount") - } -+ ps1Str := fmt.Sprintf("[%s] Warewulf> ", containerName) -+ if len(lowerObjects) != 0 && nodename == "" { -+ options := fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", -+ path.Join(tempDir, "lower"), containerPath, path.Join(tempDir, "work")) -+ wwlog.Debug("overlay options: %s", options) -+ err = syscall.Mount("overlay", containerPath, "overlay", 0, options) -+ if err != nil { -+ wwlog.Warn(fmt.Sprintf("Couldn't create overlay for ephermal mount points: %s", err)) -+ } -+ } else if nodename != "" { -+ nodeDB, err := node.New() -+ if err != nil { -+ wwlog.Error("Could not open node configuration: %s", err) -+ os.Exit(1) -+ } -+ -+ nodes, err := nodeDB.FindAllNodes() -+ if err != nil { -+ wwlog.Error("Could not get node list: %s", err) -+ os.Exit(1) -+ } -+ nodes = node.FilterByName(nodes, []string{nodename}) -+ if len(nodes) != 1 { -+ wwlog.Error("No single node idendified with %s", nodename) -+ os.Exit(1) -+ } -+ overlays := nodes[0].SystemOverlay.GetSlice() -+ overlays = append(overlays, nodes[0].RuntimeOverlay.GetSlice()...) -+ err = overlay.BuildOverlayIndir(nodes[0], overlays, path.Join(tempDir, "nodeoverlay")) -+ if err != nil { -+ wwlog.Error("Could not build overlay: %s", err) -+ os.Exit(1) -+ } -+ options := fmt.Sprintf("lowerdir=%s:%s:%s", -+ path.Join(tempDir, "lower"), containerPath, path.Join(tempDir, "nodeoverlay")) -+ wwlog.Debug("overlay options: %s", options) -+ err = syscall.Mount("overlay", containerPath, "overlay", 0, options) -+ if err != nil { -+ wwlog.Warn(fmt.Sprintf("Couldn't create overlay for node render overlay: %s", err)) -+ os.Exit(1) -+ } -+ ps1Str = fmt.Sprintf("[%s|ro|%s] Warewulf> ", containerName, nodename) -+ } -+ if !util.IsWriteAble(containerPath) && nodename == "" { -+ wwlog.Verbose("mounting %s ro", containerPath) -+ ps1Str = fmt.Sprintf("[%s|ro] Warewulf> ", containerName) -+ err = syscall.Mount(containerPath, containerPath, "", syscall.MS_BIND, "") -+ if err != nil { -+ return errors.Wrap(err, "failed to prepare bind mount") -+ } -+ err = syscall.Mount(containerPath, containerPath, "", syscall.MS_REMOUNT|syscall.MS_RDONLY|syscall.MS_BIND, "") -+ if err != nil { -+ return errors.Wrap(err, "failed to remount ro") -+ } -+ } - - err = syscall.Mount("/dev", path.Join(containerPath, "/dev"), "", syscall.MS_BIND, "") - if err != nil { - return errors.Wrap(err, "failed to mount /dev") - } - -- for _, b := range binds { -- var source string -- var dest string -- -- bind := util.SplitValidPaths(b, ":") -- source = bind[0] -- -- if len(bind) == 1 { -- dest = source -- } else { -- dest = bind[1] -- } -- -- err := syscall.Mount(source, path.Join(containerPath, dest), "", syscall.MS_BIND, "") -+ for _, mntPnt := range mountPts { -+ err = syscall.Mount(mntPnt.Source, path.Join(containerPath, mntPnt.Dest), "", syscall.MS_BIND, "") - if err != nil { -- fmt.Printf("BIND ERROR: %s\n", err) -- os.Exit(1) -+ wwlog.Warn("Couldn't mount %s to %s: %s", mntPnt.Source, mntPnt.Dest, err) -+ } else if mntPnt.ReadOnly { -+ err = syscall.Mount(mntPnt.Source, path.Join(containerPath, mntPnt.Dest), "", syscall.MS_REMOUNT|syscall.MS_RDONLY|syscall.MS_BIND, "") -+ if err != nil { -+ wwlog.Warn("failed to following mount readonly: %s", mntPnt.Source) -+ } else { -+ wwlog.Verbose("mounted readonly from host to container: %s:%s", mntPnt.Source, mntPnt.Dest) -+ } -+ } else { -+ wwlog.Verbose("mounted from host to container: %s:%s", mntPnt.Source, mntPnt.Dest) - } - } - -@@ -84,27 +175,39 @@ func CobraRunE(cmd *cobra.Command, args []string) error { - return errors.Wrap(err, "failed to mount proc") - } - -- os.Setenv("PS1", fmt.Sprintf("[%s] Warewulf> ", containerName)) -+ os.Setenv("PS1", ps1Str) - os.Setenv("PATH", "/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin") - os.Setenv("HISTFILE", "/dev/null") - -- err = syscall.Exec(args[1], args[1:], os.Environ()) -- if err != nil { -- wwlog.Error("%s", err) -- os.Exit(1) -- } -- fileStat, _ = os.Stat(path.Join(containerPath, "/etc/passwd")) -- unixStat = fileStat.Sys().(*syscall.Stat_t) -- if passwdTime.Before(time.Unix(int64(unixStat.Ctim.Sec), int64(unixStat.Ctim.Nsec))) { -- wwlog.Warn("/etc/passwd has been modified, maybe you want to run syncuser") -- } -- wwlog.Debug("passwd: %v", time.Unix(int64(unixStat.Ctim.Sec), int64(unixStat.Ctim.Nsec))) -- fileStat, _ = os.Stat(path.Join(containerPath, "/etc/group")) -- unixStat = fileStat.Sys().(*syscall.Stat_t) -- if groupTime.Before(time.Unix(int64(unixStat.Ctim.Sec), int64(unixStat.Ctim.Nsec))) { -- wwlog.Warn("/etc/group has been modified, maybe you want to run syncuser") -- } -- wwlog.Debug("group: %v", time.Unix(int64(unixStat.Ctim.Sec), int64(unixStat.Ctim.Nsec))) -- -+ _ = syscall.Exec(args[1], args[1:], os.Environ()) -+ /* -+ Exec replaces the actual program, so nothing to do here afterwards -+ */ - return nil - } -+ -+/* -+Check if the bind mount points exists in the given container. Returns -+the invalid mount points. Directories always have '/' as suffix -+*/ -+func checkMountPoints(containerName string, binds []*warewulfconf.MountEntry) (overlayObjects []string) { -+ overlayObjects = []string{} -+ for _, b := range binds { -+ _, err := os.Stat(b.Source) -+ if err != nil { -+ wwlog.Debug("Couldn't stat %s create no mount point in container", b.Source) -+ continue -+ } -+ wwlog.Debug("Checking in container for %s", path.Join(container.RootFsDir(containerName), b.Dest)) -+ if _, err = os.Stat(path.Join(container.RootFsDir(containerName), b.Dest)); err != nil { -+ if os.IsNotExist(err) { -+ if util.IsDir(b.Dest) && !strings.HasSuffix(b.Dest, "/") { -+ b.Dest += "/" -+ } -+ overlayObjects = append(overlayObjects, b.Dest) -+ wwlog.Debug("Container %s, needs following path: %s", containerName, b.Dest) -+ } -+ } -+ } -+ return overlayObjects -+} -diff --git a/internal/app/wwctl/container/exec/child/root.go b/internal/app/wwctl/container/exec/child/root.go -index 0b7dde7c..d7060995 100644 ---- a/internal/app/wwctl/container/exec/child/root.go -+++ b/internal/app/wwctl/container/exec/child/root.go -@@ -5,17 +5,21 @@ import "github.com/spf13/cobra" - var ( - baseCmd = &cobra.Command{ - DisableFlagsInUseLine: true, -- Use: "__child", -- Hidden: true, -- RunE: CobraRunE, -- Args: cobra.MinimumNArgs(1), -- FParseErrWhitelist: cobra.FParseErrWhitelist{UnknownFlags: true}, -+ Use: "__child", -+ Hidden: true, -+ RunE: CobraRunE, -+ Args: cobra.MinimumNArgs(1), -+ FParseErrWhitelist: cobra.FParseErrWhitelist{UnknownFlags: true}, - } -- binds []string -+ binds []string -+ tempDir string -+ nodename string - ) - - func init() { -- baseCmd.PersistentFlags().StringArrayVarP(&binds, "bind", "b", []string{}, "bind points") -+ baseCmd.Flags().StringVarP(&nodename, "node", "n", "", "create ro overlay for given node") -+ baseCmd.Flags().StringArrayVarP(&binds, "bind", "b", []string{}, "bind points") -+ baseCmd.Flags().StringVar(&tempDir, "tempdir", "", "tempdir") - } - - // GetRootCommand returns the root cobra.Command for the application. -diff --git a/internal/app/wwctl/container/exec/main.go b/internal/app/wwctl/container/exec/main.go -index 722ba479..bc622de2 100644 ---- a/internal/app/wwctl/container/exec/main.go -+++ b/internal/app/wwctl/container/exec/main.go -@@ -17,9 +17,26 @@ import ( - "github.com/spf13/cobra" - ) - -+/* -+fork off a process with a new PID space -+*/ - func runContainedCmd(args []string) error { -+ var err error -+ if tempDir == "" { -+ tempDir, err = os.MkdirTemp(os.TempDir(), "overlay") -+ if err != nil { -+ wwlog.Warn("couldn't create temp dir for overlay", err) -+ } -+ defer func() { -+ err = os.RemoveAll(tempDir) -+ if err != nil { -+ wwlog.Warn("Couldn't remove temp dir for ephermal mounts:", err) -+ } -+ }() -+ } -+ logStr := fmt.Sprint(wwlog.GetLogLevel()) - wwlog.Verbose("Running contained command: %s", args[1:]) -- c := exec.Command("/proc/self/exe", append([]string{"container", "exec", "__child"}, args...)...) -+ c := exec.Command("/proc/self/exe", append([]string{"--loglevel", logStr, "--tempdir", tempDir, "container", "exec", "__child"}, args...)...) - - c.SysProcAttr = &syscall.SysProcAttr{ - Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS, -@@ -30,6 +47,11 @@ func runContainedCmd(args []string) error { - - if err := c.Run(); err != nil { - fmt.Printf("Command exited non-zero, not rebuilding/updating VNFS image\n") -+ // defer is not called before os.Exit(0) -+ err = os.RemoveAll(tempDir) -+ if err != nil { -+ wwlog.Warn("Couldn't remove temp dir for ephermal mounts:", err) -+ } - os.Exit(0) - } - return nil -@@ -38,6 +60,8 @@ func runContainedCmd(args []string) error { - func CobraRunE(cmd *cobra.Command, args []string) error { - - containerName := args[0] -+ os.Setenv("WW_CONTAINER_SHELL", containerName) -+ - var allargs []string - - if !container.ValidSource(containerName) { -@@ -48,6 +72,9 @@ func CobraRunE(cmd *cobra.Command, args []string) error { - for _, b := range binds { - allargs = append(allargs, "--bind", b) - } -+ if nodeName != "" { -+ allargs = append(allargs, "--node", nodeName) -+ } - allargs = append(allargs, args...) - containerPath := container.RootFsDir(containerName) - -@@ -109,3 +136,10 @@ func CobraRunE(cmd *cobra.Command, args []string) error { - - return nil - } -+func SetBinds(myBinds []string) { -+ binds = append(binds, myBinds...) -+} -+ -+func SetNode(myNode string) { -+ nodeName = myNode -+} -diff --git a/internal/app/wwctl/container/exec/root.go b/internal/app/wwctl/container/exec/root.go -index db8bea41..e5fc577b 100644 ---- a/internal/app/wwctl/container/exec/root.go -+++ b/internal/app/wwctl/container/exec/root.go -@@ -27,12 +27,16 @@ var ( - } - SyncUser bool - binds []string -+ tempDir string -+ nodeName string - ) - - func init() { - baseCmd.AddCommand(child.GetCommand()) - baseCmd.PersistentFlags().StringArrayVarP(&binds, "bind", "b", []string{}, "Bind a local path into the container (must exist)") - baseCmd.PersistentFlags().BoolVar(&SyncUser, "syncuser", false, "Synchronize UIDs/GIDs from host to container") -+ baseCmd.PersistentFlags().StringVar(&tempDir, "tempdir", "", "Use tempdir for constructing the overlay fs (only used if mount points don't exist in container)") -+ baseCmd.PersistentFlags().StringVarP(&nodeName, "node", "n", "", "Create a read only view of the container for the given node") - } - - // GetRootCommand returns the root cobra.Command for the application. -diff --git a/internal/app/wwctl/container/shell/main.go b/internal/app/wwctl/container/shell/main.go -index 04a095ec..b7c3fbde 100644 ---- a/internal/app/wwctl/container/shell/main.go -+++ b/internal/app/wwctl/container/shell/main.go -@@ -4,11 +4,10 @@ - package shell - - import ( -- "fmt" - "os" -- "os/exec" -- "syscall" -+ "path" - -+ cntexec "github.com/hpcng/warewulf/internal/app/wwctl/container/exec" - "github.com/hpcng/warewulf/internal/pkg/container" - "github.com/hpcng/warewulf/internal/pkg/wwlog" - "github.com/spf13/cobra" -@@ -23,28 +22,32 @@ func CobraRunE(cmd *cobra.Command, args []string) error { - wwlog.Error("Unknown Warewulf container: %s", containerName) - os.Exit(1) - } -- -- for _, b := range binds { -- allargs = append(allargs, "--bind", b) -+ /* -+ for _, b := range binds { -+ allargs = append(allargs, "--bind", b) -+ } -+ */ -+ shellName := os.Getenv("SHELL") -+ if !container.ValidSource(containerName) { -+ wwlog.Error("Unknown Warewulf container: %s", containerName) -+ os.Exit(1) - } -- allargs = append(allargs, args...) -- allargs = append(allargs, "/usr/bin/bash") -- -- c := exec.Command("/proc/self/exe", append([]string{"container", "exec"}, allargs...)...) -- -- //c := exec.Command("/bin/sh") -- c.SysProcAttr = &syscall.SysProcAttr{ -- Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS, -+ var shells []string -+ if shellName == "" { -+ shells = append(shells, "/bin/bash") -+ } else { -+ shells = append(shells, shellName, "/bin/bash") - } -- c.Stdin = os.Stdin -- c.Stdout = os.Stdout -- c.Stderr = os.Stderr -- -- os.Setenv("WW_CONTAINER_SHELL", containerName) -- -- if err := c.Run(); err != nil { -- fmt.Println(err) -- os.Exit(1) -+ for _, s := range shells { -+ if _, err := os.Stat(path.Join(container.RootFsDir(containerName), s)); err == nil { -+ shellName = s -+ break -+ } - } -- return nil -+ args = append(args, shellName) -+ allargs = append(allargs, args...) -+ wwlog.Debug("Calling exec with args: %s", allargs) -+ cntexec.SetBinds(binds) -+ cntexec.SetNode(nodeName) -+ return cntexec.CobraRunE(cmd, allargs) - } -diff --git a/internal/app/wwctl/container/shell/root.go b/internal/app/wwctl/container/shell/root.go -index 59f6c56b..dcec3f06 100644 ---- a/internal/app/wwctl/container/shell/root.go -+++ b/internal/app/wwctl/container/shell/root.go -@@ -22,11 +22,13 @@ var ( - }, - FParseErrWhitelist: cobra.FParseErrWhitelist{UnknownFlags: true}, - } -- binds []string -+ binds []string -+ nodeName string - ) - - func init() { - baseCmd.PersistentFlags().StringArrayVarP(&binds, "bind", "b", []string{}, "Bind a local path into the container (must exist)") -+ baseCmd.PersistentFlags().StringVarP(&nodeName, "node", "n", "", "Create a read only view of the container for the given node") - } - - // GetRootCommand returns the root cobra.Command for the application. -diff --git a/internal/app/wwctl/root.go b/internal/app/wwctl/root.go -index 81fe2a26..cc8927d6 100644 ---- a/internal/app/wwctl/root.go -+++ b/internal/app/wwctl/root.go -@@ -31,12 +31,14 @@ var ( - } - verboseArg bool - DebugFlag bool -+ LogLevel int - ) - - func init() { - rootCmd.PersistentFlags().BoolVarP(&verboseArg, "verbose", "v", false, "Run with increased verbosity.") - rootCmd.PersistentFlags().BoolVarP(&DebugFlag, "debug", "d", false, "Run with debugging messages enabled.") -- -+ rootCmd.PersistentFlags().IntVar(&LogLevel, "loglevel", wwlog.INFO, "Set log level to given string") -+ _ = rootCmd.PersistentFlags().MarkHidden("loglevel") - rootCmd.SetUsageTemplate(help.UsageTemplate) - rootCmd.SetHelpTemplate(help.HelpTemplate) - -@@ -65,6 +67,9 @@ func rootPersistentPreRunE(cmd *cobra.Command, args []string) error { - } else { - wwlog.SetLogLevel(wwlog.INFO) - } -+ if LogLevel != wwlog.INFO { -+ wwlog.SetLogLevel(LogLevel) -+ } - return nil - } - -diff --git a/internal/pkg/api/apiconfig/container/container.go b/internal/pkg/api/apiconfig/container/container.go -index 8bc2ba23..beb29544 100644 ---- a/internal/pkg/api/apiconfig/container/container.go -+++ b/internal/pkg/api/apiconfig/container/container.go -@@ -192,12 +192,6 @@ func ContainerImport(cip *wwapiv1.ContainerImportParameter) (containerName strin - return - } - -- fmt.Printf("Updating the container's /etc/resolv.conf\n") -- err = util.CopyFile("/etc/resolv.conf", path.Join(container.RootFsDir(cip.Name), "/etc/resolv.conf")) -- if err != nil { -- wwlog.Warn("Could not copy /etc/resolv.conf into container: %s", err) -- } -- - fmt.Printf("Building container: %s\n", cip.Name) - err = container.Build(cip.Name, true) - if err != nil { -diff --git a/internal/pkg/api/container/container.go b/internal/pkg/api/container/container.go -index 757ea873..6683c75c 100644 ---- a/internal/pkg/api/container/container.go -+++ b/internal/pkg/api/container/container.go -@@ -197,12 +197,6 @@ func ContainerImport(cip *wwapiv1.ContainerImportParameter) (containerName strin - return - } - -- wwlog.Info("Updating the container's /etc/resolv.conf") -- err = util.CopyFile("/etc/resolv.conf", path.Join(container.RootFsDir(cip.Name), "/etc/resolv.conf")) -- if err != nil { -- wwlog.Warn("Could not copy /etc/resolv.conf into container: %s", err) -- } -- - err = container.SyncUids(cip.Name, !cip.SyncUser) - if err != nil && !cip.SyncUser { - err = fmt.Errorf("error in user sync, fix error and run 'syncuser' manually: %s", err) -diff --git a/internal/pkg/buildconfig/defaults.go b/internal/pkg/buildconfig/defaults.go -index 24cb2d40..332a2f6d 100644 ---- a/internal/pkg/buildconfig/defaults.go -+++ b/internal/pkg/buildconfig/defaults.go -@@ -19,6 +19,7 @@ var ( - release string = "UNDEF" - wwclientdir string = "UNDEF" - datadir string = "UNDEF" -+ tmpdir string = "UNDEF" - ) - - func BINDIR() string { -@@ -90,3 +91,8 @@ func WWCLIENTDIR() string { - wwlog.Debug("WWCLIENTDIR = '%s'", wwclientdir) - return wwclientdir - } -+ -+func TMPDIR() string { -+ wwlog.Debug("TMPDIR = '%s'", tmpdir) -+ return tmpdir -+} -diff --git a/internal/pkg/buildconfig/setconfigs.go.in b/internal/pkg/buildconfig/setconfigs.go.in -index e10303e3..f049ffeb 100644 ---- a/internal/pkg/buildconfig/setconfigs.go.in -+++ b/internal/pkg/buildconfig/setconfigs.go.in -@@ -15,4 +15,5 @@ func init() { - version = "@VERSION@" - release = "@RELEASE@" - wwclientdir = "@WWCLIENTDIR@" -+ tmpdir = "@TMPDIR@" - } -diff --git a/internal/pkg/container/mountpoints.go b/internal/pkg/container/mountpoints.go -new file mode 100644 -index 00000000..727ff35c ---- /dev/null -+++ b/internal/pkg/container/mountpoints.go -@@ -0,0 +1,35 @@ -+package container -+ -+import ( -+ "strings" -+ -+ "github.com/hpcng/warewulf/internal/pkg/warewulfconf" -+ "github.com/hpcng/warewulf/internal/pkg/wwlog" -+) -+ -+/* -+Create a slice iof MntDetails from a string slice with following -+format "source:[:destination][:readonly]" if destination is not -+given, the source is used as destination -+*/ -+func InitMountPnts(binds []string) (mounts []*warewulfconf.MountEntry) { -+ wwlog.Debug("Trying to mount following mount points: %s", mounts) -+ for _, b := range binds { -+ bind := strings.Split(b, ":") -+ dest := bind[0] -+ if len(bind) >= 2 { -+ dest = bind[1] -+ } -+ readonly := false -+ if len(bind) >= 3 && bind[2] == "ro" { -+ readonly = true -+ } -+ mntPnt := warewulfconf.MountEntry{ -+ Source: bind[0], -+ Dest: dest, -+ ReadOnly: readonly, -+ } -+ mounts = append(mounts, &mntPnt) -+ } -+ return mounts -+} -diff --git a/internal/pkg/util/util.go b/internal/pkg/util/util.go -index 3f97fbb5..6ad445cb 100644 ---- a/internal/pkg/util/util.go -+++ b/internal/pkg/util/util.go -@@ -24,12 +24,16 @@ import ( - - // reserve some number of cpus for system/warwulfd usage - var processLimitedReserve int = 4 -+ - // maximum number of concurrent spawned processes --var processLimitedMax = MaxInt(1, runtime.NumCPU() - processLimitedReserve) -+var processLimitedMax = MaxInt(1, runtime.NumCPU()-processLimitedReserve) -+ - // Channel used as semaphore to specififed processLimitedMax - var processLimitedChan = make(chan int, processLimitedMax) -+ - // Current number of processes started + queued - var processLimitedNum int32 = 0 -+ - // Counter over total history of started processes - var processLimitedCounter uint32 = 0 - -@@ -53,7 +57,7 @@ func ProcessLimitedStatus() (running int32, queued int32) { - return - } - --func MaxInt( a int, b int ) int { -+func MaxInt(a int, b int) int { - if a > b { - return a - } -@@ -65,7 +69,7 @@ func FirstError(errs ...error) (err error) { - for _, e := range errs { - if err == nil { - err = e -- }else if e != nil { -+ } else if e != nil { - wwlog.ErrorExc(e, "Unhandled error") - } - } -@@ -207,7 +211,7 @@ func ValidateOrDie(message string, pattern string, expr string) { - } - } - --//****************************************************************************** -+// ****************************************************************************** - func FindFiles(path string) []string { - var ret []string - -@@ -244,7 +248,7 @@ func FindFiles(path string) []string { - return ret - } - --//****************************************************************************** -+// ****************************************************************************** - func FindFilterFiles( - path string, - include []string, -@@ -265,7 +269,7 @@ func FindFilterFiles( - return ofiles, errors.Wrapf(err, "Failed to change path: %s", path) - } - -- for i := range(ignore) { -+ for i := range ignore { - ignore[i] = strings.TrimLeft(ignore[i], "/") - ignore[i] = strings.TrimPrefix(ignore[i], "./") - wwlog.Debug("Ignore pattern (%d): %s", i, ignore[i]) -@@ -282,7 +286,6 @@ func FindFilterFiles( - - dev := path_stat.Sys().(*syscall.Stat_t).Dev - -- - includeDirs := []string{} - ignoreDirs := []string{} - err = filepath.Walk(".", func(location string, info os.FileInfo, err error) error { -@@ -306,13 +309,13 @@ func FindFilterFiles( - return nil - } - -- for _, ignoreDir := range(ignoreDirs) { -+ for _, ignoreDir := range ignoreDirs { - if strings.HasPrefix(location, ignoreDir) { - wwlog.Debug("Ignored (dir): %s", file) - return nil - } - } -- for i, pattern := range(ignore) { -+ for i, pattern := range ignore { - m, err := filepath.Match(pattern, location) - if err != nil { - return err -@@ -325,14 +328,14 @@ func FindFilterFiles( - } - } - -- for _, includeDir := range(includeDirs) { -+ for _, includeDir := range includeDirs { - if strings.HasPrefix(location, includeDir) { - wwlog.Debug("Included (dir): %s", file) - ofiles = append(ofiles, location) - return nil - } - } -- for i, pattern := range(include) { -+ for i, pattern := range include { - m, err := filepath.Match(pattern, location) - if err != nil { - return err -@@ -352,7 +355,7 @@ func FindFilterFiles( - return ofiles, err - } - --//****************************************************************************** -+// ****************************************************************************** - func ExecInteractive(command string, a ...string) error { - wwlog.Debug("ExecInteractive(%s, %s)", command, a) - c := exec.Command(command, a...) -@@ -538,20 +541,22 @@ func AppendLines(fileName string, lines []string) error { - return nil - } - --/******************************************************************************* -+/* -+****************************************************************************** -+ - Create an archive using cpio - */ - func CpioCreate( - ifiles []string, - ofile string, - format string, -- cpio_args ...string ) (err error) { -+ cpio_args ...string) (err error) { - - args := []string{ - "--quiet", - "--create", - "-H", format, -- "--file=" + ofile } -+ "--file=" + ofile} - - args = append(args, cpio_args...) - -@@ -574,14 +579,16 @@ func CpioCreate( - wwlog.Debug(string(out)) - } - -- return FirstError(err, <- err_in) -+ return FirstError(err, <-err_in) - } - --/******************************************************************************* -+/* -+****************************************************************************** -+ - Compress a file using gzip or pigz - */ - func FileGz( -- file string ) (err error) { -+ file string) (err error) { - - file_gz := file + ".gz" - -@@ -608,41 +615,41 @@ func FileGz( - proc := exec.Command( - compressor, - "--keep", -- file ) -+ file) - - out, err := proc.CombinedOutput() - if len(out) > 0 { - outStr := string(out[:]) - if err != nil && strings.HasSuffix(compressor, "gzip") && strings.Contains(outStr, "unrecognized option") { -- var gzippedFile *os.File -- var gzipStderr io.ReadCloser -- -+ var gzippedFile *os.File -+ var gzipStderr io.ReadCloser -+ - /* Older version of gzip, try it another way: */ - wwlog.Verbose("%s does not recognize the --keep flag, trying redirected stdout", compressor) -- -+ - /* Open the output file for writing: */ - gzippedFile, err = os.Create(file_gz) - if err != nil { - return errors.Wrapf(err, "Unable to open compressed image file for writing: %s", file_gz) - } -- -+ - /* We'll execute gzip with output to stdout and attach stdout to the compressed file we just - created: -- */ -+ */ - proc = exec.Command( - compressor, - "--stdout", -- file ) -+ file) - proc.Stdout = gzippedFile - gzipStderr, err = proc.StderrPipe() - if err != nil { - return errors.Wrapf(err, "Unable to open stderr pipe for compression program: %s", compressor) - } -- -+ - /* Execute the command: */ - err = proc.Start() - if err != nil { -- _ = proc.Wait() -+ _ = proc.Wait() - gzippedFile.Close() - os.Remove(file_gz) - err = errors.Wrapf(err, "Unable to successfully execute compression program: %s", compressor) -@@ -664,7 +671,9 @@ func FileGz( - return err - } - --/******************************************************************************* -+/* -+****************************************************************************** -+ - Create an archive using cpio - */ - func BuildFsImage( -@@ -675,7 +684,7 @@ func BuildFsImage( - ignore []string, - ignore_xdev bool, - format string, -- cpio_args ...string ) (err error) { -+ cpio_args ...string) (err error) { - - err = os.MkdirAll(path.Dir(imagePath), 0755) - if err != nil { -@@ -709,16 +718,16 @@ func BuildFsImage( - ".", - include, - ignore, -- ignore_xdev ) -+ ignore_xdev) - if err != nil { - return errors.Wrapf(err, "Failed discovering files for %s: %s", name, rootfsPath) - } - - err = CpioCreate( - files, -- imagePath, -+ imagePath, - format, -- cpio_args...) -+ cpio_args...) - if err != nil { - return errors.Wrapf(err, "Failed creating image for %s: %s", name, imagePath) - } -@@ -727,15 +736,17 @@ func BuildFsImage( - - err = FileGz(imagePath) - if err != nil { -- return errors.Wrapf(err, "Failed to compress image for %s: %s", name, imagePath + ".gz") -+ return errors.Wrapf(err, "Failed to compress image for %s: %s", name, imagePath+".gz") - } - -- wwlog.Info("Compressed image for %s: %s", name, imagePath + ".gz") -+ wwlog.Info("Compressed image for %s: %s", name, imagePath+".gz") - - return nil - } - --/******************************************************************************* -+/* -+****************************************************************************** -+ - Runs wwctl command - */ - func RunWWCTL(args ...string) (out []byte, err error) { -@@ -744,7 +755,7 @@ func RunWWCTL(args ...string) (out []byte, err error) { - running, queued := ProcessLimitedStatus() - - wwlog.Verbose("Starting wwctl process %d (%d running, %d queued): %v", -- index, running, queued, args ) -+ index, running, queued, args) - - proc := exec.Command("wwctl", args...) - -@@ -787,3 +798,21 @@ func ByteToString(b int64) string { - } - return fmt.Sprintf("%.1f %ciB", float64(b)/float64(div), "KMGTPE"[exp]) - } -+ -+/* -+Check if the w-bit of a file/dir. unix.Access(file,unix.W_OK) will -+not show this. -+*/ -+func IsWriteAble(path string) bool { -+ info, err := os.Stat(path) -+ if err != nil { -+ return false -+ } -+ -+ // Check if the user bit is enabled in file permission -+ if info.Mode().Perm()&(1<<(uint(7))) == 0 { -+ wwlog.Debug("Write permission bit is not set for: %s", path) -+ return false -+ } -+ return true -+} -diff --git a/internal/pkg/warewulfconf/datastructure.go b/internal/pkg/warewulfconf/datastructure.go -index c3eebf8c..170c94cd 100644 ---- a/internal/pkg/warewulfconf/datastructure.go -+++ b/internal/pkg/warewulfconf/datastructure.go -@@ -7,19 +7,20 @@ import ( - ) - - type ControllerConf struct { -- WWInternal int `yaml:"WW_INTERNAL"` -- Comment string `yaml:"comment,omitempty"` -- Ipaddr string `yaml:"ipaddr"` -- Ipaddr6 string `yaml:"ipaddr6,omitempty"` -- Netmask string `yaml:"netmask"` -- Network string `yaml:"network,omitempty"` -- Ipv6net string `yaml:"ipv6net,omitempty"` -- Fqdn string `yaml:"fqdn,omitempty"` -- Warewulf *WarewulfConf `yaml:"warewulf"` -- Dhcp *DhcpConf `yaml:"dhcp"` -- Tftp *TftpConf `yaml:"tftp"` -- Nfs *NfsConf `yaml:"nfs"` -- current bool -+ WWInternal int `yaml:"WW_INTERNAL"` -+ Comment string `yaml:"comment,omitempty"` -+ Ipaddr string `yaml:"ipaddr"` -+ Ipaddr6 string `yaml:"ipaddr6,omitempty"` -+ Netmask string `yaml:"netmask"` -+ Network string `yaml:"network,omitempty"` -+ Ipv6net string `yaml:"ipv6net,omitempty"` -+ Fqdn string `yaml:"fqdn,omitempty"` -+ Warewulf *WarewulfConf `yaml:"warewulf"` -+ Dhcp *DhcpConf `yaml:"dhcp"` -+ Tftp *TftpConf `yaml:"tftp"` -+ Nfs *NfsConf `yaml:"nfs"` -+ MountsContainer []*MountEntry `yaml:"container mounts"` -+ current bool - } - - type WarewulfConf struct { -@@ -59,6 +60,16 @@ type NfsExportConf struct { - Mount bool `default:"true" yaml:"mount"` - } - -+/* -+Describe a mount point for a container exec -+*/ -+type MountEntry struct { -+ Source string `yaml:"source" default:"/etc/resolv.conf"` -+ Dest string `yaml:"dest,omitempty" default:"/etc/resolv.conf"` -+ ReadOnly bool `yaml:"readonly,omitempty" default:"false"` -+ Options string `yaml:"options,omitempty"` // ignored at the moment -+} -+ - func (s *NfsConf) Unmarshal(unmarshal func(interface{}) error) error { - if err := defaults.Set(s); err != nil { - return err -diff --git a/print_mnts b/print_mnts -new file mode 100755 -index 00000000..28986c71 -Binary files /dev/null and b/print_mnts differ diff --git a/Fix-for-CVE-2022-41723.patch b/Fix-for-CVE-2022-41723.patch deleted file mode 100644 index 487b244..0000000 --- a/Fix-for-CVE-2022-41723.patch +++ /dev/null @@ -1,370 +0,0 @@ -From bc2ccefd481066a25257a5f654f20d601491150f Mon Sep 17 00:00:00 2001 -From: Christian Goll -Date: Thu, 16 Feb 2023 11:44:09 +0100 -Subject: [PATCH] Fix for CVE-2022-41723 - ---- - go.mod | 74 +++++++++++++++++++++++++++++++++++++++++++++++++-- - go.sum | 83 ++++++---------------------------------------------------- - 2 files changed, 80 insertions(+), 77 deletions(-) - -diff --git a/go.mod b/go.mod -index 6159de0..32debb2 100644 ---- a/go.mod -+++ b/go.mod -@@ -1,6 +1,6 @@ - module github.com/hpcng/warewulf - --go 1.16 -+go 1.19 - - require ( - github.com/brotherpowers/ipsubnet v0.0.0-20170914094241-30bc98f0a5b1 -@@ -20,10 +20,80 @@ require ( - github.com/spf13/cobra v1.1.1 - github.com/stretchr/testify v1.7.0 - github.com/talos-systems/go-smbios v0.1.1 -- golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 -+ golang.org/x/term v0.3.0 - google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e - google.golang.org/grpc v1.45.0 - google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.2.0 - google.golang.org/protobuf v1.27.1 - gopkg.in/yaml.v2 v2.4.0 - ) -+ -+require ( -+ github.com/BurntSushi/toml v0.3.1 // indirect -+ github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3 // indirect -+ github.com/VividCortex/ewma v1.1.1 // indirect -+ github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect -+ github.com/apex/log v1.4.0 // indirect -+ github.com/beorn7/perks v1.0.1 // indirect -+ github.com/cespare/xxhash/v2 v2.1.1 // indirect -+ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect -+ github.com/containerd/containerd v1.5.0-beta.4 // indirect -+ github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b // indirect -+ github.com/containers/ocicrypt v1.1.0 // indirect -+ github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect -+ github.com/cyphar/filepath-securejoin v0.2.2 // indirect -+ github.com/davecgh/go-spew v1.1.1 // indirect -+ github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e // indirect -+ github.com/docker/distribution v2.7.1+incompatible // indirect -+ github.com/docker/docker v1.4.2-0.20191219165747-a9416c67da9f // indirect -+ github.com/docker/docker-credential-helpers v0.6.3 // indirect -+ github.com/docker/go-connections v0.4.0 // indirect -+ github.com/docker/go-metrics v0.0.1 // indirect -+ github.com/docker/go-units v0.4.0 // indirect -+ github.com/ghodss/yaml v1.0.0 // indirect -+ github.com/gogo/protobuf v1.3.2 // indirect -+ github.com/golang/snappy v0.0.3 // indirect -+ github.com/gorilla/mux v1.7.4 // indirect -+ github.com/inconshreveable/mousetrap v1.0.0 // indirect -+ github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a // indirect -+ github.com/klauspost/compress v1.12.1 // indirect -+ github.com/klauspost/pgzip v1.2.5 // indirect -+ github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a // indirect -+ github.com/mattn/go-colorable v0.1.9 // indirect -+ github.com/mattn/go-isatty v0.0.14 // indirect -+ github.com/mattn/go-runewidth v0.0.9 // indirect -+ github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect -+ github.com/miekg/pkcs11 v1.0.3 // indirect -+ github.com/moby/sys/mountinfo v0.4.1 // indirect -+ github.com/mtrmac/gpgme v0.1.2 // indirect -+ github.com/opencontainers/go-digest v1.0.0 // indirect -+ github.com/opencontainers/runc v1.0.0-rc93 // indirect -+ github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d // indirect -+ github.com/pmezard/go-difflib v1.0.0 // indirect -+ github.com/pquerna/ffjson v0.0.0-20190813045741-dac163c6c0a9 // indirect -+ github.com/prometheus/client_golang v1.7.1 // indirect -+ github.com/prometheus/client_model v0.2.0 // indirect -+ github.com/prometheus/common v0.10.0 // indirect -+ github.com/prometheus/procfs v0.6.0 // indirect -+ github.com/rootless-containers/proto v0.1.0 // indirect -+ github.com/russross/blackfriday/v2 v2.0.1 // indirect -+ github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect -+ github.com/sirupsen/logrus v1.8.1 // indirect -+ github.com/spf13/pflag v1.0.5 // indirect -+ github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980 // indirect -+ github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect -+ github.com/ulikunitz/xz v0.5.10 // indirect -+ github.com/urfave/cli v1.22.4 // indirect -+ github.com/vbatts/go-mtree v0.5.0 // indirect -+ github.com/vbauerster/mpb/v5 v5.3.0 // indirect -+ go.etcd.io/bbolt v1.3.5 // indirect -+ go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1 // indirect -+ golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 // indirect -+ golang.org/x/net v0.4.1-0.20230214201333-88ed8ca3307d //indirect -+ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a // indirect -+ golang.org/x/sys v0.3.0 // indirect -+ golang.org/x/text v0.5.0 // indirect -+ gopkg.in/square/go-jose.v2 v2.5.1 // indirect -+ gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect -+ sigs.k8s.io/yaml v1.3.0 // indirect -+) -diff --git a/go.sum b/go.sum -index a58cfc2..49bc9a1 100644 ---- a/go.sum -+++ b/go.sum -@@ -10,28 +10,18 @@ cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6T - cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= - cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= - cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= --cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= --cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= --cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= --cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= - cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= - cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= - cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= --cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= --cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= --cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= - cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= - cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= - cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= - cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= - cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= - cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= --cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= - cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= - cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= - cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= --cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= --cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= - dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= - github.com/14rcole/gopopulate v0.0.0-20180821133914-b175b219e774/go.mod h1:6/0dYRLLXyJjbkIPeeGyoJ/eKOSI0eU6eTlCBYibgd0= - github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -@@ -112,7 +102,6 @@ github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8n - github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= - github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= - github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= --github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= - github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= - github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= - github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -@@ -337,8 +326,6 @@ github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb - github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= - github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= - github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= --github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= --github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= - github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= - github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= - github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -@@ -364,26 +351,21 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a - github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= - github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= - github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= --github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= - github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= - github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= - github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= - github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= - github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= - github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= --github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= - github.com/google/go-intervals v0.0.2/go.mod h1:MkaR3LNRfeKLPmqgJYs4E66z5InYjmCjbbr4TQlcT6Y= - github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= - github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= - github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= --github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= - github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= - github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= - github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= - github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= - github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= --github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= --github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= - github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= - github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= - github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -@@ -405,7 +387,6 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de - github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= - github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= - github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= --github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= - github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= - github.com/grpc-ecosystem/grpc-gateway/v2 v2.10.0 h1:ESEyqQqXXFIcImj/BE8oKEX37Zsuceb2cZI+EL/zNCY= - github.com/grpc-ecosystem/grpc-gateway/v2 v2.10.0/go.mod h1:XnLCLFp3tjoZJszVKjfpyAK6J8sYIcQXWQxmqLWF21I= -@@ -755,9 +736,7 @@ github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf - github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= - github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= - github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= --github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= - github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= --github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= - github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= - github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= - github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= -@@ -773,7 +752,6 @@ go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= - go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= - go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= - go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= --go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= - go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= - go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= - go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -@@ -845,7 +823,6 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR - golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= - golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= - golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= --golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= - golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= - golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= - golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -@@ -857,26 +834,20 @@ golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLL - golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= - golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= - golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= --golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= --golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= --golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= --golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= - golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= --golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= - golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= - golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= - golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= - golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= - golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= - golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= --golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk= --golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -+golang.org/x/net v0.4.1-0.20230214201333-88ed8ca3307d h1:KHU/KRz6+/yWyRHEC24m7T5gou5VSh62duch955ktBY= -+golang.org/x/net v0.4.1-0.20230214201333-88ed8ca3307d/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= - golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= - golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= - golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= - golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= - golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= --golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= - golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= - golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= - golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -@@ -936,15 +907,9 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w - golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= - golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= - golang.org/x/sys v0.0.0-20200327173247-9dae0f8f5775/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= --golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= --golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= --golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= --golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= --golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= - golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= - golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= - golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= --golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= - golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= - golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= - golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -@@ -959,13 +924,12 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w - golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= - golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= - golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= --golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= - golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= --golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= --golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -+golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= -+golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= - golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= --golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= --golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -+golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI= -+golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= - golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= - golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= - golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -@@ -973,8 +937,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= - golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= - golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= - golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= --golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= --golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -+golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= -+golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= - golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= - golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= - golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -@@ -1016,18 +980,8 @@ golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapK - golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= - golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= - golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= --golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= - golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= --golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= --golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= --golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= --golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= --golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= --golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= - golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= --golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= --golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= --golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= - golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= - golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= - golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -@@ -1044,19 +998,12 @@ google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsb - google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= - google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= - google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= --google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= - google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= --google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= --google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= --google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= --google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= --google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= - google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= - google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= - google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= - google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= - google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= --google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= - google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= - google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= - google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -@@ -1077,19 +1024,9 @@ google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvx - google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= - google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= - google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= --google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= - google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= --google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= --google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= --google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= --google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= - google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= --google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= - google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= --google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= --google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= --google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= --google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= - google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= - google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e h1:fNKDNuUyC4WH+inqDMpfXDdfvwfYILbsX+oskGZ8hxg= - google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= -@@ -1105,10 +1042,7 @@ google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQ - google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= - google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= - google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= --google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= --google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= - google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= --google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= - google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= - google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= - google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -@@ -1175,7 +1109,6 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh - honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= - honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= - honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= --honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= - k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= - k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= - k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= --- -2.39.1 - diff --git a/config-ww4.sh b/config-ww4.sh index 1a33d30..ddb5351 100644 --- a/config-ww4.sh +++ b/config-ww4.sh @@ -61,7 +61,7 @@ network: $IP4NET range start: $DYNSTART range end: $DYNEND EOF - exit 1 + exit 0 fi DYNSIZE=20 @@ -78,7 +78,7 @@ DYNEND="${DYNPRE}.${DYNEND}" if is_ip_in_range $IP4 ; then echo "ERROR: ip address is in range for dynamic address, please set this manually" - exit 1 + exit 0 fi diff --git a/grub-boot.patch b/grub-boot.patch new file mode 100644 index 0000000..9f5e8da --- /dev/null +++ b/grub-boot.patch @@ -0,0 +1,1647 @@ +diff --git a/CHANGELOG.md b/CHANGELOG.md +index 70280717..93304050 100644 +--- a/CHANGELOG.md ++++ b/CHANGELOG.md +@@ -124,6 +124,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 + - Support importing containers with symlinked `/bin/sh` #797 + - Don't panic on malformed passwd #527 + ++- first container imported container is added to the default profile ++- grub in combination can now be set as boot method with `warewulf.grubboot: true` in ++ `warewulf.conf`. For unknown nodes `grub.efi` and `shim.efi` will be extracted from ++ the host running warewulf. If node has container it will get these binaries from the ++ container image. ++ ++- Added support for booting nodes with grub. Enable this behavior using ++ warewulf.grubboot: true in warewulf.conf. For unknown nodes, grub.efi ++ and shim.efi are extracted from the Warewulf host. If the booted node ++ has a container these binaries are extracted from the container image. + ## [4.4.0] 2023-01-18 + + ### Added +diff --git a/Makefile b/Makefile +index 4ef5a5bf..e85ac05b 100644 +--- a/Makefile ++++ b/Makefile +@@ -94,8 +94,10 @@ install: build docs + install -d -m 0755 $(DESTDIR)$(WWCHROOTDIR) + install -d -m 0755 $(DESTDIR)$(WWPROVISIONDIR) + install -d -m 0755 $(DESTDIR)$(WWOVERLAYDIR)/wwinit/$(WWCLIENTDIR) ++ install -d -m 0755 $(DESTDIR)$(WWOVERLAYDIR)/host/$(TFTPDIR)/warewulf/ + install -d -m 0755 $(DESTDIR)$(WWCONFIGDIR)/examples + install -d -m 0755 $(DESTDIR)$(WWCONFIGDIR)/ipxe ++ install -d -m 0755 $(DESTDIR)$(WWCONFIGDIR)/grub + install -d -m 0755 $(DESTDIR)$(BASHCOMPDIR) + install -d -m 0755 $(DESTDIR)$(MANDIR)/man1 + install -d -m 0755 $(DESTDIR)$(MANDIR)/man5 +@@ -112,6 +114,8 @@ install: build docs + test -f $(DESTDIR)$(DATADIR)/warewulf/defaults.conf || install -m 0644 etc/defaults.conf $(DESTDIR)$(DATADIR)/warewulf/defaults.conf + for f in etc/examples/*.ww; do install -m 0644 $$f $(DESTDIR)$(WWCONFIGDIR)/examples/; done + for f in etc/ipxe/*.ipxe; do install -m 0644 $$f $(DESTDIR)$(WWCONFIGDIR)/ipxe/; done ++ install -m 0644 etc/grub/grub.cfg.ww $(DESTDIR)$(WWCONFIGDIR)/grub/grub.cfg.ww ++ install -m 0644 etc/grub/chainload.ww $(DESTDIR)$(WWOVERLAYDIR)/host$(TFTPDIR)/warewulf/grub.cfg.ww + (cd overlays && find * -type f -exec install -D -m 0644 {} $(DESTDIR)$(WWOVERLAYDIR)/{} \;) + (cd overlays && find * -type d -exec mkdir -pv $(DESTDIR)$(WWOVERLAYDIR)/{} \;) + (cd overlays && find * -type l -exec cp -av {} $(DESTDIR)$(WWOVERLAYDIR)/{} \;) +diff --git a/etc/grub/chainload.ww b/etc/grub/chainload.ww +new file mode 100644 +index 00000000..916aff84 +--- /dev/null ++++ b/etc/grub/chainload.ww +@@ -0,0 +1,18 @@ ++# This file is autogenerated by warewulf ++# Host: {{ .BuildHost }} ++# Time: {{ .BuildTime }} ++# Source: {{ .BuildSource }} ++echo "================================================================================" ++echo "Warewulf v4 now booting with grub" ++echo ++echo "Warewulf Controller: {{.Ipaddr}}" ++echo "Chain loading specific grub.cfg" ++uri="(http,{{.Ipaddr}}:{{.Warewulf.Port}})/efiboot/grub.cfg" ++echo $uri ++configfile $uri ++echo "MESSAGE: This node is unconfigured. Please have your system administrator add a" ++echo " configuration for this node with HW address: ${net_default_mac}" ++echo "" ++echo "Rebooting in 1 minute..." ++sleep 60 ++reboot +diff --git a/etc/grub/grub.cfg.ww b/etc/grub/grub.cfg.ww +new file mode 100644 +index 00000000..79357fae +--- /dev/null ++++ b/etc/grub/grub.cfg.ww +@@ -0,0 +1,29 @@ ++echo "================================================================================" ++echo "Warewulf v4 now booting with grub: {{.Fqdn}} ({{.Hwaddr}})" ++echo "================================================================================" ++uri="(http,{{.Ipaddr}}:9873)/provision/${net_default_mac}?assetkey=" ++kernel="${uri}&stage=kernel" ++container="${uri}&stage=container&compress=gz" ++system="${uri}&stage=system&compress=gz" ++runtime="${uri}&stage=runtime&compress=gz" ++echo "Warewulf Controller: {{.Ipaddr}}" ++{{if .KernelOverride }} ++echo "Kernel: {{.KernelOverride}}" ++{{else}} ++echo "Kernel: {{.ContainerName}} (container default)" ++{{end}} ++echo "KernelArgs: {{.KernelArgs}}" ++linux $kernel wwid=${net_default_mac} {{.KernelArgs}} ++if [ x$? = x0 ] ; then ++echo "Loading Container: {{.ContainerName}}" ++initrd $container $system $runtime ++echo ++boot ++else ++echo "MESSAGE: This node is unconfigured. Please have your system administrator add a" ++echo " configuration for this node with HW address: ${net_default_mac}" ++echo "" ++echo "Rebooting in 1 minute..." ++sleep 60 ++reboot ++fi +diff --git a/go.mod b/go.mod +index c9e07d29..4d304414 100644 +--- a/go.mod ++++ b/go.mod +@@ -11,6 +11,7 @@ require ( + github.com/creasty/defaults v1.7.0 + github.com/fatih/color v1.15.0 + github.com/golang/glog v1.0.0 ++ github.com/golang/protobuf v1.5.2 + github.com/google/uuid v1.3.0 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2 + github.com/manifoldco/promptui v0.9.0 +@@ -56,7 +57,6 @@ require ( + github.com/docker/go-units v0.4.0 // indirect + github.com/ghodss/yaml v1.0.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect +- github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/snappy v0.0.3 // indirect + github.com/gorilla/mux v1.7.4 // indirect + github.com/inconshreveable/mousetrap v1.0.1 // indirect +diff --git a/internal/app/wwctl/container/delete/main.go b/internal/app/wwctl/container/delete/main.go +index 576ccef7..71e5a3d7 100644 +--- a/internal/app/wwctl/container/delete/main.go ++++ b/internal/app/wwctl/container/delete/main.go +@@ -5,7 +5,8 @@ import ( + + "github.com/hpcng/warewulf/internal/pkg/api/container" + "github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1" +- "github.com/hpcng/warewulf/internal/pkg/api/util" ++ apiutil "github.com/hpcng/warewulf/internal/pkg/api/util" ++ + "github.com/spf13/cobra" + ) + +@@ -13,8 +14,9 @@ func CobraRunE(cmd *cobra.Command, args []string) (err error) { + cdp := &wwapiv1.ContainerDeleteParameter{ + ContainerNames: args, + } ++ + if !SetYes { +- yes := util.ConfirmationPrompt(fmt.Sprintf("Are you sure you want to delete container %s", args)) ++ yes := apiutil.ConfirmationPrompt(fmt.Sprintf("Are you sure you want to delete container %s", args)) + if !yes { + return + } +diff --git a/internal/app/wwctl/container/exec/main.go b/internal/app/wwctl/container/exec/main.go +index bc622de2..f34ada9f 100644 +--- a/internal/app/wwctl/container/exec/main.go ++++ b/internal/app/wwctl/container/exec/main.go +@@ -133,7 +133,6 @@ func CobraRunE(cmd *cobra.Command, args []string) error { + wwlog.Error("Could not build container %s: %s", containerName, err) + os.Exit(1) + } +- + return nil + } + func SetBinds(myBinds []string) { +diff --git a/internal/app/wwctl/container/imprt/main.go b/internal/app/wwctl/container/imprt/main.go +index 1ec28322..4114b45c 100644 +--- a/internal/app/wwctl/container/imprt/main.go ++++ b/internal/app/wwctl/container/imprt/main.go +@@ -1,7 +1,9 @@ + package imprt + + import ( +- "github.com/hpcng/warewulf/internal/pkg/api/container" ++ "github.com/hpcng/warewulf/internal/pkg/container" ++ ++ apicontainer "github.com/hpcng/warewulf/internal/pkg/api/container" + "github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1" + "github.com/spf13/cobra" + ) +@@ -13,6 +15,9 @@ func CobraRunE(cmd *cobra.Command, args []string) (err error) { + if len(args) == 2 { + name = args[1] + } ++ if list, _ := container.ListSources(); len(list) == 0 { ++ SetDefault = true ++ } + + cip := &wwapiv1.ContainerImportParameter{ + Source: args[0], +@@ -23,6 +28,7 @@ func CobraRunE(cmd *cobra.Command, args []string) (err error) { + Default: SetDefault, + SyncUser: SyncUser, + } +- _, err = container.ContainerImport(cip) ++ ++ _, err = apicontainer.ContainerImport(cip) + return + } +diff --git a/internal/app/wwctl/profile/delete/main.go b/internal/app/wwctl/profile/delete/main.go +index 71d3dabf..137ff4ae 100644 +--- a/internal/app/wwctl/profile/delete/main.go ++++ b/internal/app/wwctl/profile/delete/main.go +@@ -5,6 +5,7 @@ import ( + "os" + + "github.com/hpcng/warewulf/internal/pkg/node" ++ "github.com/hpcng/warewulf/internal/pkg/util" + "github.com/hpcng/warewulf/internal/pkg/wwlog" + "github.com/manifoldco/promptui" + "github.com/pkg/errors" +@@ -13,6 +14,9 @@ import ( + + func CobraRunE(cmd *cobra.Command, args []string) error { + var count int ++ if util.InSlice(args, "default") { ++ return fmt.Errorf("can't delete the `default` profile ") ++ } + + nodeDB, err := node.New() + if err != nil { +diff --git a/internal/pkg/api/profile/profile.go b/internal/pkg/api/profile/profile.go +index e6ca6c68..50e60afc 100644 +--- a/internal/pkg/api/profile/profile.go ++++ b/internal/pkg/api/profile/profile.go +@@ -24,7 +24,8 @@ func ProfileSet(set *wwapiv1.ProfileSetParameter) (err error) { + if err != nil { + return errors.Wrap(err, "Could not open database") + } +- return apinode.DbSave(&nodeDB) ++ dbError := apinode.DbSave(&nodeDB) ++ return dbError + } + + // ProfileSetParameterCheck does error checking on ProfileSetParameter. +diff --git a/internal/pkg/config/warewulf.go b/internal/pkg/config/warewulf.go +index e8f08ac5..9a0dc3f4 100644 +--- a/internal/pkg/config/warewulf.go ++++ b/internal/pkg/config/warewulf.go +@@ -10,4 +10,5 @@ type WarewulfConf struct { + EnableHostOverlay bool `yaml:"host overlay" default:"true"` + Syslog bool `yaml:"syslog" default:"false"` + DataStore string `yaml:"datastore" default:"/var/lib/warewulf"` ++ GrubBoot bool `yaml:"grubboot" default:"false"` + } +diff --git a/internal/pkg/configure/tftp.go b/internal/pkg/configure/tftp.go +index 82e023f3..058ca059 100644 +--- a/internal/pkg/configure/tftp.go ++++ b/internal/pkg/configure/tftp.go +@@ -1,12 +1,12 @@ + package configure + + import ( +- "fmt" + "os" + "path" + + warewulfconf "github.com/hpcng/warewulf/internal/pkg/config" + "github.com/hpcng/warewulf/internal/pkg/util" ++ "github.com/hpcng/warewulf/internal/pkg/warewulfd" + "github.com/hpcng/warewulf/internal/pkg/wwlog" + ) + +@@ -20,28 +20,34 @@ func TFTP() error { + return err + } + +- fmt.Printf("Writing PXE files to: %s\n", tftpdir) +- copyCheck := make(map[string]bool) +- for _, f := range controller.TFTP.IpxeBinaries { +- if !path.IsAbs(f) { +- f = path.Join(controller.Paths.Ipxesource, f) +- } +- if copyCheck[f] { +- continue +- } +- copyCheck[f] = true +- err = util.SafeCopyFile(f, path.Join(tftpdir, path.Base(f))) ++ if controller.Warewulf.GrubBoot { ++ err := warewulfd.CopyShimGrub() + if err != nil { +- wwlog.Warn("ipxe binary could not be copied, booting may not work: %s", err) ++ wwlog.Warn("error when copying shim/grub binaries: %s", err) ++ } ++ } else { ++ wwlog.Info("Writing PXE files to: %s", tftpdir) ++ copyCheck := make(map[string]bool) ++ for _, f := range controller.TFTP.IpxeBinaries { ++ if !path.IsAbs(f) { ++ f = path.Join(controller.Paths.Ipxesource, f) ++ } ++ if copyCheck[f] { ++ continue ++ } ++ copyCheck[f] = true ++ err = util.SafeCopyFile(f, path.Join(tftpdir, path.Base(f))) ++ if err != nil { ++ wwlog.Warn("ipxe binary could not be copied, booting may not work: %s", err) ++ } + } + } +- + if !controller.TFTP.Enabled { + wwlog.Info("Warewulf does not auto start TFTP services due to disable by warewulf.conf") + os.Exit(0) + } + +- fmt.Printf("Enabling and restarting the TFTP services\n") ++ wwlog.Info("Enabling and restarting the TFTP services") + err = util.SystemdStart(controller.TFTP.SystemdName) + if err != nil { + wwlog.Error("%s", err) +diff --git a/internal/pkg/container/shimgrub.go b/internal/pkg/container/shimgrub.go +new file mode 100644 +index 00000000..a590690f +--- /dev/null ++++ b/internal/pkg/container/shimgrub.go +@@ -0,0 +1,94 @@ ++package container ++ ++import ( ++ "os" ++ "path" ++ "path/filepath" ++ ++ "github.com/hpcng/warewulf/internal/pkg/wwlog" ++) ++ ++func shimDirs() []string { ++ return []string{ ++ `/usr/share/efi/x86_64/`, ++ `/usr/lib64/efi/`, ++ `/boot/efi/EFI/*/`, ++ } ++} ++func shimNames() []string { ++ return []string{ ++ `shim.efi`, ++ `shim-sles.efi`, ++ `shimx64.efi`, ++ `shim-susesigned.efi`, ++ } ++} ++ ++func grubDirs() []string { ++ return []string{ ++ `/usr/lib64/efi/`, ++ `/usr/share/grub2/*-efi/`, ++ `/usr/share/efi/*/`, ++ `/boot/efi/EFI/*/`, ++ } ++} ++func grubNames() []string { ++ return []string{ ++ `grub-tpm.efi`, ++ `grub.efi`, ++ `grubx64.efi`, ++ `grubia32.efi`, ++ `grubaa64.efi`, ++ `grubarm.efi`, ++ } ++} ++ ++/* ++find the path of the shim binary in container ++*/ ++func ShimFind(container string) string { ++ var container_path string ++ if container != "" { ++ container_path = RootFsDir(container) ++ } else { ++ container_path = "/" ++ } ++ wwlog.Debug("Finding grub under paths: %s", container_path) ++ return BootLoaderFindPath(container_path, shimNames, shimDirs) ++} ++ ++/* ++find a grub.efi in the used container ++*/ ++func GrubFind(container string) string { ++ var container_path string ++ if container != "" { ++ container_path = RootFsDir(container) ++ } else { ++ container_path = "/" ++ } ++ wwlog.Debug("Finding grub under paths: %s", container_path) ++ return BootLoaderFindPath(container_path, grubNames, grubDirs) ++} ++ ++/* ++find the path of the shim binary in container ++*/ ++func BootLoaderFindPath(cpath string, names func() []string, paths func() []string) string { ++ for _, bdir := range paths() { ++ wwlog.Debug("Checking shim directory: %s", bdir) ++ for _, bname := range names() { ++ wwlog.Debug("Checking for bootloader name: %s", bname) ++ shimPaths, _ := filepath.Glob(path.Join(cpath, bdir, bname)) ++ for _, shimPath := range shimPaths { ++ wwlog.Debug("Checking for bootloader path: %s", shimPath) ++ // Only succeeds if shimPath exists and, if a ++ // symlink, links to a path that also exists ++ if _, err := os.Stat(shimPath); err == nil { ++ return shimPath ++ } ++ } ++ } ++ } ++ return "" ++} +diff --git a/internal/pkg/node/constructors.go b/internal/pkg/node/constructors.go +index 08c27b19..053d3a63 100644 +--- a/internal/pkg/node/constructors.go ++++ b/internal/pkg/node/constructors.go +@@ -31,6 +31,7 @@ defaultnode: + init: /sbin/init + root: initramfs + ipxe template: default ++ boot method: ipxe + profiles: + - default + network devices: +@@ -306,6 +307,36 @@ func (config *NodeYaml) ListAllProfiles() []string { + return ret + } + ++/* ++return a map where the key is the profile id ++*/ ++func (config *NodeYaml) MapAllProfiles() (retMap map[string]*NodeInfo, err error) { ++ retMap = make(map[string]*NodeInfo) ++ profileList, err := config.FindAllProfiles() ++ if err != nil { ++ return ++ } ++ for _, pr := range profileList { ++ retMap[pr.Id.Get()] = &pr ++ } ++ return ++} ++ ++/* ++return a map where the key is the node id ++*/ ++func (config *NodeYaml) MapAllNodes() (retMap map[string]*NodeInfo, err error) { ++ retMap = make(map[string]*NodeInfo) ++ nodeList, err := config.FindAllNodes() ++ if err != nil { ++ return ++ } ++ for _, nd := range nodeList { ++ retMap[nd.Id.Get()] = &nd ++ } ++ return ++} ++ + /* + FindDiscoverableNode returns the first discoverable node and an + interface to associate with the discovered interface. If the node has +diff --git a/internal/pkg/node/datastructure.go b/internal/pkg/node/datastructure.go +index 989c007a..34432454 100644 +--- a/internal/pkg/node/datastructure.go ++++ b/internal/pkg/node/datastructure.go +@@ -154,6 +154,7 @@ type NodeInfo struct { + ClusterName Entry + ContainerName Entry + Ipxe Entry ++ Grub Entry + RuntimeOverlay Entry + SystemOverlay Entry + Root Entry +diff --git a/internal/pkg/node/util.go b/internal/pkg/node/util.go +index e90efee6..8e1273c3 100644 +--- a/internal/pkg/node/util.go ++++ b/internal/pkg/node/util.go +@@ -6,6 +6,9 @@ import ( + "strings" + ) + ++/* ++get node by its hardware/MAC address, return error otherwise ++*/ + func (config *NodeYaml) FindByHwaddr(hwa string) (NodeInfo, error) { + if _, err := net.ParseMAC(hwa); err != nil { + return NodeInfo{}, errors.New("invalid hardware address: " + hwa) +@@ -26,6 +29,9 @@ func (config *NodeYaml) FindByHwaddr(hwa string) (NodeInfo, error) { + return ret, errors.New("No nodes found with HW Addr: " + hwa) + } + ++/* ++get node by its ip address, return error otherwise ++*/ + func (config *NodeYaml) FindByIpaddr(ipaddr string) (NodeInfo, error) { + if addr := net.ParseIP(ipaddr); addr == nil { + return NodeInfo{}, errors.New("invalid IP:" + ipaddr) +diff --git a/internal/pkg/overlay/datastructure.go b/internal/pkg/overlay/datastructure.go +index 15c9d3d5..0adc2f39 100644 +--- a/internal/pkg/overlay/datastructure.go ++++ b/internal/pkg/overlay/datastructure.go +@@ -49,19 +49,15 @@ func InitStruct(nodeInfo *node.NodeInfo) TemplateStruct { + controller := warewulfconf.Get() + nodeDB, err := node.New() + if err != nil { +- wwlog.Error("%s", err) +- os.Exit(1) ++ wwlog.Warn("Problems opening nodes.conf: %s", err) + } +- allNodes, err := nodeDB.FindAllNodes() ++ tstruct.AllNodes, err = nodeDB.FindAllNodes() + if err != nil { +- wwlog.Error("%s", err) +- os.Exit(1) ++ wwlog.Warn("couldn't get all nodes: %s", err) + } + // init some convenience vars + tstruct.Id = nodeInfo.Id.Get() + tstruct.Hostname = nodeInfo.Id.Get() +- // Backwards compatibility for templates using "Keys" +- tstruct.AllNodes = allNodes + tstruct.Nfs = *controller.NFS + tstruct.Dhcp = *controller.DHCP + tstruct.Tftp = *controller.TFTP +diff --git a/internal/pkg/warewulfd/copyshim.go b/internal/pkg/warewulfd/copyshim.go +new file mode 100644 +index 00000000..44ea45f4 +--- /dev/null ++++ b/internal/pkg/warewulfd/copyshim.go +@@ -0,0 +1,45 @@ ++package warewulfd ++ ++import ( ++ "fmt" ++ "os" ++ "path" ++ ++ warewulfconf "github.com/hpcng/warewulf/internal/pkg/config" ++ "github.com/hpcng/warewulf/internal/pkg/wwlog" ++ ++ "github.com/hpcng/warewulf/internal/pkg/container" ++ "github.com/hpcng/warewulf/internal/pkg/util" ++) ++ ++/* ++Copies the default shim, which is the shim located on host ++to the tftp directory ++*/ ++ ++func CopyShimGrub() (err error) { ++ conf := warewulfconf.Get() ++ wwlog.Debug("copy shim and grub binaries from host") ++ shimPath := container.ShimFind("") ++ if shimPath == "" { ++ return fmt.Errorf("no shim found on the host os") ++ } ++ err = util.CopyFile(shimPath, path.Join(conf.Paths.Tftpdir, "warewulf", "shim.efi")) ++ if err != nil { ++ return err ++ } ++ _ = os.Chmod(path.Join(conf.Paths.Tftpdir, "warewulf", "shim.efi"), 0o755) ++ grubPath := container.GrubFind("") ++ if grubPath == "" { ++ return fmt.Errorf("no grub found on host os") ++ } ++ err = util.CopyFile(grubPath, path.Join(conf.Paths.Tftpdir, "warewulf", "grub.efi")) ++ if err != nil { ++ return err ++ } ++ _ = os.Chmod(path.Join(conf.Paths.Tftpdir, "warewulf", "grub.efi"), 0o755) ++ err = util.CopyFile(grubPath, path.Join(conf.Paths.Tftpdir, "warewulf", "grubx64.efi")) ++ _ = os.Chmod(path.Join(conf.Paths.Tftpdir, "warewulf", "grubx64.efi"), 0o755) ++ ++ return ++} +diff --git a/internal/pkg/warewulfd/parser.go b/internal/pkg/warewulfd/parser.go +index 1e62dd0f..063d10b4 100644 +--- a/internal/pkg/warewulfd/parser.go ++++ b/internal/pkg/warewulfd/parser.go +@@ -5,6 +5,7 @@ import ( + "strconv" + "strings" + ++ "github.com/hpcng/warewulf/internal/pkg/wwlog" + "github.com/pkg/errors" + ) + +@@ -16,6 +17,7 @@ type parserInfo struct { + uuid string + stage string + overlay string ++ efifile string + compress string + } + +@@ -25,16 +27,22 @@ func parseReq(req *http.Request) (parserInfo, error) { + url := strings.Split(req.URL.Path, "?")[0] + path_parts := strings.Split(url, "/") + +- if len(path_parts) != 3 { ++ if len(path_parts) < 3 { + return ret, errors.New("unknown path components in GET") + } + + // handle when stage was passed in the url path /[stage]/hwaddr + stage := path_parts[1] +- hwaddr := path_parts[2] +- hwaddr = strings.ReplaceAll(hwaddr, "-", ":") +- hwaddr = strings.ToLower(hwaddr) +- ++ hwaddr := "" ++ if stage != "efiboot" { ++ hwaddr = path_parts[2] ++ hwaddr = strings.ReplaceAll(hwaddr, "-", ":") ++ hwaddr = strings.ToLower(hwaddr) ++ } else if len(path_parts) > 3 { ++ ret.efifile = strings.Join(path_parts[2:], "/") ++ } else { ++ ret.efifile = path_parts[2] ++ } + ret.hwaddr = hwaddr + ret.ipaddr = strings.Split(req.RemoteAddr, ":")[0] + ret.remoteport, _ = strconv.Atoi(strings.Split(req.RemoteAddr, ":")[1]) +@@ -63,6 +71,8 @@ func parseReq(req *http.Request) (parserInfo, error) { + ret.stage = "system" + } else if stage == "overlay-runtime" { + ret.stage = "runtime" ++ } else if stage == "efiboot" { ++ ret.stage = "efiboot" + } + } + +@@ -76,7 +86,11 @@ func parseReq(req *http.Request) (parserInfo, error) { + return ret, errors.New("no stage encoded in GET") + } + if ret.hwaddr == "" { +- return ret, errors.New("no hwaddr encoded in GET") ++ ret.hwaddr = ArpFind(ret.ipaddr) ++ wwlog.Verbose("node mac not encoded, arp cache got %s for %s", ret.hwaddr, ret.ipaddr) ++ if ret.hwaddr == "" { ++ return ret, errors.New("no hwaddr encoded in GET") ++ } + } + if ret.ipaddr == "" { + return ret, errors.New("could not obtain ipaddr from HTTP request") +diff --git a/internal/pkg/warewulfd/provision.go b/internal/pkg/warewulfd/provision.go +index f2851a26..b04a8a91 100644 +--- a/internal/pkg/warewulfd/provision.go ++++ b/internal/pkg/warewulfd/provision.go +@@ -3,7 +3,9 @@ package warewulfd + import ( + "bytes" + "errors" ++ "fmt" + "net/http" ++ "os" + "path" + "strconv" + "strings" +@@ -17,7 +19,7 @@ import ( + "github.com/hpcng/warewulf/internal/pkg/wwlog" + ) + +-type iPxeTemplate struct { ++type templateVars struct { + Message string + WaitTime string + Hostname string +@@ -32,13 +34,10 @@ type iPxeTemplate struct { + KernelOverride string + } + +-var status_stages = map[string]string{ +- "ipxe": "IPXE", +- "kernel": "KERNEL", +- "kmods": "KMODS_OVERLAY", +- "system": "SYSTEM_OVERLAY", +- "runtime": "RUNTIME_OVERLAY"} +- ++/* ++Handles all the http request for warewulfd, different stages are encoded ++in the GET request. ++*/ + func ProvisionSend(w http.ResponseWriter, req *http.Request) { + conf := warewulfconf.Get() + +@@ -59,8 +58,17 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { + } + } + ++ status_stages := map[string]string{ ++ "efiboot": "EFI", ++ "ipxe": "IPXE", ++ "kernel": "KERNEL", ++ "kmods": "KMODS_OVERLAY", ++ "system": "SYSTEM_OVERLAY", ++ "runtime": "RUNTIME_OVERLAY"} ++ + status_stage := status_stages[rinfo.stage] +- var stage_file string ++ var stage_file string = "" ++ updateSentDB := true + + // TODO: when module version is upgraded to go1.18, should be 'any' type + var tmpl_data interface{} +@@ -83,13 +91,13 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { + wwlog.Error("%s (unknown/unconfigured node)", rinfo.hwaddr) + if rinfo.stage == "ipxe" { + stage_file = path.Join(conf.Paths.Sysconfdir, "/warewulf/ipxe/unconfigured.ipxe") +- tmpl_data = iPxeTemplate{ ++ tmpl_data = templateVars{ + Hwaddr: rinfo.hwaddr} + } + + } else if rinfo.stage == "ipxe" { + stage_file = path.Join(conf.Paths.Sysconfdir, "warewulf/ipxe/"+node.Ipxe.Get()+".ipxe") +- tmpl_data = iPxeTemplate{ ++ tmpl_data = templateVars{ + Id: node.Id.Get(), + Cluster: node.ClusterName.Get(), + Fqdn: node.Id.Get(), +@@ -100,8 +108,20 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { + ContainerName: node.ContainerName.Get(), + KernelArgs: node.Kernel.Args.Get(), + KernelOverride: node.Kernel.Override.Get()} +- + } else if rinfo.stage == "kernel" { ++ if DBGetWWinit(node.Id.Get()) { ++ DBReset(node.Id.Get()) ++ } ++ if DBSize(node.Id.Get()) == 0 { ++ fd, err := os.Open(path.Join(path.Join(conf.Paths.Tftpdir, "warewulf", "grub.cfg"))) ++ if err != nil { ++ wwlog.Warn("no grub.cfg detected for potential tftp boot node: %s", node) ++ } else { ++ defer fd.Close() ++ DBAddImage(node.Id.Get(), "grub.cfg", fd) ++ } ++ ++ } + if node.Kernel.Override.Defined() { + stage_file = kernel.KernelImage(node.Kernel.Override.Get()) + } else if node.ContainerName.Defined() { +@@ -137,7 +157,6 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { + } else { + context = rinfo.stage + } +- + stage_file, err = getOverlayFile( + node, + context, +@@ -154,6 +173,64 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { + wwlog.ErrorExc(err, "") + return + } ++ } else if rinfo.stage == "efiboot" { ++ wwlog.Debug("requested method: %s", req.Method) ++ containerName := node.ContainerName.Get() ++ switch rinfo.efifile { ++ case "shim.efi": ++ stage_file = container.ShimFind(containerName) ++ if stage_file == "" { ++ wwlog.ErrorExc(fmt.Errorf("could't find shim.efi"), containerName) ++ w.WriteHeader(http.StatusNotFound) ++ return ++ } ++ case "grub.efi", "grub-tpm.efi", "grubx64.efi", "grubia32.efi", "grubaa64.efi", "grubarm.efi": ++ stage_file = container.GrubFind(containerName) ++ if stage_file == "" { ++ wwlog.ErrorExc(fmt.Errorf("could't find grub.efi"), containerName) ++ w.WriteHeader(http.StatusNotFound) ++ return ++ } ++ case "grub.cfg": ++ stage_file = path.Join(conf.Paths.Sysconfdir, "warewulf/grub/grub.cfg.ww") ++ tmpl_data = templateVars{ ++ Id: node.Id.Get(), ++ Cluster: node.ClusterName.Get(), ++ Fqdn: node.Id.Get(), ++ Ipaddr: conf.Ipaddr, ++ Port: strconv.Itoa(conf.Warewulf.Port), ++ Hostname: node.Id.Get(), ++ Hwaddr: rinfo.hwaddr, ++ ContainerName: node.ContainerName.Get(), ++ KernelArgs: node.Kernel.Args.Get(), ++ KernelOverride: node.Kernel.Override.Get()} ++ if stage_file == "" { ++ wwlog.ErrorExc(fmt.Errorf("could't find grub.cfg template"), containerName) ++ w.WriteHeader(http.StatusNotFound) ++ return ++ } ++ default: ++ wwlog.ErrorExc(fmt.Errorf("could't find efiboot file: %s", rinfo.efifile), "") ++ } ++ } else if rinfo.stage == "shim" { ++ if node.ContainerName.Defined() { ++ stage_file = container.ShimFind(node.ContainerName.Get()) ++ ++ if stage_file == "" { ++ wwlog.Error("No kernel found for container %s", node.ContainerName.Get()) ++ } ++ } else { ++ wwlog.Warn("No container set for this %s", node.Id.Get()) ++ } ++ } else if rinfo.stage == "grub" { ++ if node.ContainerName.Defined() { ++ stage_file = container.GrubFind(node.ContainerName.Get()) ++ if stage_file == "" { ++ wwlog.Error("No grub found for container %s", node.ContainerName.Get()) ++ } ++ } else { ++ wwlog.Warn("No conainer set for node %s", node.Id.Get()) ++ } + } + + wwlog.Serv("stage_file '%s'", stage_file) +@@ -187,11 +264,12 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { + + w.Header().Set("Content-Type", "text") + w.Header().Set("Content-Length", strconv.Itoa(buf.Len())) ++ reader := bytes.NewReader(buf.Bytes()) ++ DBAddImage(node.Id.Get(), stage_file, reader) + _, err = buf.WriteTo(w) + if err != nil { + wwlog.ErrorExc(err, "") + } +- + wwlog.Send("%15s: %s", node.Id.Get(), stage_file) + + } else { +@@ -210,7 +288,7 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { + w.WriteHeader(http.StatusNotFound) + } + +- err = sendFile(w, req, stage_file, node.Id.Get()) ++ err = sendFile(w, req, stage_file, node.Id.Get(), updateSentDB) + if err != nil { + wwlog.ErrorExc(err, "") + return +diff --git a/internal/pkg/warewulfd/provision_test.go b/internal/pkg/warewulfd/provision_test.go +index 870e1de0..5ec8d72e 100644 +--- a/internal/pkg/warewulfd/provision_test.go ++++ b/internal/pkg/warewulfd/provision_test.go +@@ -1,16 +1,18 @@ + package warewulfd + + import ( +- "github.com/stretchr/testify/assert" +- "io/ioutil" ++ "io" + "net/http" + "net/http/httptest" + "os" + "path" + "testing" + ++ "github.com/stretchr/testify/assert" ++ + warewulfconf "github.com/hpcng/warewulf/internal/pkg/config" + "github.com/hpcng/warewulf/internal/pkg/node" ++ "github.com/hpcng/warewulf/internal/pkg/wwlog" + ) + + var provisionSendTests = []struct { +@@ -18,37 +20,80 @@ var provisionSendTests = []struct { + url string + body string + status int ++ ip string + }{ +- {"system overlay", "/overlay-system/00:00:00:ff:ff:ff", "system overlay", 200}, +- {"runtime overlay", "/overlay-runtime/00:00:00:ff:ff:ff", "runtime overlay", 200}, +- {"fake overlay", "/overlay-system/00:00:00:ff:ff:ff?overlay=fake", "", 404}, +- {"specific overlay", "/overlay-system/00:00:00:ff:ff:ff?overlay=o1", "specific overlay", 200}, ++ {"system overlay", "/overlay-system/00:00:00:ff:ff:ff", "system overlay", 200, "10.10.10.10:9873"}, ++ {"runtime overlay", "/overlay-runtime/00:00:00:ff:ff:ff", "runtime overlay", 200, "10.10.10.10:9873"}, ++ {"fake overlay", "/overlay-system/00:00:00:ff:ff:ff?overlay=fake", "", 404, "10.10.10.10:9873:9873"}, ++ {"specific overlay", "/overlay-system/00:00:00:ff:ff:ff?overlay=o1", "specific overlay", 200, "10.10.10.10:9873"}, ++ {"find shim", "/efiboot/shim.efi", "", 200, "10.10.10.10:9873"}, ++ {"find shim", "/efiboot/shim.efi", "", 404, "10.10.10.11:9873"}, ++ {"find grub", "/efiboot/grub.efi", "", 200, "10.10.10.10:9873"}, ++ {"find grub", "/efiboot/grub.efi", "", 404, "10.10.10.11:9873"}, + } + + func Test_ProvisionSend(t *testing.T) { +- file, err := os.CreateTemp(os.TempDir(), "ww-test-nodes.conf-*") ++ conf_file, err := os.CreateTemp(os.TempDir(), "ww-test-nodes.conf-*") + assert.NoError(t, err) +- defer file.Close() ++ defer conf_file.Close() + { +- _, err := file.WriteString(`WW_INTERNAL: 43 ++ _, err := conf_file.WriteString(`WW_INTERNAL: 43 ++nodeprofiles: ++ default: ++ container name: suse + nodes: + n1: + network devices: + default: +- hwaddr: 00:00:00:ff:ff:ff`) ++ hwaddr: 00:00:00:ff:ff:ff ++ n2: ++ network devices: ++ default: ++ hwaddr: 00:00:00:00:ff:ff ++ container name: none`) ++ assert.NoError(t, err) ++ } ++ assert.NoError(t, conf_file.Sync()) ++ node.ConfigFile = conf_file.Name() ++ ++ // create a arp file as for grub we look up the ip address through the arp cache ++ arp_file, err := os.CreateTemp(os.TempDir(), "ww-arp") ++ assert.NoError(t, err) ++ defer arp_file.Close() ++ { ++ _, err := arp_file.WriteString(`IP address HW type Flags HW address Mask Device ++10.10.10.10 0x1 0x2 00:00:00:ff:ff:ff * dummy ++10.10.10.11 0x1 0x2 00:00:00:00:ff:ff * dummy`) + assert.NoError(t, err) + } +- assert.NoError(t, file.Sync()) +- node.ConfigFile = file.Name() ++ assert.NoError(t, arp_file.Sync()) ++ SetArpFile(arp_file.Name()) ++ ++ conf := warewulfconf.Get() ++ containerDir, imageDirErr := os.MkdirTemp(os.TempDir(), "ww-test-container-*") ++ assert.NoError(t, imageDirErr) ++ defer os.RemoveAll(containerDir) ++ conf.Paths.WWChrootdir = containerDir ++ assert.NoError(t, os.MkdirAll(path.Join(containerDir, "suse/rootfs/usr/lib64/efi"), 0700)) ++ { ++ _, err := os.Create(path.Join(containerDir, "suse/rootfs/usr/lib64/efi", "shim.efi")) ++ assert.NoError(t, err) ++ } ++ assert.NoError(t, os.MkdirAll(path.Join(containerDir, "suse/rootfs/usr/share/efi/x86_64/"), 0700)) ++ { ++ _, err := os.Create(path.Join(containerDir, "suse/rootfs/usr/share/efi/x86_64/", "grub.efi")) ++ assert.NoError(t, err) ++ } ++ + dbErr := LoadNodeDB() + assert.NoError(t, dbErr) + + provisionDir, provisionDirErr := os.MkdirTemp(os.TempDir(), "ww-test-provision-*") + assert.NoError(t, provisionDirErr) + defer os.RemoveAll(provisionDir) +- conf := warewulfconf.Get() + conf.Paths.WWProvisiondir = provisionDir + conf.Warewulf.Secure = false ++ wwlog.SetLogLevel(wwlog.DEBUG) + assert.NoError(t, os.MkdirAll(path.Join(provisionDir, "overlays", "n1"), 0700)) + assert.NoError(t, os.WriteFile(path.Join(provisionDir, "overlays", "n1", "__SYSTEM__.img"), []byte("system overlay"), 0600)) + assert.NoError(t, os.WriteFile(path.Join(provisionDir, "overlays", "n1", "__RUNTIME__.img"), []byte("runtime overlay"), 0600)) +@@ -57,12 +102,13 @@ nodes: + for _, tt := range provisionSendTests { + t.Run(tt.description, func(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, tt.url, nil) ++ req.RemoteAddr = tt.ip + w := httptest.NewRecorder() + ProvisionSend(w, req) + res := w.Result() + defer res.Body.Close() + +- data, readErr := ioutil.ReadAll(res.Body) ++ data, readErr := io.ReadAll(res.Body) + assert.NoError(t, readErr) + assert.Equal(t, tt.body, string(data)) + assert.Equal(t, tt.status, res.StatusCode) +diff --git a/internal/pkg/warewulfd/sentDB.go b/internal/pkg/warewulfd/sentDB.go +new file mode 100644 +index 00000000..ee64e4c5 +--- /dev/null ++++ b/internal/pkg/warewulfd/sentDB.go +@@ -0,0 +1,144 @@ ++package warewulfd ++ ++import ( ++ "crypto/sha256" ++ "fmt" ++ "io" ++ "path" ++ "sync" ++ ++ "github.com/hpcng/warewulf/internal/pkg/wwlog" ++) ++ ++/* ++store the sent files name and its checksum ++*/ ++type SentFiles struct { ++ Files []File `json:"files:"` ++ sha256sum [32]byte ++ Sha256hex string `json:"sha256"` ++ wwinit bool ++} ++ ++type File struct { ++ FileName string `json:"file name"` ++ sha256sum [32]byte ++ Sha256hex string `json:"sha256"` ++} ++ ++/* ++Database for the checksum of sent files, this ++values can be used for measured boot in combination with ++TPM devicces ++*/ ++var sentDB map[string]*SentFiles ++ ++// mutex for locking the map ++var mu sync.Mutex ++ ++func init() { ++ sentDB = map[string]*SentFiles{} ++} ++ ++/* ++Adds the image with the name to the database ++*/ ++func DBAddImage(node string, fileName string, content io.ReadSeeker) { ++ wwlog.Debug("adding file %s for node %s to sentDB", node, fileName) ++ hasher := sha256.New() ++ sent := File{ ++ FileName: path.Base(fileName), ++ } ++ if _, err := io.Copy(hasher, content); err != nil { ++ wwlog.SecWarn("couldn't create hash of %s for %s", fileName, node) ++ return ++ } ++ copy(sent.sha256sum[:], hasher.Sum(nil)) ++ sent.Sha256hex = fmt.Sprintf("%x", sent.sha256sum) ++ mu.Lock() ++ if _, ok := sentDB[node]; !ok { ++ sentDB[node] = new(SentFiles) ++ } ++ sentDB[node].Files = append(sentDB[node].Files, sent) ++ for i := 0; i < sha256.Size; i++ { ++ sentDB[node].sha256sum[i] = 1 ++ } ++ for _, sntFile := range sentDB[node].Files { ++ sentDB[node].sha256sum = sha256.Sum256(append(sentDB[node].sha256sum[:], sntFile.sha256sum[:]...)) ++ } ++ sentDB[node].Sha256hex = fmt.Sprintf("%x", sentDB[node].sha256sum) ++ mu.Unlock() ++ hasher.Reset() ++} ++ ++/* ++Get the final sum of all the hashed files ++*/ ++func DBGetSum(node string) (ret [sha256.Size]byte) { ++ mu.Lock() ++ defer mu.Unlock() ++ if sentNode, ok := sentDB[node]; ok { ++ ret = sentNode.sha256sum ++ return ++ } ++ ret = [sha256.Size]byte{0} ++ return ++} ++ ++/* ++Reset the database for a single node ++*/ ++func DBReset(node string) { ++ mu.Lock() ++ if _, ok := sentDB[node]; !ok { ++ sentDB[node] = new(SentFiles) ++ } ++ sentDB[node] = new(SentFiles) ++ mu.Unlock() ++} ++ ++/* ++Reset the database ++*/ ++func DBResetAll() { ++ sentDB = make(map[string]*SentFiles) ++} ++ ++/* ++Get the size of the DB ++*/ ++func DBSize(node string) int { ++ mu.Lock() ++ if _, ok := sentDB[node]; !ok { ++ sentDB[node] = new(SentFiles) ++ } ++ size := len(sentDB[node].Files) ++ mu.Unlock() ++ return size ++} ++ ++/* ++Check if wwinit was sent ++*/ ++func DBGetWWinit(node string) bool { ++ mu.Lock() ++ if _, ok := sentDB[node]; !ok { ++ sentDB[node] = new(SentFiles) ++ } ++ ret := sentDB[node].wwinit ++ mu.Unlock() ++ return ret ++} ++ ++/* ++Mark that wwinit was sent ++*/ ++ ++func DBWWinitSent(node string) { ++ mu.Lock() ++ if _, ok := sentDB[node]; !ok { ++ sentDB[node] = new(SentFiles) ++ } ++ sentDB[node].wwinit = true ++ mu.Unlock() ++} +diff --git a/internal/pkg/warewulfd/sentstatus.go b/internal/pkg/warewulfd/sentstatus.go +new file mode 100644 +index 00000000..36350367 +--- /dev/null ++++ b/internal/pkg/warewulfd/sentstatus.go +@@ -0,0 +1,34 @@ ++package warewulfd ++ ++import ( ++ "encoding/json" ++ "net/http" ++ ++ "github.com/hpcng/warewulf/internal/pkg/wwlog" ++ "github.com/pkg/errors" ++) ++ ++func sentStatusJSON() ([]byte, error) { ++ wwlog.Debug("Request for node sent status data...") ++ ++ ret, err := json.MarshalIndent(sentDB, "", " ") ++ if err != nil { ++ return ret, errors.Wrap(err, "could not marshal JSON data from status structure") ++ } ++ ++ return ret, nil ++ ++} ++ ++func SentStatus(w http.ResponseWriter, req *http.Request) { ++ status, err := sentStatusJSON() ++ if err != nil { ++ w.WriteHeader(http.StatusInternalServerError) ++ return ++ } ++ ++ _, err = w.Write(status) ++ if err != nil { ++ wwlog.Warn("Could not send sent status JSON: %s", err) ++ } ++} +diff --git a/internal/pkg/warewulfd/sentsums_test.go b/internal/pkg/warewulfd/sentsums_test.go +new file mode 100644 +index 00000000..41fcfe00 +--- /dev/null ++++ b/internal/pkg/warewulfd/sentsums_test.go +@@ -0,0 +1,23 @@ ++package warewulfd ++ ++import ( ++ "bytes" ++ "testing" ++) ++ ++func Test_SumOne(t *testing.T) { ++ firstText := `Scalable. Flexible. Today, Warewulf unites the ecosystem with the ability to provision containers directly to the bare metal hardware at massive scale, simplistically while retaining massive flexibility.` ++ secondText := `Being open source for over two-decades, and pioneering the concept of stateless node management, Warewulf is among the most successful HPC cluster platforms in the industry with support from OpenHPC, contributors around the world, and usage from every industry.` ++ DBAddImage("n01", "firstText", bytes.NewReader([]byte(firstText))) ++ DBAddImage("n01", "secondText", bytes.NewReader([]byte(secondText))) ++ if sum_n02 := DBGetSum("n02"); sum_n02 != [32]byte{} { ++ t.Errorf("Sum of second entry must be zero") ++ } ++ if sum_n01 := DBGetSum("n01"); sum_n01 == [32]byte{} { ++ t.Errorf("Sum of entry must not be zero") ++ } ++ DBResetAll() ++ if sum_n01 := DBGetSum("n01"); sum_n01 != [32]byte{} { ++ t.Errorf("Sum after reset must be zero") ++ } ++} +diff --git a/internal/pkg/warewulfd/util.go b/internal/pkg/warewulfd/util.go +index 3290a678..cefa1cab 100644 +--- a/internal/pkg/warewulfd/util.go ++++ b/internal/pkg/warewulfd/util.go +@@ -1,8 +1,11 @@ + package warewulfd + + import ( ++ "bufio" ++ "io" + "net/http" + "os" ++ "strings" + + "github.com/hpcng/warewulf/internal/pkg/node" + nodepkg "github.com/hpcng/warewulf/internal/pkg/node" +@@ -15,7 +18,7 @@ func sendFile( + w http.ResponseWriter, + req *http.Request, + filename string, +- sendto string) error { ++ sendto string, updateSentDB bool) error { + + fd, err := os.Open(filename) + if err != nil { +@@ -36,7 +39,14 @@ func sendFile( + filename, + stat.ModTime(), + fd) +- ++ // seek back ++ _, err = fd.Seek(0, io.SeekStart) ++ if err != nil { ++ wwlog.Warn("couldn't seek in file: %s", filename) ++ } ++ if updateSentDB { ++ DBAddImage(sendto, filename, fd) ++ } + wwlog.Send("%15s: %s", sendto, filename) + + return nil +@@ -70,3 +80,34 @@ func getOverlayFile( + + return + } ++ ++var arpFile string ++ ++func init() { ++ arpFile = "/proc/net/arp" ++} ++ ++func SetArpFile(newName string) { ++ arpFile = newName ++} ++ ++/* ++returns the mac address if it has an entry in the arp cache ++*/ ++func ArpFind(ip string) (mac string) { ++ arpCache, err := os.Open(arpFile) ++ if err != nil { ++ return ++ } ++ defer arpCache.Close() ++ ++ scanner := bufio.NewScanner(arpCache) ++ scanner.Scan() ++ for scanner.Scan() { ++ fields := strings.Fields(scanner.Text()) ++ if strings.EqualFold(fields[0], ip) { ++ return fields[3] ++ } ++ } ++ return ++} +diff --git a/internal/pkg/warewulfd/warewulfd.go b/internal/pkg/warewulfd/warewulfd.go +index 796054b0..4c480ebe 100644 +--- a/internal/pkg/warewulfd/warewulfd.go ++++ b/internal/pkg/warewulfd/warewulfd.go +@@ -5,6 +5,7 @@ import ( + "os" + "os/signal" + "strconv" ++ "strings" + "syscall" + + warewulfconf "github.com/hpcng/warewulf/internal/pkg/config" +@@ -14,6 +15,23 @@ import ( + + // TODO: https://github.com/danderson/netboot/blob/master/pixiecore/dhcp.go + // TODO: https://github.com/pin/tftp ++/* ++wrapper type for the server mux as shim requests http://efiboot//grub.efi ++which is filtered out by http to `301 Moved Permanently` what ++shim.efi can't handle. So filter out `//` before they hit go/http. ++Makes go/http more to behave like apache ++*/ ++type slashFix struct { ++ mux http.Handler ++} ++ ++/* ++Filter out the '//' ++*/ ++func (h *slashFix) ServeHTTP(w http.ResponseWriter, r *http.Request) { ++ r.URL.Path = strings.Replace(r.URL.Path, "//", "/", -1) ++ h.mux.ServeHTTP(w, r) ++} + + func RunServer() error { + err := DaemonInitLogging() +@@ -49,21 +67,27 @@ func RunServer() error { + wwlog.Error("Could not prepopulate node status DB: %s", err) + } + +- http.HandleFunc("/provision/", ProvisionSend) +- http.HandleFunc("/ipxe/", ProvisionSend) +- http.HandleFunc("/kernel/", ProvisionSend) +- http.HandleFunc("/kmods/", ProvisionSend) +- http.HandleFunc("/container/", ProvisionSend) +- http.HandleFunc("/overlay-system/", ProvisionSend) +- http.HandleFunc("/overlay-runtime/", ProvisionSend) +- http.HandleFunc("/status", StatusSend) ++ if err != nil { ++ wwlog.Warn("couldn't copy default shim: %s", err) ++ } ++ var wwHandler http.ServeMux ++ wwHandler.HandleFunc("/provision/", ProvisionSend) ++ wwHandler.HandleFunc("/ipxe/", ProvisionSend) ++ wwHandler.HandleFunc("/efiboot/", ProvisionSend) ++ wwHandler.HandleFunc("/kernel/", ProvisionSend) ++ wwHandler.HandleFunc("/kmods/", ProvisionSend) ++ wwHandler.HandleFunc("/container/", ProvisionSend) ++ wwHandler.HandleFunc("/overlay-system/", ProvisionSend) ++ wwHandler.HandleFunc("/overlay-runtime/", ProvisionSend) ++ wwHandler.HandleFunc("/status", StatusSend) ++ wwHandler.HandleFunc("/sentstatus", SentStatus) + + conf := warewulfconf.Get() + + daemonPort := conf.Warewulf.Port + wwlog.Serv("Starting HTTPD REST service on port %d", daemonPort) + +- err = http.ListenAndServe(":"+strconv.Itoa(daemonPort), nil) ++ err = http.ListenAndServe(":"+strconv.Itoa(daemonPort), &slashFix{&wwHandler}) + if err != nil { + return errors.Wrap(err, "Could not start listening service") + } +diff --git a/overlays/host/etc/dhcp/dhcpd.conf.ww b/overlays/host/etc/dhcp/dhcpd.conf.ww +index 4a9637f7..03fe54cb 100644 +--- a/overlays/host/etc/dhcp/dhcpd.conf.ww ++++ b/overlays/host/etc/dhcp/dhcpd.conf.ww +@@ -1,4 +1,4 @@ +-{{if .Dhcp.Enabled -}} ++{{if $.Dhcp.Enabled -}} + # This file is autogenerated by warewulf + # Host: {{.BuildHost}} + # Time: {{.BuildTime}} +@@ -15,8 +15,29 @@ option space ipxe; + option ipxe.no-pxedhcp code 176 = unsigned integer 8; + option ipxe.no-pxedhcp 1; + ++option space PXE; ++option PXE.mtftp-ip code 1 = ip-address; ++option PXE.mtftp-cport code 2 = unsigned integer 16; ++option PXE.mtftp-sport code 3 = unsigned integer 16; ++option PXE.mtftp-tmout code 4 = unsigned integer 8; ++option PXE.mtftp-delay code 5 = unsigned integer 8; ++ + option architecture-type code 93 = unsigned integer 16; + ++{{- if $.Warewulf.GrubBoot }} ++if substring (option vendor-class-identifier, 0, 9) = "PXEClient" { ++ next-server {{ $.Ipaddr }}; ++ if substring (option vendor-class-identifier, 15, 5) = "00000" { ++ # pure BIOS clients will get iPXE configuration ++ filename "http://{{$.Ipaddr}}:{{$.Warewulf.Port}}/ipxe/${mac:hexhyp}"; ++ } else { ++ # EFI clients will get shim and grub instead ++ filename "warewulf/shim.efi"; ++ } ++} elsif substring (option vendor-class-identifier, 0, 10) = "HTTPClient" { ++ filename "http://{{$.Ipaddr}}:{{$.Warewulf.Port}}/efiboot/shim.efi"; ++} ++{{- else }} + if exists user-class and option user-class = "iPXE" { + filename "http://{{$.Ipaddr}}:{{$.Warewulf.Port}}/ipxe/${mac:hexhyp}"; + } else { +@@ -24,39 +45,32 @@ if exists user-class and option user-class = "iPXE" { + if option architecture-type = {{ $type }} { + filename "/warewulf/{{ basename $name }}"; + } +-{{ end }} ++{{- end }}{{/* range IpxeBinaries */}} + } ++{{- end }}{{/* BootMethod */}} + +-{{if eq .Dhcp.Template "static" -}} + subnet {{$.Network}} netmask {{$.Netmask}} { +- next-server {{$.Ipaddr}}; ++ max-lease-time 120; ++ range {{$.Dhcp.RangeStart}} {{$.Dhcp.RangeEnd}}; ++ next-server {{.Ipaddr}}; + } +-{{range $nodes := .AllNodes}} ++{{- range $nodes := $.AllNodes}} + {{- range $netname, $netdevs := $nodes.NetDevs}} +-host {{$nodes.Id.Get}}-{{if $netdevs.Device.Defined}}{{$netdevs.Device.Get}}{{else}}{{$netname}}{{end}} { ++host {{$nodes.Id.Get}}-{{$netdevs.Device.Get}} ++{ ++ {{- if $netdevs.Primary.GetB}} + {{- if $netdevs.Hwaddr.Defined}} + hardware ethernet {{$netdevs.Hwaddr.Get}}; +- {{- end}} ++ {{- end }} + {{- if $netdevs.Ipaddr.Defined}} + fixed-address {{$netdevs.Ipaddr.Get}}; +- {{- end}} +- {{- if $netdevs.Primary.GetB}} ++ {{- end }} + option host-name "{{$nodes.Id.Get}}"; +- {{else}} +- option host-name "{{$nodes.Id.Get}}-{{if $netdevs.Device.Defined}}{{$netdevs.Device.Get}}{{else}}{{$netname}}{{end}}"; +- {{- end}} +-} +-{{end -}} +-{{end -}} +- +-{{else -}} +-subnet {{$.Network}} netmask {{$.Netmask}} { +- max-lease-time 120; +- range {{$.Dhcp.RangeStart}} {{$.Dhcp.RangeEnd}}; +- next-server {{$.Ipaddr}}; ++ {{- end }} + } +-{{end}} ++{{end -}}{{/* range NetDevs */}} ++{{end -}}{{/* range AllNodes */}} + + {{- else}} + {{abort}} +-{{- end}} ++{{- end}}{{/* dhcp enabled */}} +diff --git a/overlays/host/etc/dhcpd.conf.ww b/overlays/host/etc/dhcpd.conf.ww +new file mode 120000 +index 00000000..e5b73f7b +--- /dev/null ++++ b/overlays/host/etc/dhcpd.conf.ww +@@ -0,0 +1 @@ ++dhcp/dhcpd.conf.ww +\ No newline at end of file +diff --git a/overlays/wwinit/warewulf/init.d/80-wwclient b/overlays/wwinit/warewulf/init.d/80-wwclient +new file mode 100644 +index 00000000..38347110 +--- /dev/null ++++ b/overlays/wwinit/warewulf/init.d/80-wwclient +@@ -0,0 +1,7 @@ ++#!/bin/sh ++. /warewulf/config ++# Only start if the systemd is no available ++test -d /run/systemd/system && exit 0 ++echo "Starting wwclient" ++nohup /warewulf/wwclient >/var/log/wwclient.log 2>&1 `_ ++can be used with the advantage that secure boot can be used. That means ++that only the signed kernel of a distribution can be booted. This can ++be a huge security benefit for some scenarios. ++ ++In order to enable the grub boot method it has to be enabled in `warewulf.conf`. ++Nodes which are not known to warewulf will then booted with the shim/grub from ++the host on which warewulf is installed. ++ ++ ++Boot process ++============ ++ ++The boot process can be summarized with following diagram ++ ++.. graphviz:: ++ ++ digraph foo { ++ node [shape=box]; ++ subgraph boot { ++ "EFI" [label="EFI",row=boot]; ++ "Shim" [label="Shim",row=boot]; ++ "Grub" [label="Grub",row=boot]; ++ "Kernel" [label="kernel",row=boot]; ++ EFI -> Shim[label="Check for Microsoft signature"]; ++ Shim -> Grub[label="Check for Distribution signature"]; ++ Grub->Kernel[label="Check for Distribution or MOK signature"]; ++ } ++ } ++ ++If secure boot is enabled at every step a signature is checked and the boot process ++will fail if this check fails. Also at moment a Shim only includes the key ++of one Distribution, which means that every Distribution needs a separate ++`shim` and `grub` executable and warewulf extracts these binaries from ++the containers. ++ ++For the case when the node is unknown to warewulf or ++can't be identified during the `tFTP`` boot phase, the shim/grub binaries of ++the host in which warewulf is running will be used. ++ ++PXE/tFTP boot ++------------- ++ ++The standard network boot process with `grub` and `iPXE` has following steps ++ ++.. graphviz:: ++ ++ digraph G{ ++ node [shape=box]; ++ compound=true; ++ edge [label2node=true] ++ bios [shape=record label="{Bios | boots filename from nextboot per tFTP}"] ++ subgraph cluster0 { ++ label="iPXE boot" ++ iPXE; ++ ipxe_cfg [shape=record label="{ipxe.cfg|generated for indivdual node}"]; ++ iPXE -> ipxe_cfg [label="http"]; ++ } ++ bios->iPXE [lhead=cluster0,label="filename=iPXE.efi"]; ++ bios->shim [lhead=cluster1,label="filename=shim.efi"]; ++ subgraph cluster1{ ++ label="Grub boot" ++ shim[shape=record label="{shim.efi|from ww4 host}"]; ++ grub[shape=record label="{grubx64.efi | name hardcoded in shim.efi|from ww4 host}"] ++ shim->grub[label="tFTP"]; ++ grubcfg[shape=record label="{grub.cfg|static under tFTP root}"]; ++ grub->grubcfg[label="tFTP"]; ++ } ++ kernel [shape=record label="{kernel|ramdisk (root fs)|wwinit overlay}|extracted from node container"]; ++ grubcfg->kernel[ltail=cluster1,label="http"]; ++ ipxe_cfg->kernel[ltail=cluster0,label="http"]; ++ } ++ ++As the tFTP server is independent of warewulf, the `shim` and `grub` EFI binaries ++for the tFTP server are copied from the host on which warewulf is running. ++This means that for secure boot the distributor e.g. SUSE of the container in ++the `default` profile must match the distributor of the container which then ++also must be signed by the SUSE key. ++ ++http boot ++--------- ++ ++Modern EFI systems have the possibility to directly boot per http. The flow diagram ++is the following: ++ ++.. graphviz:: ++ ++ digraph G{ ++ node [shape=box]; ++ efi [shape=record label="{EFI|boots from URI defined in filename}"]; ++ shim [shape=record label="{shim.efi|replaces shim.efi with grubx64.efi in URI|extracted from node container}"]; ++ grub [shape=record label="{grub.efi|checks for grub.cfg|extracted from node container}"] ++ kernel [shape=record label="{kernel|ramdisk (root fs)|wwinit overlay}|extracted from node container"]; ++ efi->shim [label="http"]; ++ shim->grub [label="http"]; ++ grub->kernel [label="http"]; ++ } ++ ++The main difference is that the initial `shim.efi` and `grub.efi` are delivered by http with warewulf ++and are taken directly from the container assigned to the node. This means that secure boot will work ++for containers from different distributors. ++ ++Install shim and efi ++-------------------- ++ ++The `shim.efi` and `grub.efi` must be installed via the package manager directly into the container. ++ ++Install on SUSE systems ++^^^^^^^^^^^^^^^^^^^^^^^ ++ ++.. code-block:: console ++ ++ # wwctl container shell leap15.5 ++ [leap15.5] Warewulf> zypper install grub2 shim ++ ++ ++Install on EL system ++^^^^^^^^^^^^^^^^^^^^ ++ ++.. code-block:: console ++ ++ # wwctl container shell rocky9 ++ [rocky9] Warewulf> dnf install shim-x64.x86_64 grub2-pc.x86_64 +\ No newline at end of file +diff --git a/userdocs/contents/security.rst b/userdocs/contents/security.rst +index 1f585de1..b0bb42a8 100644 +--- a/userdocs/contents/security.rst ++++ b/userdocs/contents/security.rst +@@ -55,7 +55,7 @@ when a user lands on a compute node, there is generally nothing + stopping them from spoofing a provision request and downloading the + provisioned raw materials for inspection. + +-In Warewulf there are two ways to secure the provisioning process: ++In Warewulf there are ways multiple to secure the provisioning process: + + #. The provisioning connections and transfers are not secure due to + not being able to manage a secure root of trust through a PXE +@@ -77,6 +77,11 @@ In Warewulf there are two ways to secure the provisioning process: + provision and communicate with requests from that system matching + that asset tag. + ++#. When the nodes are booted via `shim` and `grub` Secure Boot can be ++ enabled. This means that the nodes only boot the kernel which is ++ provided by the distributor and also custom complied modules can't ++ be loaded. ++ + Summary + ======= + +diff --git a/userdocs/index.rst b/userdocs/index.rst +index 71fb9da5..9d7a4b8c 100644 +--- a/userdocs/index.rst ++++ b/userdocs/index.rst +@@ -18,6 +18,7 @@ Welcome to the Warewulf User Guide! + Warewulf Initialization + Container Management + Kernel Management ++ Boot Management + Node Configuration + Node Profiles + Warewulf Overlays +diff --git a/warewulf.spec.in b/warewulf.spec.in +index 318703fb..e71a71a4 100644 +--- a/warewulf.spec.in ++++ b/warewulf.spec.in +@@ -139,6 +139,7 @@ yq e ' + %config(noreplace) %{_sysconfdir}/warewulf/wwapi*.conf + %config(noreplace) %{_sysconfdir}/warewulf/examples + %config(noreplace) %{_sysconfdir}/warewulf/ipxe ++%config(noreplace) %{_sysconfdir}/warewulf/grub + %config(noreplace) %attr(0640,-,-) %{_sysconfdir}/warewulf/nodes.conf + %{_sysconfdir}/bash_completion.d/wwctl + diff --git a/vendor.tar.gz b/vendor.tar.gz index 0d717d2..a2720b3 100644 --- a/vendor.tar.gz +++ b/vendor.tar.gz @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fce8aa6a5c92d1ca8f334a35686e206a6567c1022dc037666366e273fb57dd5c -size 5488991 +oid sha256:ade18e7f3ca08bf1fee19b7c86350e80f463c6884dc6790d6d568e15fb3c56ae +size 5811432 diff --git a/warewulf4-v4.5.0rc0.tar.gz b/warewulf4-v4.5.0rc0.tar.gz new file mode 100644 index 0000000..a4c0e22 --- /dev/null +++ b/warewulf4-v4.5.0rc0.tar.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e0bb66935b1e4b11e542ef521284389a6e492fc775adfe8a9384fcc8efff76f2 +size 16927353 diff --git a/warewulf4.changes b/warewulf4.changes index fa21c8b..ad3d57b 100644 --- a/warewulf4.changes +++ b/warewulf4.changes @@ -1,3 +1,23 @@ +------------------------------------------------------------------- +Thu Dec 21 14:48:08 UTC 2023 - Christian Goll + +- updated to v4.5.0rc0 pre release with following new features: + * disks, partitions and file systems can set in the configration + and if ignition is present in the container, changes to the disks will be + made + * grub can be used as alternative boot method instead of iPXE. The + grub binairy is extracted from the container and shim is executed before + the grub. This enables secure boot + * wwctl has now the genconfig subcommand which will print/create + valid configurations + * all paths e.g the overlay dir, can now be configured in warewulf.conf +- notable bug fixes are: + * Fixed a bug where profile tags were erroneously overridden by empty node + values. + * Fixed bug where tags from profiles weren't rendered +- added grub-boot.patch which includes the not merged grub support + + ------------------------------------------------------------------- Thu Oct 12 07:43:32 UTC 2023 - Christian Goll diff --git a/warewulf4.spec b/warewulf4.spec index 04ce03c..4287c42 100644 --- a/warewulf4.spec +++ b/warewulf4.spec @@ -16,24 +16,23 @@ # -#%%define rls_cndt rc3 +%define rls_cndt rc0 +%global tftpdir /srv/tftpboot +%global srvdir %{_sharedstatedir} ExclusiveArch: x86_64 aarch64 Name: warewulf4 -Version: 4.4.0 +Version: 4.5.0%{?rls_cndt} Release: 0 Summary: A suite of tools for clustering License: BSD-3-Clause Group: Productivity/Clustering/Computing URL: https://warewulf.org -Source0: https://github.com/hpcng/warewulf/archive/v%{version}%{?rls_cndt}.tar.gz#/warewulf4-v%{version}.tar.gz -Source1: vendor.tar.gz -Source3: warewulf4-rpmlintrc +#Source0: https://github.com/hpcng/warewulf/archive/v%{version}%{?rls_cndt}.tar.gz#/warewulf4-v%{version}.tar.gz +Source0: https://github.com/hpcng/warewulf/releases/download/nightly/warewulf-4.5.x.tar.gz#/warewulf4-v%{version}.tar.gz Source10: config-ww4.sh -Patch1: make-ipxe-binary-source-configureable.patch -Patch2: CreateMt-Targets.patch -Patch10: Fix-for-CVE-2022-41723.patch +Patch10: grub-boot.patch # no firewalld in sle12 %if 0%{?sle_version} >= 150000 || 0%{?suse_version} > 1500 @@ -41,14 +40,15 @@ BuildRequires: firewalld %endif BuildRequires: distribution-release BuildRequires: dracut -BuildRequires: go >= 1.15 +BuildRequires: go >= 1.20 BuildRequires: golang-packaging -BuildRequires: libgpgme-devel +BuildRequires: libgpg-error-devel BuildRequires: make BuildRequires: munge BuildRequires: sysuser-tools BuildRequires: tftp BuildRequires: yq +BuildRequires: pkgconfig(gpgme) BuildRoot: %{_tmppath}/%{name}-%{version}-build %sysusers_requires Requires: %{name}-overlay = %{version} @@ -56,6 +56,7 @@ Requires: dhcp-server Requires: ipxe-bootimgs Requires: pigz Requires: tftp +Recommends: bash-completion Recommends: ipmitool Recommends: nfs-kernel-server @@ -79,10 +80,18 @@ Includes the default overlays so that they can be updated seprately. %package api Requires: %{name} -Summary: Contains the service for the warewulf rest API +Summary: Contains the services for the warewulf rest API %description api -Containts the binaries for the access of warewulf through a rest API and from the commandline from an external host. +Contains the binaries for the access of warewulf through a rest API and from +the commandline from an external host. + +%package doc +Requires: %{name} +Summary: Contains the documentation for warewulf + +%description doc +Documention and man pages for warewulf. %package slurm Summary: Configuration template for slurm @@ -94,51 +103,68 @@ This package install the necessary configuration files in order to run a slurm cluster on the configured warewulf nodes. %prep -%setup -q -n warewulf-%{version}%{?rls_cndt} -# extract the vendor stuff -tar xzf %{S:1} +%setup -q -n warewulf-4.5.x %autopatch -p1 %build -%global tftpdir /srv/tftpboot -make %{?_smp_mflags} genconfig \ +export OFFLINE_BUILD=1 +export IPXESOURCE=%{_datadir}/ipxe +export GOFLAGS="-buildmode=pie" +make defaults \ PREFIX=%{_prefix} \ BINDIR=%{_bindir} \ SYSCONFDIR=%{_sysconfdir} \ - DATADIR=%{_datadir}/ipxe \ - LOCALSTATEDIR=%{_datadir} \ + DATADIR=%{_datadir} \ + LOCALSTATEDIR=%{_sharedstatedir} \ SHAREDSTATEDIR=%{_sharedstatedir} \ MANDIR=%{_mandir} \ INFODIR=%{_infodir} \ DOCDIR=%{_docdir} \ - SRVDIR=%{_sharedstatedir} \ + SRVDIR=%{srvdir} \ TFTPDIR=%{tftpdir} \ SYSTEMDDIR=%{_unitdir} \ - BASHCOMPDIR=%{_datadir}/bash-completion/completions/ \ + BASHCOMPDIR=/etc/bash_completion.d/ \ + FIREWALLDDIR=/usr/lib/firewalld/services \ + WWCLIENTDIR=/warewulf +make %{?_smp_mflags} build \ + PREFIX=%{_prefix} \ + BINDIR=%{_bindir} \ + SYSCONFDIR=%{_sysconfdir} \ + DATADIR=%{_datadir} \ + LOCALSTATEDIR=%{_sharedstatedir} \ + SHAREDSTATEDIR=%{_sharedstatedir} \ + MANDIR=%{_mandir} \ + INFODIR=%{_infodir} \ + DOCDIR=%{_docdir} \ + SRVDIR=%{srvdir} \ + TFTPDIR=%{tftpdir} \ + SYSTEMDDIR=%{_unitdir} \ + BASHCOMPDIR=/etc/bash_completion.d/ \ FIREWALLDDIR=/usr/lib/firewalld/services \ WWCLIENTDIR=/warewulf %install # we have a broken symlink for wwclient export NO_BRP_STALE_LINK_ERROR=yes +export IPXESOURCE=%{_datadir}/ipxe +# overlays will end up here +export OFFLINE_BUILD=1 +export LOCALSTATEDIR=%{_localstatedir}/lib %{makeinstall} # cleanup -mv %{buildroot}%{_datadir}/warewulf/overlays/host/etc/dhcp/dhcpd.conf.ww \ - %{buildroot}%{_datadir}//warewulf/overlays/host/etc/dhcpd.conf.ww -rmdir %{buildroot}%{_datadir}/warewulf/overlays/host/etc/dhcp -rm %{buildroot}%{_datadir}/warewulf/overlays/host/etc/dhcpd.conf mkdir -p %{buildroot}%{_sbindir}/ ln -s %{_sbindir}/service %{buildroot}%{_sbindir}/rcwarewulfd + mkdir -p %{buildroot}%{_datadir}/bash-completion/completions -mv -v %{buildroot}%{_sysconfdir}/bash_completion.d/warewulf %{buildroot}%{_datadir}/bash-completion/completions/wwctl -#cp %{S:2} %{buildroot}%{_sysconfdir}/warewulf/warewulf.conf -#rm -rf %{buildroot}%{_datadir}/warewulf/ipxe +mv -v %{buildroot}%{_sysconfdir}/bash_completion.d/wwctl \ + %{buildroot}%{_datadir}/bash-completion/completions/wwctl +#rm -r %{buildroot}%{_datadir}/doc/warewulf +# copy the LICESNSE.md via %%doc rm -f %{buildroot}/usr/share/doc/packages/warewulf/LICENSE.md -rm -rf %{buildroot}%{_localstatedir}/lib/warewulf -# use ipxe-bootimgs images + +# use ipxe-bootimgs images from distribution yq e ' - .dhcpd.template = "static" | .tftp.ipxe."00:00" = "undionly.kpxe" | .tftp.ipxe."00:07" = "ipxe-x86_64.efi" | .tftp.ipxe."00:09" = "ipxe-x86_64.efi" | @@ -147,6 +173,9 @@ yq e ' .["container mounts"] += {"source": "/etc/zypp/credentials.d/SCCcredentials", "dest": "/etc/zypp/credentials.d/SCCcredentials", "readonly": true}' \ -i %{buildroot}%{_sysconfdir}/warewulf/warewulf.conf sed -i 's@\(^\s*\)\(.*:.*\):@\1"\2":@' %{buildroot}%{_sysconfdir}/warewulf/warewulf.conf +# fix dhcp for SUSE +mv %{buildroot}%{_localstatedir}/lib/warewulf/overlays/host/etc/dhcp/dhcpd.conf.ww %{buildroot}%{_localstatedir}/lib/warewulf/overlays/host/etc/dhcpd.conf.ww +rmdir %{buildroot}%{_localstatedir}/lib/warewulf/overlays/host/etc/dhcp # create systemuser echo "u warewulf -" > system-user-%{name}.conf @@ -155,20 +184,18 @@ echo "g warewulf -" >> system-user-%{name}.conf install -D -m 644 system-user-%{name}.conf %{buildroot}%{_sysusersdir}/system-user-%{name}.conf install -D -m 755 %{S:10} %{buildroot}%{_datadir}/warewulf/scripts/config-warewulf.sh -# get the slurm package readay -mkdir -p %{buildroot}%{_datadir}/warewulf/overlays/host/etc/slurm -mv %{buildroot}%{_sysconfdir}/warewulf/examples/slurm.conf.ww %{buildroot}%{_datadir}/warewulf/overlays/host/etc/slurm -mkdir -p %{buildroot}%{_datadir}/warewulf/overlays/generic/etc/munge -cat > %{buildroot}%{_datadir}/warewulf/overlays/generic/etc/munge/munge.key.ww < %{buildroot}%{_localstatedir}/lib/warewulf/overlays/generic/etc/munge/munge.key.ww < %{buildroot}%{_datadir}/warewulf/overlays/generic/etc/slurm/slurm.conf.ww < %{buildroot}%{_localstatedir}/lib/warewulf/overlays/generic/etc/slurm/slurm.conf.ww < Date: Thu, 21 Dec 2023 15:06:41 +0000 Subject: [PATCH 04/13] Accepting request 1134484 from home:mslacken:pr removed old binaries OBS-URL: https://build.opensuse.org/request/show/1134484 OBS-URL: https://build.opensuse.org/package/show/network:cluster/warewulf4?expand=0&rev=45 --- vendor.tar.gz | 3 --- warewulf4-v4.4.0.tar.gz | 3 --- 2 files changed, 6 deletions(-) delete mode 100644 vendor.tar.gz delete mode 100644 warewulf4-v4.4.0.tar.gz diff --git a/vendor.tar.gz b/vendor.tar.gz deleted file mode 100644 index a2720b3..0000000 --- a/vendor.tar.gz +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ade18e7f3ca08bf1fee19b7c86350e80f463c6884dc6790d6d568e15fb3c56ae -size 5811432 diff --git a/warewulf4-v4.4.0.tar.gz b/warewulf4-v4.4.0.tar.gz deleted file mode 100644 index 8de7925..0000000 --- a/warewulf4-v4.4.0.tar.gz +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1cfe0dddcafdc7ea0fa36b2fcd0105962b8d0bd62870641b7c35efb938aed489 -size 1479705 From 1a83f2ae340f4e3610db189eb5295a924af9a1e6ec2e23f465becbb93adf66cb Mon Sep 17 00:00:00 2001 From: Christian Goll Date: Thu, 21 Dec 2023 15:09:37 +0000 Subject: [PATCH 05/13] - removed make-ipxe-binary-source-configureable.patch as incoperated upstream OBS-URL: https://build.opensuse.org/package/show/network:cluster/warewulf4?expand=0&rev=46 --- warewulf4.changes | 1 + 1 file changed, 1 insertion(+) diff --git a/warewulf4.changes b/warewulf4.changes index ad3d57b..0355b09 100644 --- a/warewulf4.changes +++ b/warewulf4.changes @@ -16,6 +16,7 @@ Thu Dec 21 14:48:08 UTC 2023 - Christian Goll values. * Fixed bug where tags from profiles weren't rendered - added grub-boot.patch which includes the not merged grub support +- removed make-ipxe-binary-source-configureable.patch as incoperated upstream ------------------------------------------------------------------- From 861909d120e469e5466bab4631f216c20eae1909f9d7348b7c96e41785f1819b Mon Sep 17 00:00:00 2001 From: Christian Goll Date: Thu, 21 Dec 2023 15:16:36 +0000 Subject: [PATCH 06/13] removed upstreamed patch OBS-URL: https://build.opensuse.org/package/show/network:cluster/warewulf4?expand=0&rev=47 --- make-ipxe-binary-source-configureable.patch | 163 -------------------- 1 file changed, 163 deletions(-) delete mode 100644 make-ipxe-binary-source-configureable.patch diff --git a/make-ipxe-binary-source-configureable.patch b/make-ipxe-binary-source-configureable.patch deleted file mode 100644 index a570ea3..0000000 --- a/make-ipxe-binary-source-configureable.patch +++ /dev/null @@ -1,163 +0,0 @@ -From 32ab50f299502fce7bf588852a75c63cf3332cf8 Mon Sep 17 00:00:00 2001 -From: Christian Goll -Date: Fri, 20 Jan 2023 15:05:42 +0100 -Subject: [PATCH] make ipxe binary source configureable - ---- - internal/pkg/buildconfig/defaults.go | 2 +- - internal/pkg/configure/tftp.go | 17 ++++++++++------- - internal/pkg/overlay/datastructure.go | 2 ++ - internal/pkg/warewulfconf/constructors.go | 12 ++++++++++-- - internal/pkg/warewulfconf/datastructure.go | 2 ++ - overlays/host/etc/dhcp/dhcpd.conf.ww | 14 +++++--------- - 6 files changed, 30 insertions(+), 19 deletions(-) - -diff --git a/internal/pkg/buildconfig/defaults.go b/internal/pkg/buildconfig/defaults.go -index 24cb2d40..17baecba 100644 ---- a/internal/pkg/buildconfig/defaults.go -+++ b/internal/pkg/buildconfig/defaults.go -@@ -27,7 +27,7 @@ func BINDIR() string { - } - - func DATADIR() string { -- wwlog.Debug("DATADIR = '%s'", bindir) -+ wwlog.Debug("DATADIR = '%s'", datadir) - return datadir - } - -diff --git a/internal/pkg/configure/tftp.go b/internal/pkg/configure/tftp.go -index d321f023..842d7fb4 100644 ---- a/internal/pkg/configure/tftp.go -+++ b/internal/pkg/configure/tftp.go -@@ -11,9 +11,8 @@ import ( - "github.com/hpcng/warewulf/internal/pkg/wwlog" - ) - --var tftpdir string = path.Join(buildconfig.TFTPDIR(), "warewulf") -- - func TFTP() error { -+ var tftpdir string = path.Join(buildconfig.TFTPDIR(), "warewulf") - controller, err := warewulfconf.New() - if err != nil { - wwlog.Error("%s", err) -@@ -27,11 +26,15 @@ func TFTP() error { - } - - fmt.Printf("Writing PXE files to: %s\n", tftpdir) -- for _, f := range [4]string{"x86_64.efi", "x86_64.kpxe", "arm64.efi"} { -- err = util.SafeCopyFile(path.Join(buildconfig.DATADIR(), "warewulf", "ipxe", f), path.Join(tftpdir, f)) -+ copyCheck := make(map[string]bool) -+ for _, f := range controller.Tftp.IpxeBinaries { -+ if copyCheck[f] { -+ continue -+ } -+ copyCheck[f] = true -+ err = util.SafeCopyFile(path.Join(buildconfig.DATADIR(), f), path.Join(tftpdir, f)) - if err != nil { -- wwlog.Error("%s", err) -- return err -+ wwlog.Warn("ipxe binary could not be copied, not booting may not work: %s", err) - } - } - -@@ -39,7 +42,7 @@ func TFTP() error { - wwlog.Info("Warewulf does not auto start TFTP services due to disable by warewulf.conf") - os.Exit(0) - } -- -+ - fmt.Printf("Enabling and restarting the TFTP services\n") - err = util.SystemdStart(controller.Tftp.SystemdName) - if err != nil { -diff --git a/internal/pkg/overlay/datastructure.go b/internal/pkg/overlay/datastructure.go -index 2a427bee..eb8004cf 100644 ---- a/internal/pkg/overlay/datastructure.go -+++ b/internal/pkg/overlay/datastructure.go -@@ -31,6 +31,7 @@ type TemplateStruct struct { - Dhcp warewulfconf.DhcpConf - Nfs warewulfconf.NfsConf - Warewulf warewulfconf.WarewulfConf -+ Tftp warewulfconf.TftpConf - AllNodes []node.NodeInfo - node.NodeConf - // backward compatiblity -@@ -64,6 +65,7 @@ func InitStruct(nodeInfo node.NodeInfo) TemplateStruct { - tstruct.AllNodes = allNodes - tstruct.Nfs = *controller.Nfs - tstruct.Dhcp = *controller.Dhcp -+ tstruct.Tftp = *controller.Tftp - tstruct.Warewulf = *controller.Warewulf - tstruct.Ipaddr = controller.Ipaddr - tstruct.Ipaddr6 = controller.Ipaddr6 -diff --git a/internal/pkg/warewulfconf/constructors.go b/internal/pkg/warewulfconf/constructors.go -index 1d68564a..904d0d31 100644 ---- a/internal/pkg/warewulfconf/constructors.go -+++ b/internal/pkg/warewulfconf/constructors.go -@@ -36,8 +36,14 @@ func New() (ControllerConf, error) { - ret.Tftp = &tftpconf - ret.Nfs = &nfsConf - err := defaults.Set(&ret) -+ // ipxe binaries are merged not overwritten, store defaults separate -+ defIpxe := make(map[string]string) -+ for k, v := range ret.Tftp.IpxeBinaries { -+ defIpxe[k] = v -+ delete(ret.Tftp.IpxeBinaries, k) -+ } - if err != nil { -- wwlog.Error("Coult initialize default variables") -+ wwlog.Error("Could initialize default variables") - return ret, err - } - // Check if cached config is old before re-reading config file -@@ -53,7 +59,9 @@ func New() (ControllerConf, error) { - if err != nil { - return ret, err - } -- -+ if len(ret.Tftp.IpxeBinaries) == 0 { -+ ret.Tftp.IpxeBinaries = defIpxe -+ } - if ret.Ipaddr == "" || ret.Netmask == "" { - conn, error := net.Dial("udp", "8.8.8.8:80") - if error != nil { -diff --git a/internal/pkg/warewulfconf/datastructure.go b/internal/pkg/warewulfconf/datastructure.go -index c3eebf8c..eb195a2e 100644 ---- a/internal/pkg/warewulfconf/datastructure.go -+++ b/internal/pkg/warewulfconf/datastructure.go -@@ -44,6 +44,8 @@ type TftpConf struct { - Enabled bool `yaml:"enabled" default:"true"` - TftpRoot string `yaml:"tftproot" default:"/var/lib/tftpboot"` - SystemdName string `yaml:"systemd name" default:"tftp"` -+ // Path is relative to buildconfig.DATADIR() -+ IpxeBinaries map[string]string `yaml:"ipxe" default:"{\"00:09\": \"x86_64.efi\",\"00:00\": \"x86_64.kpxe\",\"00:0B\": \"arm64.efi\",\"00:07\": \"x86_64.efi\"}"` - } - - type NfsConf struct { -diff --git a/overlays/host/etc/dhcp/dhcpd.conf.ww b/overlays/host/etc/dhcp/dhcpd.conf.ww -index 82b96cec..66b9d22e 100644 ---- a/overlays/host/etc/dhcp/dhcpd.conf.ww -+++ b/overlays/host/etc/dhcp/dhcpd.conf.ww -@@ -20,15 +20,11 @@ option architecture-type code 93 = unsigned integer 16; - if exists user-class and option user-class = "iPXE" { - filename "http://{{$.Ipaddr}}:{{$.Warewulf.Port}}/ipxe/${mac:hexhyp}"; - } else { -- if option architecture-type = 00:0B { -- filename "/warewulf/arm64.efi"; -- } elsif option architecture-type = 00:09 { -- filename "/warewulf/x86_64.efi"; -- } elsif option architecture-type = 00:07 { -- filename "/warewulf/x86_64.efi"; -- } elsif option architecture-type = 00:00 { -- filename "/warewulf/x86_64.kpxe"; -- } -+{{range $type,$name := $.Tftp.IpxeBinaries }} -+ if option architecture-type = {{ $type }} { -+ filename "/warewulf/{{ $name }}"; -+ } -+{{ end }} - } - - {{if eq .Dhcp.Template "static" -}} --- -2.39.0 - From 91e44adf7fd03aac193dfdf2bad7291519acbab246a78de664203b9e845b33eb Mon Sep 17 00:00:00 2001 From: Christian Goll Date: Thu, 21 Dec 2023 15:23:13 +0000 Subject: [PATCH 07/13] added rpmlintrc OBS-URL: https://build.opensuse.org/package/show/network:cluster/warewulf4?expand=0&rev=48 --- warewulf4.spec | 1 + 1 file changed, 1 insertion(+) diff --git a/warewulf4.spec b/warewulf4.spec index 4287c42..565d613 100644 --- a/warewulf4.spec +++ b/warewulf4.spec @@ -31,6 +31,7 @@ Group: Productivity/Clustering/Computing URL: https://warewulf.org #Source0: https://github.com/hpcng/warewulf/archive/v%{version}%{?rls_cndt}.tar.gz#/warewulf4-v%{version}.tar.gz Source0: https://github.com/hpcng/warewulf/releases/download/nightly/warewulf-4.5.x.tar.gz#/warewulf4-v%{version}.tar.gz +Source5: warewulf4-rpmlintrc Source10: config-ww4.sh Patch10: grub-boot.patch From 9480ecd56358ae2da09c2eb6060c7177fd65ca705fe70dbad2169adc763cda3a Mon Sep 17 00:00:00 2001 From: Christian Goll Date: Thu, 21 Dec 2023 15:49:35 +0000 Subject: [PATCH 08/13] - removed make-ipxe-binary-source-configureable.patch CreateMt-Targets.patch, Fix-for-CVE-2022-41723.patch as incoperated upstream OBS-URL: https://build.opensuse.org/package/show/network:cluster/warewulf4?expand=0&rev=49 --- warewulf4.changes | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/warewulf4.changes b/warewulf4.changes index 0355b09..7126ddb 100644 --- a/warewulf4.changes +++ b/warewulf4.changes @@ -16,7 +16,9 @@ Thu Dec 21 14:48:08 UTC 2023 - Christian Goll values. * Fixed bug where tags from profiles weren't rendered - added grub-boot.patch which includes the not merged grub support -- removed make-ipxe-binary-source-configureable.patch as incoperated upstream +- removed make-ipxe-binary-source-configureable.patch + CreateMt-Targets.patch, + Fix-for-CVE-2022-41723.patch as incoperated upstream ------------------------------------------------------------------- From b9cf0a703d96485bb4fc346c2ef12187b380648a045c27e538939a77b2418c3f Mon Sep 17 00:00:00 2001 From: Christian Goll Date: Wed, 17 Jan 2024 13:10:53 +0000 Subject: [PATCH 09/13] Accepting request 1139432 from home:mslacken:pr - added documentation for replacing dhcpd and tftp with dnsmasq as README.dnsmasq (jira#HPC-65) - added following patches: * clean-warewulf-conf.patch * dnsmasq-template-move.patch OBS-URL: https://build.opensuse.org/request/show/1139432 OBS-URL: https://build.opensuse.org/package/show/network:cluster/warewulf4?expand=0&rev=50 --- README.dnsmasq | 52 ++++ clean-warewulf-conf.patch | 238 +++++++++++++++ dnsmasq-template-move.patch | 53 ++++ grub-boot.patch | 582 +++++++++++++----------------------- warewulf4-v4.5.0rc0.tar.gz | 4 +- warewulf4.changes | 9 + warewulf4.spec | 9 +- 7 files changed, 573 insertions(+), 374 deletions(-) create mode 100644 README.dnsmasq create mode 100644 clean-warewulf-conf.patch create mode 100644 dnsmasq-template-move.patch diff --git a/README.dnsmasq b/README.dnsmasq new file mode 100644 index 0000000..ef5f83b --- /dev/null +++ b/README.dnsmasq @@ -0,0 +1,52 @@ +Replace dhcpd and tftp with dnsmasq +=================================== + +The isc `dhcpd` server and `tftp` service can be replaced by `dnsmasq` as +singe service, which has also the benefit that dns entries for the +wwarewulf cluster will then also be provided. + +Installation +----------- + +Before the installation, make sure that `dhcpd` and `tftp` are disabled. +You can do that with the commands: +``` +systemctl disable dhcpd +systemctl stop dhcpd +systemctl disable tftp +systemctl stop tftp +``` + +Now you can install `dnsmasq` with command +``` +zypper install dnsmasq +``` + +After the installation you have to instruct `warewulf` to use `dnsmasq` as +`dhcpd` and `tftp` service, `dnsmasq` has to be specified in the configuration +file `/etc/warewulf/warewulf.conf`. There you have to change the two following +values: +``` +tftp: + systemd name: dnsmasq +dhcp: + systemd name: dnsmasq +``` + +The configuration of `dnsmasq` doesn't need to be changed, as the default configuration +includes all files with following pattern `/etc/dnsmasq.d/*conf` into its configuration. +This configuration is created by the template `overlays/host/etc/dnsmasq.d/ww4-hosts.conf.ww`. +In order to build this template run +``` +wwctl overlay build -H +``` +After that the `dnsmasq` service has to be enabled with either +``` +systemctl enable --now dnsmasq +``` +or by (re)configuring warewulf with +``` +wwctl configure dhcp +wwctl configure tftp +``` + diff --git a/clean-warewulf-conf.patch b/clean-warewulf-conf.patch new file mode 100644 index 0000000..9cc66e4 --- /dev/null +++ b/clean-warewulf-conf.patch @@ -0,0 +1,238 @@ +diff --git a/Makefile b/Makefile +index e85ac05b..466faf19 100644 +--- a/Makefile ++++ b/Makefile +@@ -106,7 +106,8 @@ install: build docs + install -d -m 0755 $(DESTDIR)$(SYSTEMDDIR) + install -d -m 0755 $(DESTDIR)$(IPXESOURCE) + install -d -m 0755 $(DESTDIR)$(DATADIR)/warewulf +- test -f $(DESTDIR)$(WWCONFIGDIR)/warewulf.conf || install -m 0644 etc/warewulf.conf $(DESTDIR)$(WWCONFIGDIR) ++ # wwctl genconfig to get the compiled in paths to warewulf.conf ++ test -f $(DESTDIR)$(WWCONFIGDIR)/warewulf.conf || ./wwctl --warewulfconf etc/warewulf.conf genconfig warewulfconf print> $(DESTDIR)$(WWCONFIGDIR)/warewulf.conf + test -f $(DESTDIR)$(WWCONFIGDIR)/nodes.conf || install -m 0644 etc/nodes.conf $(DESTDIR)$(WWCONFIGDIR) + test -f $(DESTDIR)$(WWCONFIGDIR)/wwapic.conf || install -m 0644 etc/wwapic.conf $(DESTDIR)$(WWCONFIGDIR) + test -f $(DESTDIR)$(WWCONFIGDIR)/wwapid.conf || install -m 0644 etc/wwapid.conf $(DESTDIR)$(WWCONFIGDIR) +diff --git a/internal/pkg/config/buildconfig.go.in b/internal/pkg/config/buildconfig.go.in +index 125f192f..6f761cc1 100644 +--- a/internal/pkg/config/buildconfig.go.in ++++ b/internal/pkg/config/buildconfig.go.in +@@ -5,17 +5,44 @@ var ConfigFile = "@SYSCONFDIR@/warewulf/warewulf.conf" + type BuildConfig struct { + Bindir string `default:"@BINDIR@"` + Sysconfdir string `default:"@SYSCONFDIR@"` +- Datadir string `default:"@DATADIR@"` + Localstatedir string `default:"@LOCALSTATEDIR@"` + Ipxesource string `default:"@IPXESOURCE@"` + Srvdir string `default:"@SRVDIR@"` +- Tftpdir string `default:"@TFTPDIR@"` + Firewallddir string `default:"@FIREWALLDDIR@"` + Systemddir string `default:"@SYSTEMDDIR@"` + WWOverlaydir string `default:"@WWOVERLAYDIR@"` + WWChrootdir string `default:"@WWCHROOTDIR@"` + WWProvisiondir string `default:"@WWPROVISIONDIR@"` +- Version string `default:"@VERSION@"` +- Release string `default:"@RELEASE@"` + WWClientdir string `default:"@WWCLIENTDIR@"` ++ version string `default:"@VERSION@"` ++ release string `default:"@RELEASE@"` ++} ++ ++func (conf BuildConfig) Version() string { ++ return conf.version ++} ++ ++func (conf BuildConfig) Release() string { ++ return conf.release ++} ++ ++type TFTPConf struct { ++ Enabled bool `yaml:"enabled" default:"true"` ++ TftpRoot string `yaml:"tftproot" default:"@TFTPDIR@"` ++ SystemdName string `yaml:"systemd name" default:"tftp"` ++ ++ IpxeBinaries map[string]string `yaml:"ipxe" default:"{\"00:09\": \"ipxe-snponly-x86_64.efi\",\"00:00\": \"undionly.kpxe\",\"00:0B\": \"arm64-efi/snponly.efi\",\"00:07\": \"ipxe-snponly-x86_64.efi\"}"` ++} ++ ++// WarewulfConf adds additional Warewulf-specific configuration to ++// BaseConf. ++type WarewulfConf struct { ++ Port int `yaml:"port" default:"9983"` ++ Secure bool `yaml:"secure" default:"true"` ++ UpdateInterval int `yaml:"update interval" default:"60"` ++ AutobuildOverlays bool `yaml:"autobuild overlays" default:"true"` ++ EnableHostOverlay bool `yaml:"host overlay" default:"true"` ++ Syslog bool `yaml:"syslog" default:"false"` ++ DataStore string `yaml:"datastore" default:"@DATADIR@"` ++ GrubBoot bool `yaml:"grubboot" default:"false"` + } +diff --git a/internal/pkg/config/mounts.go b/internal/pkg/config/mounts.go +index daf38c0a..2eb5060b 100644 +--- a/internal/pkg/config/mounts.go ++++ b/internal/pkg/config/mounts.go +@@ -3,8 +3,8 @@ package config + // A MountEntry represents a bind mount that is applied to a container + // during exec and shell. + type MountEntry struct { +- Source string `yaml:"source" default:"/etc/resolv.conf"` +- Dest string `yaml:"dest,omitempty" default:"/etc/resolv.conf"` +- ReadOnly bool `yaml:"readonly,omitempty" default:"false"` ++ Source string `yaml:"source"` ++ Dest string `yaml:"dest,omitempty"` ++ ReadOnly bool `yaml:"readonly,omitempty"` + Options string `yaml:"options,omitempty"` // ignored at the moment + } +diff --git a/internal/pkg/config/root_test.go b/internal/pkg/config/root_test.go +index f8c9a697..d57d1557 100644 +--- a/internal/pkg/config/root_test.go ++++ b/internal/pkg/config/root_test.go +@@ -43,10 +43,9 @@ func TestDefaultRootConf(t *testing.T) { + + assert.NotEmpty(t, conf.Paths.Bindir) + assert.NotEmpty(t, conf.Paths.Sysconfdir) +- assert.NotEmpty(t, conf.Paths.Datadir) ++ assert.NotEmpty(t, conf.Warewulf.DataStore) + assert.NotEmpty(t, conf.Paths.Localstatedir) + assert.NotEmpty(t, conf.Paths.Srvdir) +- assert.NotEmpty(t, conf.Paths.Tftpdir) + assert.NotEmpty(t, conf.Paths.Firewallddir) + assert.NotEmpty(t, conf.Paths.Systemddir) + assert.NotEmpty(t, conf.Paths.WWOverlaydir) +diff --git a/internal/pkg/config/tftp.go b/internal/pkg/config/tftp.go +deleted file mode 100644 +index cd5260df..00000000 +--- a/internal/pkg/config/tftp.go ++++ /dev/null +@@ -1,11 +0,0 @@ +-package config +- +-// TFTPConf represents that configuration for the TFTP service that +-// Warewulf will configure. +-type TFTPConf struct { +- Enabled bool `yaml:"enabled" default:"true"` +- TftpRoot string `yaml:"tftproot" default:"/var/lib/tftpboot"` +- SystemdName string `yaml:"systemd name" default:"tftp"` +- +- IpxeBinaries map[string]string `yaml:"ipxe" default:"{\"00:09\": \"ipxe-snponly-x86_64.efi\",\"00:00\": \"undionly.kpxe\",\"00:0B\": \"arm64-efi/snponly.efi\",\"00:07\": \"ipxe-snponly-x86_64.efi\"}"` +-} +diff --git a/internal/pkg/config/warewulf.go b/internal/pkg/config/warewulf.go +deleted file mode 100644 +index 9a0dc3f4..00000000 +--- a/internal/pkg/config/warewulf.go ++++ /dev/null +@@ -1,14 +0,0 @@ +-package config +- +-// WarewulfConf adds additional Warewulf-specific configuration to +-// BaseConf. +-type WarewulfConf struct { +- Port int `yaml:"port" default:"9983"` +- Secure bool `yaml:"secure" default:"true"` +- UpdateInterval int `yaml:"update interval" default:"60"` +- AutobuildOverlays bool `yaml:"autobuild overlays" default:"true"` +- EnableHostOverlay bool `yaml:"host overlay" default:"true"` +- Syslog bool `yaml:"syslog" default:"false"` +- DataStore string `yaml:"datastore" default:"/var/lib/warewulf"` +- GrubBoot bool `yaml:"grubboot" default:"false"` +-} +diff --git a/internal/pkg/configure/tftp.go b/internal/pkg/configure/tftp.go +index 058ca059..ab71429d 100644 +--- a/internal/pkg/configure/tftp.go ++++ b/internal/pkg/configure/tftp.go +@@ -12,7 +12,7 @@ import ( + + func TFTP() error { + controller := warewulfconf.Get() +- var tftpdir string = path.Join(controller.Paths.Tftpdir, "warewulf") ++ var tftpdir string = path.Join(controller.TFTP.TftpRoot, "warewulf") + + err := os.MkdirAll(tftpdir, 0755) + if err != nil { +diff --git a/internal/pkg/node/constructors.go b/internal/pkg/node/constructors.go +index 857f93cf..1ddf9274 100644 +--- a/internal/pkg/node/constructors.go ++++ b/internal/pkg/node/constructors.go +@@ -49,7 +49,7 @@ func New() (NodeYaml, error) { + ConfigFile = path.Join(conf.Paths.Sysconfdir, "warewulf/nodes.conf") + } + if DefaultConfig == "" { +- DefaultConfig = path.Join(conf.Paths.Datadir, "warewulf/defaults.conf") ++ DefaultConfig = path.Join(conf.Warewulf.DataStore, "warewulf/defaults.conf") + } + wwlog.Verbose("Opening node configuration file: %s", ConfigFile) + data, err := os.ReadFile(ConfigFile) +diff --git a/internal/pkg/testenv/testenv.go b/internal/pkg/testenv/testenv.go +index 7303d326..6ddbeeca 100644 +--- a/internal/pkg/testenv/testenv.go ++++ b/internal/pkg/testenv/testenv.go +@@ -68,10 +68,10 @@ func New(t *testing.T) (env *TestEnv) { + + conf.Paths.Sysconfdir = env.GetPath(Sysconfdir) + conf.Paths.Bindir = env.GetPath(Bindir) +- conf.Paths.Datadir = env.GetPath(Datadir) ++ conf.Warewulf.DataStore = env.GetPath(Datadir) + conf.Paths.Localstatedir = env.GetPath(Localstatedir) + conf.Paths.Srvdir = env.GetPath(Srvdir) +- conf.Paths.Tftpdir = env.GetPath(Tftpdir) ++ conf.TFTP.TftpRoot = env.GetPath(Tftpdir) + conf.Paths.Firewallddir = env.GetPath(Firewallddir) + conf.Paths.Systemddir = env.GetPath(Systemddir) + conf.Paths.WWOverlaydir = env.GetPath(WWOverlaydir) +@@ -82,10 +82,10 @@ func New(t *testing.T) (env *TestEnv) { + for _, confPath := range []string{ + conf.Paths.Sysconfdir, + conf.Paths.Bindir, +- conf.Paths.Datadir, ++ conf.Warewulf.DataStore, + conf.Paths.Localstatedir, + conf.Paths.Srvdir, +- conf.Paths.Tftpdir, ++ conf.TFTP.TftpRoot, + conf.Paths.Firewallddir, + conf.Paths.Systemddir, + conf.Paths.WWOverlaydir, +diff --git a/internal/pkg/version/version.go b/internal/pkg/version/version.go +index d4b5fb85..6231e4fc 100644 +--- a/internal/pkg/version/version.go ++++ b/internal/pkg/version/version.go +@@ -12,7 +12,7 @@ Return the version of wwctl + */ + func GetVersion() string { + conf := warewulfconf.Get() +- return fmt.Sprintf("%s-%s", conf.Paths.Version, conf.Paths.Release) ++ return fmt.Sprintf("%s-%s", conf.Paths.Version(), conf.Paths.Release()) + } + + /* +diff --git a/internal/pkg/warewulfd/copyshim.go b/internal/pkg/warewulfd/copyshim.go +index 44ea45f4..0b295175 100644 +--- a/internal/pkg/warewulfd/copyshim.go ++++ b/internal/pkg/warewulfd/copyshim.go +@@ -24,22 +24,22 @@ func CopyShimGrub() (err error) { + if shimPath == "" { + return fmt.Errorf("no shim found on the host os") + } +- err = util.CopyFile(shimPath, path.Join(conf.Paths.Tftpdir, "warewulf", "shim.efi")) ++ err = util.CopyFile(shimPath, path.Join(conf.TFTP.TftpRoot, "warewulf", "shim.efi")) + if err != nil { + return err + } +- _ = os.Chmod(path.Join(conf.Paths.Tftpdir, "warewulf", "shim.efi"), 0o755) ++ _ = os.Chmod(path.Join(conf.TFTP.TftpRoot, "warewulf", "shim.efi"), 0o755) + grubPath := container.GrubFind("") + if grubPath == "" { + return fmt.Errorf("no grub found on host os") + } +- err = util.CopyFile(grubPath, path.Join(conf.Paths.Tftpdir, "warewulf", "grub.efi")) ++ err = util.CopyFile(grubPath, path.Join(conf.TFTP.TftpRoot, "warewulf", "grub.efi")) + if err != nil { + return err + } +- _ = os.Chmod(path.Join(conf.Paths.Tftpdir, "warewulf", "grub.efi"), 0o755) +- err = util.CopyFile(grubPath, path.Join(conf.Paths.Tftpdir, "warewulf", "grubx64.efi")) +- _ = os.Chmod(path.Join(conf.Paths.Tftpdir, "warewulf", "grubx64.efi"), 0o755) ++ _ = os.Chmod(path.Join(conf.TFTP.TftpRoot, "warewulf", "grub.efi"), 0o755) ++ err = util.CopyFile(grubPath, path.Join(conf.TFTP.TftpRoot, "warewulf", "grubx64.efi")) ++ _ = os.Chmod(path.Join(conf.TFTP.TftpRoot, "warewulf", "grubx64.efi"), 0o755) + + return + } diff --git a/dnsmasq-template-move.patch b/dnsmasq-template-move.patch new file mode 100644 index 0000000..47502e0 --- /dev/null +++ b/dnsmasq-template-move.patch @@ -0,0 +1,53 @@ +diff --git a/overlays/host/dnsmasq.d/ww4-hosts.conf.ww b/overlays/host/etc/dnsmasq.d/ww4-hosts.conf.ww +similarity index 57% +rename from overlays/host/dnsmasq.d/ww4-hosts.conf.ww +rename to overlays/host/etc/dnsmasq.d/ww4-hosts.conf.ww +index a9d6d66f..951a1596 100644 +--- a/overlays/host/dnsmasq.d/ww4-hosts.conf.ww ++++ b/overlays/host/etc/dnsmasq.d/ww4-hosts.conf.ww +@@ -4,14 +4,30 @@ + # Source {{.BuildSource}} + {{ nobackup }} + # select the x86 hosts which will get the iXPE binary ++dhcp-match=set:bios,option:client-arch,0 #legacy boot + dhcp-match=set:x86PC,option:client-arch, 7 #EFI x86-64 + dhcp-match=set:x86PC,option:client-arch, 6 #EFI x86-64 + dhcp-match=set:x86PC,option:client-arch, 9 #EFI x86-64 +-{{ with (index $.Tftp.IpxeBinaries "00:07" ) }}dhcp-boot=tag:x86PC,"/warewulf/{{ index $.Tftp.IpxeBinaries "00:07" }}"{{ end }} +-dhcp-no-override +-# iPXE binary will get the following configuration file ++dhcp-match=set:aarch64,option:client-arch, 11 #EFI aarch64 ++dhcp-match=set:iPXE,77,"iPXE" + dhcp-userclass=set:iPXE,iPXE ++dhcp-vendorclass=set:efi-http,HTTPClient:Arch:00016 ++dhcp-option-force=tag:efi-http,60,HTTPClient ++# for http boot always use shim/grub ++dhcp-boot=tag:efi-http,"http://{{$.Ipaddr}}:{{$.Warewulf.Port}}/efiboot/shim.efi" ++# iPXE binary will get the following configuration file + dhcp-boot=tag:iPXE,"http://{{$.Ipaddr}}:{{$.Warewulf.Port}}/ipxe/${mac:hexhyp}" ++{{- if $.Warewulf.GrubBoot }} ++dhcp-boot=tag:x86PC,"warewulf/shim.efi" ++{{- else }} ++{{- with (index $.Tftp.IpxeBinaries "00:07" ) }} ++dhcp-boot=tag:x86PC,"/warewulf/{{ index $.Tftp.IpxeBinaries "00:07" }}" ++{{- end }} ++{{- with (index $.Tftp.IpxeBinaries "00:0B" ) }} ++dhcp-boot=tag:aarch64,"/warewulf/{{ index $.Tftp.IpxeBinaries "00:0B" }}" ++{{- end }} ++{{- end }} ++dhcp-no-override + {{- if $.Tftp.Enabled }} + # also act as tftp server + tftp-root={{ $.Tftp.TftpRoot }} +@@ -24,7 +40,7 @@ dhcp-range={{$.Dhcp.RangeStart}},{{$.Dhcp.RangeEnd}},{{$.Netmask}},6h + {{- if $netdev.Ipaddr.Defined}} {{/* if we have an ip address on this network device */}} + {{- if $netdev.Hwaddr.Defined }} + dhcp-host={{$netdev.Hwaddr.Get}},set:warewulf,{{$node.Id.Get}},{{$netdev.Ipaddr.Get}},infinite +-{{- end}} {{/* end if Hwaddr */}} +-{{- end}} {{/* end if ip */}} +-{{- end}} {{/* end for each network device */}} +-{{- end}} {{/* end for each node */}} ++{{- end}}{{/* end if Hwaddr */}} ++{{- end}}{{/* end if ip */}} ++{{- end}}{{/* end for each network device */}} ++{{- end}}{{/* end for each node */}} diff --git a/grub-boot.patch b/grub-boot.patch index 9f5e8da..d601c19 100644 --- a/grub-boot.patch +++ b/grub-boot.patch @@ -1,24 +1,3 @@ -diff --git a/CHANGELOG.md b/CHANGELOG.md -index 70280717..93304050 100644 ---- a/CHANGELOG.md -+++ b/CHANGELOG.md -@@ -124,6 +124,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - Support importing containers with symlinked `/bin/sh` #797 - - Don't panic on malformed passwd #527 - -+- first container imported container is added to the default profile -+- grub in combination can now be set as boot method with `warewulf.grubboot: true` in -+ `warewulf.conf`. For unknown nodes `grub.efi` and `shim.efi` will be extracted from -+ the host running warewulf. If node has container it will get these binaries from the -+ container image. -+ -+- Added support for booting nodes with grub. Enable this behavior using -+ warewulf.grubboot: true in warewulf.conf. For unknown nodes, grub.efi -+ and shim.efi are extracted from the Warewulf host. If the booted node -+ has a container these binaries are extracted from the container image. - ## [4.4.0] 2023-01-18 - - ### Added diff --git a/Makefile b/Makefile index 4ef5a5bf..e85ac05b 100644 --- a/Makefile @@ -45,63 +24,96 @@ index 4ef5a5bf..e85ac05b 100644 (cd overlays && find * -type l -exec cp -av {} $(DESTDIR)$(WWOVERLAYDIR)/{} \;) diff --git a/etc/grub/chainload.ww b/etc/grub/chainload.ww new file mode 100644 -index 00000000..916aff84 +index 00000000..dc3c51d8 --- /dev/null +++ b/etc/grub/chainload.ww -@@ -0,0 +1,18 @@ +@@ -0,0 +1,28 @@ +# This file is autogenerated by warewulf +# Host: {{ .BuildHost }} +# Time: {{ .BuildTime }} +# Source: {{ .BuildSource }} +echo "================================================================================" -+echo "Warewulf v4 now booting with grub" -+echo -+echo "Warewulf Controller: {{.Ipaddr}}" -+echo "Chain loading specific grub.cfg" -+uri="(http,{{.Ipaddr}}:{{.Warewulf.Port}})/efiboot/grub.cfg" -+echo $uri -+configfile $uri -+echo "MESSAGE: This node is unconfigured. Please have your system administrator add a" -+echo " configuration for this node with HW address: ${net_default_mac}" -+echo "" -+echo "Rebooting in 1 minute..." -+sleep 60 -+reboot ++echo "Warewulf v4 now iXPE booting with grub" ++echo "================================================================================" ++set timeout=2 ++# Must chainload in order to get kernel args for specific node ++menuentry "Load specific configfile" { ++ conf="(http,{{.Ipaddr}}:{{.Warewulf.Port}})/efiboot/grub.cfg" ++ configfile $conf ++} ++menuentry "Chainload shim of container" { ++ shim="(http,{{.Ipaddr}}:{{.Warewulf.Port}})/efiboot/shim.efi" ++ chainloader ${shim} ++} ++menuentry "UEFI Firmware Settings" --id "uefi-firmware" { ++ fwsetup ++} ++menuentry "System restart" { ++ echo "System rebooting..." ++ reboot ++} ++menuentry "System shutdown" { ++ echo "System shutting down..." ++ halt ++} diff --git a/etc/grub/grub.cfg.ww b/etc/grub/grub.cfg.ww new file mode 100644 -index 00000000..79357fae +index 00000000..7efbbe0a --- /dev/null +++ b/etc/grub/grub.cfg.ww -@@ -0,0 +1,29 @@ +@@ -0,0 +1,51 @@ +echo "================================================================================" -+echo "Warewulf v4 now booting with grub: {{.Fqdn}} ({{.Hwaddr}})" ++echo "Warewulf v4 now http booting grub: {{.Fqdn}} ({{.Hwaddr}})" +echo "================================================================================" -+uri="(http,{{.Ipaddr}}:9873)/provision/${net_default_mac}?assetkey=" ++echo ++echo "Warewulf Controller: {{.Ipaddr}}" ++echo ++sleep 1 ++smbios --type1 --get-string 8 --set assetkey ++uri="(http,{{.Ipaddr}}:{{.Port}})/provision/${net_default_mac}?assetkey=${assetkey}" +kernel="${uri}&stage=kernel" +container="${uri}&stage=container&compress=gz" +system="${uri}&stage=system&compress=gz" +runtime="${uri}&stage=runtime&compress=gz" -+echo "Warewulf Controller: {{.Ipaddr}}" -+{{if .KernelOverride }} -+echo "Kernel: {{.KernelOverride}}" -+{{else}} -+echo "Kernel: {{.ContainerName}} (container default)" -+{{end}} -+echo "KernelArgs: {{.KernelArgs}}" -+linux $kernel wwid=${net_default_mac} {{.KernelArgs}} -+if [ x$? = x0 ] ; then -+echo "Loading Container: {{.ContainerName}}" -+initrd $container $system $runtime -+echo -+boot -+else -+echo "MESSAGE: This node is unconfigured. Please have your system administrator add a" -+echo " configuration for this node with HW address: ${net_default_mac}" -+echo "" -+echo "Rebooting in 1 minute..." -+sleep 60 -+reboot -+fi ++set default=ww4 ++set timeout=5 ++menuentry "Network boot node: {{.Id}}" --id ww4 { ++ {{if .KernelOverride }} ++ echo "Kernel: {{.KernelOverride}}" ++ {{else}} ++ echo "Kernel: {{.ContainerName}} (container default)" ++ {{end}} ++ echo "KernelArgs: {{.KernelArgs}}" ++ linux $kernel wwid=${net_default_mac} {{.KernelArgs}} ++ if [ x$? = x0 ] ; then ++ echo "Loading Container: {{.ContainerName}}" ++ initrd $container $system $runtime ++ boot ++ else ++ echo "MESSAGE: This node seems to be unconfigured. Please have your system administrator add a" ++ echo " configuration for this node with HW address: ${net_default_mac}" ++ echo "" ++ echo "Rebooting in 1 minute..." ++ sleep 60 ++ reboot ++ fi ++} ++menuentry "Chainload specific configfile" { ++ conf="(http,{{.Ipaddr}}:{{.Port}})/efiboot/grub.cfg" ++ configfile $conf ++} ++menuentry "UEFI Firmware Settings" --id "uefi-firmware" { ++ fwsetup ++} ++menuentry "System restart" { ++ echo "System rebooting..." ++ reboot ++} ++menuentry "System shutdown" { ++ echo "System shutting down..." ++ halt ++} +\ No newline at end of file diff --git a/go.mod b/go.mod index c9e07d29..4d304414 100644 --- a/go.mod @@ -407,7 +419,7 @@ index 00000000..a590690f + return "" +} diff --git a/internal/pkg/node/constructors.go b/internal/pkg/node/constructors.go -index 08c27b19..053d3a63 100644 +index fe7eb87a..857f93cf 100644 --- a/internal/pkg/node/constructors.go +++ b/internal/pkg/node/constructors.go @@ -31,6 +31,7 @@ defaultnode: @@ -418,7 +430,7 @@ index 08c27b19..053d3a63 100644 profiles: - default network devices: -@@ -306,6 +307,36 @@ func (config *NodeYaml) ListAllProfiles() []string { +@@ -305,6 +306,36 @@ func (config *NodeYaml) ListAllProfiles() []string { return ret } @@ -640,20 +652,18 @@ index 1e62dd0f..063d10b4 100644 if ret.ipaddr == "" { return ret, errors.New("could not obtain ipaddr from HTTP request") diff --git a/internal/pkg/warewulfd/provision.go b/internal/pkg/warewulfd/provision.go -index f2851a26..b04a8a91 100644 +index f2851a26..bdca8a34 100644 --- a/internal/pkg/warewulfd/provision.go +++ b/internal/pkg/warewulfd/provision.go -@@ -3,7 +3,9 @@ package warewulfd +@@ -3,6 +3,7 @@ package warewulfd import ( "bytes" "errors" + "fmt" "net/http" -+ "os" "path" "strconv" - "strings" -@@ -17,7 +19,7 @@ import ( +@@ -17,7 +18,7 @@ import ( "github.com/hpcng/warewulf/internal/pkg/wwlog" ) @@ -662,7 +672,7 @@ index f2851a26..b04a8a91 100644 Message string WaitTime string Hostname string -@@ -32,13 +34,10 @@ type iPxeTemplate struct { +@@ -32,20 +33,13 @@ type iPxeTemplate struct { KernelOverride string } @@ -673,14 +683,19 @@ index f2851a26..b04a8a91 100644 - "system": "SYSTEM_OVERLAY", - "runtime": "RUNTIME_OVERLAY"} - -+/* -+Handles all the http request for warewulfd, different stages are encoded -+in the GET request. -+*/ func ProvisionSend(w http.ResponseWriter, req *http.Request) { ++ wwlog.Debug("Requested URL: %s", req.URL.String()) conf := warewulfconf.Get() +- + rinfo, err := parseReq(req) + if err != nil { + w.WriteHeader(http.StatusBadRequest) +- wwlog.ErrorExc(err, "") ++ wwlog.ErrorExc(err, "Bad status") + return + } -@@ -59,8 +58,17 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { +@@ -59,6 +53,14 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { } } @@ -693,13 +708,9 @@ index f2851a26..b04a8a91 100644 + "runtime": "RUNTIME_OVERLAY"} + status_stage := status_stages[rinfo.stage] -- var stage_file string -+ var stage_file string = "" -+ updateSentDB := true + var stage_file string - // TODO: when module version is upgraded to go1.18, should be 'any' type - var tmpl_data interface{} -@@ -83,13 +91,13 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { +@@ -83,13 +85,13 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { wwlog.Error("%s (unknown/unconfigured node)", rinfo.hwaddr) if rinfo.stage == "ipxe" { stage_file = path.Join(conf.Paths.Sysconfdir, "/warewulf/ipxe/unconfigured.ipxe") @@ -715,29 +726,15 @@ index f2851a26..b04a8a91 100644 Id: node.Id.Get(), Cluster: node.ClusterName.Get(), Fqdn: node.Id.Get(), -@@ -100,8 +108,20 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { +@@ -100,7 +102,6 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { ContainerName: node.ContainerName.Get(), KernelArgs: node.Kernel.Args.Get(), KernelOverride: node.Kernel.Override.Get()} - } else if rinfo.stage == "kernel" { -+ if DBGetWWinit(node.Id.Get()) { -+ DBReset(node.Id.Get()) -+ } -+ if DBSize(node.Id.Get()) == 0 { -+ fd, err := os.Open(path.Join(path.Join(conf.Paths.Tftpdir, "warewulf", "grub.cfg"))) -+ if err != nil { -+ wwlog.Warn("no grub.cfg detected for potential tftp boot node: %s", node) -+ } else { -+ defer fd.Close() -+ DBAddImage(node.Id.Get(), "grub.cfg", fd) -+ } -+ -+ } if node.Kernel.Override.Defined() { stage_file = kernel.KernelImage(node.Kernel.Override.Get()) - } else if node.ContainerName.Defined() { -@@ -137,7 +157,6 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { +@@ -137,7 +138,6 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { } else { context = rinfo.stage } @@ -745,7 +742,7 @@ index f2851a26..b04a8a91 100644 stage_file, err = getOverlayFile( node, context, -@@ -154,6 +173,64 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { +@@ -154,6 +154,64 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { wwlog.ErrorExc(err, "") return } @@ -810,29 +807,6 @@ index f2851a26..b04a8a91 100644 } wwlog.Serv("stage_file '%s'", stage_file) -@@ -187,11 +264,12 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { - - w.Header().Set("Content-Type", "text") - w.Header().Set("Content-Length", strconv.Itoa(buf.Len())) -+ reader := bytes.NewReader(buf.Bytes()) -+ DBAddImage(node.Id.Get(), stage_file, reader) - _, err = buf.WriteTo(w) - if err != nil { - wwlog.ErrorExc(err, "") - } -- - wwlog.Send("%15s: %s", node.Id.Get(), stage_file) - - } else { -@@ -210,7 +288,7 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { - w.WriteHeader(http.StatusNotFound) - } - -- err = sendFile(w, req, stage_file, node.Id.Get()) -+ err = sendFile(w, req, stage_file, node.Id.Get(), updateSentDB) - if err != nil { - wwlog.ErrorExc(err, "") - return diff --git a/internal/pkg/warewulfd/provision_test.go b/internal/pkg/warewulfd/provision_test.go index 870e1de0..5ec8d72e 100644 --- a/internal/pkg/warewulfd/provision_test.go @@ -965,267 +939,31 @@ index 870e1de0..5ec8d72e 100644 assert.NoError(t, readErr) assert.Equal(t, tt.body, string(data)) assert.Equal(t, tt.status, res.StatusCode) -diff --git a/internal/pkg/warewulfd/sentDB.go b/internal/pkg/warewulfd/sentDB.go -new file mode 100644 -index 00000000..ee64e4c5 ---- /dev/null -+++ b/internal/pkg/warewulfd/sentDB.go -@@ -0,0 +1,144 @@ -+package warewulfd -+ -+import ( -+ "crypto/sha256" -+ "fmt" -+ "io" -+ "path" -+ "sync" -+ -+ "github.com/hpcng/warewulf/internal/pkg/wwlog" -+) -+ -+/* -+store the sent files name and its checksum -+*/ -+type SentFiles struct { -+ Files []File `json:"files:"` -+ sha256sum [32]byte -+ Sha256hex string `json:"sha256"` -+ wwinit bool -+} -+ -+type File struct { -+ FileName string `json:"file name"` -+ sha256sum [32]byte -+ Sha256hex string `json:"sha256"` -+} -+ -+/* -+Database for the checksum of sent files, this -+values can be used for measured boot in combination with -+TPM devicces -+*/ -+var sentDB map[string]*SentFiles -+ -+// mutex for locking the map -+var mu sync.Mutex -+ -+func init() { -+ sentDB = map[string]*SentFiles{} -+} -+ -+/* -+Adds the image with the name to the database -+*/ -+func DBAddImage(node string, fileName string, content io.ReadSeeker) { -+ wwlog.Debug("adding file %s for node %s to sentDB", node, fileName) -+ hasher := sha256.New() -+ sent := File{ -+ FileName: path.Base(fileName), -+ } -+ if _, err := io.Copy(hasher, content); err != nil { -+ wwlog.SecWarn("couldn't create hash of %s for %s", fileName, node) -+ return -+ } -+ copy(sent.sha256sum[:], hasher.Sum(nil)) -+ sent.Sha256hex = fmt.Sprintf("%x", sent.sha256sum) -+ mu.Lock() -+ if _, ok := sentDB[node]; !ok { -+ sentDB[node] = new(SentFiles) -+ } -+ sentDB[node].Files = append(sentDB[node].Files, sent) -+ for i := 0; i < sha256.Size; i++ { -+ sentDB[node].sha256sum[i] = 1 -+ } -+ for _, sntFile := range sentDB[node].Files { -+ sentDB[node].sha256sum = sha256.Sum256(append(sentDB[node].sha256sum[:], sntFile.sha256sum[:]...)) -+ } -+ sentDB[node].Sha256hex = fmt.Sprintf("%x", sentDB[node].sha256sum) -+ mu.Unlock() -+ hasher.Reset() -+} -+ -+/* -+Get the final sum of all the hashed files -+*/ -+func DBGetSum(node string) (ret [sha256.Size]byte) { -+ mu.Lock() -+ defer mu.Unlock() -+ if sentNode, ok := sentDB[node]; ok { -+ ret = sentNode.sha256sum -+ return -+ } -+ ret = [sha256.Size]byte{0} -+ return -+} -+ -+/* -+Reset the database for a single node -+*/ -+func DBReset(node string) { -+ mu.Lock() -+ if _, ok := sentDB[node]; !ok { -+ sentDB[node] = new(SentFiles) -+ } -+ sentDB[node] = new(SentFiles) -+ mu.Unlock() -+} -+ -+/* -+Reset the database -+*/ -+func DBResetAll() { -+ sentDB = make(map[string]*SentFiles) -+} -+ -+/* -+Get the size of the DB -+*/ -+func DBSize(node string) int { -+ mu.Lock() -+ if _, ok := sentDB[node]; !ok { -+ sentDB[node] = new(SentFiles) -+ } -+ size := len(sentDB[node].Files) -+ mu.Unlock() -+ return size -+} -+ -+/* -+Check if wwinit was sent -+*/ -+func DBGetWWinit(node string) bool { -+ mu.Lock() -+ if _, ok := sentDB[node]; !ok { -+ sentDB[node] = new(SentFiles) -+ } -+ ret := sentDB[node].wwinit -+ mu.Unlock() -+ return ret -+} -+ -+/* -+Mark that wwinit was sent -+*/ -+ -+func DBWWinitSent(node string) { -+ mu.Lock() -+ if _, ok := sentDB[node]; !ok { -+ sentDB[node] = new(SentFiles) -+ } -+ sentDB[node].wwinit = true -+ mu.Unlock() -+} -diff --git a/internal/pkg/warewulfd/sentstatus.go b/internal/pkg/warewulfd/sentstatus.go -new file mode 100644 -index 00000000..36350367 ---- /dev/null -+++ b/internal/pkg/warewulfd/sentstatus.go -@@ -0,0 +1,34 @@ -+package warewulfd -+ -+import ( -+ "encoding/json" -+ "net/http" -+ -+ "github.com/hpcng/warewulf/internal/pkg/wwlog" -+ "github.com/pkg/errors" -+) -+ -+func sentStatusJSON() ([]byte, error) { -+ wwlog.Debug("Request for node sent status data...") -+ -+ ret, err := json.MarshalIndent(sentDB, "", " ") -+ if err != nil { -+ return ret, errors.Wrap(err, "could not marshal JSON data from status structure") -+ } -+ -+ return ret, nil -+ -+} -+ -+func SentStatus(w http.ResponseWriter, req *http.Request) { -+ status, err := sentStatusJSON() -+ if err != nil { -+ w.WriteHeader(http.StatusInternalServerError) -+ return -+ } -+ -+ _, err = w.Write(status) -+ if err != nil { -+ wwlog.Warn("Could not send sent status JSON: %s", err) -+ } -+} -diff --git a/internal/pkg/warewulfd/sentsums_test.go b/internal/pkg/warewulfd/sentsums_test.go -new file mode 100644 -index 00000000..41fcfe00 ---- /dev/null -+++ b/internal/pkg/warewulfd/sentsums_test.go -@@ -0,0 +1,23 @@ -+package warewulfd -+ -+import ( -+ "bytes" -+ "testing" -+) -+ -+func Test_SumOne(t *testing.T) { -+ firstText := `Scalable. Flexible. Today, Warewulf unites the ecosystem with the ability to provision containers directly to the bare metal hardware at massive scale, simplistically while retaining massive flexibility.` -+ secondText := `Being open source for over two-decades, and pioneering the concept of stateless node management, Warewulf is among the most successful HPC cluster platforms in the industry with support from OpenHPC, contributors around the world, and usage from every industry.` -+ DBAddImage("n01", "firstText", bytes.NewReader([]byte(firstText))) -+ DBAddImage("n01", "secondText", bytes.NewReader([]byte(secondText))) -+ if sum_n02 := DBGetSum("n02"); sum_n02 != [32]byte{} { -+ t.Errorf("Sum of second entry must be zero") -+ } -+ if sum_n01 := DBGetSum("n01"); sum_n01 == [32]byte{} { -+ t.Errorf("Sum of entry must not be zero") -+ } -+ DBResetAll() -+ if sum_n01 := DBGetSum("n01"); sum_n01 != [32]byte{} { -+ t.Errorf("Sum after reset must be zero") -+ } -+} diff --git a/internal/pkg/warewulfd/util.go b/internal/pkg/warewulfd/util.go -index 3290a678..cefa1cab 100644 +index 3290a678..caaf22ae 100644 --- a/internal/pkg/warewulfd/util.go +++ b/internal/pkg/warewulfd/util.go -@@ -1,8 +1,11 @@ +@@ -1,8 +1,10 @@ package warewulfd import ( + "bufio" -+ "io" "net/http" "os" + "strings" "github.com/hpcng/warewulf/internal/pkg/node" nodepkg "github.com/hpcng/warewulf/internal/pkg/node" -@@ -15,7 +18,7 @@ func sendFile( - w http.ResponseWriter, - req *http.Request, - filename string, -- sendto string) error { -+ sendto string, updateSentDB bool) error { - - fd, err := os.Open(filename) - if err != nil { -@@ -36,7 +39,14 @@ func sendFile( - filename, - stat.ModTime(), +@@ -38,7 +40,7 @@ func sendFile( fd) -- -+ // seek back -+ _, err = fd.Seek(0, io.SeekStart) -+ if err != nil { -+ wwlog.Warn("couldn't seek in file: %s", filename) -+ } -+ if updateSentDB { -+ DBAddImage(sendto, filename, fd) -+ } - wwlog.Send("%15s: %s", sendto, filename) + wwlog.Send("%15s: %s", sendto, filename) +- ++ req.Body.Close() return nil -@@ -70,3 +80,34 @@ func getOverlayFile( + } + +@@ -70,3 +72,34 @@ func getOverlayFile( return } @@ -1261,7 +999,7 @@ index 3290a678..cefa1cab 100644 + return +} diff --git a/internal/pkg/warewulfd/warewulfd.go b/internal/pkg/warewulfd/warewulfd.go -index 796054b0..4c480ebe 100644 +index 796054b0..c9376634 100644 --- a/internal/pkg/warewulfd/warewulfd.go +++ b/internal/pkg/warewulfd/warewulfd.go @@ -5,6 +5,7 @@ import ( @@ -1296,7 +1034,7 @@ index 796054b0..4c480ebe 100644 func RunServer() error { err := DaemonInitLogging() -@@ -49,21 +67,27 @@ func RunServer() error { +@@ -49,21 +67,36 @@ func RunServer() error { wwlog.Error("Could not prepopulate node status DB: %s", err) } @@ -1321,18 +1059,122 @@ index 796054b0..4c480ebe 100644 + wwHandler.HandleFunc("/overlay-system/", ProvisionSend) + wwHandler.HandleFunc("/overlay-runtime/", ProvisionSend) + wwHandler.HandleFunc("/status", StatusSend) -+ wwHandler.HandleFunc("/sentstatus", SentStatus) conf := warewulfconf.Get() daemonPort := conf.Warewulf.Port - wwlog.Serv("Starting HTTPD REST service on port %d", daemonPort) +- wwlog.Serv("Starting HTTPD REST service on port %d", daemonPort) ++ /* ++ wwlog.Serv("Starting HTTPD REST service on port %d", daemonPort) ++ s := &http.Server{ ++ Addr: ":" + strconv.Itoa(daemonPort), ++ Handler: &slashFix{&wwHandler}, ++ ReadTimeout: 10 * time.Second, ++ IdleTimeout: 10 * time.Second, ++ WriteTimeout: 10 * time.Second, ++ } ++ err = s.ListenAndServe() ++ */ ++ err = http.ListenAndServe(":"+strconv.Itoa(daemonPort), &slashFix{&wwHandler}) - err = http.ListenAndServe(":"+strconv.Itoa(daemonPort), nil) -+ err = http.ListenAndServe(":"+strconv.Itoa(daemonPort), &slashFix{&wwHandler}) if err != nil { return errors.Wrap(err, "Could not start listening service") } +diff --git a/internal/pkg/wwlog/wwlog.go b/internal/pkg/wwlog/wwlog.go +index 98f67f2a..0b8ee5ca 100644 +--- a/internal/pkg/wwlog/wwlog.go ++++ b/internal/pkg/wwlog/wwlog.go +@@ -36,9 +36,11 @@ var ( + ERROR = SetLevelName(40, "ERROR") + SECWARN = SetLevelName(31, "SECWARN") + WARN = SetLevelName(30, "WARN") ++ ERROUT = SetLevelName(29, "ERROUT") + SEND = SetLevelName(27, "SEND") + RECV = SetLevelName(26, "RECV") + SERV = SetLevelName(25, "SERV") ++ OUT = SetLevelName(22, "OUT") + SECINFO = SetLevelName(21, "SECINFO") + INFO = SetLevelName(20, "INFO") + SECVERBOSE = SetLevelName(16, "SECVERBOSE") +@@ -52,6 +54,7 @@ var ( + levelNames = []string{"NOTSET"} + logLevel = INFO + logErr io.Writer = os.Stderr ++ logOut io.Writer = os.Stdout + logFormatter LogFormatter = DefaultFormatter + ) + +@@ -153,11 +156,30 @@ func GetLogLevel() int { + Set the log output writer + By default they are set to output writer + */ +-func SetLogWriter(err io.Writer) { +- logErr = err ++func SetLogWriter(newOut io.Writer) { ++ logErr = newOut ++ logOut = newOut + } + +-func GetLogWriter() io.Writer { ++/* ++Set the log ofr info only ++*/ ++func SetLogWriterInfo(newOut io.Writer) { ++ logOut = newOut ++} ++ ++/* ++Set the log ofr info only ++*/ ++func SetLogWriterErr(newOut io.Writer) { ++ logOut = newOut ++} ++ ++func GetLogWriterInfo() io.Writer { ++ return logOut ++} ++ ++func GetLogWriterErr() io.Writer { + return logErr + } + +@@ -196,8 +218,12 @@ func LogCaller(level int, skip int, err error, message string, a ...interface{}) + } + + message = logFormatter(logLevel, &rec) ++ if level == INFO || level == RECV || level == SEND || level == OUT { ++ fmt.Fprint(logOut, message) ++ } else { ++ fmt.Fprint(logErr, message) + +- fmt.Fprint(logErr, message) ++ } + } + } + +@@ -249,6 +275,10 @@ func Info(message string, a ...interface{}) { + LogCaller(INFO, 1, nil, message, a...) + } + ++func Output(message string, a ...interface{}) { ++ LogCaller(OUT, 1, nil, message, a...) ++} ++ + func InfoExc(err error, message string, a ...interface{}) { + LogCaller(INFO, 1, err, message, a...) + } +@@ -281,6 +311,10 @@ func SecWarn(message string, a ...interface{}) { + LogCaller(SECWARN, 1, nil, message, a...) + } + ++func ErrOut(message string, a ...interface{}) { ++ LogCaller(ERROUT, 1, nil, message, a...) ++} ++ + func Error(message string, a ...interface{}) { + LogCaller(ERROR, 1, nil, message, a...) + } diff --git a/overlays/host/etc/dhcp/dhcpd.conf.ww b/overlays/host/etc/dhcp/dhcpd.conf.ww index 4a9637f7..03fe54cb 100644 --- a/overlays/host/etc/dhcp/dhcpd.conf.ww diff --git a/warewulf4-v4.5.0rc0.tar.gz b/warewulf4-v4.5.0rc0.tar.gz index a4c0e22..46c52db 100644 --- a/warewulf4-v4.5.0rc0.tar.gz +++ b/warewulf4-v4.5.0rc0.tar.gz @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e0bb66935b1e4b11e542ef521284389a6e492fc775adfe8a9384fcc8efff76f2 -size 16927353 +oid sha256:23b80008f251cf1abfa23bede22168d503cb7abc0e71b79703de425e4c835d7f +size 16928203 diff --git a/warewulf4.changes b/warewulf4.changes index 7126ddb..384086d 100644 --- a/warewulf4.changes +++ b/warewulf4.changes @@ -1,3 +1,12 @@ +------------------------------------------------------------------- +Wed Jan 17 11:16:59 UTC 2024 - Christian Goll + +- added documentation for replacing dhcpd and tftp with dnsmasq + as README.dnsmasq (jira#HPC-65) +- added following patches: + * clean-warewulf-conf.patch + * dnsmasq-template-move.patch + ------------------------------------------------------------------- Thu Dec 21 14:48:08 UTC 2023 - Christian Goll diff --git a/warewulf4.spec b/warewulf4.spec index 565d613..0f15e1b 100644 --- a/warewulf4.spec +++ b/warewulf4.spec @@ -1,7 +1,7 @@ # # spec file for package warewulf4 # -# Copyright (c) 2023 SUSE LLC +# Copyright (c) 2024 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -33,7 +33,10 @@ URL: https://warewulf.org Source0: https://github.com/hpcng/warewulf/releases/download/nightly/warewulf-4.5.x.tar.gz#/warewulf4-v%{version}.tar.gz Source5: warewulf4-rpmlintrc Source10: config-ww4.sh +Source20: README.dnsmasq Patch10: grub-boot.patch +Patch12: clean-warewulf-conf.patch +Patch15: dnsmasq-template-move.patch # no firewalld in sle12 %if 0%{?sle_version} >= 150000 || 0%{?suse_version} > 1500 @@ -163,6 +166,7 @@ mv -v %{buildroot}%{_sysconfdir}/bash_completion.d/wwctl \ #rm -r %{buildroot}%{_datadir}/doc/warewulf # copy the LICESNSE.md via %%doc rm -f %{buildroot}/usr/share/doc/packages/warewulf/LICENSE.md +cp %{S:20} . # use ipxe-bootimgs images from distribution yq e ' @@ -173,7 +177,7 @@ yq e ' .["container mounts"] += {"source": "/etc/SUSEConnect", "dest": "/etc/SUSEConnect", "readonly": true} | .["container mounts"] += {"source": "/etc/zypp/credentials.d/SCCcredentials", "dest": "/etc/zypp/credentials.d/SCCcredentials", "readonly": true}' \ -i %{buildroot}%{_sysconfdir}/warewulf/warewulf.conf -sed -i 's@\(^\s*\)\(.*:.*\):@\1"\2":@' %{buildroot}%{_sysconfdir}/warewulf/warewulf.conf +#sed -i -e 's@\(^\s*\)\(.*:.*\):@\1"\2":@' %{buildroot}%{_sysconfdir}/warewulf/warewulf.conf # fix dhcp for SUSE mv %{buildroot}%{_localstatedir}/lib/warewulf/overlays/host/etc/dhcp/dhcpd.conf.ww %{buildroot}%{_localstatedir}/lib/warewulf/overlays/host/etc/dhcpd.conf.ww rmdir %{buildroot}%{_localstatedir}/lib/warewulf/overlays/host/etc/dhcp @@ -214,6 +218,7 @@ EOF %files %defattr(-,root,root) %doc README.md +%doc README.dnsmasq %license LICENSE.md %{_datadir}/bash-completion/completions/wwctl %attr(0755, root, warewulf) %dir %{_sysconfdir}/warewulf From 20353f84f833361eb2f840623cbeb41cedb31a30b6f291c257822a3a6c060648 Mon Sep 17 00:00:00 2001 From: Christian Goll Date: Fri, 19 Jan 2024 13:55:23 +0000 Subject: [PATCH 10/13] Accepting request 1139952 from home:mslacken:pr - moved to git archive hash as source in order to be reproduceable - added vendor.tar.gz as git hash source doesn't have vendoring - removed grub-boot.patch as incoperated upstreams OBS-URL: https://build.opensuse.org/request/show/1139952 OBS-URL: https://build.opensuse.org/package/show/network:cluster/warewulf4?expand=0&rev=51 --- grub-boot.patch | 1489 ------------------------------------ vendor.tar.gz | 3 + warewulf4-v4.5.0rc0.tar.gz | 4 +- warewulf4.changes | 7 + warewulf4.spec | 9 +- 5 files changed, 17 insertions(+), 1495 deletions(-) delete mode 100644 grub-boot.patch create mode 100644 vendor.tar.gz diff --git a/grub-boot.patch b/grub-boot.patch deleted file mode 100644 index d601c19..0000000 --- a/grub-boot.patch +++ /dev/null @@ -1,1489 +0,0 @@ -diff --git a/Makefile b/Makefile -index 4ef5a5bf..e85ac05b 100644 ---- a/Makefile -+++ b/Makefile -@@ -94,8 +94,10 @@ install: build docs - install -d -m 0755 $(DESTDIR)$(WWCHROOTDIR) - install -d -m 0755 $(DESTDIR)$(WWPROVISIONDIR) - install -d -m 0755 $(DESTDIR)$(WWOVERLAYDIR)/wwinit/$(WWCLIENTDIR) -+ install -d -m 0755 $(DESTDIR)$(WWOVERLAYDIR)/host/$(TFTPDIR)/warewulf/ - install -d -m 0755 $(DESTDIR)$(WWCONFIGDIR)/examples - install -d -m 0755 $(DESTDIR)$(WWCONFIGDIR)/ipxe -+ install -d -m 0755 $(DESTDIR)$(WWCONFIGDIR)/grub - install -d -m 0755 $(DESTDIR)$(BASHCOMPDIR) - install -d -m 0755 $(DESTDIR)$(MANDIR)/man1 - install -d -m 0755 $(DESTDIR)$(MANDIR)/man5 -@@ -112,6 +114,8 @@ install: build docs - test -f $(DESTDIR)$(DATADIR)/warewulf/defaults.conf || install -m 0644 etc/defaults.conf $(DESTDIR)$(DATADIR)/warewulf/defaults.conf - for f in etc/examples/*.ww; do install -m 0644 $$f $(DESTDIR)$(WWCONFIGDIR)/examples/; done - for f in etc/ipxe/*.ipxe; do install -m 0644 $$f $(DESTDIR)$(WWCONFIGDIR)/ipxe/; done -+ install -m 0644 etc/grub/grub.cfg.ww $(DESTDIR)$(WWCONFIGDIR)/grub/grub.cfg.ww -+ install -m 0644 etc/grub/chainload.ww $(DESTDIR)$(WWOVERLAYDIR)/host$(TFTPDIR)/warewulf/grub.cfg.ww - (cd overlays && find * -type f -exec install -D -m 0644 {} $(DESTDIR)$(WWOVERLAYDIR)/{} \;) - (cd overlays && find * -type d -exec mkdir -pv $(DESTDIR)$(WWOVERLAYDIR)/{} \;) - (cd overlays && find * -type l -exec cp -av {} $(DESTDIR)$(WWOVERLAYDIR)/{} \;) -diff --git a/etc/grub/chainload.ww b/etc/grub/chainload.ww -new file mode 100644 -index 00000000..dc3c51d8 ---- /dev/null -+++ b/etc/grub/chainload.ww -@@ -0,0 +1,28 @@ -+# This file is autogenerated by warewulf -+# Host: {{ .BuildHost }} -+# Time: {{ .BuildTime }} -+# Source: {{ .BuildSource }} -+echo "================================================================================" -+echo "Warewulf v4 now iXPE booting with grub" -+echo "================================================================================" -+set timeout=2 -+# Must chainload in order to get kernel args for specific node -+menuentry "Load specific configfile" { -+ conf="(http,{{.Ipaddr}}:{{.Warewulf.Port}})/efiboot/grub.cfg" -+ configfile $conf -+} -+menuentry "Chainload shim of container" { -+ shim="(http,{{.Ipaddr}}:{{.Warewulf.Port}})/efiboot/shim.efi" -+ chainloader ${shim} -+} -+menuentry "UEFI Firmware Settings" --id "uefi-firmware" { -+ fwsetup -+} -+menuentry "System restart" { -+ echo "System rebooting..." -+ reboot -+} -+menuentry "System shutdown" { -+ echo "System shutting down..." -+ halt -+} -diff --git a/etc/grub/grub.cfg.ww b/etc/grub/grub.cfg.ww -new file mode 100644 -index 00000000..7efbbe0a ---- /dev/null -+++ b/etc/grub/grub.cfg.ww -@@ -0,0 +1,51 @@ -+echo "================================================================================" -+echo "Warewulf v4 now http booting grub: {{.Fqdn}} ({{.Hwaddr}})" -+echo "================================================================================" -+echo -+echo "Warewulf Controller: {{.Ipaddr}}" -+echo -+sleep 1 -+smbios --type1 --get-string 8 --set assetkey -+uri="(http,{{.Ipaddr}}:{{.Port}})/provision/${net_default_mac}?assetkey=${assetkey}" -+kernel="${uri}&stage=kernel" -+container="${uri}&stage=container&compress=gz" -+system="${uri}&stage=system&compress=gz" -+runtime="${uri}&stage=runtime&compress=gz" -+set default=ww4 -+set timeout=5 -+menuentry "Network boot node: {{.Id}}" --id ww4 { -+ {{if .KernelOverride }} -+ echo "Kernel: {{.KernelOverride}}" -+ {{else}} -+ echo "Kernel: {{.ContainerName}} (container default)" -+ {{end}} -+ echo "KernelArgs: {{.KernelArgs}}" -+ linux $kernel wwid=${net_default_mac} {{.KernelArgs}} -+ if [ x$? = x0 ] ; then -+ echo "Loading Container: {{.ContainerName}}" -+ initrd $container $system $runtime -+ boot -+ else -+ echo "MESSAGE: This node seems to be unconfigured. Please have your system administrator add a" -+ echo " configuration for this node with HW address: ${net_default_mac}" -+ echo "" -+ echo "Rebooting in 1 minute..." -+ sleep 60 -+ reboot -+ fi -+} -+menuentry "Chainload specific configfile" { -+ conf="(http,{{.Ipaddr}}:{{.Port}})/efiboot/grub.cfg" -+ configfile $conf -+} -+menuentry "UEFI Firmware Settings" --id "uefi-firmware" { -+ fwsetup -+} -+menuentry "System restart" { -+ echo "System rebooting..." -+ reboot -+} -+menuentry "System shutdown" { -+ echo "System shutting down..." -+ halt -+} -\ No newline at end of file -diff --git a/go.mod b/go.mod -index c9e07d29..4d304414 100644 ---- a/go.mod -+++ b/go.mod -@@ -11,6 +11,7 @@ require ( - github.com/creasty/defaults v1.7.0 - github.com/fatih/color v1.15.0 - github.com/golang/glog v1.0.0 -+ github.com/golang/protobuf v1.5.2 - github.com/google/uuid v1.3.0 - github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2 - github.com/manifoldco/promptui v0.9.0 -@@ -56,7 +57,6 @@ require ( - github.com/docker/go-units v0.4.0 // indirect - github.com/ghodss/yaml v1.0.0 // indirect - github.com/gogo/protobuf v1.3.2 // indirect -- github.com/golang/protobuf v1.5.2 // indirect - github.com/golang/snappy v0.0.3 // indirect - github.com/gorilla/mux v1.7.4 // indirect - github.com/inconshreveable/mousetrap v1.0.1 // indirect -diff --git a/internal/app/wwctl/container/delete/main.go b/internal/app/wwctl/container/delete/main.go -index 576ccef7..71e5a3d7 100644 ---- a/internal/app/wwctl/container/delete/main.go -+++ b/internal/app/wwctl/container/delete/main.go -@@ -5,7 +5,8 @@ import ( - - "github.com/hpcng/warewulf/internal/pkg/api/container" - "github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1" -- "github.com/hpcng/warewulf/internal/pkg/api/util" -+ apiutil "github.com/hpcng/warewulf/internal/pkg/api/util" -+ - "github.com/spf13/cobra" - ) - -@@ -13,8 +14,9 @@ func CobraRunE(cmd *cobra.Command, args []string) (err error) { - cdp := &wwapiv1.ContainerDeleteParameter{ - ContainerNames: args, - } -+ - if !SetYes { -- yes := util.ConfirmationPrompt(fmt.Sprintf("Are you sure you want to delete container %s", args)) -+ yes := apiutil.ConfirmationPrompt(fmt.Sprintf("Are you sure you want to delete container %s", args)) - if !yes { - return - } -diff --git a/internal/app/wwctl/container/exec/main.go b/internal/app/wwctl/container/exec/main.go -index bc622de2..f34ada9f 100644 ---- a/internal/app/wwctl/container/exec/main.go -+++ b/internal/app/wwctl/container/exec/main.go -@@ -133,7 +133,6 @@ func CobraRunE(cmd *cobra.Command, args []string) error { - wwlog.Error("Could not build container %s: %s", containerName, err) - os.Exit(1) - } -- - return nil - } - func SetBinds(myBinds []string) { -diff --git a/internal/app/wwctl/container/imprt/main.go b/internal/app/wwctl/container/imprt/main.go -index 1ec28322..4114b45c 100644 ---- a/internal/app/wwctl/container/imprt/main.go -+++ b/internal/app/wwctl/container/imprt/main.go -@@ -1,7 +1,9 @@ - package imprt - - import ( -- "github.com/hpcng/warewulf/internal/pkg/api/container" -+ "github.com/hpcng/warewulf/internal/pkg/container" -+ -+ apicontainer "github.com/hpcng/warewulf/internal/pkg/api/container" - "github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1" - "github.com/spf13/cobra" - ) -@@ -13,6 +15,9 @@ func CobraRunE(cmd *cobra.Command, args []string) (err error) { - if len(args) == 2 { - name = args[1] - } -+ if list, _ := container.ListSources(); len(list) == 0 { -+ SetDefault = true -+ } - - cip := &wwapiv1.ContainerImportParameter{ - Source: args[0], -@@ -23,6 +28,7 @@ func CobraRunE(cmd *cobra.Command, args []string) (err error) { - Default: SetDefault, - SyncUser: SyncUser, - } -- _, err = container.ContainerImport(cip) -+ -+ _, err = apicontainer.ContainerImport(cip) - return - } -diff --git a/internal/app/wwctl/profile/delete/main.go b/internal/app/wwctl/profile/delete/main.go -index 71d3dabf..137ff4ae 100644 ---- a/internal/app/wwctl/profile/delete/main.go -+++ b/internal/app/wwctl/profile/delete/main.go -@@ -5,6 +5,7 @@ import ( - "os" - - "github.com/hpcng/warewulf/internal/pkg/node" -+ "github.com/hpcng/warewulf/internal/pkg/util" - "github.com/hpcng/warewulf/internal/pkg/wwlog" - "github.com/manifoldco/promptui" - "github.com/pkg/errors" -@@ -13,6 +14,9 @@ import ( - - func CobraRunE(cmd *cobra.Command, args []string) error { - var count int -+ if util.InSlice(args, "default") { -+ return fmt.Errorf("can't delete the `default` profile ") -+ } - - nodeDB, err := node.New() - if err != nil { -diff --git a/internal/pkg/api/profile/profile.go b/internal/pkg/api/profile/profile.go -index e6ca6c68..50e60afc 100644 ---- a/internal/pkg/api/profile/profile.go -+++ b/internal/pkg/api/profile/profile.go -@@ -24,7 +24,8 @@ func ProfileSet(set *wwapiv1.ProfileSetParameter) (err error) { - if err != nil { - return errors.Wrap(err, "Could not open database") - } -- return apinode.DbSave(&nodeDB) -+ dbError := apinode.DbSave(&nodeDB) -+ return dbError - } - - // ProfileSetParameterCheck does error checking on ProfileSetParameter. -diff --git a/internal/pkg/config/warewulf.go b/internal/pkg/config/warewulf.go -index e8f08ac5..9a0dc3f4 100644 ---- a/internal/pkg/config/warewulf.go -+++ b/internal/pkg/config/warewulf.go -@@ -10,4 +10,5 @@ type WarewulfConf struct { - EnableHostOverlay bool `yaml:"host overlay" default:"true"` - Syslog bool `yaml:"syslog" default:"false"` - DataStore string `yaml:"datastore" default:"/var/lib/warewulf"` -+ GrubBoot bool `yaml:"grubboot" default:"false"` - } -diff --git a/internal/pkg/configure/tftp.go b/internal/pkg/configure/tftp.go -index 82e023f3..058ca059 100644 ---- a/internal/pkg/configure/tftp.go -+++ b/internal/pkg/configure/tftp.go -@@ -1,12 +1,12 @@ - package configure - - import ( -- "fmt" - "os" - "path" - - warewulfconf "github.com/hpcng/warewulf/internal/pkg/config" - "github.com/hpcng/warewulf/internal/pkg/util" -+ "github.com/hpcng/warewulf/internal/pkg/warewulfd" - "github.com/hpcng/warewulf/internal/pkg/wwlog" - ) - -@@ -20,28 +20,34 @@ func TFTP() error { - return err - } - -- fmt.Printf("Writing PXE files to: %s\n", tftpdir) -- copyCheck := make(map[string]bool) -- for _, f := range controller.TFTP.IpxeBinaries { -- if !path.IsAbs(f) { -- f = path.Join(controller.Paths.Ipxesource, f) -- } -- if copyCheck[f] { -- continue -- } -- copyCheck[f] = true -- err = util.SafeCopyFile(f, path.Join(tftpdir, path.Base(f))) -+ if controller.Warewulf.GrubBoot { -+ err := warewulfd.CopyShimGrub() - if err != nil { -- wwlog.Warn("ipxe binary could not be copied, booting may not work: %s", err) -+ wwlog.Warn("error when copying shim/grub binaries: %s", err) -+ } -+ } else { -+ wwlog.Info("Writing PXE files to: %s", tftpdir) -+ copyCheck := make(map[string]bool) -+ for _, f := range controller.TFTP.IpxeBinaries { -+ if !path.IsAbs(f) { -+ f = path.Join(controller.Paths.Ipxesource, f) -+ } -+ if copyCheck[f] { -+ continue -+ } -+ copyCheck[f] = true -+ err = util.SafeCopyFile(f, path.Join(tftpdir, path.Base(f))) -+ if err != nil { -+ wwlog.Warn("ipxe binary could not be copied, booting may not work: %s", err) -+ } - } - } -- - if !controller.TFTP.Enabled { - wwlog.Info("Warewulf does not auto start TFTP services due to disable by warewulf.conf") - os.Exit(0) - } - -- fmt.Printf("Enabling and restarting the TFTP services\n") -+ wwlog.Info("Enabling and restarting the TFTP services") - err = util.SystemdStart(controller.TFTP.SystemdName) - if err != nil { - wwlog.Error("%s", err) -diff --git a/internal/pkg/container/shimgrub.go b/internal/pkg/container/shimgrub.go -new file mode 100644 -index 00000000..a590690f ---- /dev/null -+++ b/internal/pkg/container/shimgrub.go -@@ -0,0 +1,94 @@ -+package container -+ -+import ( -+ "os" -+ "path" -+ "path/filepath" -+ -+ "github.com/hpcng/warewulf/internal/pkg/wwlog" -+) -+ -+func shimDirs() []string { -+ return []string{ -+ `/usr/share/efi/x86_64/`, -+ `/usr/lib64/efi/`, -+ `/boot/efi/EFI/*/`, -+ } -+} -+func shimNames() []string { -+ return []string{ -+ `shim.efi`, -+ `shim-sles.efi`, -+ `shimx64.efi`, -+ `shim-susesigned.efi`, -+ } -+} -+ -+func grubDirs() []string { -+ return []string{ -+ `/usr/lib64/efi/`, -+ `/usr/share/grub2/*-efi/`, -+ `/usr/share/efi/*/`, -+ `/boot/efi/EFI/*/`, -+ } -+} -+func grubNames() []string { -+ return []string{ -+ `grub-tpm.efi`, -+ `grub.efi`, -+ `grubx64.efi`, -+ `grubia32.efi`, -+ `grubaa64.efi`, -+ `grubarm.efi`, -+ } -+} -+ -+/* -+find the path of the shim binary in container -+*/ -+func ShimFind(container string) string { -+ var container_path string -+ if container != "" { -+ container_path = RootFsDir(container) -+ } else { -+ container_path = "/" -+ } -+ wwlog.Debug("Finding grub under paths: %s", container_path) -+ return BootLoaderFindPath(container_path, shimNames, shimDirs) -+} -+ -+/* -+find a grub.efi in the used container -+*/ -+func GrubFind(container string) string { -+ var container_path string -+ if container != "" { -+ container_path = RootFsDir(container) -+ } else { -+ container_path = "/" -+ } -+ wwlog.Debug("Finding grub under paths: %s", container_path) -+ return BootLoaderFindPath(container_path, grubNames, grubDirs) -+} -+ -+/* -+find the path of the shim binary in container -+*/ -+func BootLoaderFindPath(cpath string, names func() []string, paths func() []string) string { -+ for _, bdir := range paths() { -+ wwlog.Debug("Checking shim directory: %s", bdir) -+ for _, bname := range names() { -+ wwlog.Debug("Checking for bootloader name: %s", bname) -+ shimPaths, _ := filepath.Glob(path.Join(cpath, bdir, bname)) -+ for _, shimPath := range shimPaths { -+ wwlog.Debug("Checking for bootloader path: %s", shimPath) -+ // Only succeeds if shimPath exists and, if a -+ // symlink, links to a path that also exists -+ if _, err := os.Stat(shimPath); err == nil { -+ return shimPath -+ } -+ } -+ } -+ } -+ return "" -+} -diff --git a/internal/pkg/node/constructors.go b/internal/pkg/node/constructors.go -index fe7eb87a..857f93cf 100644 ---- a/internal/pkg/node/constructors.go -+++ b/internal/pkg/node/constructors.go -@@ -31,6 +31,7 @@ defaultnode: - init: /sbin/init - root: initramfs - ipxe template: default -+ boot method: ipxe - profiles: - - default - network devices: -@@ -305,6 +306,36 @@ func (config *NodeYaml) ListAllProfiles() []string { - return ret - } - -+/* -+return a map where the key is the profile id -+*/ -+func (config *NodeYaml) MapAllProfiles() (retMap map[string]*NodeInfo, err error) { -+ retMap = make(map[string]*NodeInfo) -+ profileList, err := config.FindAllProfiles() -+ if err != nil { -+ return -+ } -+ for _, pr := range profileList { -+ retMap[pr.Id.Get()] = &pr -+ } -+ return -+} -+ -+/* -+return a map where the key is the node id -+*/ -+func (config *NodeYaml) MapAllNodes() (retMap map[string]*NodeInfo, err error) { -+ retMap = make(map[string]*NodeInfo) -+ nodeList, err := config.FindAllNodes() -+ if err != nil { -+ return -+ } -+ for _, nd := range nodeList { -+ retMap[nd.Id.Get()] = &nd -+ } -+ return -+} -+ - /* - FindDiscoverableNode returns the first discoverable node and an - interface to associate with the discovered interface. If the node has -diff --git a/internal/pkg/node/datastructure.go b/internal/pkg/node/datastructure.go -index 989c007a..34432454 100644 ---- a/internal/pkg/node/datastructure.go -+++ b/internal/pkg/node/datastructure.go -@@ -154,6 +154,7 @@ type NodeInfo struct { - ClusterName Entry - ContainerName Entry - Ipxe Entry -+ Grub Entry - RuntimeOverlay Entry - SystemOverlay Entry - Root Entry -diff --git a/internal/pkg/node/util.go b/internal/pkg/node/util.go -index e90efee6..8e1273c3 100644 ---- a/internal/pkg/node/util.go -+++ b/internal/pkg/node/util.go -@@ -6,6 +6,9 @@ import ( - "strings" - ) - -+/* -+get node by its hardware/MAC address, return error otherwise -+*/ - func (config *NodeYaml) FindByHwaddr(hwa string) (NodeInfo, error) { - if _, err := net.ParseMAC(hwa); err != nil { - return NodeInfo{}, errors.New("invalid hardware address: " + hwa) -@@ -26,6 +29,9 @@ func (config *NodeYaml) FindByHwaddr(hwa string) (NodeInfo, error) { - return ret, errors.New("No nodes found with HW Addr: " + hwa) - } - -+/* -+get node by its ip address, return error otherwise -+*/ - func (config *NodeYaml) FindByIpaddr(ipaddr string) (NodeInfo, error) { - if addr := net.ParseIP(ipaddr); addr == nil { - return NodeInfo{}, errors.New("invalid IP:" + ipaddr) -diff --git a/internal/pkg/overlay/datastructure.go b/internal/pkg/overlay/datastructure.go -index 15c9d3d5..0adc2f39 100644 ---- a/internal/pkg/overlay/datastructure.go -+++ b/internal/pkg/overlay/datastructure.go -@@ -49,19 +49,15 @@ func InitStruct(nodeInfo *node.NodeInfo) TemplateStruct { - controller := warewulfconf.Get() - nodeDB, err := node.New() - if err != nil { -- wwlog.Error("%s", err) -- os.Exit(1) -+ wwlog.Warn("Problems opening nodes.conf: %s", err) - } -- allNodes, err := nodeDB.FindAllNodes() -+ tstruct.AllNodes, err = nodeDB.FindAllNodes() - if err != nil { -- wwlog.Error("%s", err) -- os.Exit(1) -+ wwlog.Warn("couldn't get all nodes: %s", err) - } - // init some convenience vars - tstruct.Id = nodeInfo.Id.Get() - tstruct.Hostname = nodeInfo.Id.Get() -- // Backwards compatibility for templates using "Keys" -- tstruct.AllNodes = allNodes - tstruct.Nfs = *controller.NFS - tstruct.Dhcp = *controller.DHCP - tstruct.Tftp = *controller.TFTP -diff --git a/internal/pkg/warewulfd/copyshim.go b/internal/pkg/warewulfd/copyshim.go -new file mode 100644 -index 00000000..44ea45f4 ---- /dev/null -+++ b/internal/pkg/warewulfd/copyshim.go -@@ -0,0 +1,45 @@ -+package warewulfd -+ -+import ( -+ "fmt" -+ "os" -+ "path" -+ -+ warewulfconf "github.com/hpcng/warewulf/internal/pkg/config" -+ "github.com/hpcng/warewulf/internal/pkg/wwlog" -+ -+ "github.com/hpcng/warewulf/internal/pkg/container" -+ "github.com/hpcng/warewulf/internal/pkg/util" -+) -+ -+/* -+Copies the default shim, which is the shim located on host -+to the tftp directory -+*/ -+ -+func CopyShimGrub() (err error) { -+ conf := warewulfconf.Get() -+ wwlog.Debug("copy shim and grub binaries from host") -+ shimPath := container.ShimFind("") -+ if shimPath == "" { -+ return fmt.Errorf("no shim found on the host os") -+ } -+ err = util.CopyFile(shimPath, path.Join(conf.Paths.Tftpdir, "warewulf", "shim.efi")) -+ if err != nil { -+ return err -+ } -+ _ = os.Chmod(path.Join(conf.Paths.Tftpdir, "warewulf", "shim.efi"), 0o755) -+ grubPath := container.GrubFind("") -+ if grubPath == "" { -+ return fmt.Errorf("no grub found on host os") -+ } -+ err = util.CopyFile(grubPath, path.Join(conf.Paths.Tftpdir, "warewulf", "grub.efi")) -+ if err != nil { -+ return err -+ } -+ _ = os.Chmod(path.Join(conf.Paths.Tftpdir, "warewulf", "grub.efi"), 0o755) -+ err = util.CopyFile(grubPath, path.Join(conf.Paths.Tftpdir, "warewulf", "grubx64.efi")) -+ _ = os.Chmod(path.Join(conf.Paths.Tftpdir, "warewulf", "grubx64.efi"), 0o755) -+ -+ return -+} -diff --git a/internal/pkg/warewulfd/parser.go b/internal/pkg/warewulfd/parser.go -index 1e62dd0f..063d10b4 100644 ---- a/internal/pkg/warewulfd/parser.go -+++ b/internal/pkg/warewulfd/parser.go -@@ -5,6 +5,7 @@ import ( - "strconv" - "strings" - -+ "github.com/hpcng/warewulf/internal/pkg/wwlog" - "github.com/pkg/errors" - ) - -@@ -16,6 +17,7 @@ type parserInfo struct { - uuid string - stage string - overlay string -+ efifile string - compress string - } - -@@ -25,16 +27,22 @@ func parseReq(req *http.Request) (parserInfo, error) { - url := strings.Split(req.URL.Path, "?")[0] - path_parts := strings.Split(url, "/") - -- if len(path_parts) != 3 { -+ if len(path_parts) < 3 { - return ret, errors.New("unknown path components in GET") - } - - // handle when stage was passed in the url path /[stage]/hwaddr - stage := path_parts[1] -- hwaddr := path_parts[2] -- hwaddr = strings.ReplaceAll(hwaddr, "-", ":") -- hwaddr = strings.ToLower(hwaddr) -- -+ hwaddr := "" -+ if stage != "efiboot" { -+ hwaddr = path_parts[2] -+ hwaddr = strings.ReplaceAll(hwaddr, "-", ":") -+ hwaddr = strings.ToLower(hwaddr) -+ } else if len(path_parts) > 3 { -+ ret.efifile = strings.Join(path_parts[2:], "/") -+ } else { -+ ret.efifile = path_parts[2] -+ } - ret.hwaddr = hwaddr - ret.ipaddr = strings.Split(req.RemoteAddr, ":")[0] - ret.remoteport, _ = strconv.Atoi(strings.Split(req.RemoteAddr, ":")[1]) -@@ -63,6 +71,8 @@ func parseReq(req *http.Request) (parserInfo, error) { - ret.stage = "system" - } else if stage == "overlay-runtime" { - ret.stage = "runtime" -+ } else if stage == "efiboot" { -+ ret.stage = "efiboot" - } - } - -@@ -76,7 +86,11 @@ func parseReq(req *http.Request) (parserInfo, error) { - return ret, errors.New("no stage encoded in GET") - } - if ret.hwaddr == "" { -- return ret, errors.New("no hwaddr encoded in GET") -+ ret.hwaddr = ArpFind(ret.ipaddr) -+ wwlog.Verbose("node mac not encoded, arp cache got %s for %s", ret.hwaddr, ret.ipaddr) -+ if ret.hwaddr == "" { -+ return ret, errors.New("no hwaddr encoded in GET") -+ } - } - if ret.ipaddr == "" { - return ret, errors.New("could not obtain ipaddr from HTTP request") -diff --git a/internal/pkg/warewulfd/provision.go b/internal/pkg/warewulfd/provision.go -index f2851a26..bdca8a34 100644 ---- a/internal/pkg/warewulfd/provision.go -+++ b/internal/pkg/warewulfd/provision.go -@@ -3,6 +3,7 @@ package warewulfd - import ( - "bytes" - "errors" -+ "fmt" - "net/http" - "path" - "strconv" -@@ -17,7 +18,7 @@ import ( - "github.com/hpcng/warewulf/internal/pkg/wwlog" - ) - --type iPxeTemplate struct { -+type templateVars struct { - Message string - WaitTime string - Hostname string -@@ -32,20 +33,13 @@ type iPxeTemplate struct { - KernelOverride string - } - --var status_stages = map[string]string{ -- "ipxe": "IPXE", -- "kernel": "KERNEL", -- "kmods": "KMODS_OVERLAY", -- "system": "SYSTEM_OVERLAY", -- "runtime": "RUNTIME_OVERLAY"} -- - func ProvisionSend(w http.ResponseWriter, req *http.Request) { -+ wwlog.Debug("Requested URL: %s", req.URL.String()) - conf := warewulfconf.Get() -- - rinfo, err := parseReq(req) - if err != nil { - w.WriteHeader(http.StatusBadRequest) -- wwlog.ErrorExc(err, "") -+ wwlog.ErrorExc(err, "Bad status") - return - } - -@@ -59,6 +53,14 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { - } - } - -+ status_stages := map[string]string{ -+ "efiboot": "EFI", -+ "ipxe": "IPXE", -+ "kernel": "KERNEL", -+ "kmods": "KMODS_OVERLAY", -+ "system": "SYSTEM_OVERLAY", -+ "runtime": "RUNTIME_OVERLAY"} -+ - status_stage := status_stages[rinfo.stage] - var stage_file string - -@@ -83,13 +85,13 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { - wwlog.Error("%s (unknown/unconfigured node)", rinfo.hwaddr) - if rinfo.stage == "ipxe" { - stage_file = path.Join(conf.Paths.Sysconfdir, "/warewulf/ipxe/unconfigured.ipxe") -- tmpl_data = iPxeTemplate{ -+ tmpl_data = templateVars{ - Hwaddr: rinfo.hwaddr} - } - - } else if rinfo.stage == "ipxe" { - stage_file = path.Join(conf.Paths.Sysconfdir, "warewulf/ipxe/"+node.Ipxe.Get()+".ipxe") -- tmpl_data = iPxeTemplate{ -+ tmpl_data = templateVars{ - Id: node.Id.Get(), - Cluster: node.ClusterName.Get(), - Fqdn: node.Id.Get(), -@@ -100,7 +102,6 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { - ContainerName: node.ContainerName.Get(), - KernelArgs: node.Kernel.Args.Get(), - KernelOverride: node.Kernel.Override.Get()} -- - } else if rinfo.stage == "kernel" { - if node.Kernel.Override.Defined() { - stage_file = kernel.KernelImage(node.Kernel.Override.Get()) -@@ -137,7 +138,6 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { - } else { - context = rinfo.stage - } -- - stage_file, err = getOverlayFile( - node, - context, -@@ -154,6 +154,64 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { - wwlog.ErrorExc(err, "") - return - } -+ } else if rinfo.stage == "efiboot" { -+ wwlog.Debug("requested method: %s", req.Method) -+ containerName := node.ContainerName.Get() -+ switch rinfo.efifile { -+ case "shim.efi": -+ stage_file = container.ShimFind(containerName) -+ if stage_file == "" { -+ wwlog.ErrorExc(fmt.Errorf("could't find shim.efi"), containerName) -+ w.WriteHeader(http.StatusNotFound) -+ return -+ } -+ case "grub.efi", "grub-tpm.efi", "grubx64.efi", "grubia32.efi", "grubaa64.efi", "grubarm.efi": -+ stage_file = container.GrubFind(containerName) -+ if stage_file == "" { -+ wwlog.ErrorExc(fmt.Errorf("could't find grub.efi"), containerName) -+ w.WriteHeader(http.StatusNotFound) -+ return -+ } -+ case "grub.cfg": -+ stage_file = path.Join(conf.Paths.Sysconfdir, "warewulf/grub/grub.cfg.ww") -+ tmpl_data = templateVars{ -+ Id: node.Id.Get(), -+ Cluster: node.ClusterName.Get(), -+ Fqdn: node.Id.Get(), -+ Ipaddr: conf.Ipaddr, -+ Port: strconv.Itoa(conf.Warewulf.Port), -+ Hostname: node.Id.Get(), -+ Hwaddr: rinfo.hwaddr, -+ ContainerName: node.ContainerName.Get(), -+ KernelArgs: node.Kernel.Args.Get(), -+ KernelOverride: node.Kernel.Override.Get()} -+ if stage_file == "" { -+ wwlog.ErrorExc(fmt.Errorf("could't find grub.cfg template"), containerName) -+ w.WriteHeader(http.StatusNotFound) -+ return -+ } -+ default: -+ wwlog.ErrorExc(fmt.Errorf("could't find efiboot file: %s", rinfo.efifile), "") -+ } -+ } else if rinfo.stage == "shim" { -+ if node.ContainerName.Defined() { -+ stage_file = container.ShimFind(node.ContainerName.Get()) -+ -+ if stage_file == "" { -+ wwlog.Error("No kernel found for container %s", node.ContainerName.Get()) -+ } -+ } else { -+ wwlog.Warn("No container set for this %s", node.Id.Get()) -+ } -+ } else if rinfo.stage == "grub" { -+ if node.ContainerName.Defined() { -+ stage_file = container.GrubFind(node.ContainerName.Get()) -+ if stage_file == "" { -+ wwlog.Error("No grub found for container %s", node.ContainerName.Get()) -+ } -+ } else { -+ wwlog.Warn("No conainer set for node %s", node.Id.Get()) -+ } - } - - wwlog.Serv("stage_file '%s'", stage_file) -diff --git a/internal/pkg/warewulfd/provision_test.go b/internal/pkg/warewulfd/provision_test.go -index 870e1de0..5ec8d72e 100644 ---- a/internal/pkg/warewulfd/provision_test.go -+++ b/internal/pkg/warewulfd/provision_test.go -@@ -1,16 +1,18 @@ - package warewulfd - - import ( -- "github.com/stretchr/testify/assert" -- "io/ioutil" -+ "io" - "net/http" - "net/http/httptest" - "os" - "path" - "testing" - -+ "github.com/stretchr/testify/assert" -+ - warewulfconf "github.com/hpcng/warewulf/internal/pkg/config" - "github.com/hpcng/warewulf/internal/pkg/node" -+ "github.com/hpcng/warewulf/internal/pkg/wwlog" - ) - - var provisionSendTests = []struct { -@@ -18,37 +20,80 @@ var provisionSendTests = []struct { - url string - body string - status int -+ ip string - }{ -- {"system overlay", "/overlay-system/00:00:00:ff:ff:ff", "system overlay", 200}, -- {"runtime overlay", "/overlay-runtime/00:00:00:ff:ff:ff", "runtime overlay", 200}, -- {"fake overlay", "/overlay-system/00:00:00:ff:ff:ff?overlay=fake", "", 404}, -- {"specific overlay", "/overlay-system/00:00:00:ff:ff:ff?overlay=o1", "specific overlay", 200}, -+ {"system overlay", "/overlay-system/00:00:00:ff:ff:ff", "system overlay", 200, "10.10.10.10:9873"}, -+ {"runtime overlay", "/overlay-runtime/00:00:00:ff:ff:ff", "runtime overlay", 200, "10.10.10.10:9873"}, -+ {"fake overlay", "/overlay-system/00:00:00:ff:ff:ff?overlay=fake", "", 404, "10.10.10.10:9873:9873"}, -+ {"specific overlay", "/overlay-system/00:00:00:ff:ff:ff?overlay=o1", "specific overlay", 200, "10.10.10.10:9873"}, -+ {"find shim", "/efiboot/shim.efi", "", 200, "10.10.10.10:9873"}, -+ {"find shim", "/efiboot/shim.efi", "", 404, "10.10.10.11:9873"}, -+ {"find grub", "/efiboot/grub.efi", "", 200, "10.10.10.10:9873"}, -+ {"find grub", "/efiboot/grub.efi", "", 404, "10.10.10.11:9873"}, - } - - func Test_ProvisionSend(t *testing.T) { -- file, err := os.CreateTemp(os.TempDir(), "ww-test-nodes.conf-*") -+ conf_file, err := os.CreateTemp(os.TempDir(), "ww-test-nodes.conf-*") - assert.NoError(t, err) -- defer file.Close() -+ defer conf_file.Close() - { -- _, err := file.WriteString(`WW_INTERNAL: 43 -+ _, err := conf_file.WriteString(`WW_INTERNAL: 43 -+nodeprofiles: -+ default: -+ container name: suse - nodes: - n1: - network devices: - default: -- hwaddr: 00:00:00:ff:ff:ff`) -+ hwaddr: 00:00:00:ff:ff:ff -+ n2: -+ network devices: -+ default: -+ hwaddr: 00:00:00:00:ff:ff -+ container name: none`) -+ assert.NoError(t, err) -+ } -+ assert.NoError(t, conf_file.Sync()) -+ node.ConfigFile = conf_file.Name() -+ -+ // create a arp file as for grub we look up the ip address through the arp cache -+ arp_file, err := os.CreateTemp(os.TempDir(), "ww-arp") -+ assert.NoError(t, err) -+ defer arp_file.Close() -+ { -+ _, err := arp_file.WriteString(`IP address HW type Flags HW address Mask Device -+10.10.10.10 0x1 0x2 00:00:00:ff:ff:ff * dummy -+10.10.10.11 0x1 0x2 00:00:00:00:ff:ff * dummy`) - assert.NoError(t, err) - } -- assert.NoError(t, file.Sync()) -- node.ConfigFile = file.Name() -+ assert.NoError(t, arp_file.Sync()) -+ SetArpFile(arp_file.Name()) -+ -+ conf := warewulfconf.Get() -+ containerDir, imageDirErr := os.MkdirTemp(os.TempDir(), "ww-test-container-*") -+ assert.NoError(t, imageDirErr) -+ defer os.RemoveAll(containerDir) -+ conf.Paths.WWChrootdir = containerDir -+ assert.NoError(t, os.MkdirAll(path.Join(containerDir, "suse/rootfs/usr/lib64/efi"), 0700)) -+ { -+ _, err := os.Create(path.Join(containerDir, "suse/rootfs/usr/lib64/efi", "shim.efi")) -+ assert.NoError(t, err) -+ } -+ assert.NoError(t, os.MkdirAll(path.Join(containerDir, "suse/rootfs/usr/share/efi/x86_64/"), 0700)) -+ { -+ _, err := os.Create(path.Join(containerDir, "suse/rootfs/usr/share/efi/x86_64/", "grub.efi")) -+ assert.NoError(t, err) -+ } -+ - dbErr := LoadNodeDB() - assert.NoError(t, dbErr) - - provisionDir, provisionDirErr := os.MkdirTemp(os.TempDir(), "ww-test-provision-*") - assert.NoError(t, provisionDirErr) - defer os.RemoveAll(provisionDir) -- conf := warewulfconf.Get() - conf.Paths.WWProvisiondir = provisionDir - conf.Warewulf.Secure = false -+ wwlog.SetLogLevel(wwlog.DEBUG) - assert.NoError(t, os.MkdirAll(path.Join(provisionDir, "overlays", "n1"), 0700)) - assert.NoError(t, os.WriteFile(path.Join(provisionDir, "overlays", "n1", "__SYSTEM__.img"), []byte("system overlay"), 0600)) - assert.NoError(t, os.WriteFile(path.Join(provisionDir, "overlays", "n1", "__RUNTIME__.img"), []byte("runtime overlay"), 0600)) -@@ -57,12 +102,13 @@ nodes: - for _, tt := range provisionSendTests { - t.Run(tt.description, func(t *testing.T) { - req := httptest.NewRequest(http.MethodGet, tt.url, nil) -+ req.RemoteAddr = tt.ip - w := httptest.NewRecorder() - ProvisionSend(w, req) - res := w.Result() - defer res.Body.Close() - -- data, readErr := ioutil.ReadAll(res.Body) -+ data, readErr := io.ReadAll(res.Body) - assert.NoError(t, readErr) - assert.Equal(t, tt.body, string(data)) - assert.Equal(t, tt.status, res.StatusCode) -diff --git a/internal/pkg/warewulfd/util.go b/internal/pkg/warewulfd/util.go -index 3290a678..caaf22ae 100644 ---- a/internal/pkg/warewulfd/util.go -+++ b/internal/pkg/warewulfd/util.go -@@ -1,8 +1,10 @@ - package warewulfd - - import ( -+ "bufio" - "net/http" - "os" -+ "strings" - - "github.com/hpcng/warewulf/internal/pkg/node" - nodepkg "github.com/hpcng/warewulf/internal/pkg/node" -@@ -38,7 +40,7 @@ func sendFile( - fd) - - wwlog.Send("%15s: %s", sendto, filename) -- -+ req.Body.Close() - return nil - } - -@@ -70,3 +72,34 @@ func getOverlayFile( - - return - } -+ -+var arpFile string -+ -+func init() { -+ arpFile = "/proc/net/arp" -+} -+ -+func SetArpFile(newName string) { -+ arpFile = newName -+} -+ -+/* -+returns the mac address if it has an entry in the arp cache -+*/ -+func ArpFind(ip string) (mac string) { -+ arpCache, err := os.Open(arpFile) -+ if err != nil { -+ return -+ } -+ defer arpCache.Close() -+ -+ scanner := bufio.NewScanner(arpCache) -+ scanner.Scan() -+ for scanner.Scan() { -+ fields := strings.Fields(scanner.Text()) -+ if strings.EqualFold(fields[0], ip) { -+ return fields[3] -+ } -+ } -+ return -+} -diff --git a/internal/pkg/warewulfd/warewulfd.go b/internal/pkg/warewulfd/warewulfd.go -index 796054b0..c9376634 100644 ---- a/internal/pkg/warewulfd/warewulfd.go -+++ b/internal/pkg/warewulfd/warewulfd.go -@@ -5,6 +5,7 @@ import ( - "os" - "os/signal" - "strconv" -+ "strings" - "syscall" - - warewulfconf "github.com/hpcng/warewulf/internal/pkg/config" -@@ -14,6 +15,23 @@ import ( - - // TODO: https://github.com/danderson/netboot/blob/master/pixiecore/dhcp.go - // TODO: https://github.com/pin/tftp -+/* -+wrapper type for the server mux as shim requests http://efiboot//grub.efi -+which is filtered out by http to `301 Moved Permanently` what -+shim.efi can't handle. So filter out `//` before they hit go/http. -+Makes go/http more to behave like apache -+*/ -+type slashFix struct { -+ mux http.Handler -+} -+ -+/* -+Filter out the '//' -+*/ -+func (h *slashFix) ServeHTTP(w http.ResponseWriter, r *http.Request) { -+ r.URL.Path = strings.Replace(r.URL.Path, "//", "/", -1) -+ h.mux.ServeHTTP(w, r) -+} - - func RunServer() error { - err := DaemonInitLogging() -@@ -49,21 +67,36 @@ func RunServer() error { - wwlog.Error("Could not prepopulate node status DB: %s", err) - } - -- http.HandleFunc("/provision/", ProvisionSend) -- http.HandleFunc("/ipxe/", ProvisionSend) -- http.HandleFunc("/kernel/", ProvisionSend) -- http.HandleFunc("/kmods/", ProvisionSend) -- http.HandleFunc("/container/", ProvisionSend) -- http.HandleFunc("/overlay-system/", ProvisionSend) -- http.HandleFunc("/overlay-runtime/", ProvisionSend) -- http.HandleFunc("/status", StatusSend) -+ if err != nil { -+ wwlog.Warn("couldn't copy default shim: %s", err) -+ } -+ var wwHandler http.ServeMux -+ wwHandler.HandleFunc("/provision/", ProvisionSend) -+ wwHandler.HandleFunc("/ipxe/", ProvisionSend) -+ wwHandler.HandleFunc("/efiboot/", ProvisionSend) -+ wwHandler.HandleFunc("/kernel/", ProvisionSend) -+ wwHandler.HandleFunc("/kmods/", ProvisionSend) -+ wwHandler.HandleFunc("/container/", ProvisionSend) -+ wwHandler.HandleFunc("/overlay-system/", ProvisionSend) -+ wwHandler.HandleFunc("/overlay-runtime/", ProvisionSend) -+ wwHandler.HandleFunc("/status", StatusSend) - - conf := warewulfconf.Get() - - daemonPort := conf.Warewulf.Port -- wwlog.Serv("Starting HTTPD REST service on port %d", daemonPort) -+ /* -+ wwlog.Serv("Starting HTTPD REST service on port %d", daemonPort) -+ s := &http.Server{ -+ Addr: ":" + strconv.Itoa(daemonPort), -+ Handler: &slashFix{&wwHandler}, -+ ReadTimeout: 10 * time.Second, -+ IdleTimeout: 10 * time.Second, -+ WriteTimeout: 10 * time.Second, -+ } -+ err = s.ListenAndServe() -+ */ -+ err = http.ListenAndServe(":"+strconv.Itoa(daemonPort), &slashFix{&wwHandler}) - -- err = http.ListenAndServe(":"+strconv.Itoa(daemonPort), nil) - if err != nil { - return errors.Wrap(err, "Could not start listening service") - } -diff --git a/internal/pkg/wwlog/wwlog.go b/internal/pkg/wwlog/wwlog.go -index 98f67f2a..0b8ee5ca 100644 ---- a/internal/pkg/wwlog/wwlog.go -+++ b/internal/pkg/wwlog/wwlog.go -@@ -36,9 +36,11 @@ var ( - ERROR = SetLevelName(40, "ERROR") - SECWARN = SetLevelName(31, "SECWARN") - WARN = SetLevelName(30, "WARN") -+ ERROUT = SetLevelName(29, "ERROUT") - SEND = SetLevelName(27, "SEND") - RECV = SetLevelName(26, "RECV") - SERV = SetLevelName(25, "SERV") -+ OUT = SetLevelName(22, "OUT") - SECINFO = SetLevelName(21, "SECINFO") - INFO = SetLevelName(20, "INFO") - SECVERBOSE = SetLevelName(16, "SECVERBOSE") -@@ -52,6 +54,7 @@ var ( - levelNames = []string{"NOTSET"} - logLevel = INFO - logErr io.Writer = os.Stderr -+ logOut io.Writer = os.Stdout - logFormatter LogFormatter = DefaultFormatter - ) - -@@ -153,11 +156,30 @@ func GetLogLevel() int { - Set the log output writer - By default they are set to output writer - */ --func SetLogWriter(err io.Writer) { -- logErr = err -+func SetLogWriter(newOut io.Writer) { -+ logErr = newOut -+ logOut = newOut - } - --func GetLogWriter() io.Writer { -+/* -+Set the log ofr info only -+*/ -+func SetLogWriterInfo(newOut io.Writer) { -+ logOut = newOut -+} -+ -+/* -+Set the log ofr info only -+*/ -+func SetLogWriterErr(newOut io.Writer) { -+ logOut = newOut -+} -+ -+func GetLogWriterInfo() io.Writer { -+ return logOut -+} -+ -+func GetLogWriterErr() io.Writer { - return logErr - } - -@@ -196,8 +218,12 @@ func LogCaller(level int, skip int, err error, message string, a ...interface{}) - } - - message = logFormatter(logLevel, &rec) -+ if level == INFO || level == RECV || level == SEND || level == OUT { -+ fmt.Fprint(logOut, message) -+ } else { -+ fmt.Fprint(logErr, message) - -- fmt.Fprint(logErr, message) -+ } - } - } - -@@ -249,6 +275,10 @@ func Info(message string, a ...interface{}) { - LogCaller(INFO, 1, nil, message, a...) - } - -+func Output(message string, a ...interface{}) { -+ LogCaller(OUT, 1, nil, message, a...) -+} -+ - func InfoExc(err error, message string, a ...interface{}) { - LogCaller(INFO, 1, err, message, a...) - } -@@ -281,6 +311,10 @@ func SecWarn(message string, a ...interface{}) { - LogCaller(SECWARN, 1, nil, message, a...) - } - -+func ErrOut(message string, a ...interface{}) { -+ LogCaller(ERROUT, 1, nil, message, a...) -+} -+ - func Error(message string, a ...interface{}) { - LogCaller(ERROR, 1, nil, message, a...) - } -diff --git a/overlays/host/etc/dhcp/dhcpd.conf.ww b/overlays/host/etc/dhcp/dhcpd.conf.ww -index 4a9637f7..03fe54cb 100644 ---- a/overlays/host/etc/dhcp/dhcpd.conf.ww -+++ b/overlays/host/etc/dhcp/dhcpd.conf.ww -@@ -1,4 +1,4 @@ --{{if .Dhcp.Enabled -}} -+{{if $.Dhcp.Enabled -}} - # This file is autogenerated by warewulf - # Host: {{.BuildHost}} - # Time: {{.BuildTime}} -@@ -15,8 +15,29 @@ option space ipxe; - option ipxe.no-pxedhcp code 176 = unsigned integer 8; - option ipxe.no-pxedhcp 1; - -+option space PXE; -+option PXE.mtftp-ip code 1 = ip-address; -+option PXE.mtftp-cport code 2 = unsigned integer 16; -+option PXE.mtftp-sport code 3 = unsigned integer 16; -+option PXE.mtftp-tmout code 4 = unsigned integer 8; -+option PXE.mtftp-delay code 5 = unsigned integer 8; -+ - option architecture-type code 93 = unsigned integer 16; - -+{{- if $.Warewulf.GrubBoot }} -+if substring (option vendor-class-identifier, 0, 9) = "PXEClient" { -+ next-server {{ $.Ipaddr }}; -+ if substring (option vendor-class-identifier, 15, 5) = "00000" { -+ # pure BIOS clients will get iPXE configuration -+ filename "http://{{$.Ipaddr}}:{{$.Warewulf.Port}}/ipxe/${mac:hexhyp}"; -+ } else { -+ # EFI clients will get shim and grub instead -+ filename "warewulf/shim.efi"; -+ } -+} elsif substring (option vendor-class-identifier, 0, 10) = "HTTPClient" { -+ filename "http://{{$.Ipaddr}}:{{$.Warewulf.Port}}/efiboot/shim.efi"; -+} -+{{- else }} - if exists user-class and option user-class = "iPXE" { - filename "http://{{$.Ipaddr}}:{{$.Warewulf.Port}}/ipxe/${mac:hexhyp}"; - } else { -@@ -24,39 +45,32 @@ if exists user-class and option user-class = "iPXE" { - if option architecture-type = {{ $type }} { - filename "/warewulf/{{ basename $name }}"; - } --{{ end }} -+{{- end }}{{/* range IpxeBinaries */}} - } -+{{- end }}{{/* BootMethod */}} - --{{if eq .Dhcp.Template "static" -}} - subnet {{$.Network}} netmask {{$.Netmask}} { -- next-server {{$.Ipaddr}}; -+ max-lease-time 120; -+ range {{$.Dhcp.RangeStart}} {{$.Dhcp.RangeEnd}}; -+ next-server {{.Ipaddr}}; - } --{{range $nodes := .AllNodes}} -+{{- range $nodes := $.AllNodes}} - {{- range $netname, $netdevs := $nodes.NetDevs}} --host {{$nodes.Id.Get}}-{{if $netdevs.Device.Defined}}{{$netdevs.Device.Get}}{{else}}{{$netname}}{{end}} { -+host {{$nodes.Id.Get}}-{{$netdevs.Device.Get}} -+{ -+ {{- if $netdevs.Primary.GetB}} - {{- if $netdevs.Hwaddr.Defined}} - hardware ethernet {{$netdevs.Hwaddr.Get}}; -- {{- end}} -+ {{- end }} - {{- if $netdevs.Ipaddr.Defined}} - fixed-address {{$netdevs.Ipaddr.Get}}; -- {{- end}} -- {{- if $netdevs.Primary.GetB}} -+ {{- end }} - option host-name "{{$nodes.Id.Get}}"; -- {{else}} -- option host-name "{{$nodes.Id.Get}}-{{if $netdevs.Device.Defined}}{{$netdevs.Device.Get}}{{else}}{{$netname}}{{end}}"; -- {{- end}} --} --{{end -}} --{{end -}} -- --{{else -}} --subnet {{$.Network}} netmask {{$.Netmask}} { -- max-lease-time 120; -- range {{$.Dhcp.RangeStart}} {{$.Dhcp.RangeEnd}}; -- next-server {{$.Ipaddr}}; -+ {{- end }} - } --{{end}} -+{{end -}}{{/* range NetDevs */}} -+{{end -}}{{/* range AllNodes */}} - - {{- else}} - {{abort}} --{{- end}} -+{{- end}}{{/* dhcp enabled */}} -diff --git a/overlays/host/etc/dhcpd.conf.ww b/overlays/host/etc/dhcpd.conf.ww -new file mode 120000 -index 00000000..e5b73f7b ---- /dev/null -+++ b/overlays/host/etc/dhcpd.conf.ww -@@ -0,0 +1 @@ -+dhcp/dhcpd.conf.ww -\ No newline at end of file -diff --git a/overlays/wwinit/warewulf/init.d/80-wwclient b/overlays/wwinit/warewulf/init.d/80-wwclient -new file mode 100644 -index 00000000..38347110 ---- /dev/null -+++ b/overlays/wwinit/warewulf/init.d/80-wwclient -@@ -0,0 +1,7 @@ -+#!/bin/sh -+. /warewulf/config -+# Only start if the systemd is no available -+test -d /run/systemd/system && exit 0 -+echo "Starting wwclient" -+nohup /warewulf/wwclient >/var/log/wwclient.log 2>&1 `_ -+can be used with the advantage that secure boot can be used. That means -+that only the signed kernel of a distribution can be booted. This can -+be a huge security benefit for some scenarios. -+ -+In order to enable the grub boot method it has to be enabled in `warewulf.conf`. -+Nodes which are not known to warewulf will then booted with the shim/grub from -+the host on which warewulf is installed. -+ -+ -+Boot process -+============ -+ -+The boot process can be summarized with following diagram -+ -+.. graphviz:: -+ -+ digraph foo { -+ node [shape=box]; -+ subgraph boot { -+ "EFI" [label="EFI",row=boot]; -+ "Shim" [label="Shim",row=boot]; -+ "Grub" [label="Grub",row=boot]; -+ "Kernel" [label="kernel",row=boot]; -+ EFI -> Shim[label="Check for Microsoft signature"]; -+ Shim -> Grub[label="Check for Distribution signature"]; -+ Grub->Kernel[label="Check for Distribution or MOK signature"]; -+ } -+ } -+ -+If secure boot is enabled at every step a signature is checked and the boot process -+will fail if this check fails. Also at moment a Shim only includes the key -+of one Distribution, which means that every Distribution needs a separate -+`shim` and `grub` executable and warewulf extracts these binaries from -+the containers. -+ -+For the case when the node is unknown to warewulf or -+can't be identified during the `tFTP`` boot phase, the shim/grub binaries of -+the host in which warewulf is running will be used. -+ -+PXE/tFTP boot -+------------- -+ -+The standard network boot process with `grub` and `iPXE` has following steps -+ -+.. graphviz:: -+ -+ digraph G{ -+ node [shape=box]; -+ compound=true; -+ edge [label2node=true] -+ bios [shape=record label="{Bios | boots filename from nextboot per tFTP}"] -+ subgraph cluster0 { -+ label="iPXE boot" -+ iPXE; -+ ipxe_cfg [shape=record label="{ipxe.cfg|generated for indivdual node}"]; -+ iPXE -> ipxe_cfg [label="http"]; -+ } -+ bios->iPXE [lhead=cluster0,label="filename=iPXE.efi"]; -+ bios->shim [lhead=cluster1,label="filename=shim.efi"]; -+ subgraph cluster1{ -+ label="Grub boot" -+ shim[shape=record label="{shim.efi|from ww4 host}"]; -+ grub[shape=record label="{grubx64.efi | name hardcoded in shim.efi|from ww4 host}"] -+ shim->grub[label="tFTP"]; -+ grubcfg[shape=record label="{grub.cfg|static under tFTP root}"]; -+ grub->grubcfg[label="tFTP"]; -+ } -+ kernel [shape=record label="{kernel|ramdisk (root fs)|wwinit overlay}|extracted from node container"]; -+ grubcfg->kernel[ltail=cluster1,label="http"]; -+ ipxe_cfg->kernel[ltail=cluster0,label="http"]; -+ } -+ -+As the tFTP server is independent of warewulf, the `shim` and `grub` EFI binaries -+for the tFTP server are copied from the host on which warewulf is running. -+This means that for secure boot the distributor e.g. SUSE of the container in -+the `default` profile must match the distributor of the container which then -+also must be signed by the SUSE key. -+ -+http boot -+--------- -+ -+Modern EFI systems have the possibility to directly boot per http. The flow diagram -+is the following: -+ -+.. graphviz:: -+ -+ digraph G{ -+ node [shape=box]; -+ efi [shape=record label="{EFI|boots from URI defined in filename}"]; -+ shim [shape=record label="{shim.efi|replaces shim.efi with grubx64.efi in URI|extracted from node container}"]; -+ grub [shape=record label="{grub.efi|checks for grub.cfg|extracted from node container}"] -+ kernel [shape=record label="{kernel|ramdisk (root fs)|wwinit overlay}|extracted from node container"]; -+ efi->shim [label="http"]; -+ shim->grub [label="http"]; -+ grub->kernel [label="http"]; -+ } -+ -+The main difference is that the initial `shim.efi` and `grub.efi` are delivered by http with warewulf -+and are taken directly from the container assigned to the node. This means that secure boot will work -+for containers from different distributors. -+ -+Install shim and efi -+-------------------- -+ -+The `shim.efi` and `grub.efi` must be installed via the package manager directly into the container. -+ -+Install on SUSE systems -+^^^^^^^^^^^^^^^^^^^^^^^ -+ -+.. code-block:: console -+ -+ # wwctl container shell leap15.5 -+ [leap15.5] Warewulf> zypper install grub2 shim -+ -+ -+Install on EL system -+^^^^^^^^^^^^^^^^^^^^ -+ -+.. code-block:: console -+ -+ # wwctl container shell rocky9 -+ [rocky9] Warewulf> dnf install shim-x64.x86_64 grub2-pc.x86_64 -\ No newline at end of file -diff --git a/userdocs/contents/security.rst b/userdocs/contents/security.rst -index 1f585de1..b0bb42a8 100644 ---- a/userdocs/contents/security.rst -+++ b/userdocs/contents/security.rst -@@ -55,7 +55,7 @@ when a user lands on a compute node, there is generally nothing - stopping them from spoofing a provision request and downloading the - provisioned raw materials for inspection. - --In Warewulf there are two ways to secure the provisioning process: -+In Warewulf there are ways multiple to secure the provisioning process: - - #. The provisioning connections and transfers are not secure due to - not being able to manage a secure root of trust through a PXE -@@ -77,6 +77,11 @@ In Warewulf there are two ways to secure the provisioning process: - provision and communicate with requests from that system matching - that asset tag. - -+#. When the nodes are booted via `shim` and `grub` Secure Boot can be -+ enabled. This means that the nodes only boot the kernel which is -+ provided by the distributor and also custom complied modules can't -+ be loaded. -+ - Summary - ======= - -diff --git a/userdocs/index.rst b/userdocs/index.rst -index 71fb9da5..9d7a4b8c 100644 ---- a/userdocs/index.rst -+++ b/userdocs/index.rst -@@ -18,6 +18,7 @@ Welcome to the Warewulf User Guide! - Warewulf Initialization - Container Management - Kernel Management -+ Boot Management - Node Configuration - Node Profiles - Warewulf Overlays -diff --git a/warewulf.spec.in b/warewulf.spec.in -index 318703fb..e71a71a4 100644 ---- a/warewulf.spec.in -+++ b/warewulf.spec.in -@@ -139,6 +139,7 @@ yq e ' - %config(noreplace) %{_sysconfdir}/warewulf/wwapi*.conf - %config(noreplace) %{_sysconfdir}/warewulf/examples - %config(noreplace) %{_sysconfdir}/warewulf/ipxe -+%config(noreplace) %{_sysconfdir}/warewulf/grub - %config(noreplace) %attr(0640,-,-) %{_sysconfdir}/warewulf/nodes.conf - %{_sysconfdir}/bash_completion.d/wwctl - diff --git a/vendor.tar.gz b/vendor.tar.gz new file mode 100644 index 0000000..d191620 --- /dev/null +++ b/vendor.tar.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:482f13f9cd5bb0193aed6380022341d7ebd35dc6fbe2152cc804b010cf405069 +size 5800023 diff --git a/warewulf4-v4.5.0rc0.tar.gz b/warewulf4-v4.5.0rc0.tar.gz index 46c52db..e6c31c3 100644 --- a/warewulf4-v4.5.0rc0.tar.gz +++ b/warewulf4-v4.5.0rc0.tar.gz @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:23b80008f251cf1abfa23bede22168d503cb7abc0e71b79703de425e4c835d7f -size 16928203 +oid sha256:3b13cd5bcb740391cbb21420ebe54acbd2e0aad627d896d9887018bc3118e7d0 +size 10978414 diff --git a/warewulf4.changes b/warewulf4.changes index 384086d..d3ca806 100644 --- a/warewulf4.changes +++ b/warewulf4.changes @@ -1,3 +1,10 @@ +------------------------------------------------------------------- +Fri Jan 19 13:45:18 UTC 2024 - Christian Goll + +- moved to git archive hash as source in order to be reproduceable +- added vendor.tar.gz as git hash source doesn't have vendoring +- removed grub-boot.patch as incoperated upstreams + ------------------------------------------------------------------- Wed Jan 17 11:16:59 UTC 2024 - Christian Goll diff --git a/warewulf4.spec b/warewulf4.spec index 0f15e1b..dec1055 100644 --- a/warewulf4.spec +++ b/warewulf4.spec @@ -19,6 +19,7 @@ %define rls_cndt rc0 %global tftpdir /srv/tftpboot %global srvdir %{_sharedstatedir} +%global githash 5b0de8ea5397ca42584335517fd4959d7ffe3da5 ExclusiveArch: x86_64 aarch64 @@ -29,12 +30,11 @@ Summary: A suite of tools for clustering License: BSD-3-Clause Group: Productivity/Clustering/Computing URL: https://warewulf.org -#Source0: https://github.com/hpcng/warewulf/archive/v%{version}%{?rls_cndt}.tar.gz#/warewulf4-v%{version}.tar.gz -Source0: https://github.com/hpcng/warewulf/releases/download/nightly/warewulf-4.5.x.tar.gz#/warewulf4-v%{version}.tar.gz +Source0: https://github.com/hpcng/warewulf/archive/%{githash}.tar.gz#/warewulf4-v%{version}.tar.gz +Source1: vendor.tar.gz Source5: warewulf4-rpmlintrc Source10: config-ww4.sh Source20: README.dnsmasq -Patch10: grub-boot.patch Patch12: clean-warewulf-conf.patch Patch15: dnsmasq-template-move.patch @@ -107,8 +107,9 @@ This package install the necessary configuration files in order to run a slurm cluster on the configured warewulf nodes. %prep -%setup -q -n warewulf-4.5.x +%setup -q -n warewulf-%{githash} %autopatch -p1 +tar xzf %{S:1} %build export OFFLINE_BUILD=1 From b13668784d3726125300081e06fa161b6d0796541dcc45995bfab7027fdadae4 Mon Sep 17 00:00:00 2001 From: Christian Goll Date: Fri, 19 Jan 2024 14:45:36 +0000 Subject: [PATCH 11/13] Accepting request 1139960 from home:mslacken:pr conflicts OBS-URL: https://build.opensuse.org/request/show/1139960 OBS-URL: https://build.opensuse.org/package/show/network:cluster/warewulf4?expand=0&rev=52 --- warewulf4.spec | 1 + 1 file changed, 1 insertion(+) diff --git a/warewulf4.spec b/warewulf4.spec index dec1055..c844c92 100644 --- a/warewulf4.spec +++ b/warewulf4.spec @@ -85,6 +85,7 @@ Includes the default overlays so that they can be updated seprately. %package api Requires: %{name} Summary: Contains the services for the warewulf rest API +Conflicts: warewulf-provision-x86_64-initramfs %description api Contains the binaries for the access of warewulf through a rest API and from From 86908176bcf043c1b64cf1b50547d3c7f3eabe6eb5463a043f772cf37834bec9 Mon Sep 17 00:00:00 2001 From: Christian Goll Date: Fri, 19 Jan 2024 16:04:14 +0000 Subject: [PATCH 12/13] Accepting request 1139989 from home:mslacken:pr better versioning OBS-URL: https://build.opensuse.org/request/show/1139989 OBS-URL: https://build.opensuse.org/package/show/network:cluster/warewulf4?expand=0&rev=53 --- warewulf4-v4.5.0rc0.tar.gz => warewulf4-v4.5.0~rc0.tar.gz | 0 warewulf4.spec | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename warewulf4-v4.5.0rc0.tar.gz => warewulf4-v4.5.0~rc0.tar.gz (100%) diff --git a/warewulf4-v4.5.0rc0.tar.gz b/warewulf4-v4.5.0~rc0.tar.gz similarity index 100% rename from warewulf4-v4.5.0rc0.tar.gz rename to warewulf4-v4.5.0~rc0.tar.gz diff --git a/warewulf4.spec b/warewulf4.spec index c844c92..1603e10 100644 --- a/warewulf4.spec +++ b/warewulf4.spec @@ -24,7 +24,7 @@ ExclusiveArch: x86_64 aarch64 Name: warewulf4 -Version: 4.5.0%{?rls_cndt} +Version: 4.5.0~%{?rls_cndt} Release: 0 Summary: A suite of tools for clustering License: BSD-3-Clause From 7a696005e9f8ac7ad650649f74073ab5b5ff9c0d977d64a32898ef98dac4c162 Mon Sep 17 00:00:00 2001 From: Christian Goll Date: Wed, 24 Jan 2024 14:32:41 +0000 Subject: [PATCH 13/13] Accepting request 1141266 from home:mslacken:pr - make only overlay dir beeing config files - moved to git archive hash as source in order to be reproducible - removed grub-boot.patch as incorporated upstreams OBS-URL: https://build.opensuse.org/request/show/1141266 OBS-URL: https://build.opensuse.org/package/show/network:cluster/warewulf4?expand=0&rev=54 --- warewulf4.changes | 9 +++++++-- warewulf4.spec | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/warewulf4.changes b/warewulf4.changes index d3ca806..6558839 100644 --- a/warewulf4.changes +++ b/warewulf4.changes @@ -1,9 +1,14 @@ +------------------------------------------------------------------- +Wed Jan 24 14:31:17 UTC 2024 - Christian Goll + +- make only overlay dir beeing config files + ------------------------------------------------------------------- Fri Jan 19 13:45:18 UTC 2024 - Christian Goll -- moved to git archive hash as source in order to be reproduceable +- moved to git archive hash as source in order to be reproducible - added vendor.tar.gz as git hash source doesn't have vendoring -- removed grub-boot.patch as incoperated upstreams +- removed grub-boot.patch as incorporated upstreams ------------------------------------------------------------------- Wed Jan 17 11:16:59 UTC 2024 - Christian Goll diff --git a/warewulf4.spec b/warewulf4.spec index 1603e10..4062b7e 100644 --- a/warewulf4.spec +++ b/warewulf4.spec @@ -256,7 +256,7 @@ EOF # admin will read the changelog %{_localstatedir}/lib/warewulf/overlays %dir %{_localstatedir}/lib/warewulf -%config(noreplace) %{_localstatedir}/lib/warewulf +%config(noreplace) %{_localstatedir}/lib/warewulf/overlays %exclude %{_localstatedir}/lib/warewulf/overlays/host/etc/slurm %exclude %{_localstatedir}/lib/warewulf/overlays/generic/etc/slurm %exclude %{_localstatedir}/lib/warewulf/overlays/generic/etc/munge