diff --git a/internal/agent/install.go b/internal/agent/install.go index b8aab03..4df54f5 100644 --- a/internal/agent/install.go +++ b/internal/agent/install.go @@ -29,9 +29,7 @@ import ( "github.com/kairos-io/kairos-sdk/utils" qr "github.com/mudler/go-nodepair/qrcode" "github.com/mudler/go-pluggable" - yip "github.com/mudler/yip/pkg/schema" "github.com/pterm/pterm" - "github.com/twpayne/go-vfs/v4" ) func displayInfo(agentConfig *Config) { @@ -217,27 +215,9 @@ func RunInstall(c *config.Config) error { utils.SetEnv(c.Env) utils.SetEnv(c.Install.Env) - // If nousers is enabled we do not check for the validity of the users and such - // At this point, the config should be fully parsed and the yip stages ready - if !c.Install.NoUsers { - found := false - cc, _ := c.Config.String() - yamlConfig, err := yip.Load(cc, vfs.OSFS, nil, nil) - if err != nil { - return err - } - for _, stage := range yamlConfig.Stages { - for _, x := range stage { - if len(x.Users) > 0 { - found = true - break - } - } - - } - if !found { - return fmt.Errorf("No users found in any stage\nWe require at least one user or the install option 'install.nousers: true' to be set in the config in order to allow installing a system with no users.") - } + err := c.CheckForUsers() + if err != nil { + return err } // UKI path. Check if we are on UKI AND if we are running off a cd, otherwise it makes no sense to run the install diff --git a/internal/agent/reset.go b/internal/agent/reset.go index 9ac43f4..70cf677 100644 --- a/internal/agent/reset.go +++ b/internal/agent/reset.go @@ -38,6 +38,10 @@ func reset(reboot, unattended, resetOem bool, dir ...string) error { if err != nil { return err } + err = cfg.CheckForUsers() + if err != nil { + return err + } // Load the installation Config from the cloud-config data resetSpec, err := config.ReadResetSpecFromConfig(cfg) if err != nil { @@ -65,6 +69,10 @@ func resetUki(reboot, unattended, resetOem bool, dir ...string) error { if err != nil { return err } + err = cfg.CheckForUsers() + if err != nil { + return err + } // Load the installation Config from the cloud-config data resetSpec, err := config.ReadUkiResetSpecFromConfig(cfg) if err != nil { diff --git a/internal/agent/upgrade.go b/internal/agent/upgrade.go index 1633996..e698070 100644 --- a/internal/agent/upgrade.go +++ b/internal/agent/upgrade.go @@ -11,7 +11,6 @@ import ( "github.com/kairos-io/kairos-agent/v2/internal/bus" "github.com/kairos-io/kairos-agent/v2/pkg/action" config "github.com/kairos-io/kairos-agent/v2/pkg/config" - v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1" "github.com/kairos-io/kairos-agent/v2/pkg/uki" internalutils "github.com/kairos-io/kairos-agent/v2/pkg/utils" events "github.com/kairos-io/kairos-sdk/bus" @@ -64,6 +63,7 @@ func ListNewerReleases(includePrereleases bool) ([]string, error) { return tagList.FullImages() } +// TODO: Check where force and preReleases is being used? They dont seem to be used anywhere? func Upgrade( source string, force, strictValidations bool, dirs []string, upgradeEntry string, preReleases bool) error { bus.Manager.Initialize() @@ -71,16 +71,27 @@ func Upgrade( if internalutils.UkiBootMode() == internalutils.UkiHDD { return upgradeUki(source, dirs, upgradeEntry, strictValidations) } else { - return upgrade(source, force, strictValidations, dirs, upgradeEntry, preReleases) + return upgrade(source, dirs, upgradeEntry, strictValidations) } } -func upgrade(source string, force, strictValidations bool, dirs []string, upgradeEntry string, preReleases bool) error { - upgradeSpec, c, err := generateUpgradeSpec(source, force, strictValidations, dirs, upgradeEntry, preReleases) +func upgrade(sourceImageURL string, dirs []string, upgradeEntry string, strictValidations bool) error { + c, err := getConfig(sourceImageURL, dirs, upgradeEntry, strictValidations) + if err != nil { + return err + } + utils.SetEnv(c.Env) + + err = c.CheckForUsers() if err != nil { return err } + // Load the upgrade Config from the system + upgradeSpec, err := config.ReadUpgradeSpecFromConfig(c) + if err != nil { + return err + } err = upgradeSpec.Sanitize() if err != nil { return err @@ -96,6 +107,55 @@ func upgrade(source string, force, strictValidations bool, dirs []string, upgrad return hook.Run(*c, upgradeSpec, hook.AfterUpgrade...) } +func upgradeUki(sourceImageURL string, dirs []string, upgradeEntry string, strictValidations bool) error { + c, err := getConfig(sourceImageURL, dirs, upgradeEntry, strictValidations) + if err != nil { + return err + } + utils.SetEnv(c.Env) + + err = c.CheckForUsers() + if err != nil { + return err + } + + // Load the upgrade Config from the system + upgradeSpec, err := config.ReadUkiUpgradeSpecFromConfig(c) + if err != nil { + return err + } + + err = upgradeSpec.Sanitize() + if err != nil { + return err + } + + upgradeAction := uki.NewUpgradeAction(c, upgradeSpec) + + err = upgradeAction.Run() + if err != nil { + return err + } + + return hook.Run(*c, upgradeSpec, hook.AfterUpgrade...) +} + +func getConfig(sourceImageURL string, dirs []string, upgradeEntry string, strictValidations bool) (*config.Config, error) { + cliConf, err := generateUpgradeConfForCLIArgs(sourceImageURL, upgradeEntry) + if err != nil { + return nil, err + } + + c, err := config.Scan(collector.Directories(dirs...), + collector.Readers(strings.NewReader(cliConf)), + collector.StrictValidation(strictValidations)) + if err != nil { + return nil, err + } + return c, err + +} + func allReleases() (versioneer.TagList, error) { artifact, err := versioneer.NewArtifactFromOSRelease() if err != nil { @@ -155,30 +215,6 @@ func generateUpgradeConfForCLIArgs(source, upgradeEntry string) (string, error) return string(d), err } -func generateUpgradeSpec(sourceImageURL string, force, strictValidations bool, dirs []string, upgradeEntry string, preReleases bool) (*v1.UpgradeSpec, *config.Config, error) { - cliConf, err := generateUpgradeConfForCLIArgs(sourceImageURL, upgradeEntry) - if err != nil { - return nil, nil, err - } - - c, err := config.Scan(collector.Directories(dirs...), - collector.Readers(strings.NewReader(cliConf)), - collector.StrictValidation(strictValidations)) - if err != nil { - return nil, nil, err - } - - utils.SetEnv(c.Env) - - // Load the upgrade Config from the system - upgradeSpec, err := config.ReadUpgradeSpecFromConfig(c) - if err != nil { - return nil, nil, err - } - - return upgradeSpec, c, nil -} - func getReleasesFromProvider(includePrereleases bool) ([]string, error) { var result []string bus.Manager.Response(events.EventAvailableReleases, func(p *pluggable.Plugin, r *pluggable.EventResponse) { @@ -199,42 +235,6 @@ func getReleasesFromProvider(includePrereleases bool) ([]string, error) { return result, nil } -func upgradeUki(source string, dirs []string, upgradeEntry string, strictValidations bool) error { - cliConf, err := generateUpgradeConfForCLIArgs(source, upgradeEntry) - if err != nil { - return err - } - - c, err := config.Scan(collector.Directories(dirs...), - collector.Readers(strings.NewReader(cliConf)), - collector.StrictValidation(strictValidations)) - if err != nil { - return err - } - - utils.SetEnv(c.Env) - - // Load the upgrade Config from the system - upgradeSpec, err := config.ReadUkiUpgradeSpecFromConfig(c) - if err != nil { - return err - } - - err = upgradeSpec.Sanitize() - if err != nil { - return err - } - - upgradeAction := uki.NewUpgradeAction(c, upgradeSpec) - - err = upgradeAction.Run() - if err != nil { - return err - } - - return hook.Run(*c, upgradeSpec, hook.AfterUpgrade...) -} - // ExtraConfigUpgrade is the struct that holds the upgrade options that come from flags and events type ExtraConfigUpgrade struct { Upgrade struct { diff --git a/pkg/config/config.go b/pkg/config/config.go index 8e60d55..12603fb 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -189,6 +189,54 @@ func (c Config) LoadInstallState() (*v1.InstallState, error) { return installState, nil } +func contains(s []string, str string) bool { + for _, v := range s { + if v == str { + return true + } + } + return false +} + +// CheckForUsers will check the config for any users and validate that at least we have 1 admin. +// Since Kairos 3.3.x we don't ship a default user with the system, so before a system with no specific users +// was relying in our default cloud-configs which created a kairos user ALWAYS (with SUDO!) +// But now we don't ship it anymore. So a user upgrading from 3.2.x to 3.3.x that created no users, will end up with a blocked +// system. +// So we need to see if they are setting a user in their config and if not refuse to continue +func (c Config) CheckForUsers() (err error) { + // If nousers is enabled we do not check for the validity of the users and such + // At this point, the config should be fully parsed and the yip stages ready + if !c.Install.NoUsers { + anyAdmin := false + cc, _ := c.Config.String() + yamlConfig, err := yip.Load(cc, vfs.OSFS, nil, nil) + if err != nil { + return err + } + for _, stage := range yamlConfig.Stages { + for _, x := range stage { + if len(x.Users) > 0 { + for _, user := range x.Users { + if contains(user.Groups, "admin") || user.PrimaryGroup == "admin" { + anyAdmin = true + break + } + } + } + } + + } + if !anyAdmin { + return fmt.Errorf("No users found in any stage that are part of the 'admin' group.\n" + + "In Kairos 3.3.x we no longer ship a default hardcoded user with the system configs and require users to provide their own user." + + "Please provide at least 1 user that is part of the 'admin' group(for sudo) with your cloud configs." + + "If you still want to continue without creating any users in the system, set 'install.nousers: true' to be in the config in order to allow a system with no users.") + } + } + return err +} + // Sanitize checks the consistency of the struct, returns error // if unsolvable inconsistencies are found func (c *Config) Sanitize() error { diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index cb9d9db..eca43f1 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -22,6 +22,7 @@ import ( "reflect" "strings" + pkgConfig "github.com/kairos-io/kairos-agent/v2/pkg/config" "github.com/kairos-io/kairos-agent/v2/pkg/constants" v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1" fsutils "github.com/kairos-io/kairos-agent/v2/pkg/utils/fs" @@ -265,4 +266,40 @@ var _ = Describe("Schema", func() { Expect(err).Should(HaveOccurred()) }) }) + + Describe("Validate users in config", func() { + It("Validates a existing user in the system", func() { + cc := `#cloud-config +stages: + initramfs: + - name: "Set user and password" + users: + kairos: + passwd: "kairos" + groups: + - "admin" +` + config, err := pkgConfig.Scan(collector.Readers(strings.NewReader(cc)), collector.NoLogs) + Expect(err).ToNot(HaveOccurred()) + Expect(config.CheckForUsers()).ToNot(HaveOccurred()) + }) + It("Fails if there is no user", func() { + config, err := pkgConfig.Scan() + Expect(err).ToNot(HaveOccurred()) + Expect(config.CheckForUsers()).To(HaveOccurred()) + }) + It("Fails if there is user but its not admin", func() { + cc := `#cloud-config +stages: + initramfs: + - name: "Set user and password" + users: + kairos: + passwd: "kairos" +` + config, err := pkgConfig.Scan(collector.Readers(strings.NewReader(cc)), collector.NoLogs) + Expect(err).ToNot(HaveOccurred()) + Expect(config.CheckForUsers()).To(HaveOccurred()) + }) + }) })