Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions pkg/osbuild/groups_stage.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ func NewGroupsStage(options *GroupsStageOptions) *Stage {
}

func NewGroupsStageOptions(groups []users.Group) *GroupsStageOptions {
if len(groups) == 0 {
return nil
}

options := GroupsStageOptions{
Groups: map[string]GroupsStageOptionsGroup{},
}
Expand Down
78 changes: 78 additions & 0 deletions pkg/osbuild/kickstart_stage.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,78 @@ func NewKickstartStage(options *KickstartStageOptions) *Stage {
}
}

// adjustRootUserOptions handles any options relating to the root user for the
// kickstart stage. It returns the [RootPasswordOptions] if necessary and
// modifies the userCustomizations array accordingly. It also validates that no
// unsupported options are set.
//
// User options for the root user have no effect in kickstart. In other
// words, the
//
// user --name "root" ...
//
// line is ignored, because when the users are being processed, the root
// user already exists. To set a root password, the rootpw command must be
// used. If an SSH key is added for the root user however, we need to keep
// the root user account in the kickstart options, because osbuild will use
// it to add the sshkey line for the root user. Unfortunately, this means
// that we will get a bare user line for root that will have no effect.
// See also https://github.com/osbuild/osbuild/issues/2178
func adjustRootUserOptions(userOptions map[string]UsersStageOptionsUser) (*RootPasswordOptions, error) {
var rootpw *RootPasswordOptions
for name, user := range userOptions {
if name == "root" {
if user.Password != nil {
rootpw = &RootPasswordOptions{
IsCrypted: true, // NewUserStageOptions() always encrypts plaintext passwords
Password: *user.Password,
}

// remove the password since the --password option for the user
// kickstart command has no effect on root
user.Password = nil
}

// return an error if any other field is set (except SSH)
unsupportedOptionsSet := make([]string, 0, 7)
if user.ExpireDate != nil {
unsupportedOptionsSet = append(unsupportedOptionsSet, "expiredate")
}
if user.ForcePasswordReset != nil {
unsupportedOptionsSet = append(unsupportedOptionsSet, "force_password_reset")
}
if user.GID != nil {
unsupportedOptionsSet = append(unsupportedOptionsSet, "gid")
}
if user.Groups != nil {
unsupportedOptionsSet = append(unsupportedOptionsSet, "groups")
}
if user.Home != nil {
unsupportedOptionsSet = append(unsupportedOptionsSet, "home")
}
if user.Shell != nil {
unsupportedOptionsSet = append(unsupportedOptionsSet, "shell")
}
if user.UID != nil {
unsupportedOptionsSet = append(unsupportedOptionsSet, "uid")
}
if len(unsupportedOptionsSet) > 0 {
return nil, fmt.Errorf("unsupported options for user \"root\": %s", strings.Join(unsupportedOptionsSet, ", "))
}

// if the ssh key is set, update the options in the map (unset
// password), otherwise remove it entirely
if user.Key != nil {
userOptions[name] = user
} else {
delete(userOptions, name)
}
return rootpw, nil
}
}
return nil, nil
}

func NewKickstartStageOptions(
path string,
userCustomizations []users.User,
Expand All @@ -154,12 +226,18 @@ func NewKickstartStageOptions(
groups = groupsOptions.Groups
}

rootpw, err := adjustRootUserOptions(users)
if err != nil {
return nil, fmt.Errorf("org.osbuild.kickstart: %w", err)
}

return &KickstartStageOptions{
Path: path,
OSTreeCommit: nil,
LiveIMG: nil,
Users: users,
Groups: groups,
RootPassword: rootpw,
}, nil
}

Expand Down
175 changes: 175 additions & 0 deletions pkg/osbuild/kickstart_stage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/osbuild/images/internal/common"
"github.com/osbuild/images/pkg/customizations/users"
"github.com/osbuild/images/pkg/osbuild"
)

Expand All @@ -31,3 +33,176 @@ func TestKickstartStageJsonHappy(t *testing.T) {
}
}`)
}

func TestKickstartStageUsers(t *testing.T) {
type testCase struct {
users []users.User
expected *osbuild.KickstartStageOptions
expErr string
}

testCases := map[string]testCase{
"empty": {
users: nil,
expected: &osbuild.KickstartStageOptions{},
expErr: "",
},
"1-user": {
users: []users.User{
{
Name: "user",
Description: common.ToPtr("I am user"),
Password: common.ToPtr("$6$fakesalt$fakehashedpassword"),
Key: common.ToPtr("ssh-ed25519 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"),
Home: common.ToPtr("/var/home/user"),
Shell: common.ToPtr("/usr/bin/fish"),
Groups: []string{"grp1", "wheel"},
UID: common.ToPtr(1010),
GID: common.ToPtr(1020),
ExpireDate: common.ToPtr(1756486205),
ForcePasswordReset: common.ToPtr(false),
},
},
expected: &osbuild.KickstartStageOptions{
Users: map[string]osbuild.UsersStageOptionsUser{
"user": {
UID: common.ToPtr(1010),
GID: common.ToPtr(1020),
Groups: []string{"grp1", "wheel"},
Description: common.ToPtr("I am user"),
Home: common.ToPtr("/var/home/user"),
Shell: common.ToPtr("/usr/bin/fish"),
Password: common.ToPtr("$6$fakesalt$fakehashedpassword"),
Key: common.ToPtr("ssh-ed25519 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"),
ExpireDate: common.ToPtr(1756486205),
ForcePasswordReset: common.ToPtr(false),
},
},
},
expErr: "",
},
"2-user+root": {
users: []users.User{
{
Name: "user",
Description: common.ToPtr("I am user"),
Password: common.ToPtr("$6$fakesalt$fakehashedpassword"),
Key: common.ToPtr("ssh-ed25519 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"),
Home: common.ToPtr("/var/home/user"),
Shell: common.ToPtr("/usr/bin/fish"),
Groups: []string{"grp1", "wheel"},
UID: common.ToPtr(1010),
GID: common.ToPtr(1020),
ExpireDate: common.ToPtr(1756486205),
ForcePasswordReset: common.ToPtr(false),
},
{
Name: "root",
Password: common.ToPtr("$6$fakesaltroot$fakehashedpasswordroot"),
Key: common.ToPtr("ssh-ed25519 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"),
},
},
expected: &osbuild.KickstartStageOptions{
Users: map[string]osbuild.UsersStageOptionsUser{
"user": {
Description: common.ToPtr("I am user"),
Password: common.ToPtr("$6$fakesalt$fakehashedpassword"),
Key: common.ToPtr("ssh-ed25519 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"),
Home: common.ToPtr("/var/home/user"),
Shell: common.ToPtr("/usr/bin/fish"),
Groups: []string{"grp1", "wheel"},
UID: common.ToPtr(1010),
GID: common.ToPtr(1020),
ExpireDate: common.ToPtr(1756486205),
ForcePasswordReset: common.ToPtr(false),
},
"root": {
Key: common.ToPtr("ssh-ed25519 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"),
},
},
RootPassword: &osbuild.RootPasswordOptions{
Lock: false,
PlainText: false,
IsCrypted: true,
AllowSSH: false,
Password: "$6$fakesaltroot$fakehashedpasswordroot",
},
},
},
"2-user+root-error": {
users: []users.User{
{
Name: "user",
Description: common.ToPtr("I am user"),
Password: common.ToPtr("$6$fakesalt$fakehashedpassword"),
Key: common.ToPtr("ssh-ed25519 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"),
Home: common.ToPtr("/var/home/user"),
Shell: common.ToPtr("/usr/bin/fish"),
Groups: []string{"grp1", "wheel"},
UID: common.ToPtr(1010),
GID: common.ToPtr(1020),
ExpireDate: common.ToPtr(1756486205),
ForcePasswordReset: common.ToPtr(false),
},
{
Name: "root",
Description: common.ToPtr("super!"),
Password: common.ToPtr("$6$fakesaltroot$fakehashedpasswordroot"),
Key: common.ToPtr("ssh-ed25519 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"),
Home: common.ToPtr("/rooot"),
Shell: common.ToPtr("/usr/bin/zsh"),
Groups: []string{"wheel?"},
UID: common.ToPtr(10),
GID: common.ToPtr(20),
ExpireDate: common.ToPtr(1756486205),
ForcePasswordReset: common.ToPtr(false),
},
},
expected: &osbuild.KickstartStageOptions{
Users: map[string]osbuild.UsersStageOptionsUser{
"user": {
Description: common.ToPtr("I am user"),
Password: common.ToPtr("$6$fakesalt$fakehashedpassword"),
Key: common.ToPtr("ssh-ed25519 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"),
Home: common.ToPtr("/var/home/user"),
Shell: common.ToPtr("/usr/bin/fish"),
Groups: []string{"grp1", "wheel"},
UID: common.ToPtr(1010),
GID: common.ToPtr(1020),
ExpireDate: common.ToPtr(1756486205),
ForcePasswordReset: common.ToPtr(false),
},
"root": {
Description: common.ToPtr("super!"),
Password: common.ToPtr("$6$fakesaltroot$fakehashedpasswordroot"),
Key: common.ToPtr("ssh-ed25519 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"),
Home: common.ToPtr("/rooot"),
Shell: common.ToPtr("/usr/bin/zsh"),
Groups: []string{"wheel?"},
UID: common.ToPtr(10),
GID: common.ToPtr(20),
ExpireDate: common.ToPtr(1756486205),
ForcePasswordReset: common.ToPtr(false),
},
},
},
expErr: "org.osbuild.kickstart: unsupported options for user \"root\": expiredate, force_password_reset, gid, groups, home, shell, uid",
},
}

for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)

ksOpts, err := osbuild.NewKickstartStageOptions("", tc.users, nil)
if tc.expErr != "" {
assert.EqualError(err, tc.expErr)
return
}

assert.NoError(err)
assert.Equal(tc.expected, ksOpts)
})
}

}
5 changes: 5 additions & 0 deletions test/configs/bigiso.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@
"customizations": {
"hostname": "custombase",
"user": [
{
"name": "root",
"description": "Mr. Root",
"password": "$6$BhyxFBgrEFh0VrPJ$MllG8auiU26x2pmzL4.1maHzPHrA.4gTdCvlATFp8HJU9UPee4zCS9BVl2HOzKaUYD/zEm8r/OF05F2icWB0K/"
},
{
"name": "admin",
"description": "admin",
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
10b7a78a33cc91cdfd161418e23767f4dd4cd7b7
ffc964d978a7b38963d21407cf823bf60da77cdb
Loading