From 4056c5a69846c889662362ef8b22d559d3729e2e Mon Sep 17 00:00:00 2001 From: Djebran Lezzoum Date: Thu, 1 Feb 2024 10:37:29 +0100 Subject: [PATCH] create ostree user sudoers file In the context of compatibility with edge-management when creating image ISO artifact. When creating an ostree user, the user is created with username and ssh-key without a password, the user has no possibility to manage the system as has no password to enter when using sudo command. Create a sudoer file at first boot stage. FIXES: https://issues.redhat.com/browse/THEEDGE-3837 --- pkg/manifest/os.go | 7 ++++ pkg/manifest/os_test.go | 72 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/pkg/manifest/os.go b/pkg/manifest/os.go index ee441533de..56035e8318 100644 --- a/pkg/manifest/os.go +++ b/pkg/manifest/os.go @@ -806,6 +806,13 @@ func usersFirstBootOptions(users []users.User) *osbuild.FirstBootStageOptions { cmds = append(cmds, fmt.Sprintf("mkdir -p %s", sshdir)) cmds = append(cmds, fmt.Sprintf("sh -c 'echo %q >> %q'", *user.Key, filepath.Join(sshdir, "authorized_keys"))) cmds = append(cmds, fmt.Sprintf("chown %s:%s -Rc %s", user.Name, user.Name, sshdir)) + if user.Name != "root" { + sudoersPath := filepath.Join("/etc", "sudoers.d") + sudoersUserFilePath := filepath.Join(sudoersPath, user.Name) + cmds = append(cmds, fmt.Sprintf(`sh -c 'echo -e "%s\tALL=(ALL)\tNOPASSWD: ALL" >> %q'`, user.Name, sudoersUserFilePath)) + cmds = append(cmds, fmt.Sprintf("chmod 440 %s", sudoersUserFilePath)) + cmds = append(cmds, fmt.Sprintf("restorecon -rvF %s", sudoersPath)) + } } } cmds = append(cmds, fmt.Sprintf("restorecon -rvF %s", varhome)) diff --git a/pkg/manifest/os_test.go b/pkg/manifest/os_test.go index eb51ed71e4..0e6e56a511 100644 --- a/pkg/manifest/os_test.go +++ b/pkg/manifest/os_test.go @@ -3,6 +3,7 @@ package manifest import ( "testing" + "github.com/osbuild/images/pkg/customizations/users" "github.com/osbuild/images/pkg/osbuild" "github.com/osbuild/images/pkg/platform" "github.com/osbuild/images/pkg/rpmmd" @@ -152,3 +153,74 @@ func TestRhcInsightsPackages(t *testing.T) { } CheckPkgSetInclude(t, os.getPackageSetChain(DISTRO_NULL), []string{"rhc", "subscription-manager", "insights-client"}) } + +func TestCustomizationsUsersCommands(t *testing.T) { + testCases := []struct { + Name string + OSTreeRef string + UserName string + UserSSHKey string + ExpectedCommands []string + }{ + { + Name: "pipeline stage commands with regular user should be valid", + OSTreeRef: "rhel/9/x86_64/edge", + UserName: "test-user", + UserSSHKey: "ssh-rsa test-user", + ExpectedCommands: []string{ + "mkdir -p /var/home/test-user/.ssh", + `sh -c 'echo "ssh-rsa test-user" >> "/var/home/test-user/.ssh/authorized_keys"'`, + "chown test-user:test-user -Rc /var/home/test-user/.ssh", + `sh -c 'echo -e "test-user\tALL=(ALL)\tNOPASSWD: ALL" >> "/etc/sudoers.d/test-user"'`, + "chmod 440 /etc/sudoers.d/test-user", + "restorecon -rvF /etc/sudoers.d", + "restorecon -rvF /var/home", + "restorecon -rvF /var/roothome", + }, + }, + { + Name: "pipeline stages commands with root user should be valid", + OSTreeRef: "rhel/9/x86_64/edge", + UserName: "root", + UserSSHKey: "ssh-rsa test-root", + ExpectedCommands: []string{ + "mkdir -p /var/roothome/.ssh", + `sh -c 'echo "ssh-rsa test-root" >> "/var/roothome/.ssh/authorized_keys"'`, + "chown root:root -Rc /var/roothome/.ssh", + "restorecon -rvF /var/home", + "restorecon -rvF /var/roothome", + }, + }, + { + Name: "pipeline stages commands should not be generated when OSTreeRef empty", + OSTreeRef: "", + UserName: "test-user", + UserSSHKey: "ssh-rsa test-user", + ExpectedCommands: []string{}, + }, + } + + for _, testCase := range testCases { + testCase := testCase + t.Run(testCase.Name, func(t *testing.T) { + os := NewTestOS() + os.OSTreeRef = testCase.OSTreeRef + os.Users = append(os.Users, users.User{Name: testCase.UserName, Key: &testCase.UserSSHKey}) + pipeline := os.serialize() + // check if first boot stage exist in pipeline stages + firstBootStageExist := false + for _, s := range pipeline.Stages { + if s.Type == "org.osbuild.first-boot" { + firstBootStageExist = true + break + } + } + if len(testCase.ExpectedCommands) > 0 { + require.True(t, firstBootStageExist, "no first boot stage was generated") + CheckFirstBootStageOptions(t, pipeline.Stages, testCase.ExpectedCommands) + } else { + assert.False(t, firstBootStageExist, "first boot stage was generated") + } + }) + } +}