github.com/tompreston/snapd@v0.0.0-20210817193607-954edfcb9611/seed/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 seed_test
    21  
    22  import (
    23  	"fmt"
    24  	"io/ioutil"
    25  	"os"
    26  	"path/filepath"
    27  
    28  	. "gopkg.in/check.v1"
    29  
    30  	"github.com/snapcore/snapd/asserts"
    31  	"github.com/snapcore/snapd/seed"
    32  	"github.com/snapcore/snapd/seed/seedtest"
    33  	"github.com/snapcore/snapd/snap"
    34  	"github.com/snapcore/snapd/snap/squashfs"
    35  	"github.com/snapcore/snapd/testutil"
    36  )
    37  
    38  type validateSuite struct {
    39  	testutil.BaseTest
    40  
    41  	*seedtest.TestingSeed16
    42  }
    43  
    44  var _ = Suite(&validateSuite{})
    45  
    46  var coreYaml = `name: core
    47  version: 1.0
    48  type: os
    49  `
    50  
    51  const snapdYaml = `name: snapd
    52  version: 1.0
    53  type: snapd
    54  `
    55  
    56  const packageCore18 = `
    57  name: core18
    58  version: 18.04
    59  type: base
    60  `
    61  
    62  func (s *validateSuite) SetUpTest(c *C) {
    63  	s.BaseTest.SetUpTest(c)
    64  	s.BaseTest.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {}))
    65  
    66  	s.TestingSeed16 = &seedtest.TestingSeed16{}
    67  	s.SetupAssertSigning("canonical")
    68  	s.Brands.Register("my-brand", brandPrivKey, map[string]interface{}{
    69  		"verification": "verified",
    70  	})
    71  
    72  	s.SeedDir = c.MkDir()
    73  
    74  	s.AddCleanup(seed.MockTrusted(s.StoreSigning.Trusted))
    75  
    76  	err := os.MkdirAll(s.SnapsDir(), 0755)
    77  	c.Assert(err, IsNil)
    78  	err = os.Mkdir(s.AssertsDir(), 0755)
    79  	c.Assert(err, IsNil)
    80  
    81  	modelChain := s.MakeModelAssertionChain("my-brand", "my-model", map[string]interface{}{
    82  		"classic": "true",
    83  	})
    84  	s.WriteAssertions("model.asserts", modelChain...)
    85  }
    86  
    87  func (s *validateSuite) makeSnapInSeed(c *C, snapYaml string) {
    88  	publisher := "canonical"
    89  	fname, decl, rev := s.MakeAssertedSnap(c, snapYaml, nil, snap.R(1), publisher)
    90  	err := os.Rename(filepath.Join(s.SnapsDir(), fname), filepath.Join(s.SnapsDir(), fmt.Sprintf("%s_%s.snap", decl.SnapName(), snap.R(1))))
    91  	c.Assert(err, IsNil)
    92  
    93  	acct, err := s.StoreSigning.Find(asserts.AccountType, map[string]string{"account-id": publisher})
    94  	c.Assert(err, IsNil)
    95  	s.WriteAssertions(fmt.Sprintf("%s.asserts", decl.SnapName()), rev, decl, acct)
    96  }
    97  
    98  func (s *validateSuite) makeSeedYaml(c *C, seedYaml string) string {
    99  	tmpf := filepath.Join(s.SeedDir, "seed.yaml")
   100  	err := ioutil.WriteFile(tmpf, []byte(seedYaml), 0644)
   101  	c.Assert(err, IsNil)
   102  	return tmpf
   103  }
   104  
   105  func (s *validateSuite) TestValidateFromYamlSnapHappy(c *C) {
   106  	s.makeSnapInSeed(c, coreYaml)
   107  	s.makeSnapInSeed(c, `name: gtk-common-themes
   108  version: 19.04`)
   109  	seedFn := s.makeSeedYaml(c, `
   110  snaps:
   111   - name: core
   112     channel: stable
   113     file: core_1.snap
   114   - name: gtk-common-themes
   115     channel: stable/ubuntu-19.04
   116     file: gtk-common-themes_1.snap
   117  `)
   118  
   119  	err := seed.ValidateFromYaml(seedFn)
   120  	c.Assert(err, IsNil)
   121  }
   122  
   123  func (s *validateSuite) TestValidateFromYamlSnapMissingBase(c *C) {
   124  	s.makeSnapInSeed(c, `name: need-base
   125  base: some-base
   126  version: 1.0`)
   127  	s.makeSnapInSeed(c, coreYaml)
   128  	seedFn := s.makeSeedYaml(c, `
   129  snaps:
   130   - name: core
   131     file: core_1.snap
   132   - name: need-base
   133     file: need-base_1.snap
   134  `)
   135  
   136  	err := seed.ValidateFromYaml(seedFn)
   137  	c.Assert(err, ErrorMatches, `cannot validate seed:
   138   - cannot use snap "need-base": base "some-base" is missing`)
   139  }
   140  
   141  func (s *validateSuite) TestValidateFromYamlSnapMissingDefaultProvider(c *C) {
   142  	s.makeSnapInSeed(c, coreYaml)
   143  	s.makeSnapInSeed(c, `name: need-df
   144  version: 1.0
   145  plugs:
   146   gtk-3-themes:
   147    interface: content
   148    default-provider: gtk-common-themes
   149  `)
   150  	seedFn := s.makeSeedYaml(c, `
   151  snaps:
   152   - name: core
   153     file: core_1.snap
   154   - name: need-df
   155     file: need-df_1.snap
   156  `)
   157  
   158  	err := seed.ValidateFromYaml(seedFn)
   159  	c.Assert(err, ErrorMatches, `cannot validate seed:
   160   - cannot use snap "need-df": default provider "gtk-common-themes" is missing`)
   161  }
   162  
   163  func (s *validateSuite) TestValidateFromYamlSnapSnapdHappy(c *C) {
   164  	s.makeSnapInSeed(c, snapdYaml)
   165  	s.makeSnapInSeed(c, packageCore18)
   166  	s.makeSnapInSeed(c, `name: some-snap
   167  version: 1.0
   168  base: core18
   169  `)
   170  	seedFn := s.makeSeedYaml(c, `
   171  snaps:
   172   - name: snapd
   173     file: snapd_1.snap
   174   - name: some-snap
   175     file: some-snap_1.snap
   176   - name: core18
   177     file: core18_1.snap
   178  `)
   179  
   180  	err := seed.ValidateFromYaml(seedFn)
   181  	c.Assert(err, IsNil)
   182  }
   183  
   184  func (s *validateSuite) TestValidateFromYamlSnapMissingCore(c *C) {
   185  	s.makeSnapInSeed(c, snapdYaml)
   186  	s.makeSnapInSeed(c, `name: some-snap
   187  version: 1.0`)
   188  	seedFn := s.makeSeedYaml(c, `
   189  snaps:
   190   - name: snapd
   191     file: snapd_1.snap
   192   - name: some-snap
   193     file: some-snap_1.snap
   194  `)
   195  
   196  	err := seed.ValidateFromYaml(seedFn)
   197  	c.Assert(err, ErrorMatches, `cannot validate seed:
   198   - cannot use snap "some-snap": required snap "core" missing`)
   199  }
   200  
   201  func (s *validateSuite) TestValidateFromYamlSnapMissingSnapdOrCore(c *C) {
   202  	s.makeSnapInSeed(c, packageCore18)
   203  	s.makeSnapInSeed(c, `name: some-snap
   204  version: 1.0
   205  base: core18`)
   206  	seedFn := s.makeSeedYaml(c, `
   207  snaps:
   208   - name: some-snap
   209     file: some-snap_1.snap
   210   - name: core18
   211     file: core18_1.snap
   212  `)
   213  
   214  	err := seed.ValidateFromYaml(seedFn)
   215  	c.Assert(err, ErrorMatches, `cannot validate seed:
   216   - essential snap core or snapd must be part of the seed`)
   217  }
   218  
   219  func (s *validateSuite) TestValidateFromYamlSnapMissingSnapd(c *C) {
   220  	modelChain := s.MakeModelAssertionChain("my-brand", "my-model", map[string]interface{}{
   221  		"classic":        "true",
   222  		"required-snaps": []interface{}{"snapd"},
   223  	})
   224  	s.WriteAssertions("model.asserts", modelChain...)
   225  
   226  	s.makeSnapInSeed(c, packageCore18)
   227  	s.makeSnapInSeed(c, `name: some-snap
   228  version: 1.0
   229  base: core18`)
   230  	seedFn := s.makeSeedYaml(c, `
   231  snaps:
   232   - name: some-snap
   233     file: some-snap_1.snap
   234   - name: core18
   235     file: core18_1.snap
   236  `)
   237  
   238  	err := seed.ValidateFromYaml(seedFn)
   239  	c.Assert(err, ErrorMatches, `cannot validate seed:
   240   - essential snap "snapd" required by the model is missing in the seed`)
   241  }
   242  
   243  func (s *validateSuite) makeBrokenSnap(c *C, snapYaml string) (snapPath string) {
   244  	snapBuildDir := c.MkDir()
   245  	metaSnapYaml := filepath.Join(snapBuildDir, "meta", "snap.yaml")
   246  	err := os.MkdirAll(filepath.Dir(metaSnapYaml), 0755)
   247  	c.Assert(err, IsNil)
   248  	err = ioutil.WriteFile(metaSnapYaml, []byte(snapYaml), 0644)
   249  	c.Assert(err, IsNil)
   250  
   251  	// need to build the snap "manually" pack.Snap() will do validation
   252  	snapPath = filepath.Join(c.MkDir(), "broken.snap")
   253  	d := squashfs.New(snapPath)
   254  	err = d.Build(snapBuildDir, nil)
   255  	c.Assert(err, IsNil)
   256  
   257  	return snapPath
   258  }
   259  
   260  func (s *validateSuite) TestValidateFromYamlSnapSnapInvalid(c *C) {
   261  	s.makeSnapInSeed(c, coreYaml)
   262  
   263  	// "version" is missing in this yaml
   264  	snapYaml := `name: some-snap-invalid-yaml`
   265  	snapPath := s.makeBrokenSnap(c, snapYaml)
   266  
   267  	// put the broken snap in place
   268  	dst := filepath.Join(s.SnapsDir(), "some-snap-invalid-yaml_1.snap")
   269  	err := os.Rename(snapPath, dst)
   270  	c.Assert(err, IsNil)
   271  
   272  	seedFn := s.makeSeedYaml(c, `
   273  snaps:
   274   - name: core
   275     file: core_1.snap
   276   - name: some-snap-invalid-yaml
   277     unasserted: true
   278     file: some-snap-invalid-yaml_1.snap
   279  `)
   280  
   281  	err = seed.ValidateFromYaml(seedFn)
   282  	c.Assert(err, ErrorMatches, `cannot validate seed:
   283   - cannot use snap "/.*/snaps/some-snap-invalid-yaml_1.snap": invalid snap version: cannot be empty`)
   284  }
   285  
   286  func (s *validateSuite) TestValidateFromYamlSnapMultipleErrors(c *C) {
   287  	s.makeSnapInSeed(c, coreYaml)
   288  	s.makeSnapInSeed(c, `name: need-df
   289  version: 1.0
   290  plugs:
   291   gtk-3-themes:
   292    interface: content
   293    default-provider: gtk-common-themes
   294  `)
   295  
   296  	// "version" is missing in this yaml
   297  	snapYaml := `name: some-snap-invalid-yaml`
   298  	snapPath := s.makeBrokenSnap(c, snapYaml)
   299  
   300  	// put the broken snap in place
   301  	dst := filepath.Join(s.SnapsDir(), "some-snap-invalid-yaml_1.snap")
   302  	err := os.Rename(snapPath, dst)
   303  	c.Assert(err, IsNil)
   304  
   305  	seedFn := s.makeSeedYaml(c, `
   306  snaps:
   307   - name: core
   308     file: core_1.snap
   309   - name: need-df
   310     file: need-df_1.snap
   311   - name: some-snap-invalid-yaml
   312     unasserted: true
   313     file: some-snap-invalid-yaml_1.snap
   314  `)
   315  
   316  	err = seed.ValidateFromYaml(seedFn)
   317  	c.Assert(err, ErrorMatches, `cannot validate seed:
   318   - cannot use snap "/.*/snaps/some-snap-invalid-yaml_1.snap": invalid snap version: cannot be empty
   319   - cannot use snap "need-df": default provider "gtk-common-themes" is missing`)
   320  }
   321  
   322  func (s *validateSuite) TestValidateFromYamlSnapSnapMissing(c *C) {
   323  	s.makeSnapInSeed(c, coreYaml)
   324  	seedFn := s.makeSeedYaml(c, `
   325  snaps:
   326   - name: core
   327     file: core_1.snap
   328   - name: some-snap
   329     file: some-snap_1.snap
   330  `)
   331  
   332  	err := seed.ValidateFromYaml(seedFn)
   333  	c.Assert(err, ErrorMatches, `cannot validate seed:
   334   - cannot compute snap "/.*/snaps/some-snap_1.snap" digest:.* no such file or directory`)
   335  }
   336  
   337  func (s *validateSuite) TestValidateFromYamlSnapMissingAssertions(c *C) {
   338  	s.makeSnapInSeed(c, snapdYaml)
   339  	s.makeSnapInSeed(c, packageCore18)
   340  	s.makeSnapInSeed(c, `name: some-snap
   341  version: 1.0
   342  base: core18
   343  `)
   344  	seedFn := s.makeSeedYaml(c, `
   345  snaps:
   346   - name: snapd
   347     file: snapd_1.snap
   348   - name: some-snap
   349     file: some-snap_1.snap
   350   - name: core18
   351     file: core18_1.snap
   352  `)
   353  
   354  	err := os.Remove(filepath.Join(s.AssertsDir(), "some-snap.asserts"))
   355  	c.Assert(err, IsNil)
   356  
   357  	err = seed.ValidateFromYaml(seedFn)
   358  	c.Assert(err, ErrorMatches, `cannot validate seed:
   359   - cannot find signatures with metadata for snap "some-snap" .*`)
   360  
   361  }
   362  
   363  func (s *validateSuite) TestValidateFromYamlDuplicatedSnap(c *C) {
   364  	s.makeSnapInSeed(c, coreYaml)
   365  	s.makeSnapInSeed(c, `name: gtk-common-themes
   366  version: 19.04`)
   367  	seedFn := s.makeSeedYaml(c, `
   368  snaps:
   369   - name: core
   370     channel: stable
   371     file: core_1.snap
   372   - name: gtk-common-themes
   373     channel: stable/ubuntu-19.04
   374     file: gtk-common-themes_1.snap
   375   - name: gtk-common-themes
   376     channel: stable/ubuntu-19.04
   377     file: gtk-common-themes_1.snap
   378  `)
   379  
   380  	err := seed.ValidateFromYaml(seedFn)
   381  	c.Assert(err, ErrorMatches, `cannot validate seed:
   382   - cannot read seed yaml: snap name "gtk-common-themes" must be unique`)
   383  }
   384  
   385  func (s *validateSuite) TestValidateFromYamlRegressionLP1825437(c *C) {
   386  	s.makeSnapInSeed(c, coreYaml)
   387  	s.makeSnapInSeed(c, `name: gtk-common-themes
   388  version: 19.04`)
   389  	seedFn := s.makeSeedYaml(c, `
   390  snaps:
   391   -
   392     name: core
   393     channel: stable
   394     file: core_1.snap
   395   -
   396   -
   397     name: gtk-common-themes
   398     channel: stable/ubuntu-19.04
   399     file: gtk-common-themes_1.snap
   400  `)
   401  
   402  	err := seed.ValidateFromYaml(seedFn)
   403  	c.Assert(err, ErrorMatches, `cannot validate seed:
   404   - cannot read seed yaml: empty element in seed`)
   405  }