github.com/chipaca/snappy@v0.0.0-20210104084008-1f06296fe8ad/gadget/validate_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2019-2020 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 gadget_test
    21  
    22  import (
    23  	"bytes"
    24  	"fmt"
    25  	"os"
    26  	"path/filepath"
    27  
    28  	. "gopkg.in/check.v1"
    29  
    30  	"github.com/snapcore/snapd/gadget"
    31  )
    32  
    33  type validateGadgetTestSuite struct {
    34  	dir string
    35  }
    36  
    37  var _ = Suite(&validateGadgetTestSuite{})
    38  
    39  func (s *validateGadgetTestSuite) SetUpTest(c *C) {
    40  	s.dir = c.MkDir()
    41  }
    42  
    43  func (s *validateGadgetTestSuite) TestRuleValidateStructureReservedLabels(c *C) {
    44  	for _, tc := range []struct {
    45  		role, label, err string
    46  	}{
    47  		{label: "ubuntu-seed", err: `label "ubuntu-seed" is reserved`},
    48  		// 2020-12-02: disable for customer hotfix
    49  		/*{label: "ubuntu-boot", err: `label "ubuntu-boot" is reserved`},*/
    50  		{label: "ubuntu-data", err: `label "ubuntu-data" is reserved`},
    51  		{label: "ubuntu-save", err: `label "ubuntu-save" is reserved`},
    52  		// these are ok
    53  		{role: "system-boot", label: "ubuntu-boot"},
    54  		{label: "random-ubuntu-label"},
    55  	} {
    56  		err := gadget.RuleValidateVolumeStructure(&gadget.VolumeStructure{
    57  			Type:       "21686148-6449-6E6F-744E-656564454649",
    58  			Role:       tc.role,
    59  			Filesystem: "ext4",
    60  			Label:      tc.label,
    61  			Size:       10 * 1024,
    62  		})
    63  		if tc.err == "" {
    64  			c.Check(err, IsNil)
    65  		} else {
    66  			c.Check(err, ErrorMatches, tc.err)
    67  		}
    68  	}
    69  
    70  }
    71  
    72  func (s *validateGadgetTestSuite) TestEnsureVolumeRuleConsistency(c *C) {
    73  	state := func(seed bool, label string) *gadget.ValidationState {
    74  		systemDataVolume := &gadget.VolumeStructure{Label: label}
    75  		systemSeedVolume := (*gadget.VolumeStructure)(nil)
    76  		if seed {
    77  			systemSeedVolume = &gadget.VolumeStructure{}
    78  		}
    79  		return &gadget.ValidationState{
    80  			SystemSeed: systemSeedVolume,
    81  			SystemData: systemDataVolume,
    82  		}
    83  	}
    84  
    85  	for i, tc := range []struct {
    86  		s   *gadget.ValidationState
    87  		err string
    88  	}{
    89  
    90  		// we have the system-seed role
    91  		{state(true, ""), ""},
    92  		{state(true, "foobar"), `.* must have an implicit label or "ubuntu-data", not "foobar"`},
    93  		{state(true, "writable"), `.* must have an implicit label or "ubuntu-data", not "writable"`},
    94  		{state(true, "ubuntu-data"), ""},
    95  
    96  		// we don't have the system-seed role (old systems)
    97  		{state(false, ""), ""}, // implicit is ok
    98  		{state(false, "foobar"), `.* must have an implicit label or "writable", not "foobar"`},
    99  		{state(false, "writable"), ""},
   100  		{state(false, "ubuntu-data"), `.* must have an implicit label or "writable", not "ubuntu-data"`},
   101  	} {
   102  		c.Logf("tc: %v %p %v", i, tc.s.SystemSeed, tc.s.SystemData.Label)
   103  
   104  		err := gadget.EnsureVolumeRuleConsistency(tc.s, nil)
   105  		if tc.err != "" {
   106  			c.Check(err, ErrorMatches, tc.err)
   107  		} else {
   108  			c.Check(err, IsNil)
   109  		}
   110  	}
   111  
   112  	// Check system-seed label
   113  	for i, tc := range []struct {
   114  		l   string
   115  		err string
   116  	}{
   117  		{"", ""},
   118  		{"foobar", `system-seed structure must have an implicit label or "ubuntu-seed", not "foobar"`},
   119  		{"ubuntu-seed", ""},
   120  	} {
   121  		c.Logf("tc: %v %v", i, tc.l)
   122  		s := state(true, "")
   123  		s.SystemSeed.Label = tc.l
   124  		err := gadget.EnsureVolumeRuleConsistency(s, nil)
   125  		if tc.err != "" {
   126  			c.Check(err, ErrorMatches, tc.err)
   127  		} else {
   128  			c.Check(err, IsNil)
   129  		}
   130  	}
   131  
   132  	// Check system-seed without system-data
   133  	vs := &gadget.ValidationState{}
   134  	err := gadget.EnsureVolumeRuleConsistency(vs, nil)
   135  	c.Assert(err, IsNil)
   136  	vs.SystemSeed = &gadget.VolumeStructure{}
   137  	err = gadget.EnsureVolumeRuleConsistency(vs, nil)
   138  	c.Assert(err, ErrorMatches, "the system-seed role requires system-data to be defined")
   139  
   140  	// Check system-save
   141  	vsWithSave := &gadget.ValidationState{
   142  		SystemData: &gadget.VolumeStructure{},
   143  		SystemSeed: &gadget.VolumeStructure{},
   144  		SystemSave: &gadget.VolumeStructure{},
   145  	}
   146  	err = gadget.EnsureVolumeRuleConsistency(vsWithSave, nil)
   147  	c.Assert(err, IsNil)
   148  	// use illegal label on system-save
   149  	vsWithSave.SystemSave.Label = "foo"
   150  	err = gadget.EnsureVolumeRuleConsistency(vsWithSave, nil)
   151  	c.Assert(err, ErrorMatches, `system-save structure must have an implicit label or "ubuntu-save", not "foo"`)
   152  	// complains when either system-seed or system-data is missing
   153  	vsWithSave.SystemSeed = nil
   154  	err = gadget.EnsureVolumeRuleConsistency(vsWithSave, nil)
   155  	c.Assert(err, ErrorMatches, "system-save requires system-seed and system-data structures")
   156  	vsWithSave.SystemData = nil
   157  	err = gadget.EnsureVolumeRuleConsistency(vsWithSave, nil)
   158  	c.Assert(err, ErrorMatches, "system-save requires system-seed and system-data structures")
   159  }
   160  
   161  func (s *validateGadgetTestSuite) TestValidateConsistencyWithoutConstraints(c *C) {
   162  	for i, tc := range []struct {
   163  		role  string
   164  		label string
   165  		err   string
   166  	}{
   167  		// when constraints are nil, the system-seed role and ubuntu-data label on the
   168  		// system-data structure should be consistent
   169  		{"system-seed", "", ""},
   170  		{"system-seed", "writable", `.* must have an implicit label or "ubuntu-data", not "writable"`},
   171  		{"system-seed", "ubuntu-data", ""},
   172  		{"", "", ""},
   173  		{"", "writable", ""},
   174  		{"", "ubuntu-data", `.* must have an implicit label or "writable", not "ubuntu-data"`},
   175  	} {
   176  		c.Logf("tc: %v %v %v", i, tc.role, tc.label)
   177  		b := &bytes.Buffer{}
   178  
   179  		fmt.Fprintf(b, `
   180  volumes:
   181    pc:
   182      bootloader: grub
   183      schema: mbr
   184      structure:`)
   185  
   186  		if tc.role == "system-seed" {
   187  			fmt.Fprintf(b, `
   188        - name: Recovery
   189          size: 10M
   190          type: 83
   191          role: system-seed`)
   192  		}
   193  
   194  		fmt.Fprintf(b, `
   195        - name: Data
   196          size: 10M
   197          type: 83
   198          role: system-data
   199          filesystem-label: %s`, tc.label)
   200  
   201  		makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, b.Bytes())
   202  		ginfo, err := gadget.ReadInfo(s.dir, nil)
   203  		c.Assert(err, IsNil)
   204  		err = gadget.Validate(ginfo, nil, nil)
   205  		if tc.err != "" {
   206  			c.Check(err, ErrorMatches, tc.err)
   207  		} else {
   208  			c.Check(err, IsNil)
   209  		}
   210  	}
   211  }
   212  
   213  func (s *validateGadgetTestSuite) TestValidateConsistencyWithConstraints(c *C) {
   214  	bloader := `
   215  volumes:
   216    pc:
   217      bootloader: grub
   218      schema: mbr
   219      structure:`
   220  
   221  	for i, tc := range []struct {
   222  		addSeed     bool
   223  		dataLabel   string
   224  		requireSeed bool
   225  		addSave     bool
   226  		saveLabel   string
   227  		err         string
   228  	}{
   229  		// when constraints are nil, the system-seed role and ubuntu-data label on the
   230  		// system-data structure should be consistent
   231  		{addSeed: true, requireSeed: true},
   232  		{addSeed: true, err: `.* model does not support the system-seed role`},
   233  		{addSeed: true, dataLabel: "writable", requireSeed: true,
   234  			err: `.* system-data structure must have an implicit label or "ubuntu-data", not "writable"`},
   235  		{addSeed: true, dataLabel: "writable",
   236  			err: `.* model does not support the system-seed role`},
   237  		{addSeed: true, dataLabel: "ubuntu-data", requireSeed: true},
   238  		{addSeed: true, dataLabel: "ubuntu-data",
   239  			err: `.* model does not support the system-seed role`},
   240  		{dataLabel: "writable", requireSeed: true,
   241  			err: `.* model requires system-seed structure, but none was found`},
   242  		{dataLabel: "writable"},
   243  		{dataLabel: "ubuntu-data", requireSeed: true,
   244  			err: `.* model requires system-seed structure, but none was found`},
   245  		{dataLabel: "ubuntu-data", err: `.* system-data structure must have an implicit label or "writable", not "ubuntu-data"`},
   246  		{addSave: true, err: `.* system-save requires system-seed and system-data structures`},
   247  		{addSeed: true, requireSeed: true, addSave: true, saveLabel: "foo",
   248  			err: `.* system-save structure must have an implicit label or "ubuntu-save", not "foo"`},
   249  	} {
   250  		c.Logf("tc: %v %v %v %v", i, tc.addSeed, tc.dataLabel, tc.requireSeed)
   251  		b := &bytes.Buffer{}
   252  
   253  		fmt.Fprintf(b, bloader)
   254  		if tc.addSeed {
   255  			fmt.Fprintf(b, `
   256        - name: Recovery
   257          size: 10M
   258          type: 83
   259          role: system-seed`)
   260  		}
   261  
   262  		fmt.Fprintf(b, `
   263        - name: Data
   264          size: 10M
   265          type: 83
   266          role: system-data
   267          filesystem-label: %s`, tc.dataLabel)
   268  		if tc.addSave {
   269  			fmt.Fprintf(b, `
   270        - name: Save
   271          size: 10M
   272          type: 83
   273          role: system-save`)
   274  			if tc.saveLabel != "" {
   275  				fmt.Fprintf(b, `
   276          filesystem-label: %s`, tc.saveLabel)
   277  
   278  			}
   279  		}
   280  
   281  		makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, b.Bytes())
   282  
   283  		constraints := &modelConstraints{
   284  			classic:    false,
   285  			systemSeed: tc.requireSeed,
   286  		}
   287  
   288  		ginfo, err := gadget.ReadInfo(s.dir, constraints)
   289  		c.Assert(err, IsNil)
   290  		err = gadget.Validate(ginfo, constraints, nil)
   291  		if tc.err != "" {
   292  			c.Check(err, ErrorMatches, tc.err)
   293  		} else {
   294  			c.Check(err, IsNil)
   295  		}
   296  	}
   297  
   298  	// test error with no volumes
   299  	makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, []byte(bloader))
   300  
   301  	constraints := &modelConstraints{
   302  		systemSeed: true,
   303  	}
   304  	ginfo, err := gadget.ReadInfo(s.dir, constraints)
   305  	c.Assert(err, IsNil)
   306  	err = gadget.Validate(ginfo, constraints, nil)
   307  	c.Assert(err, ErrorMatches, ".*: model requires system-seed partition, but no system-seed or system-data partition found")
   308  }
   309  
   310  func (s *validateGadgetTestSuite) TestValidateRoleDuplicated(c *C) {
   311  
   312  	for _, role := range []string{"system-seed", "system-data", "system-boot"} {
   313  		gadgetYamlContent := fmt.Sprintf(`
   314  volumes:
   315    pc:
   316      bootloader: grub
   317      structure:
   318        - name: foo
   319          type: DA,21686148-6449-6E6F-744E-656564454649
   320          size: 1M
   321          role: %[1]s
   322        - name: bar
   323          type: DA,21686148-6449-6E6F-744E-656564454649
   324          size: 1M
   325          role: %[1]s
   326  `, role)
   327  		makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, []byte(gadgetYamlContent))
   328  
   329  		ginfo, err := gadget.ReadInfo(s.dir, nil)
   330  		c.Assert(err, IsNil)
   331  		err = gadget.Validate(ginfo, nil, nil)
   332  		c.Assert(err, ErrorMatches, fmt.Sprintf(`invalid volume "pc": cannot have more than one partition with %s role`, role))
   333  	}
   334  }
   335  
   336  func (s *validateGadgetTestSuite) TestRuleValidateHybridGadget(c *C) {
   337  	// this is the kind of volumes setup recommended to be
   338  	// prepared for a possible UC18 -> UC20 transition
   339  	hybridyGadgetYaml := []byte(`volumes:
   340    hybrid:
   341      bootloader: grub
   342      structure:
   343        - name: mbr
   344          type: mbr
   345          size: 440
   346          content:
   347            - image: pc-boot.img
   348        - name: BIOS Boot
   349          type: DA,21686148-6449-6E6F-744E-656564454649
   350          size: 1M
   351          offset: 1M
   352          offset-write: mbr+92
   353          content:
   354            - image: pc-core.img
   355        - name: EFI System
   356          type: EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B
   357          filesystem: vfat
   358          filesystem-label: system-boot
   359          size: 1200M
   360          content:
   361            - source: grubx64.efi
   362              target: EFI/boot/grubx64.efi
   363            - source: shim.efi.signed
   364              target: EFI/boot/bootx64.efi
   365            - source: mmx64.efi
   366              target: EFI/boot/mmx64.efi
   367            - source: grub.cfg
   368              target: EFI/ubuntu/grub.cfg
   369        - name: Ubuntu Boot
   370          type: 0FC63DAF-8483-4772-8E79-3D69D8477DE4
   371          filesystem: ext4
   372          filesystem-label: ubuntu-boot
   373          size: 750M
   374  `)
   375  
   376  	constraints := &modelConstraints{
   377  		classic: false,
   378  	}
   379  	giMeta, err := gadget.InfoFromGadgetYaml(hybridyGadgetYaml, constraints)
   380  	c.Assert(err, IsNil)
   381  
   382  	err = gadget.Validate(giMeta, constraints, nil)
   383  	c.Check(err, IsNil)
   384  }
   385  
   386  func (s *validateGadgetTestSuite) TestValidateContentMissingRawContent(c *C) {
   387  	var gadgetYamlContent = `
   388  volumes:
   389    pc:
   390      bootloader: grub
   391      structure:
   392        - name: foo
   393          type: DA,21686148-6449-6E6F-744E-656564454649
   394          size: 1M
   395          offset: 1M
   396          content:
   397            - image: foo.img
   398  
   399  `
   400  	makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, []byte(gadgetYamlContent))
   401  
   402  	ginfo, err := gadget.ReadInfo(s.dir, nil)
   403  	c.Assert(err, IsNil)
   404  	err = gadget.ValidateContent(ginfo, s.dir)
   405  	c.Assert(err, ErrorMatches, `invalid layout of volume "pc": cannot lay out structure #0 \("foo"\): content "foo.img": stat .*/foo.img: no such file or directory`)
   406  }
   407  
   408  func (s *validateGadgetTestSuite) TestValidateContentMultiVolumeContent(c *C) {
   409  	var gadgetYamlContent = `
   410  volumes:
   411    first:
   412      bootloader: grub
   413      structure:
   414        - name: first-foo
   415          type: DA,21686148-6449-6E6F-744E-656564454649
   416          size: 1M
   417          content:
   418            - image: first.img
   419    second:
   420      structure:
   421        - name: second-foo
   422          type: DA,21686148-6449-6E6F-744E-656564454649
   423          size: 1M
   424          content:
   425            - image: second.img
   426  
   427  `
   428  	makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, []byte(gadgetYamlContent))
   429  	// only content for the first volume
   430  	makeSizedFile(c, filepath.Join(s.dir, "first.img"), 1, nil)
   431  
   432  	ginfo, err := gadget.ReadInfo(s.dir, nil)
   433  	c.Assert(err, IsNil)
   434  	err = gadget.ValidateContent(ginfo, s.dir)
   435  	c.Assert(err, ErrorMatches, `invalid layout of volume "second": cannot lay out structure #0 \("second-foo"\): content "second.img": stat .*/second.img: no such file or directory`)
   436  }
   437  
   438  func (s *validateGadgetTestSuite) TestValidateContentFilesystemContent(c *C) {
   439  	var gadgetYamlContent = `
   440  volumes:
   441    bad:
   442      bootloader: grub
   443      structure:
   444        - name: bad-struct
   445          type: DA,21686148-6449-6E6F-744E-656564454649
   446          size: 1M
   447          filesystem: ext4
   448          content:
   449            - source: foo/
   450              target: /
   451  
   452  `
   453  	makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, []byte(gadgetYamlContent))
   454  
   455  	ginfo, err := gadget.ReadInfo(s.dir, nil)
   456  	c.Assert(err, IsNil)
   457  	err = gadget.ValidateContent(ginfo, s.dir)
   458  	c.Assert(err, ErrorMatches, `invalid volume "bad": structure #0 \("bad-struct"\), content source:foo/: source path does not exist`)
   459  
   460  	// make it a file, which conflicts with foo/ as 'source'
   461  	fooPath := filepath.Join(s.dir, "foo")
   462  	makeSizedFile(c, fooPath, 1, nil)
   463  	err = gadget.ValidateContent(ginfo, s.dir)
   464  	c.Assert(err, ErrorMatches, `invalid volume "bad": structure #0 \("bad-struct"\), content source:foo/: cannot specify trailing / for a source which is not a directory`)
   465  
   466  	// make it a directory
   467  	err = os.Remove(fooPath)
   468  	c.Assert(err, IsNil)
   469  	err = os.Mkdir(fooPath, 0755)
   470  	c.Assert(err, IsNil)
   471  	// validate should no longer complain
   472  	err = gadget.ValidateContent(ginfo, s.dir)
   473  	c.Assert(err, IsNil)
   474  }
   475  
   476  var gadgetYamlContentNoSave = `
   477  volumes:
   478    vol1:
   479      bootloader: grub
   480      structure:
   481        - name: ubuntu-seed
   482          role: system-seed
   483          type: DA,21686148-6449-6E6F-744E-656564454649
   484          size: 1M
   485          filesystem: ext4
   486        - name: ubuntu-boot
   487          type: DA,21686148-6449-6E6F-744E-656564454649
   488          size: 1M
   489          filesystem: ext4
   490        - name: ubuntu-data
   491          role: system-data
   492          type: DA,21686148-6449-6E6F-744E-656564454649
   493          size: 1M
   494          filesystem: ext4
   495  `
   496  
   497  var gadgetYamlContentWithSave = gadgetYamlContentNoSave + `
   498        - name: ubuntu-save
   499          role: system-save
   500          type: DA,21686148-6449-6E6F-744E-656564454649
   501          size: 1M
   502          filesystem: ext4
   503  `
   504  
   505  func (s *validateGadgetTestSuite) TestValidateEncryptionSupportErr(c *C) {
   506  	makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, []byte(gadgetYamlContentNoSave))
   507  
   508  	mod := &modelConstraints{systemSeed: true}
   509  	ginfo, err := gadget.ReadInfo(s.dir, mod)
   510  	c.Assert(err, IsNil)
   511  	err = gadget.Validate(ginfo, mod, &gadget.ValidationConstraints{
   512  		EncryptedData: true,
   513  	})
   514  	c.Assert(err, ErrorMatches, `gadget does not support encrypted data: volume "vol1" has no structure with system-save role`)
   515  }
   516  
   517  func (s *validateGadgetTestSuite) TestValidateEncryptionSupportHappy(c *C) {
   518  	makeSizedFile(c, filepath.Join(s.dir, "meta/gadget.yaml"), 0, []byte(gadgetYamlContentWithSave))
   519  	mod := &modelConstraints{systemSeed: true}
   520  	ginfo, err := gadget.ReadInfo(s.dir, mod)
   521  	c.Assert(err, IsNil)
   522  	err = gadget.Validate(ginfo, mod, &gadget.ValidationConstraints{
   523  		EncryptedData: true,
   524  	})
   525  	c.Assert(err, IsNil)
   526  }