From 20353f84f833361eb2f840623cbeb41cedb31a30b6f291c257822a3a6c060648 Mon Sep 17 00:00:00 2001 From: Christian Goll Date: Fri, 19 Jan 2024 13:55:23 +0000 Subject: [PATCH] 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