github.com/stolowski/snapd@v0.0.0-20210407085831-115137ce5a22/osutil/user_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package osutil_test 21 22 import ( 23 "fmt" 24 "io/ioutil" 25 "os" 26 "os/user" 27 "path/filepath" 28 "strconv" 29 30 "gopkg.in/check.v1" 31 32 "github.com/snapcore/snapd/osutil" 33 "github.com/snapcore/snapd/osutil/sys" 34 "github.com/snapcore/snapd/testutil" 35 ) 36 37 type createUserSuite struct { 38 testutil.BaseTest 39 40 mockHome string 41 restorer func() 42 43 mockAddUser *testutil.MockCmd 44 mockUserMod *testutil.MockCmd 45 mockPasswd *testutil.MockCmd 46 } 47 48 var _ = check.Suite(&createUserSuite{}) 49 50 func (s *createUserSuite) SetUpTest(c *check.C) { 51 s.mockHome = c.MkDir() 52 s.restorer = osutil.MockUserLookup(func(string) (*user.User, error) { 53 current, err := user.Current() 54 if err != nil { 55 c.Fatalf("user.Current() failed with %s", err) 56 } 57 return &user.User{ 58 HomeDir: s.mockHome, 59 Gid: current.Gid, 60 Uid: current.Uid, 61 }, nil 62 }) 63 s.mockAddUser = testutil.MockCommand(c, "adduser", "") 64 s.mockUserMod = testutil.MockCommand(c, "usermod", "") 65 s.mockPasswd = testutil.MockCommand(c, "passwd", "") 66 } 67 68 func (s *createUserSuite) TearDownTest(c *check.C) { 69 s.restorer() 70 s.mockAddUser.Restore() 71 s.mockUserMod.Restore() 72 s.mockPasswd.Restore() 73 } 74 75 func (s *createUserSuite) TestAddUserExtraUsersFalse(c *check.C) { 76 err := osutil.AddUser("lakatos", &osutil.AddUserOptions{ 77 Gecos: "my gecos", 78 ExtraUsers: false, 79 }) 80 c.Assert(err, check.IsNil) 81 82 c.Check(s.mockAddUser.Calls(), check.DeepEquals, [][]string{ 83 {"adduser", "--force-badname", "--gecos", "my gecos", "--disabled-password", "lakatos"}, 84 }) 85 } 86 87 func (s *createUserSuite) TestAddUserExtraUsersTrue(c *check.C) { 88 err := osutil.AddUser("lakatos", &osutil.AddUserOptions{ 89 Gecos: "my gecos", 90 ExtraUsers: true, 91 }) 92 c.Assert(err, check.IsNil) 93 94 c.Check(s.mockAddUser.Calls(), check.DeepEquals, [][]string{ 95 {"adduser", "--force-badname", "--gecos", "my gecos", "--disabled-password", "--extrausers", "lakatos"}, 96 }) 97 } 98 99 func (s *createUserSuite) TestAddSudoUser(c *check.C) { 100 mockSudoers := c.MkDir() 101 restorer := osutil.MockSudoersDotD(mockSudoers) 102 defer restorer() 103 104 err := osutil.AddUser("karl.sagan", &osutil.AddUserOptions{ 105 Gecos: "my gecos", 106 Sudoer: true, 107 ExtraUsers: true, 108 }) 109 c.Assert(err, check.IsNil) 110 111 c.Check(s.mockAddUser.Calls(), check.DeepEquals, [][]string{ 112 {"adduser", "--force-badname", "--gecos", "my gecos", "--disabled-password", "--extrausers", "karl.sagan"}, 113 }) 114 115 fs, _ := filepath.Glob(filepath.Join(mockSudoers, "*")) 116 c.Assert(fs, check.HasLen, 1) 117 c.Assert(filepath.Base(fs[0]), check.Equals, "create-user-karl%2Esagan") 118 c.Check(fs[0], testutil.FileEquals, ` 119 # Created by snap create-user 120 121 # User rules for karl.sagan 122 karl.sagan ALL=(ALL) NOPASSWD:ALL 123 `) 124 } 125 126 func (s *createUserSuite) TestAddUserSSHKeys(c *check.C) { 127 err := osutil.AddUser("karl.sagan", &osutil.AddUserOptions{ 128 SSHKeys: []string{"ssh-key1", "ssh-key2"}, 129 }) 130 c.Assert(err, check.IsNil) 131 c.Check(filepath.Join(s.mockHome, ".ssh", "authorized_keys"), testutil.FileEquals, "ssh-key1\nssh-key2") 132 133 } 134 135 func (s *createUserSuite) TestAddUserInvalidUsername(c *check.C) { 136 err := osutil.AddUser("k!", nil) 137 c.Assert(err, check.ErrorMatches, `cannot add user "k!": name contains invalid characters`) 138 } 139 140 func (s *createUserSuite) TestAddUserWithPassword(c *check.C) { 141 mockSudoers := c.MkDir() 142 restorer := osutil.MockSudoersDotD(mockSudoers) 143 defer restorer() 144 145 err := osutil.AddUser("karl.sagan", &osutil.AddUserOptions{ 146 Gecos: "my gecos", 147 Password: "$6$salt$hash", 148 }) 149 c.Assert(err, check.IsNil) 150 151 c.Check(s.mockAddUser.Calls(), check.DeepEquals, [][]string{ 152 {"adduser", "--force-badname", "--gecos", "my gecos", "--disabled-password", "karl.sagan"}, 153 }) 154 c.Check(s.mockUserMod.Calls(), check.DeepEquals, [][]string{ 155 {"usermod", "--password", "$6$salt$hash", "karl.sagan"}, 156 }) 157 } 158 159 func (s *createUserSuite) TestAddUserWithPasswordForceChange(c *check.C) { 160 mockSudoers := c.MkDir() 161 restorer := osutil.MockSudoersDotD(mockSudoers) 162 defer restorer() 163 164 err := osutil.AddUser("karl.popper", &osutil.AddUserOptions{ 165 Gecos: "my gecos", 166 Password: "$6$salt$hash", 167 ForcePasswordChange: true, 168 }) 169 c.Assert(err, check.IsNil) 170 171 c.Check(s.mockAddUser.Calls(), check.DeepEquals, [][]string{ 172 {"adduser", "--force-badname", "--gecos", "my gecos", "--disabled-password", "karl.popper"}, 173 }) 174 c.Check(s.mockUserMod.Calls(), check.DeepEquals, [][]string{ 175 {"usermod", "--password", "$6$salt$hash", "karl.popper"}, 176 }) 177 c.Check(s.mockPasswd.Calls(), check.DeepEquals, [][]string{ 178 {"passwd", "--expire", "karl.popper"}, 179 }) 180 } 181 182 func (s *createUserSuite) TestAddUserPasswordForceChangeUnhappy(c *check.C) { 183 mockSudoers := c.MkDir() 184 restorer := osutil.MockSudoersDotD(mockSudoers) 185 defer restorer() 186 187 err := osutil.AddUser("karl.popper", &osutil.AddUserOptions{ 188 Gecos: "my gecos", 189 ForcePasswordChange: true, 190 }) 191 c.Assert(err, check.ErrorMatches, `cannot force password change when no password is provided`) 192 } 193 194 func (s *createUserSuite) TestUserMaybeSudoUser(c *check.C) { 195 oldUser := os.Getenv("SUDO_USER") 196 defer func() { os.Setenv("SUDO_USER", oldUser) }() 197 198 for _, t := range []struct { 199 SudoUsername string 200 CurrentUsername string 201 CurrentUid int 202 }{ 203 // simulate regular "root", no SUDO_USER set 204 {"", os.Getenv("USER"), 0}, 205 // simulate a normal sudo invocation 206 {"guy", "guy", 0}, 207 // simulate running "sudo -u some-user -i" as root 208 // (LP: #1638656) 209 {"root", os.Getenv("USER"), 1000}, 210 } { 211 restore := osutil.MockUserCurrent(func() (*user.User, error) { 212 return &user.User{ 213 Username: t.CurrentUsername, 214 Uid: strconv.Itoa(t.CurrentUid), 215 }, nil 216 }) 217 defer restore() 218 219 os.Setenv("SUDO_USER", t.SudoUsername) 220 cur, err := osutil.UserMaybeSudoUser() 221 c.Assert(err, check.IsNil) 222 c.Check(cur.Username, check.Equals, t.CurrentUsername) 223 } 224 } 225 226 func (s *createUserSuite) TestUidGid(c *check.C) { 227 for k, t := range map[string]struct { 228 User *user.User 229 Uid sys.UserID 230 Gid sys.GroupID 231 Err string 232 }{ 233 "happy": {&user.User{Uid: "10", Gid: "10"}, 10, 10, ""}, 234 "bad uid": {&user.User{Uid: "x", Gid: "10"}, sys.FlagID, sys.FlagID, "cannot parse user id x"}, 235 "bad gid": {&user.User{Uid: "10", Gid: "x"}, sys.FlagID, sys.FlagID, "cannot parse group id x"}, 236 } { 237 uid, gid, err := osutil.UidGid(t.User) 238 c.Check(uid, check.Equals, t.Uid, check.Commentf(k)) 239 c.Check(gid, check.Equals, t.Gid, check.Commentf(k)) 240 if t.Err == "" { 241 c.Check(err, check.IsNil, check.Commentf(k)) 242 } else { 243 c.Check(err, check.ErrorMatches, ".*"+t.Err+".*", check.Commentf(k)) 244 } 245 } 246 } 247 248 func (s *createUserSuite) TestAddUserUnhappy(c *check.C) { 249 mockAddUser := testutil.MockCommand(c, "adduser", "echo some error; exit 1") 250 defer mockAddUser.Restore() 251 252 err := osutil.AddUser("lakatos", nil) 253 c.Assert(err, check.ErrorMatches, "adduser failed with: some error") 254 255 } 256 257 func (s *createUserSuite) TestIsValidUsername(c *check.C) { 258 for k, v := range map[string]bool{ 259 "a": true, 260 "a-b": true, 261 "a+b": true, 262 "a.b": true, 263 "a_b": true, 264 "1": true, 265 "1+": true, 266 "1.": true, 267 "1_": true, 268 "-": false, 269 "+": false, 270 ".": false, 271 "_": false, 272 "-a": false, 273 "+a": false, 274 ".a": false, 275 "_a": false, 276 "a:b": false, 277 "inval!d": false, 278 } { 279 c.Check(osutil.IsValidUsername(k), check.Equals, v) 280 } 281 } 282 283 type delUserSuite struct { 284 mockUserDel *testutil.MockCmd 285 opts *osutil.DelUserOptions 286 sudoersd string 287 restore func() 288 } 289 290 var _ = check.Suite(&delUserSuite{opts: nil}) 291 var _ = check.Suite(&delUserSuite{opts: &osutil.DelUserOptions{ExtraUsers: true}}) 292 293 func (s *delUserSuite) SetUpTest(c *check.C) { 294 s.mockUserDel = testutil.MockCommand(c, "userdel", "") 295 s.sudoersd = c.MkDir() 296 s.restore = osutil.MockSudoersDotD(s.sudoersd) 297 } 298 299 func (s *delUserSuite) TearDownTest(c *check.C) { 300 s.mockUserDel.Restore() 301 s.restore() 302 } 303 304 func (s *delUserSuite) expectedCmd(u string) []string { 305 if s.opts != nil && s.opts.ExtraUsers { 306 return []string{"userdel", "--remove", "--extrausers", u} 307 } 308 return []string{"userdel", "--remove", u} 309 } 310 311 func (s *delUserSuite) TestDelUser(c *check.C) { 312 c.Assert(osutil.DelUser("u1", s.opts), check.IsNil) 313 c.Assert(s.mockUserDel.Calls(), check.DeepEquals, [][]string{s.expectedCmd("u1")}) 314 } 315 316 func (s *delUserSuite) TestDelUserRemovesSudoersIfPresent(c *check.C) { 317 f1 := osutil.SudoersFile("u1") 318 319 // only create u1's sudoers file 320 c.Assert(ioutil.WriteFile(f1, nil, 0600), check.IsNil) 321 322 // neither of the delusers fail 323 c.Assert(osutil.DelUser("u1", s.opts), check.IsNil) 324 c.Assert(osutil.DelUser("u2", s.opts), check.IsNil) 325 326 // but u1's sudoers file is no more 327 c.Check(f1, testutil.FileAbsent) 328 329 // sanity check 330 c.Check(s.mockUserDel.Calls(), check.DeepEquals, [][]string{ 331 s.expectedCmd("u1"), 332 s.expectedCmd("u2"), 333 }) 334 } 335 336 func (s *delUserSuite) TestDelUserSudoersRemovalFailure(c *check.C) { 337 f1 := osutil.SudoersFile("u1") 338 339 // create a directory that'll mess with the removal 340 c.Assert(os.MkdirAll(filepath.Join(f1, "ook", "ook"), 0700), check.IsNil) 341 342 // delusers fails 343 c.Assert(osutil.DelUser("u1", s.opts), check.ErrorMatches, `cannot remove sudoers file for user "u1": .*`) 344 345 // sanity check 346 c.Check(s.mockUserDel.Calls(), check.DeepEquals, [][]string{ 347 s.expectedCmd("u1"), 348 }) 349 } 350 351 func (s *delUserSuite) TestDelUserFails(c *check.C) { 352 mockUserDel := testutil.MockCommand(c, "userdel", "exit 99") 353 defer mockUserDel.Restore() 354 355 c.Assert(osutil.DelUser("u1", s.opts), check.ErrorMatches, `cannot delete user "u1": exit status 99`) 356 c.Check(mockUserDel.Calls(), check.DeepEquals, [][]string{s.expectedCmd("u1")}) 357 } 358 359 type ensureUserSuite struct { 360 mockUserAdd *testutil.MockCmd 361 mockGroupAdd *testutil.MockCmd 362 mockGroupDel *testutil.MockCmd 363 } 364 365 var _ = check.Suite(&ensureUserSuite{}) 366 367 func (s *ensureUserSuite) SetUpTest(c *check.C) { 368 s.mockUserAdd = testutil.MockCommand(c, "useradd", "") 369 s.mockGroupAdd = testutil.MockCommand(c, "groupadd", "") 370 s.mockGroupDel = testutil.MockCommand(c, "groupdel", "") 371 } 372 373 func (s *ensureUserSuite) TearDownTest(c *check.C) { 374 s.mockUserAdd.Restore() 375 s.mockGroupAdd.Restore() 376 s.mockGroupDel.Restore() 377 } 378 379 func (s *ensureUserSuite) TestEnsureUserGroupExtraUsersFalse(c *check.C) { 380 falsePath = osutil.LookPathDefault("false", "/bin/false") 381 err := osutil.EnsureUserGroup("lakatos", 123456, false) 382 c.Assert(err, check.IsNil) 383 384 c.Check(s.mockGroupAdd.Calls(), check.DeepEquals, [][]string{ 385 {"groupadd", "--system", "--gid", "123456", "lakatos"}, 386 }) 387 c.Check(s.mockUserAdd.Calls(), check.DeepEquals, [][]string{ 388 {"useradd", "--system", "--home-dir", "/nonexistent", "--no-create-home", "--shell", falsePath, "--gid", "123456", "--no-user-group", "--uid", "123456", "lakatos"}, 389 }) 390 } 391 392 func (s *ensureUserSuite) TestEnsureUserGroupExtraUsersTrue(c *check.C) { 393 falsePath = osutil.LookPathDefault("false", "/bin/false") 394 err := osutil.EnsureUserGroup("lakatos", 123456, true) 395 c.Assert(err, check.IsNil) 396 397 c.Check(s.mockGroupAdd.Calls(), check.DeepEquals, [][]string{ 398 {"groupadd", "--system", "--gid", "123456", "--extrausers", "lakatos"}, 399 }) 400 c.Check(s.mockUserAdd.Calls(), check.DeepEquals, [][]string{ 401 {"useradd", "--system", "--home-dir", "/nonexistent", "--no-create-home", "--shell", falsePath, "--gid", "123456", "--no-user-group", "--uid", "123456", "--extrausers", "lakatos"}, 402 }) 403 } 404 405 func (s *ensureUserSuite) TestEnsureUserGroupBadUser(c *check.C) { 406 err := osutil.EnsureUserGroup("k!", 123456, false) 407 c.Assert(err, check.ErrorMatches, `cannot add user/group "k!": name contains invalid characters`) 408 409 // shouldn't run these on error 410 c.Check(s.mockGroupAdd.Calls(), check.DeepEquals, [][]string(nil)) 411 c.Check(s.mockUserAdd.Calls(), check.DeepEquals, [][]string(nil)) 412 } 413 414 func (s *ensureUserSuite) TestEnsureUserGroupUnexpectedFindUidError(c *check.C) { 415 restore := osutil.MockFindUid(func(string) (uint64, error) { 416 return 0, fmt.Errorf("some odd FindUid error") 417 }) 418 defer restore() 419 420 err := osutil.EnsureUserGroup("lakatos", 1234, false) 421 c.Assert(err, check.ErrorMatches, `some odd FindUid error`) 422 423 // shouldn't run these on error 424 c.Check(s.mockGroupAdd.Calls(), check.DeepEquals, [][]string(nil)) 425 c.Check(s.mockUserAdd.Calls(), check.DeepEquals, [][]string(nil)) 426 } 427 428 func (s *ensureUserSuite) TestEnsureUserGroupUnexpectedFindGidError(c *check.C) { 429 restore := osutil.MockFindGid(func(string) (uint64, error) { 430 return 0, fmt.Errorf("some odd FindGid error") 431 }) 432 defer restore() 433 434 err := osutil.EnsureUserGroup("lakatos", 1234, false) 435 c.Assert(err, check.ErrorMatches, `some odd FindGid error`) 436 437 // shouldn't run these on error 438 c.Check(s.mockGroupAdd.Calls(), check.DeepEquals, [][]string(nil)) 439 c.Check(s.mockUserAdd.Calls(), check.DeepEquals, [][]string(nil)) 440 } 441 442 func (s *ensureUserSuite) TestEnsureUserGroupUnexpectedUid(c *check.C) { 443 restore := osutil.MockFindUid(func(string) (uint64, error) { 444 return uint64(5432), nil 445 }) 446 defer restore() 447 restore = osutil.MockFindGid(func(string) (uint64, error) { 448 return uint64(1234), nil 449 }) 450 defer restore() 451 452 err := osutil.EnsureUserGroup("lakatos", 1234, false) 453 c.Assert(err, check.ErrorMatches, `found unexpected uid for user "lakatos": 5432`) 454 455 // shouldn't run these on error 456 c.Check(s.mockGroupAdd.Calls(), check.DeepEquals, [][]string(nil)) 457 c.Check(s.mockUserAdd.Calls(), check.DeepEquals, [][]string(nil)) 458 } 459 460 func (s *ensureUserSuite) TestEnsureUserGroupUnexpectedGid(c *check.C) { 461 restore := osutil.MockFindUid(func(string) (uint64, error) { 462 return uint64(1234), nil 463 }) 464 defer restore() 465 restore = osutil.MockFindGid(func(string) (uint64, error) { 466 return uint64(5432), nil 467 }) 468 defer restore() 469 470 err := osutil.EnsureUserGroup("lakatos", 1234, false) 471 c.Assert(err, check.ErrorMatches, `found unexpected gid for group "lakatos": 5432`) 472 473 // shouldn't run these on error 474 c.Check(s.mockGroupAdd.Calls(), check.DeepEquals, [][]string(nil)) 475 c.Check(s.mockUserAdd.Calls(), check.DeepEquals, [][]string(nil)) 476 } 477 478 func (s *ensureUserSuite) TestEnsureUserGroupFoundBoth(c *check.C) { 479 restore := osutil.MockFindUid(func(string) (uint64, error) { 480 return uint64(1234), nil 481 }) 482 defer restore() 483 restore = osutil.MockFindGid(func(string) (uint64, error) { 484 return uint64(1234), nil 485 }) 486 defer restore() 487 488 err := osutil.EnsureUserGroup("lakatos", 1234, false) 489 c.Assert(err, check.IsNil) 490 491 // we found both with expected values, shouldn't run these 492 c.Check(s.mockGroupAdd.Calls(), check.DeepEquals, [][]string(nil)) 493 c.Check(s.mockUserAdd.Calls(), check.DeepEquals, [][]string(nil)) 494 } 495 496 func (s *ensureUserSuite) TestEnsureUserGroupUnexpectedGroupMissing(c *check.C) { 497 restore := osutil.MockFindUid(func(string) (uint64, error) { 498 return uint64(1234), nil 499 }) 500 defer restore() 501 502 err := osutil.EnsureUserGroup("lakatos", 1234, false) 503 c.Assert(err, check.ErrorMatches, `cannot add user/group "lakatos": user exists and group does not`) 504 505 // shouldn't run these on error 506 c.Check(s.mockGroupAdd.Calls(), check.DeepEquals, [][]string(nil)) 507 c.Check(s.mockUserAdd.Calls(), check.DeepEquals, [][]string(nil)) 508 } 509 510 func (s *ensureUserSuite) TestEnsureUserGroupUnexpectedUserMissing(c *check.C) { 511 restore := osutil.MockFindGid(func(string) (uint64, error) { 512 return uint64(1234), nil 513 }) 514 defer restore() 515 516 err := osutil.EnsureUserGroup("lakatos", 1234, false) 517 c.Assert(err, check.ErrorMatches, `cannot add user/group "lakatos": group exists and user does not`) 518 519 // shouldn't run these on error 520 c.Check(s.mockGroupAdd.Calls(), check.DeepEquals, [][]string(nil)) 521 c.Check(s.mockUserAdd.Calls(), check.DeepEquals, [][]string(nil)) 522 } 523 524 func (s *ensureUserSuite) TestEnsureUserGroupFailedGroupadd(c *check.C) { 525 mockGroupAdd := testutil.MockCommand(c, "groupadd", "echo some error; exit 1") 526 defer mockGroupAdd.Restore() 527 528 err := osutil.EnsureUserGroup("lakatos", 123456, false) 529 c.Assert(err, check.ErrorMatches, "groupadd failed with: some error") 530 531 // shouldn't run this on error 532 c.Check(s.mockUserAdd.Calls(), check.DeepEquals, [][]string(nil)) 533 } 534 535 func (s *ensureUserSuite) TestEnsureUserGroupFailedUseraddClassic(c *check.C) { 536 mockUserAdd := testutil.MockCommand(c, "useradd", "echo some error; exit 1") 537 defer mockUserAdd.Restore() 538 539 err := osutil.EnsureUserGroup("lakatos", 123456, false) 540 c.Assert(err, check.ErrorMatches, "useradd failed with: some error") 541 542 c.Check(s.mockGroupDel.Calls(), check.DeepEquals, [][]string{ 543 {"groupdel", "lakatos"}, 544 }) 545 } 546 547 func (s *ensureUserSuite) TestEnsureUserGroupFailedUseraddCore(c *check.C) { 548 mockUserAdd := testutil.MockCommand(c, "useradd", "echo some error; exit 1") 549 defer mockUserAdd.Restore() 550 551 err := osutil.EnsureUserGroup("lakatos", 123456, true) 552 c.Assert(err, check.ErrorMatches, "useradd failed with: some error") 553 554 // TODO: LP: #1840375 555 /* 556 c.Check(s.mockGroupDel.Calls(), check.DeepEquals, [][]string{ 557 {"groupdel", "--extrausers", "lakatos"}, 558 }) 559 */ 560 c.Check(s.mockGroupDel.Calls(), check.DeepEquals, [][]string(nil)) 561 }