use grub/shim from host system

Also the boot method is now configured globaly as this is
the only way to make sure that unkonwn nodes get the right
stuff

Signed-off-by: Christian Goll <cgoll@suse.com>
This commit is contained in:
Christian Goll
2023-10-12 14:57:23 +02:00
committed by Jonathon Anderson
parent 938eff2cfa
commit 4379c4c18d
18 changed files with 135 additions and 125 deletions

View File

@@ -127,8 +127,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Update iPXE building script
- Send Info, Recv, Send, and Out messages to stdout; and others to stderr
- default is now protected and can't be deleted
- 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.
## [4.4.0] 2023-01-18
### Added

View File

@@ -94,6 +94,7 @@ 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
@@ -113,7 +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
for f in etc/grub/*; do install -m 0644 $$f $(DESTDIR)$(WWCONFIGDIR)/grub/; 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)/{} \;)

28
etc/grub/chainload.ww Normal file
View File

@@ -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
}

View File

@@ -1,25 +1,51 @@
echo "================================================================================"
echo "Warewulf v4 now booting with grub"
echo "Warewulf v4 now http booting grub: {{.Fqdn}} ({{.Hwaddr}})"
echo "================================================================================"
echo
uri="(http,{{.Ipaddr}}:9873)/provision/${net_default_mac}?assetkey="
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"
echo "Warewulf Controller: {{.Ipaddr}}"
echo "Trying to load a kernel... "
linux $kernel wwid="${net_default_mac}" "{{.KernelArgs}}"
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 initrd..."
initrd $container $system
echo "Booting..."
echo "Loading Container: {{.ContainerName}}"
initrd $container $system $runtime
boot
else
echo "MESSAGE: This node is unconfigured. Please have your system administrator add a"
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
}

2
go.mod
View File

@@ -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

View File

@@ -2,15 +2,11 @@ package delete
import (
"fmt"
"os"
"github.com/hpcng/warewulf/internal/pkg/api/container"
"github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1"
apiutil "github.com/hpcng/warewulf/internal/pkg/api/util"
"github.com/hpcng/warewulf/internal/pkg/util"
"github.com/hpcng/warewulf/internal/pkg/node"
"github.com/hpcng/warewulf/internal/pkg/wwlog"
"github.com/spf13/cobra"
)
@@ -18,21 +14,7 @@ func CobraRunE(cmd *cobra.Command, args []string) (err error) {
cdp := &wwapiv1.ContainerDeleteParameter{
ContainerNames: args,
}
nodeDB, err := node.New()
if err != nil {
wwlog.Error("Failed to open node database: %s", err)
os.Exit(1)
}
profiles, err := nodeDB.MapAllProfiles()
if err != nil {
wwlog.Error("Could not load all profiles: %s", err)
os.Exit(1)
}
if util.InSlice(args, profiles["default"].ContainerName.Get()) {
return fmt.Errorf("can't delete container which is in the default profile")
}
if !SetYes {
yes := apiutil.ConfirmationPrompt(fmt.Sprintf("Are you sure you want to delete container %s", args))
if !yes {

View File

@@ -12,9 +12,7 @@ import (
"time"
"github.com/hpcng/warewulf/internal/pkg/container"
"github.com/hpcng/warewulf/internal/pkg/node"
"github.com/hpcng/warewulf/internal/pkg/util"
"github.com/hpcng/warewulf/internal/pkg/warewulfd"
"github.com/hpcng/warewulf/internal/pkg/wwlog"
"github.com/spf13/cobra"
)
@@ -135,16 +133,6 @@ func CobraRunE(cmd *cobra.Command, args []string) error {
wwlog.Error("Could not build container %s: %s", containerName, err)
os.Exit(1)
}
nodeDB, err := node.New()
if err != nil {
wwlog.Warn("couldn't open node database, so can't update shim/grub if this container is used in default profile: %s", err)
} else {
profileMap, _ := nodeDB.MapAllProfiles()
if _, ok := profileMap["default"]; ok && profileMap["default"].ContainerName.Get() == containerName {
return warewulfd.CopyShimGrub()
}
}
return nil
}
func SetBinds(myBinds []string) {

View File

@@ -271,10 +271,6 @@ func ContainerImport(cip *wwapiv1.ContainerImportParameter) (containerName strin
err = errors.Wrap(err, "failed to update profile")
return
}
err = warewulfd.CopyShimGrub()
if err != nil {
wwlog.Warn("couldn't copy shim/grub of default container: %s", err)
}
}
}
// TODO: We need this in a function with a flock around it.

View File

@@ -8,7 +8,6 @@ import (
"github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1"
"github.com/hpcng/warewulf/internal/pkg/node"
"github.com/hpcng/warewulf/internal/pkg/util"
"github.com/hpcng/warewulf/internal/pkg/warewulfd"
"github.com/hpcng/warewulf/internal/pkg/wwlog"
"github.com/pkg/errors"
"gopkg.in/yaml.v2"
@@ -33,12 +32,6 @@ func ProfileSet(set *wwapiv1.ProfileSetParameter) (err error) {
return
}
dbError := apinode.DbSave(&nodeDB)
if util.InSlice(set.ProfileNames, "default") {
err = warewulfd.CopyShimGrub()
if err != nil {
wwlog.Warn("shim/grub couldn't be copied: %s", err)
}
}
return dbError
}

View File

@@ -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"`
}

View File

@@ -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,7 +20,13 @@ func TFTP() error {
return err
}
fmt.Printf("Writing PXE files to: %s\n", tftpdir)
if controller.Warewulf.GrubBoot {
err := warewulfd.CopyShimGrub()
if err != nil {
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) {
@@ -35,13 +41,13 @@ func TFTP() error {
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)

View File

@@ -35,11 +35,15 @@ func GrubFind(container string) string {
if container_path == "" {
return ""
}
return GrubFindPath(container_path)
}
func GrubFindPath(grub_path string) string {
for _, grubdir := range grubDirs() {
wwlog.Debug("Checking grub directory: %s", grubdir)
for _, grubname := range grubNames() {
wwlog.Debug("Checking for grub name: %s", grubname)
grubPaths, _ := filepath.Glob(path.Join(container_path, grubdir, grubname))
grubPaths, _ := filepath.Glob(path.Join(grub_path, grubdir, grubname))
for _, grubpath := range grubPaths {
wwlog.Debug("Checking for grub path: %s", grubpath)
// Only succeeds if grubpath exists and, if a

View File

@@ -25,7 +25,7 @@ func shimNames() []string {
}
/*
find the path of the shim binary
find the path of the shim binary in container
*/
func ShimFind(container string) string {
container_path := RootFsDir(container)
@@ -33,11 +33,18 @@ func ShimFind(container string) string {
if container_path == "" {
return ""
}
return ShimFindPath(container_path)
}
/*
find the path of the shim binary in container
*/
func ShimFindPath(shimpath string) string {
for _, shimdir := range shimDirs() {
wwlog.Debug("Checking shim directory: %s", shimdir)
for _, shimname := range shimNames() {
wwlog.Debug("Checking for shim name: %s", shimname)
shimPaths, _ := filepath.Glob(path.Join(container_path, shimdir, shimname))
shimPaths, _ := filepath.Glob(path.Join(shimpath, shimdir, shimname))
for _, shimPath := range shimPaths {
wwlog.Debug("Checking for shim path: %s", shimPath)
// Only succeeds if shimPath exists and, if a

View File

@@ -52,7 +52,6 @@ type NodeConf struct {
PrimaryNetDev string `yaml:"primary network,omitempty" lopt:"primarynet" sopt:"p" comment:"Set the primary network interface"`
Disks map[string]*Disk `yaml:"disks,omitempty"`
FileSystems map[string]*FileSystem `yaml:"filesystems,omitempty"`
BootMethod string `yaml:"boot method,omitempty" lopt:"bootmethod" comment:" boot method, can be grub or ipxe"`
}
type IpmiConf struct {
@@ -166,7 +165,6 @@ type NodeInfo struct {
Ipmi *IpmiEntry
Profiles Entry
PrimaryNetDev Entry
BootMethod Entry
NetDevs map[string]*NetDevEntry
Tags map[string]*Entry
Disks map[string]*DiskEntry

View File

@@ -9,49 +9,29 @@ import (
"github.com/hpcng/warewulf/internal/pkg/wwlog"
"github.com/hpcng/warewulf/internal/pkg/container"
"github.com/hpcng/warewulf/internal/pkg/node"
"github.com/hpcng/warewulf/internal/pkg/util"
)
/*
Copies the default shim, which is the shim located in the default container
Copies the default shim, which is the shim located on host
to the tftp directory
*/
func CopyShimGrub() (err error) {
wwlog.Debug("copy shim and grub binaries")
nodeDB, err := node.New()
if err != nil {
return err
}
conf := warewulfconf.Get()
if err != nil {
return err
}
profiles, err := nodeDB.MapAllProfiles()
if err != nil {
return err
}
if _, ok := profiles["default"]; !ok {
return fmt.Errorf("default profile doesn't exist")
}
// *Entry.Get doesn't work the same as it works for nodes!
if profiles["default"].BootMethod.Get() == "ipxe" || profiles["default"].BootMethod.Get() == "" {
wwlog.Verbose("default profile uses ipxe boot")
return
}
shimPath := container.ShimFind(profiles["default"].ContainerName.Get())
wwlog.Debug("copy shim and grub binaries from host")
shimPath := container.ShimFindPath("/")
if shimPath == "" {
return fmt.Errorf("no shim found in the container: %s", profiles["default"].ContainerName.Get())
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(profiles["default"].ContainerName.Get())
grubPath := container.GrubFindPath("/")
if grubPath == "" {
return fmt.Errorf("no grub found in the container: %s", profiles["default"].ContainerName.Get())
return fmt.Errorf("no grub found on host os")
}
err = util.CopyFile(grubPath, path.Join(conf.Paths.Tftpdir, "warewulf", "grub.efi"))
if err != nil {

View File

@@ -65,7 +65,6 @@ func RunServer() error {
wwlog.Error("Could not prepopulate node status DB: %s", err)
}
err = CopyShimGrub()
if err != nil {
wwlog.Warn("couldn't copy default shim: %s", err)
}

View File

@@ -24,12 +24,12 @@ option PXE.mtftp-delay code 5 = unsigned integer 8;
option architecture-type code 93 = unsigned integer 16;
{{- if eq $.ProfileMap.default.BootMethod.Get "grub" }}
{{- if $.Warewulf.GrubBoot }}
if substring (option vendor-class-identifier, 0, 9) = "PXEClient" {
next-server {{ $.Ipaddr }};
filename "warewulf/shim.efi";
} elsif substring (option vendor-class-identifier, 0, 10) = "HTTPClient" {
filename "http://{{$.Ipaddr}}:{{$.Warewulf.Port}}/grub/shim.efi";
filename "http://{{$.Ipaddr}}:{{$.Warewulf.Port}}/efiboot/shim.efi";
}
{{- else }}
if exists user-class and option user-class = "iPXE" {

View File

@@ -7,13 +7,9 @@ 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 bootm the field `boot method` has
to be set to `grub` at least for the `default` profile in `nodes.conf`.
This can also be done with the command
.. code-block:: console
# wwctl profile set default --bootmethod grub
In order to enable the grub boot method it has to ne 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
@@ -43,8 +39,8 @@ of one Distribution, which means that every Distribution needs a separate
the containers.
For the case when the node is unknown to warewulf or
can't be identified during the `tFTP`` boot phase, the binaries are
extracted from the container defined in the `default` profile.
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
-------------
@@ -68,8 +64,8 @@ The standard network boot process with `grub` and `iPXE` has following steps
bios->shim [lhead=cluster1,label="filename=shim.efi"];
subgraph cluster1{
label="Grub boot"
shim[shape=record label="{shim.efi|extracted from container of default profile}"];
grub[shape=record label="{grubx64.efi | name hardcoded in shim.efi|extracted from container of default profile}"]
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"];
@@ -80,7 +76,7 @@ The standard network boot process with `grub` and `iPXE` has following steps
}
As the tFTP server is independent of warewulf, the `shim` and `grub` EFI binaries
for the tFTP server are copied from the container defined in the `default` profile.
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.