Skip to content

Commit

Permalink
Check for user+admin validity before actions (#575)
Browse files Browse the repository at this point in the history
  • Loading branch information
Itxaka authored Oct 15, 2024
1 parent a3aadbb commit 97d25b8
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 87 deletions.
26 changes: 3 additions & 23 deletions internal/agent/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions internal/agent/reset.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
128 changes: 64 additions & 64 deletions internal/agent/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -64,23 +63,35 @@ 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()

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
Expand All @@ -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 {
Expand Down Expand Up @@ -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) {
Expand All @@ -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 {
Expand Down
48 changes: 48 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
37 changes: 37 additions & 0 deletions pkg/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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())
})
})
})

0 comments on commit 97d25b8

Please sign in to comment.