github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/cmd/snap/cmd_auto_import_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 main_test 21 22 import ( 23 "fmt" 24 "io/ioutil" 25 "net/http" 26 "os" 27 "path/filepath" 28 29 . "gopkg.in/check.v1" 30 31 "github.com/snapcore/snapd/boot" 32 snap "github.com/snapcore/snapd/cmd/snap" 33 "github.com/snapcore/snapd/dirs" 34 "github.com/snapcore/snapd/logger" 35 "github.com/snapcore/snapd/osutil" 36 "github.com/snapcore/snapd/release" 37 "github.com/snapcore/snapd/snap/snaptest" 38 "github.com/snapcore/snapd/testutil" 39 ) 40 41 func makeMockMountInfo(c *C, content string) string { 42 fn := filepath.Join(c.MkDir(), "mountinfo") 43 err := ioutil.WriteFile(fn, []byte(content), 0644) 44 c.Assert(err, IsNil) 45 return fn 46 } 47 48 func (s *SnapSuite) TestAutoImportAssertsHappy(c *C) { 49 restore := release.MockOnClassic(false) 50 defer restore() 51 52 fakeAssertData := []byte("my-assertion") 53 54 n := 0 55 total := 2 56 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 57 switch n { 58 case 0: 59 c.Check(r.Method, Equals, "POST") 60 c.Check(r.URL.Path, Equals, "/v2/assertions") 61 postData, err := ioutil.ReadAll(r.Body) 62 c.Assert(err, IsNil) 63 c.Check(postData, DeepEquals, fakeAssertData) 64 fmt.Fprintln(w, `{"type": "sync", "result": {"ready": true, "status": "Done"}}`) 65 n++ 66 case 1: 67 c.Check(r.Method, Equals, "POST") 68 c.Check(r.URL.Path, Equals, "/v2/users") 69 postData, err := ioutil.ReadAll(r.Body) 70 c.Assert(err, IsNil) 71 c.Check(string(postData), Equals, `{"action":"create","sudoer":true,"known":true}`) 72 73 fmt.Fprintln(w, `{"type": "sync", "result": [{"username": "foo"}]}`) 74 n++ 75 default: 76 c.Fatalf("unexpected request: %v (expected %d got %d)", r, total, n) 77 } 78 79 }) 80 81 fakeAssertsFn := filepath.Join(c.MkDir(), "auto-import.assert") 82 err := ioutil.WriteFile(fakeAssertsFn, fakeAssertData, 0644) 83 c.Assert(err, IsNil) 84 85 mockMountInfoFmt := ` 86 24 0 8:18 / %s rw,relatime shared:1 - ext4 /dev/sdb2 rw,errors=remount-ro,data=ordered` 87 content := fmt.Sprintf(mockMountInfoFmt, filepath.Dir(fakeAssertsFn)) 88 restore = snap.MockMountInfoPath(makeMockMountInfo(c, content)) 89 defer restore() 90 91 logbuf, restore := logger.MockLogger() 92 defer restore() 93 94 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"auto-import"}) 95 c.Assert(err, IsNil) 96 c.Assert(rest, DeepEquals, []string{}) 97 c.Check(s.Stdout(), Equals, `created user "foo"`+"\n") 98 // matches because we may get a: 99 // "WARNING: cannot create syslog logger\n" 100 // in the output 101 c.Check(logbuf.String(), Matches, fmt.Sprintf("(?ms).*imported %s\n", fakeAssertsFn)) 102 c.Check(n, Equals, total) 103 } 104 105 func (s *SnapSuite) TestAutoImportAssertsNotImportedFromLoop(c *C) { 106 restore := release.MockOnClassic(false) 107 defer restore() 108 109 fakeAssertData := []byte("bad-assertion") 110 111 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 112 // assertion is ignored, nothing is posted to this endpoint 113 panic("not reached") 114 }) 115 116 fakeAssertsFn := filepath.Join(c.MkDir(), "auto-import.assert") 117 err := ioutil.WriteFile(fakeAssertsFn, fakeAssertData, 0644) 118 c.Assert(err, IsNil) 119 120 mockMountInfoFmtWithLoop := ` 121 24 0 8:18 / %s rw,relatime shared:1 - squashfs /dev/loop1 rw,errors=remount-ro,data=ordered` 122 content := fmt.Sprintf(mockMountInfoFmtWithLoop, filepath.Dir(fakeAssertsFn)) 123 restore = snap.MockMountInfoPath(makeMockMountInfo(c, content)) 124 defer restore() 125 126 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"auto-import"}) 127 c.Assert(err, IsNil) 128 c.Assert(rest, DeepEquals, []string{}) 129 c.Check(s.Stdout(), Equals, "") 130 c.Check(s.Stderr(), Equals, "") 131 } 132 133 func (s *SnapSuite) TestAutoImportCandidatesHappy(c *C) { 134 dirs := make([]string, 4) 135 args := make([]interface{}, len(dirs)) 136 files := make([]string, len(dirs)) 137 for i := range dirs { 138 dirs[i] = c.MkDir() 139 args[i] = dirs[i] 140 files[i] = filepath.Join(dirs[i], "auto-import.assert") 141 err := ioutil.WriteFile(files[i], nil, 0644) 142 c.Assert(err, IsNil) 143 } 144 145 mockMountInfoFmtWithLoop := ` 146 too short 147 24 0 8:18 / %[1]s rw,relatime foo ext3 /dev/meep2 no,separator 148 24 0 8:18 / %[2]s rw,relatime - ext3 /dev/meep2 rw,errors=remount-ro,data=ordered 149 24 0 8:18 / %[3]s rw,relatime opt:1 - ext4 /dev/meep3 rw,errors=remount-ro,data=ordered 150 24 0 8:18 / %[4]s rw,relatime opt:1 opt:2 - ext2 /dev/meep1 rw,errors=remount-ro,data=ordered 151 ` 152 153 content := fmt.Sprintf(mockMountInfoFmtWithLoop, args...) 154 restore := snap.MockMountInfoPath(makeMockMountInfo(c, content)) 155 defer restore() 156 157 l, err := snap.AutoImportCandidates() 158 c.Check(err, IsNil) 159 c.Check(l, DeepEquals, files[1:]) 160 } 161 162 func (s *SnapSuite) TestAutoImportAssertsHappyNotOnClassic(c *C) { 163 restore := release.MockOnClassic(true) 164 defer restore() 165 166 fakeAssertData := []byte("my-assertion") 167 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 168 c.Errorf("auto-import on classic is disabled, but something tried to do a %q with %s", r.Method, r.URL.Path) 169 }) 170 171 fakeAssertsFn := filepath.Join(c.MkDir(), "auto-import.assert") 172 err := ioutil.WriteFile(fakeAssertsFn, fakeAssertData, 0644) 173 c.Assert(err, IsNil) 174 175 mockMountInfoFmt := ` 176 24 0 8:18 / %s rw,relatime shared:1 - ext4 /dev/sdb2 rw,errors=remount-ro,data=ordered` 177 content := fmt.Sprintf(mockMountInfoFmt, filepath.Dir(fakeAssertsFn)) 178 restore = snap.MockMountInfoPath(makeMockMountInfo(c, content)) 179 defer restore() 180 181 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"auto-import"}) 182 c.Assert(err, IsNil) 183 c.Assert(rest, DeepEquals, []string{}) 184 c.Check(s.Stdout(), Equals, "") 185 c.Check(s.Stderr(), Equals, "auto-import is disabled on classic\n") 186 } 187 188 func (s *SnapSuite) TestAutoImportIntoSpool(c *C) { 189 restore := release.MockOnClassic(false) 190 defer restore() 191 192 logbuf, restore := logger.MockLogger() 193 defer restore() 194 195 fakeAssertData := []byte("good-assertion") 196 197 // ensure we can not connect 198 snap.ClientConfig.BaseURL = "can-not-connect-to-this-url" 199 200 fakeAssertsFn := filepath.Join(c.MkDir(), "auto-import.assert") 201 err := ioutil.WriteFile(fakeAssertsFn, fakeAssertData, 0644) 202 c.Assert(err, IsNil) 203 204 mockMountInfoFmt := ` 205 24 0 8:18 / %s rw,relatime shared:1 - squashfs /dev/sc1 rw,errors=remount-ro,data=ordered` 206 content := fmt.Sprintf(mockMountInfoFmt, filepath.Dir(fakeAssertsFn)) 207 restore = snap.MockMountInfoPath(makeMockMountInfo(c, content)) 208 defer restore() 209 210 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"auto-import"}) 211 c.Assert(err, IsNil) 212 c.Assert(rest, DeepEquals, []string{}) 213 c.Check(s.Stdout(), Equals, "") 214 // matches because we may get a: 215 // "WARNING: cannot create syslog logger\n" 216 // in the output 217 c.Check(logbuf.String(), Matches, "(?ms).*queuing for later.*\n") 218 219 files, err := ioutil.ReadDir(dirs.SnapAssertsSpoolDir) 220 c.Assert(err, IsNil) 221 c.Check(files, HasLen, 1) 222 c.Check(files[0].Name(), Equals, "iOkaeet50rajLvL-0Qsf2ELrTdn3XIXRIBlDewcK02zwRi3_TJlUOTl9AaiDXmDn.assert") 223 } 224 225 func (s *SnapSuite) TestAutoImportFromSpoolHappy(c *C) { 226 restore := release.MockOnClassic(false) 227 defer restore() 228 229 fakeAssertData := []byte("my-assertion") 230 231 n := 0 232 total := 2 233 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 234 switch n { 235 case 0: 236 c.Check(r.Method, Equals, "POST") 237 c.Check(r.URL.Path, Equals, "/v2/assertions") 238 postData, err := ioutil.ReadAll(r.Body) 239 c.Assert(err, IsNil) 240 c.Check(postData, DeepEquals, fakeAssertData) 241 fmt.Fprintln(w, `{"type": "sync", "result": {"ready": true, "status": "Done"}}`) 242 n++ 243 case 1: 244 c.Check(r.Method, Equals, "POST") 245 c.Check(r.URL.Path, Equals, "/v2/users") 246 postData, err := ioutil.ReadAll(r.Body) 247 c.Assert(err, IsNil) 248 c.Check(string(postData), Equals, `{"action":"create","sudoer":true,"known":true}`) 249 250 fmt.Fprintln(w, `{"type": "sync", "result": [{"username": "foo"}]}`) 251 n++ 252 default: 253 c.Fatalf("unexpected request: %v (expected %d got %d)", r, total, n) 254 } 255 256 }) 257 258 fakeAssertsFn := filepath.Join(dirs.SnapAssertsSpoolDir, "1234343") 259 err := os.MkdirAll(filepath.Dir(fakeAssertsFn), 0755) 260 c.Assert(err, IsNil) 261 err = ioutil.WriteFile(fakeAssertsFn, fakeAssertData, 0644) 262 c.Assert(err, IsNil) 263 264 logbuf, restore := logger.MockLogger() 265 defer restore() 266 267 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"auto-import"}) 268 c.Assert(err, IsNil) 269 c.Assert(rest, DeepEquals, []string{}) 270 c.Check(s.Stdout(), Equals, `created user "foo"`+"\n") 271 // matches because we may get a: 272 // "WARNING: cannot create syslog logger\n" 273 // in the output 274 c.Check(logbuf.String(), Matches, fmt.Sprintf("(?ms).*imported %s\n", fakeAssertsFn)) 275 c.Check(n, Equals, total) 276 277 c.Check(osutil.FileExists(fakeAssertsFn), Equals, false) 278 } 279 280 func (s *SnapSuite) TestAutoImportIntoSpoolUnhappyTooBig(c *C) { 281 restore := release.MockOnClassic(false) 282 defer restore() 283 284 _, restoreLogger := logger.MockLogger() 285 defer restoreLogger() 286 287 // fake data is bigger than the default assertion limit 288 fakeAssertData := make([]byte, 641*1024) 289 290 // ensure we can not connect 291 snap.ClientConfig.BaseURL = "can-not-connect-to-this-url" 292 293 fakeAssertsFn := filepath.Join(c.MkDir(), "auto-import.assert") 294 err := ioutil.WriteFile(fakeAssertsFn, fakeAssertData, 0644) 295 c.Assert(err, IsNil) 296 297 mockMountInfoFmt := ` 298 24 0 8:18 / %s rw,relatime shared:1 - squashfs /dev/sc1 rw,errors=remount-ro,data=ordered` 299 content := fmt.Sprintf(mockMountInfoFmt, filepath.Dir(fakeAssertsFn)) 300 restore = snap.MockMountInfoPath(makeMockMountInfo(c, content)) 301 defer restore() 302 303 _, err = snap.Parser(snap.Client()).ParseArgs([]string{"auto-import"}) 304 c.Assert(err, ErrorMatches, "cannot queue .*, file size too big: 656384") 305 } 306 307 func (s *SnapSuite) TestAutoImportUnhappyInInstallMode(c *C) { 308 restore := release.MockOnClassic(false) 309 defer restore() 310 311 _, restoreLogger := logger.MockLogger() 312 defer restoreLogger() 313 314 mockProcCmdlinePath := filepath.Join(c.MkDir(), "cmdline") 315 err := ioutil.WriteFile(mockProcCmdlinePath, []byte("foo=bar snapd_recovery_mode=install snapd_recovery_system=20191118"), 0644) 316 c.Assert(err, IsNil) 317 318 restore = boot.MockProcCmdline(mockProcCmdlinePath) 319 defer restore() 320 321 _, err = snap.Parser(snap.Client()).ParseArgs([]string{"auto-import"}) 322 c.Assert(err, IsNil) 323 c.Check(s.Stdout(), Equals, "") 324 c.Check(s.Stderr(), Equals, "auto-import is disabled in install-mode\n") 325 } 326 327 var mountStatic = []string{"mount", "-t", "ext4,vfat", "-o", "ro", "--make-private"} 328 329 func (s *SnapSuite) TestAutoImportFromRemovable(c *C) { 330 restore := release.MockOnClassic(false) 331 defer restore() 332 333 _, restoreLogger := logger.MockLogger() 334 defer restoreLogger() 335 336 rootdir := c.MkDir() 337 dirs.SetRootDir(rootdir) 338 339 var umounts []string 340 restore = snap.MockSyscallUmount(func(p string, _ int) error { 341 umounts = append(umounts, p) 342 return nil 343 }) 344 defer restore() 345 346 var tmpdirIdx int 347 restore = snap.MockIoutilTempDir(func(where string, p string) (string, error) { 348 c.Check(where, Equals, "") 349 tmpdirIdx++ 350 return filepath.Join(rootdir, fmt.Sprintf("/tmp/%s%d", p, tmpdirIdx)), nil 351 }) 352 defer restore() 353 354 mountCmd := testutil.MockCommand(c, "mount", "") 355 defer mountCmd.Restore() 356 357 snaptest.PopulateDir(rootdir, [][]string{ 358 // removable without partitions 359 {"sys/block/sdremovable/removable", "1\n"}, 360 // fixed disk 361 {"sys/block/sdfixed/removable", "0\n"}, 362 // removable with partitions 363 {"sys/block/sdpart/removable", "1\n"}, 364 {"sys/block/sdpart/sdpart1/partition", "1\n"}, 365 {"sys/block/sdpart/sdpart2/partition", "0\n"}, 366 {"sys/block/sdpart/sdpart3/partition", "1\n"}, 367 // removable but subdevices are not partitions? 368 {"sys/block/sdother/removable", "1\n"}, 369 {"sys/block/sdother/sdother1/partition", "0\n"}, 370 }) 371 372 // do not mock mountinfo contents, we just want to observe whether we 373 // try to mount and umount the right stuff 374 375 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"auto-import"}) 376 c.Assert(err, IsNil) 377 c.Check(s.Stdout(), Equals, "") 378 c.Check(s.Stderr(), Equals, "") 379 c.Check(mountCmd.Calls(), DeepEquals, [][]string{ 380 append(mountStatic, "/dev/sdpart1", filepath.Join(rootdir, "/tmp/snapd-auto-import-mount-1")), 381 append(mountStatic, "/dev/sdpart3", filepath.Join(rootdir, "/tmp/snapd-auto-import-mount-2")), 382 append(mountStatic, "/dev/sdremovable", filepath.Join(rootdir, "/tmp/snapd-auto-import-mount-3")), 383 }) 384 c.Check(umounts, DeepEquals, []string{ 385 filepath.Join(rootdir, "/tmp/snapd-auto-import-mount-3"), 386 filepath.Join(rootdir, "/tmp/snapd-auto-import-mount-2"), 387 filepath.Join(rootdir, "/tmp/snapd-auto-import-mount-1"), 388 }) 389 } 390 391 func (s *SnapSuite) TestAutoImportNoRemovable(c *C) { 392 restore := release.MockOnClassic(false) 393 defer restore() 394 395 rootdir := c.MkDir() 396 dirs.SetRootDir(rootdir) 397 398 var umounts []string 399 restore = snap.MockSyscallUmount(func(p string, _ int) error { 400 return fmt.Errorf("unexpected call") 401 }) 402 defer restore() 403 404 mountCmd := testutil.MockCommand(c, "mount", "exit 1") 405 defer mountCmd.Restore() 406 407 snaptest.PopulateDir(rootdir, [][]string{ 408 // fixed disk 409 {"sys/block/sdfixed/removable", "0\n"}, 410 // removable but subdevices are not partitions? 411 {"sys/block/sdother/removable", "1\n"}, 412 {"sys/block/sdother/sdother1/partition", "0\n"}, 413 }) 414 415 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"auto-import"}) 416 c.Assert(err, IsNil) 417 c.Check(s.Stdout(), Equals, "") 418 c.Check(s.Stderr(), Equals, "") 419 c.Check(mountCmd.Calls(), HasLen, 0) 420 c.Check(umounts, HasLen, 0) 421 } 422 423 func (s *SnapSuite) TestAutoImportFromMount(c *C) { 424 restore := release.MockOnClassic(false) 425 defer restore() 426 427 _, restoreLogger := logger.MockLogger() 428 defer restoreLogger() 429 430 mountCmd := testutil.MockCommand(c, "mount", "") 431 432 rootdir := c.MkDir() 433 dirs.SetRootDir(rootdir) 434 435 var umounts []string 436 restore = snap.MockSyscallUmount(func(p string, _ int) error { 437 c.Assert(umounts, HasLen, 0) 438 umounts = append(umounts, p) 439 return nil 440 }) 441 defer restore() 442 443 var tmpdircalls int 444 restore = snap.MockIoutilTempDir(func(where string, p string) (string, error) { 445 c.Check(where, Equals, "") 446 c.Assert(tmpdircalls, Equals, 0) 447 tmpdircalls++ 448 return filepath.Join(rootdir, fmt.Sprintf("/tmp/%s1", p)), nil 449 }) 450 defer restore() 451 452 // do not mock mountinfo contents, we just want to observe whether we 453 // try to mount and umount the right stuff 454 455 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"auto-import", "--mount", "/dev/foobar"}) 456 c.Assert(err, IsNil) 457 c.Check(s.Stdout(), Equals, "") 458 c.Check(s.Stderr(), Equals, "") 459 c.Check(mountCmd.Calls(), DeepEquals, [][]string{ 460 append(mountStatic, "/dev/foobar", filepath.Join(rootdir, "/tmp/snapd-auto-import-mount-1")), 461 }) 462 c.Check(umounts, DeepEquals, []string{ 463 filepath.Join(rootdir, "/tmp/snapd-auto-import-mount-1"), 464 }) 465 } 466 467 func (s *SnapSuite) TestAutoImportUC20CandidatesIgnoresSystemPartitions(c *C) { 468 469 mountDirs := []string{ 470 "/writable/system-data/var/lib/snapd/seed", 471 "/var/lib/snapd/seed", 472 "/run/mnt/ubuntu-boot", 473 "/run/mnt/ubuntu-seed", 474 "/run/mnt/ubuntu-data", 475 "/mnt/real-device", 476 } 477 478 rootDir := c.MkDir() 479 dirs.SetRootDir(rootDir) 480 defer func() { dirs.SetRootDir("") }() 481 482 args := make([]interface{}, 0, len(mountDirs)+1) 483 args = append(args, dirs.GlobalRootDir) 484 // pretend there are auto-import.asserts on all of them 485 for _, dir := range mountDirs { 486 args = append(args, dir) 487 file := filepath.Join(rootDir, dir, "auto-import.assert") 488 c.Assert(os.MkdirAll(filepath.Dir(file), 0755), IsNil) 489 c.Assert(ioutil.WriteFile(file, nil, 0644), IsNil) 490 } 491 492 mockMountInfoFmtWithLoop := ` 493 24 0 8:18 / %[1]s%[2]s rw,relatime foo ext3 /dev/meep2 no,separator 494 24 0 8:18 / %[1]s%[3]s rw,relatime - ext3 /dev/meep2 rw,errors=remount-ro,data=ordered 495 24 0 8:18 / %[1]s%[4]s rw,relatime opt:1 - ext4 /dev/meep3 rw,errors=remount-ro,data=ordered 496 24 0 8:18 / %[1]s%[5]s rw,relatime opt:1 opt:2 - ext2 /dev/meep4 rw,errors=remount-ro,data=ordered 497 24 0 8:18 / %[1]s%[6]s rw,relatime opt:1 opt:2 - ext2 /dev/meep5 rw,errors=remount-ro,data=ordered 498 24 0 8:18 / %[1]s%[7]s rw,relatime opt:1 opt:2 - ext2 /dev/meep78 rw,errors=remount-ro,data=ordered 499 ` 500 501 content := fmt.Sprintf(mockMountInfoFmtWithLoop, args...) 502 restore := snap.MockMountInfoPath(makeMockMountInfo(c, content)) 503 defer restore() 504 505 l, err := snap.AutoImportCandidates() 506 c.Check(err, IsNil) 507 508 // only device should be the /mnt/real-device one 509 c.Check(l, DeepEquals, []string{filepath.Join(rootDir, "/mnt/real-device", "auto-import.assert")}) 510 }