gitee.com/mysnapcore/mysnapd@v0.1.0/daemon/api_validate_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 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 daemon_test
    21  
    22  import (
    23  	"fmt"
    24  	"net/http"
    25  	"net/url"
    26  	"sort"
    27  	"strings"
    28  
    29  	"gopkg.in/check.v1"
    30  
    31  	"gitee.com/mysnapcore/mysnapd/asserts"
    32  	"gitee.com/mysnapcore/mysnapd/asserts/assertstest"
    33  	"gitee.com/mysnapcore/mysnapd/asserts/snapasserts"
    34  	"gitee.com/mysnapcore/mysnapd/daemon"
    35  	"gitee.com/mysnapcore/mysnapd/overlord/assertstate"
    36  	"gitee.com/mysnapcore/mysnapd/overlord/assertstate/assertstatetest"
    37  	"gitee.com/mysnapcore/mysnapd/overlord/auth"
    38  	"gitee.com/mysnapcore/mysnapd/overlord/snapstate"
    39  	"gitee.com/mysnapcore/mysnapd/overlord/state"
    40  	"gitee.com/mysnapcore/mysnapd/snap"
    41  	"gitee.com/mysnapcore/mysnapd/snap/naming"
    42  	"gitee.com/mysnapcore/mysnapd/testutil"
    43  )
    44  
    45  var _ = check.Suite(&apiValidationSetsSuite{})
    46  
    47  type apiValidationSetsSuite struct {
    48  	apiBaseSuite
    49  
    50  	storeSigning              *assertstest.StoreStack
    51  	dev1Signing               *assertstest.SigningDB
    52  	dev1acct                  *asserts.Account
    53  	acct1Key                  *asserts.AccountKey
    54  	mockSeqFormingAssertionFn func(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error)
    55  }
    56  
    57  type byName []*snapasserts.InstalledSnap
    58  
    59  func (b byName) Len() int      { return len(b) }
    60  func (b byName) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
    61  func (b byName) Less(i, j int) bool {
    62  	return b[i].SnapName() < b[j].SnapName()
    63  }
    64  
    65  func (s *apiValidationSetsSuite) SetUpTest(c *check.C) {
    66  	s.apiBaseSuite.SetUpTest(c)
    67  	d := s.daemon(c)
    68  
    69  	s.expectAuthenticatedAccess()
    70  
    71  	restore := asserts.MockMaxSupportedFormat(asserts.ValidationSetType, 1)
    72  	s.AddCleanup(restore)
    73  
    74  	s.mockSeqFormingAssertionFn = nil
    75  
    76  	s.storeSigning = assertstest.NewStoreStack("can0nical", nil)
    77  
    78  	st := d.Overlord().State()
    79  	st.Lock()
    80  	snapstate.ReplaceStore(st, s)
    81  	assertstatetest.AddMany(st, s.storeSigning.StoreAccountKey(""))
    82  	st.Unlock()
    83  
    84  	s.dev1acct = assertstest.NewAccount(s.storeSigning, "developer1", nil, "")
    85  	c.Assert(s.storeSigning.Add(s.dev1acct), check.IsNil)
    86  
    87  	// developer signing
    88  	dev1PrivKey, _ := assertstest.GenerateKey(752)
    89  	s.acct1Key = assertstest.NewAccountKey(s.storeSigning, s.dev1acct, nil, dev1PrivKey.PublicKey(), "")
    90  
    91  	s.dev1Signing = assertstest.NewSigningDB(s.dev1acct.AccountID(), dev1PrivKey)
    92  	c.Assert(s.storeSigning.Add(s.acct1Key), check.IsNil)
    93  
    94  	d.Overlord().Loop()
    95  	s.AddCleanup(func() { d.Overlord().Stop() })
    96  }
    97  
    98  func (s *apiValidationSetsSuite) mockValidationSetsTracking(st *state.State) {
    99  	st.Set("validation-sets", map[string]interface{}{
   100  		fmt.Sprintf("%s/foo", s.dev1acct.AccountID()): map[string]interface{}{
   101  			"account-id": s.dev1acct.AccountID(),
   102  			"name":       "foo",
   103  			"mode":       assertstate.Enforce,
   104  			"pinned-at":  9,
   105  			"current":    99,
   106  		},
   107  		fmt.Sprintf("%s/baz", s.dev1acct.AccountID()): map[string]interface{}{
   108  			"account-id": s.dev1acct.AccountID(),
   109  			"name":       "baz",
   110  			"mode":       assertstate.Monitor,
   111  			"pinned-at":  0,
   112  			"current":    2,
   113  		},
   114  	})
   115  }
   116  
   117  func (s *apiValidationSetsSuite) mockAssert(c *check.C, name, sequence string) asserts.Assertion {
   118  	snaps := []interface{}{map[string]interface{}{
   119  		"id":       "yOqKhntON3vR7kwEbVPsILm7bUViPDzz",
   120  		"name":     "snap-b",
   121  		"presence": "required",
   122  		"revision": "1",
   123  	}}
   124  	headers := map[string]interface{}{
   125  		"authority-id": s.dev1acct.AccountID(),
   126  		"account-id":   s.dev1acct.AccountID(),
   127  		"name":         name,
   128  		"series":       "16",
   129  		"sequence":     sequence,
   130  		"revision":     "5",
   131  		"timestamp":    "2030-11-06T09:16:26Z",
   132  		"snaps":        snaps,
   133  	}
   134  	vs, err := s.dev1Signing.Sign(asserts.ValidationSetType, headers, nil, "")
   135  	c.Assert(err, check.IsNil)
   136  	return vs
   137  }
   138  
   139  func (s *apiValidationSetsSuite) SeqFormingAssertion(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error) {
   140  	s.pokeStateLock()
   141  	return s.mockSeqFormingAssertionFn(assertType, sequenceKey, sequence, user)
   142  }
   143  
   144  func (s *apiValidationSetsSuite) TestQueryValidationSetsErrors(c *check.C) {
   145  	s.mockSeqFormingAssertionFn = func(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error) {
   146  		return nil, &asserts.NotFoundError{
   147  			Type: assertType,
   148  		}
   149  	}
   150  
   151  	st := s.d.Overlord().State()
   152  	st.Lock()
   153  	s.mockValidationSetsTracking(st)
   154  	st.Unlock()
   155  
   156  	for i, tc := range []struct {
   157  		validationSet string
   158  		// sequence is normally an int, use string for passing invalid ones.
   159  		sequence string
   160  		message  string
   161  		status   int
   162  	}{
   163  		{
   164  			validationSet: "abc/Xfoo",
   165  			message:       `invalid name "Xfoo"`,
   166  			status:        400,
   167  		},
   168  		{
   169  			validationSet: "Xfoo/bar",
   170  			message:       `invalid account ID "Xfoo"`,
   171  			status:        400,
   172  		},
   173  		{
   174  			validationSet: "foo/foo",
   175  			message:       "validation set not found",
   176  			status:        404,
   177  		},
   178  		{
   179  			validationSet: "foo/bar",
   180  			sequence:      "1999",
   181  			message:       "validation set not found",
   182  			status:        404,
   183  		},
   184  		{
   185  			validationSet: "foo/bar",
   186  			sequence:      "x",
   187  			message:       "invalid sequence argument",
   188  			status:        400,
   189  		},
   190  		{
   191  			validationSet: "foo/bar",
   192  			sequence:      "-2",
   193  			message:       "invalid sequence argument: -2",
   194  			status:        400,
   195  		},
   196  	} {
   197  		q := url.Values{}
   198  		if tc.sequence != "" {
   199  			q.Set("sequence", tc.sequence)
   200  		}
   201  		req, err := http.NewRequest("GET", fmt.Sprintf("/v2/validation-sets/%s?%s", tc.validationSet, q.Encode()), nil)
   202  		c.Assert(err, check.IsNil)
   203  		rspe := s.errorReq(c, req, nil)
   204  		c.Check(rspe.Status, check.Equals, tc.status, check.Commentf("case #%d", i))
   205  		c.Check(rspe.Message, check.Matches, tc.message)
   206  	}
   207  }
   208  
   209  func (s *apiValidationSetsSuite) TestGetValidationSetsNone(c *check.C) {
   210  	req, err := http.NewRequest("GET", "/v2/validation-sets", nil)
   211  	c.Assert(err, check.IsNil)
   212  
   213  	rsp := s.syncReq(c, req, nil)
   214  	c.Assert(rsp.Status, check.Equals, 200)
   215  	res := rsp.Result.([]daemon.ValidationSetResult)
   216  	c.Check(res, check.HasLen, 0)
   217  }
   218  
   219  func (s *apiValidationSetsSuite) TestListValidationSets(c *check.C) {
   220  	st := s.d.Overlord().State()
   221  	st.Lock()
   222  	s.mockValidationSetsTracking(st)
   223  	assertstatetest.AddMany(st, s.dev1acct, s.acct1Key)
   224  	as := s.mockAssert(c, "foo", "9")
   225  	err := assertstate.Add(st, as)
   226  	c.Check(err, check.IsNil)
   227  	as = s.mockAssert(c, "baz", "2")
   228  	err = assertstate.Add(st, as)
   229  	st.Unlock()
   230  	c.Assert(err, check.IsNil)
   231  
   232  	req, err := http.NewRequest("GET", "/v2/validation-sets", nil)
   233  	c.Assert(err, check.IsNil)
   234  	rsp := s.syncReq(c, req, nil)
   235  	c.Assert(rsp.Status, check.Equals, 200)
   236  	res := rsp.Result.([]daemon.ValidationSetResult)
   237  	c.Check(res, check.DeepEquals, []daemon.ValidationSetResult{
   238  		{
   239  			AccountID: s.dev1acct.AccountID(),
   240  			Name:      "baz",
   241  			Mode:      "monitor",
   242  			Sequence:  2,
   243  			Valid:     false,
   244  		},
   245  		{
   246  			AccountID: s.dev1acct.AccountID(),
   247  			Name:      "foo",
   248  			PinnedAt:  9,
   249  			Mode:      "enforce",
   250  			Sequence:  99,
   251  			Valid:     false,
   252  		},
   253  	})
   254  }
   255  
   256  func (s *apiValidationSetsSuite) TestGetValidationSetOne(c *check.C) {
   257  	s.mockSeqFormingAssertionFn = func(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error) {
   258  		return nil, &asserts.NotFoundError{
   259  			Type: assertType,
   260  		}
   261  	}
   262  
   263  	st := s.d.Overlord().State()
   264  	st.Lock()
   265  	as := s.mockAssert(c, "baz", "2")
   266  	assertstatetest.AddMany(st, s.dev1acct, s.acct1Key, as)
   267  	s.mockValidationSetsTracking(st)
   268  	st.Unlock()
   269  
   270  	req, err := http.NewRequest("GET", fmt.Sprintf("/v2/validation-sets/%s/baz", s.dev1acct.AccountID()), nil)
   271  	c.Assert(err, check.IsNil)
   272  
   273  	rsp := s.syncReq(c, req, nil)
   274  	c.Assert(rsp.Status, check.Equals, 200)
   275  	res := rsp.Result.(daemon.ValidationSetResult)
   276  	c.Check(res, check.DeepEquals, daemon.ValidationSetResult{
   277  		AccountID: s.dev1acct.AccountID(),
   278  		Name:      "baz",
   279  		Mode:      "monitor",
   280  		Sequence:  2,
   281  		Valid:     false,
   282  	})
   283  }
   284  
   285  func (s *apiValidationSetsSuite) TestGetValidationSetPinned(c *check.C) {
   286  	q := url.Values{}
   287  	q.Set("sequence", "9")
   288  	req, err := http.NewRequest("GET", fmt.Sprintf("/v2/validation-sets/%s/foo?%s", s.dev1acct.AccountID(), q.Encode()), nil)
   289  	c.Assert(err, check.IsNil)
   290  
   291  	st := s.d.Overlord().State()
   292  	st.Lock()
   293  	as := s.mockAssert(c, "foo", "9")
   294  	assertstatetest.AddMany(st, s.dev1acct, s.acct1Key, as)
   295  	s.mockValidationSetsTracking(st)
   296  	st.Unlock()
   297  	c.Assert(err, check.IsNil)
   298  
   299  	rsp := s.syncReq(c, req, nil)
   300  	c.Assert(rsp.Status, check.Equals, 200)
   301  	res := rsp.Result.(daemon.ValidationSetResult)
   302  	c.Check(res, check.DeepEquals, daemon.ValidationSetResult{
   303  		AccountID: s.dev1acct.AccountID(),
   304  		Name:      "foo",
   305  		PinnedAt:  9,
   306  		Mode:      "enforce",
   307  		Sequence:  99,
   308  		Valid:     false,
   309  	})
   310  }
   311  
   312  func (s *apiValidationSetsSuite) TestGetValidationSetNotFound(c *check.C) {
   313  	s.mockSeqFormingAssertionFn = func(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error) {
   314  		return nil, &asserts.NotFoundError{
   315  			Type: assertType,
   316  		}
   317  	}
   318  
   319  	req, err := http.NewRequest("GET", "/v2/validation-sets/foo/other", nil)
   320  	c.Assert(err, check.IsNil)
   321  
   322  	st := s.d.Overlord().State()
   323  	st.Lock()
   324  	s.mockValidationSetsTracking(st)
   325  	st.Unlock()
   326  
   327  	rspe := s.errorReq(c, req, nil)
   328  	c.Assert(rspe.Status, check.Equals, 404)
   329  	c.Check(string(rspe.Kind), check.Equals, "validation-set-not-found")
   330  	c.Check(rspe.Value, check.DeepEquals, map[string]interface{}{
   331  		"account-id": "foo",
   332  		"name":       "other",
   333  	})
   334  }
   335  
   336  var validationSetAssertion = []byte("type: validation-set\n" +
   337  	"format: 1\n" +
   338  	"authority-id: foo\n" +
   339  	"account-id: foo\n" +
   340  	"name: other\n" +
   341  	"sequence: 2\n" +
   342  	"revision: 5\n" +
   343  	"series: 16\n" +
   344  	"snaps:\n" +
   345  	"  -\n" +
   346  	"    id: yOqKhntON3vR7kwEbVPsILm7bUViPDzz\n" +
   347  	"    name: snap-b\n" +
   348  	"    presence: required\n" +
   349  	"    revision: 1\n" +
   350  	"timestamp: 2020-11-06T09:16:26Z\n" +
   351  	"sign-key-sha3-384: 7bbncP0c4RcufwReeiylCe0S7IMCn-tHLNSCgeOVmV3K-7_MzpAHgJDYeOjldefE\n\n" +
   352  	"AXNpZw==")
   353  
   354  func (s *apiValidationSetsSuite) TestGetValidationSetLatestFromRemote(c *check.C) {
   355  	s.mockSeqFormingAssertionFn = func(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error) {
   356  		c.Assert(assertType, check.NotNil)
   357  		c.Assert(assertType.Name, check.Equals, "validation-set")
   358  		// no sequence number element, querying the latest
   359  		c.Assert(sequenceKey, check.DeepEquals, []string{"16", "foo", "other"})
   360  		c.Assert(sequence, check.Equals, 0)
   361  		as, err := asserts.Decode(validationSetAssertion)
   362  		c.Assert(err, check.IsNil)
   363  		// validity
   364  		c.Assert(as.Type().Name, check.Equals, "validation-set")
   365  		return as, nil
   366  	}
   367  
   368  	restore := daemon.MockCheckInstalledSnaps(func(vsets *snapasserts.ValidationSets, snaps []*snapasserts.InstalledSnap, ignoreValidation map[string]bool) error {
   369  		c.Assert(vsets, check.NotNil)
   370  		sort.Sort(byName(snaps))
   371  		c.Assert(snaps, check.DeepEquals, []*snapasserts.InstalledSnap{
   372  			{
   373  				SnapRef:  naming.NewSnapRef("snap-a", "snapaid"),
   374  				Revision: snap.R(2),
   375  			},
   376  			{
   377  				SnapRef:  naming.NewSnapRef("snap-b", "snapbid"),
   378  				Revision: snap.R(4),
   379  			},
   380  		})
   381  		c.Assert(ignoreValidation, check.IsNil)
   382  		// nil indicates successful validation
   383  		return nil
   384  	})
   385  	defer restore()
   386  
   387  	req, err := http.NewRequest("GET", "/v2/validation-sets/foo/other", nil)
   388  	c.Assert(err, check.IsNil)
   389  
   390  	st := s.d.Overlord().State()
   391  	st.Lock()
   392  	s.mockValidationSetsTracking(st)
   393  
   394  	snapstate.Set(st, "snap-a", &snapstate.SnapState{
   395  		Active:   true,
   396  		Sequence: []*snap.SideInfo{{RealName: "snap-a", Revision: snap.R(2), SnapID: "snapaid"}},
   397  		Current:  snap.R(2),
   398  	})
   399  	snapstate.Set(st, "snap-b", &snapstate.SnapState{
   400  		Active:   true,
   401  		Sequence: []*snap.SideInfo{{RealName: "snap-b", Revision: snap.R(4), SnapID: "snapbid"}},
   402  		Current:  snap.R(4),
   403  	})
   404  
   405  	st.Unlock()
   406  
   407  	rsp := s.syncReq(c, req, nil)
   408  	c.Assert(rsp.Status, check.Equals, 200)
   409  	res := rsp.Result.(daemon.ValidationSetResult)
   410  	c.Check(res, check.DeepEquals, daemon.ValidationSetResult{
   411  		AccountID: "foo",
   412  		Name:      "other",
   413  		Sequence:  2,
   414  		Valid:     true,
   415  	})
   416  }
   417  
   418  func (s *apiValidationSetsSuite) TestGetValidationSetLatestFromRemoteValidationFails(c *check.C) {
   419  	s.mockSeqFormingAssertionFn = func(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error) {
   420  		as, err := asserts.Decode(validationSetAssertion)
   421  		c.Assert(err, check.IsNil)
   422  		return as, nil
   423  	}
   424  	restore := daemon.MockCheckInstalledSnaps(func(vsets *snapasserts.ValidationSets, snaps []*snapasserts.InstalledSnap, ignoreValidation map[string]bool) error {
   425  		return &snapasserts.ValidationSetsValidationError{}
   426  	})
   427  	defer restore()
   428  
   429  	req, err := http.NewRequest("GET", "/v2/validation-sets/foo/other", nil)
   430  	c.Assert(err, check.IsNil)
   431  	rsp := s.syncReq(c, req, nil)
   432  	c.Assert(rsp.Status, check.Equals, 200)
   433  
   434  	res := rsp.Result.(daemon.ValidationSetResult)
   435  	c.Check(res, check.DeepEquals, daemon.ValidationSetResult{
   436  		AccountID: "foo",
   437  		Name:      "other",
   438  		Sequence:  2,
   439  		Valid:     false,
   440  	})
   441  }
   442  
   443  func (s *apiValidationSetsSuite) TestGetValidationSetLatestFromRemoteRealValidation(c *check.C) {
   444  	s.mockSeqFormingAssertionFn = func(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error) {
   445  		as, err := asserts.Decode(validationSetAssertion)
   446  		c.Assert(err, check.IsNil)
   447  		return as, nil
   448  	}
   449  
   450  	st := s.d.Overlord().State()
   451  
   452  	for _, tc := range []struct {
   453  		revision                 snap.Revision
   454  		expectedValidationStatus bool
   455  	}{
   456  		// required at revision 1 per validationSetAssertion, so it's valid
   457  		{snap.R(1), true},
   458  		// but revision 2 is not valid
   459  		{snap.R(2), false},
   460  	} {
   461  		st.Lock()
   462  		snapstate.Set(st, "snap-b", &snapstate.SnapState{
   463  			Active:   true,
   464  			Sequence: []*snap.SideInfo{{RealName: "snap-b", Revision: tc.revision, SnapID: "yOqKhntON3vR7kwEbVPsILm7bUViPDzz"}},
   465  			Current:  tc.revision,
   466  		})
   467  		st.Unlock()
   468  
   469  		req, err := http.NewRequest("GET", "/v2/validation-sets/foo/other", nil)
   470  		c.Assert(err, check.IsNil)
   471  		rsp := s.syncReq(c, req, nil)
   472  		c.Assert(rsp.Status, check.Equals, 200)
   473  
   474  		res := rsp.Result.(daemon.ValidationSetResult)
   475  		c.Check(res, check.DeepEquals, daemon.ValidationSetResult{
   476  			AccountID: "foo",
   477  			Name:      "other",
   478  			Sequence:  2,
   479  			Valid:     tc.expectedValidationStatus,
   480  		})
   481  	}
   482  }
   483  
   484  func (s *apiValidationSetsSuite) TestGetValidationSetSpecificSequenceFromRemote(c *check.C) {
   485  	s.mockSeqFormingAssertionFn = func(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error) {
   486  		c.Assert(assertType, check.NotNil)
   487  		c.Assert(assertType.Name, check.Equals, "validation-set")
   488  		c.Assert(sequenceKey, check.DeepEquals, []string{"16", "foo", "other"})
   489  		c.Assert(sequence, check.Equals, 2)
   490  		as, err := asserts.Decode(validationSetAssertion)
   491  		c.Assert(err, check.IsNil)
   492  		return as, nil
   493  	}
   494  
   495  	restore := daemon.MockCheckInstalledSnaps(func(vsets *snapasserts.ValidationSets, snaps []*snapasserts.InstalledSnap, ignoreValidation map[string]bool) error {
   496  		c.Assert(vsets, check.NotNil)
   497  		sort.Sort(byName(snaps))
   498  		c.Assert(snaps, check.DeepEquals, []*snapasserts.InstalledSnap{
   499  			{
   500  				SnapRef:  naming.NewSnapRef("snap-a", "snapaid"),
   501  				Revision: snap.R(33),
   502  			},
   503  		})
   504  		c.Assert(ignoreValidation, check.IsNil)
   505  		// nil indicates successful validation
   506  		return nil
   507  	})
   508  	defer restore()
   509  
   510  	q := url.Values{}
   511  	q.Set("sequence", "2")
   512  	req, err := http.NewRequest("GET", "/v2/validation-sets/foo/other?"+q.Encode(), nil)
   513  	c.Assert(err, check.IsNil)
   514  
   515  	st := s.d.Overlord().State()
   516  	st.Lock()
   517  	s.mockValidationSetsTracking(st)
   518  
   519  	snapstate.Set(st, "snap-a", &snapstate.SnapState{
   520  		Active:   true,
   521  		Sequence: []*snap.SideInfo{{RealName: "snap-a", Revision: snap.R(33), SnapID: "snapaid"}},
   522  		Current:  snap.R(33),
   523  	})
   524  
   525  	st.Unlock()
   526  
   527  	rsp := s.syncReq(c, req, nil)
   528  	c.Assert(rsp.Status, check.Equals, 200)
   529  	res := rsp.Result.(daemon.ValidationSetResult)
   530  	c.Check(res, check.DeepEquals, daemon.ValidationSetResult{
   531  		AccountID: "foo",
   532  		Name:      "other",
   533  		Sequence:  2,
   534  		Valid:     true,
   535  	})
   536  }
   537  
   538  func (s *apiValidationSetsSuite) TestGetValidationSetFromRemoteFallbackToLocalAssertion(c *check.C) {
   539  	s.mockSeqFormingAssertionFn = func(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error) {
   540  		// not found in the store
   541  		return nil, &asserts.NotFoundError{
   542  			Type: assertType,
   543  		}
   544  	}
   545  	restore := daemon.MockCheckInstalledSnaps(func(vsets *snapasserts.ValidationSets, snaps []*snapasserts.InstalledSnap, ignoreValidation map[string]bool) error {
   546  		// nil indicates successful validation
   547  		return nil
   548  	})
   549  	defer restore()
   550  
   551  	st := s.d.Overlord().State()
   552  	st.Lock()
   553  	// assertion available in the local db (from snap ack)
   554  	vs := s.mockAssert(c, "bar", "2")
   555  	assertstatetest.AddMany(st, s.dev1acct, s.acct1Key, vs)
   556  	st.Unlock()
   557  
   558  	q := url.Values{}
   559  	q.Set("sequence", "2")
   560  	req, err := http.NewRequest("GET", fmt.Sprintf("/v2/validation-sets/%s/bar?%s", s.dev1acct.AccountID(), q.Encode()), nil)
   561  	c.Assert(err, check.IsNil)
   562  
   563  	rsp := s.syncReq(c, req, nil)
   564  	c.Assert(rsp.Status, check.Equals, 200)
   565  	res := rsp.Result.(daemon.ValidationSetResult)
   566  	c.Check(res, check.DeepEquals, daemon.ValidationSetResult{
   567  		AccountID: s.dev1acct.AccountID(),
   568  		Name:      "bar",
   569  		Sequence:  2,
   570  		Valid:     true,
   571  	})
   572  }
   573  
   574  func (s *apiValidationSetsSuite) TestGetValidationSetPinnedNotFound(c *check.C) {
   575  	s.mockSeqFormingAssertionFn = func(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error) {
   576  		return nil, &asserts.NotFoundError{
   577  			Type: assertType,
   578  		}
   579  	}
   580  
   581  	q := url.Values{}
   582  	q.Set("sequence", "333")
   583  	req, err := http.NewRequest("GET", "/v2/validation-sets/foo/bar?"+q.Encode(), nil)
   584  	c.Assert(err, check.IsNil)
   585  
   586  	st := s.d.Overlord().State()
   587  	st.Lock()
   588  	s.mockValidationSetsTracking(st)
   589  	st.Unlock()
   590  
   591  	rspe := s.errorReq(c, req, nil)
   592  	c.Assert(rspe.Status, check.Equals, 404)
   593  	c.Check(string(rspe.Kind), check.Equals, "validation-set-not-found")
   594  	c.Check(rspe.Value, check.DeepEquals, map[string]interface{}{
   595  		"account-id": "foo",
   596  		"name":       "bar",
   597  		"sequence":   333,
   598  	})
   599  }
   600  
   601  func (s *apiValidationSetsSuite) TestApplyValidationSetMonitorModePinnedLocalOnly(c *check.C) {
   602  	st := s.d.Overlord().State()
   603  	st.Lock()
   604  	s.mockValidationSetsTracking(st)
   605  	assertstatetest.AddMany(st, s.dev1acct, s.acct1Key)
   606  	as := s.mockAssert(c, "bar", "99")
   607  	err := assertstate.Add(st, as)
   608  	st.Unlock()
   609  	c.Assert(err, check.IsNil)
   610  
   611  	var called int
   612  	restore := daemon.MockAssertstateMonitorValidationSet(func(st *state.State, accountID, name string, sequence, userID int) (*assertstate.ValidationSetTracking, error) {
   613  		c.Assert(accountID, check.Equals, s.dev1acct.AccountID())
   614  		c.Assert(name, check.Equals, "bar")
   615  		c.Assert(sequence, check.Equals, 99)
   616  		called++
   617  		return &assertstate.ValidationSetTracking{AccountID: accountID, Name: name, PinnedAt: 99, Current: 99999}, nil
   618  	})
   619  	defer restore()
   620  
   621  	restore = daemon.MockCheckInstalledSnaps(func(vsets *snapasserts.ValidationSets, snaps []*snapasserts.InstalledSnap, ignoreValidation map[string]bool) error {
   622  		// nil indicates successful validation
   623  		return nil
   624  	})
   625  	defer restore()
   626  
   627  	body := `{"action":"apply","mode":"monitor", "sequence":99}`
   628  	req, err := http.NewRequest("POST", fmt.Sprintf("/v2/validation-sets/%s/bar", s.dev1acct.AccountID()), strings.NewReader(body))
   629  	c.Assert(err, check.IsNil)
   630  
   631  	rsp := s.syncReq(c, req, nil)
   632  	c.Assert(rsp.Status, check.Equals, 200)
   633  	res := rsp.Result.(daemon.ValidationSetResult)
   634  	c.Check(res, check.DeepEquals, daemon.ValidationSetResult{
   635  		AccountID: s.dev1acct.AccountID(),
   636  		Name:      "bar",
   637  		Mode:      "monitor",
   638  		PinnedAt:  99,
   639  		Sequence:  99999,
   640  		Valid:     true,
   641  	})
   642  	c.Check(called, check.Equals, 1)
   643  }
   644  
   645  func (s *apiValidationSetsSuite) TestApplyValidationSetMonitorModeError(c *check.C) {
   646  	restore := daemon.MockAssertstateMonitorValidationSet(func(st *state.State, accountID, name string, sequence, userID int) (*assertstate.ValidationSetTracking, error) {
   647  		return nil, fmt.Errorf("boom")
   648  	})
   649  	defer restore()
   650  
   651  	body := `{"action":"apply","mode":"monitor"}`
   652  	req, err := http.NewRequest("POST", fmt.Sprintf("/v2/validation-sets/%s/bar", s.dev1acct.AccountID()), strings.NewReader(body))
   653  	c.Assert(err, check.IsNil)
   654  
   655  	rspe := s.errorReq(c, req, nil)
   656  	c.Assert(rspe.Status, check.Equals, 400)
   657  	c.Check(rspe.Message, check.Equals, fmt.Sprintf(`cannot get validation set assertion for %s/bar: boom`, s.dev1acct.AccountID()))
   658  }
   659  
   660  func (s *apiValidationSetsSuite) TestForgetValidationSet(c *check.C) {
   661  	st := s.d.Overlord().State()
   662  
   663  	for i, sequence := range []int{0, 9} {
   664  		st.Lock()
   665  		s.mockValidationSetsTracking(st)
   666  		st.Unlock()
   667  
   668  		var body string
   669  		if sequence != 0 {
   670  			body = fmt.Sprintf(`{"action":"forget", "sequence":%d}`, sequence)
   671  		} else {
   672  			body = `{"action":"forget"}`
   673  		}
   674  
   675  		var tr assertstate.ValidationSetTracking
   676  
   677  		st.Lock()
   678  		// validity, it exists before removing
   679  		err := assertstate.GetValidationSet(st, s.dev1acct.AccountID(), "foo", &tr)
   680  		st.Unlock()
   681  		c.Assert(err, check.IsNil)
   682  		c.Check(tr.AccountID, check.Equals, s.dev1acct.AccountID())
   683  		c.Check(tr.Name, check.Equals, "foo")
   684  
   685  		req, err := http.NewRequest("POST", fmt.Sprintf("/v2/validation-sets/%s/foo", s.dev1acct.AccountID()), strings.NewReader(body))
   686  		c.Assert(err, check.IsNil)
   687  		rsp := s.syncReq(c, req, nil)
   688  		c.Assert(rsp.Status, check.Equals, 200, check.Commentf("case #%d", i))
   689  
   690  		// after forget it's removed
   691  		st.Lock()
   692  		err = assertstate.GetValidationSet(st, s.dev1acct.AccountID(), "foo", &tr)
   693  		st.Unlock()
   694  		c.Assert(err, testutil.ErrorIs, state.ErrNoState)
   695  
   696  		// and forget again fails
   697  		req, err = http.NewRequest("POST", fmt.Sprintf("/v2/validation-sets/%s/foo", s.dev1acct.AccountID()), strings.NewReader(body))
   698  		c.Assert(err, check.IsNil)
   699  		rspe := s.errorReq(c, req, nil)
   700  		c.Assert(rspe.Status, check.Equals, 404, check.Commentf("case #%d", i))
   701  	}
   702  }
   703  
   704  func (s *apiValidationSetsSuite) TestApplyValidationSetsErrors(c *check.C) {
   705  	st := s.d.Overlord().State()
   706  	st.Lock()
   707  	s.mockValidationSetsTracking(st)
   708  	st.Unlock()
   709  
   710  	for i, tc := range []struct {
   711  		validationSet string
   712  		mode          string
   713  		// sequence is normally an int, use string for passing invalid ones.
   714  		sequence string
   715  		message  string
   716  		status   int
   717  	}{
   718  		{
   719  			validationSet: "0/zzz",
   720  			mode:          "monitor",
   721  			message:       `invalid account ID "0"`,
   722  			status:        400,
   723  		},
   724  		{
   725  			validationSet: "Xfoo/bar",
   726  			mode:          "monitor",
   727  			message:       `invalid account ID "Xfoo"`,
   728  			status:        400,
   729  		},
   730  		{
   731  			validationSet: "foo/Xabc",
   732  			mode:          "monitor",
   733  			message:       `invalid name "Xabc"`,
   734  			status:        400,
   735  		},
   736  		{
   737  			validationSet: "foo/bar",
   738  			sequence:      "x",
   739  			message:       "cannot decode request body into validation set action: invalid character 'x' looking for beginning of value",
   740  			status:        400,
   741  		},
   742  		{
   743  			validationSet: "foo/bar",
   744  			mode:          "bad",
   745  			message:       `invalid mode "bad"`,
   746  			status:        400,
   747  		},
   748  		{
   749  			validationSet: "foo/bar",
   750  			sequence:      "-1",
   751  			mode:          "monitor",
   752  			message:       `invalid sequence argument: -1`,
   753  			status:        400,
   754  		},
   755  	} {
   756  		var body string
   757  		if tc.sequence != "" {
   758  			body = fmt.Sprintf(`{"action":"apply","mode":"%s", "sequence":%s}`, tc.mode, tc.sequence)
   759  		} else {
   760  			body = fmt.Sprintf(`{"action":"apply","mode":"%s"}`, tc.mode)
   761  		}
   762  		req, err := http.NewRequest("POST", fmt.Sprintf("/v2/validation-sets/%s", tc.validationSet), strings.NewReader(body))
   763  		c.Assert(err, check.IsNil)
   764  		rspe := s.errorReq(c, req, nil)
   765  		c.Check(rspe.Status, check.Equals, tc.status, check.Commentf("case #%d", i))
   766  		c.Check(rspe.Message, check.Matches, tc.message)
   767  	}
   768  }
   769  
   770  func (s *apiValidationSetsSuite) TestApplyValidationSetUnsupportedAction(c *check.C) {
   771  	body := `{"action":"baz","mode":"monitor"}`
   772  
   773  	req, err := http.NewRequest("POST", "/v2/validation-sets/foo/bar", strings.NewReader(body))
   774  	c.Assert(err, check.IsNil)
   775  
   776  	rspe := s.errorReq(c, req, nil)
   777  	c.Check(rspe.Status, check.Equals, 400)
   778  	c.Check(rspe.Message, check.Matches, `unsupported action "baz"`)
   779  }
   780  
   781  func (s *apiValidationSetsSuite) TestApplyValidationSetEnforceMode(c *check.C) {
   782  	st := s.d.Overlord().State()
   783  	st.Lock()
   784  	defer st.Unlock()
   785  
   786  	s.mockValidationSetsTracking(st)
   787  	assertstatetest.AddMany(st, s.dev1acct, s.acct1Key)
   788  	as := s.mockAssert(c, "bar", "99")
   789  	err := assertstate.Add(st, as)
   790  	c.Assert(err, check.IsNil)
   791  
   792  	var called int
   793  	restore := daemon.MockAssertstateFetchEnforceValidationSet(func(st *state.State, accountID, name string, sequence int, userID int, snaps []*snapasserts.InstalledSnap, ignoreValidation map[string]bool) (*assertstate.ValidationSetTracking, error) {
   794  		c.Check(ignoreValidation, check.HasLen, 0)
   795  		c.Assert(accountID, check.Equals, s.dev1acct.AccountID())
   796  		c.Assert(name, check.Equals, "bar")
   797  		c.Assert(sequence, check.Equals, 0)
   798  		c.Check(userID, check.Equals, 0)
   799  		called++
   800  		return &assertstate.ValidationSetTracking{AccountID: accountID, Name: name, Mode: assertstate.Enforce, Current: 99}, nil
   801  	})
   802  	defer restore()
   803  
   804  	snapstate.Set(st, "snap-b", &snapstate.SnapState{
   805  		Active:   true,
   806  		Sequence: []*snap.SideInfo{{RealName: "snap-b", Revision: snap.R(1), SnapID: "yOqKhntON3vR7kwEbVPsILm7bUViPDzz"}},
   807  		Current:  snap.R(1),
   808  	})
   809  
   810  	assertstatetest.AddMany(st, s.dev1acct, s.acct1Key)
   811  
   812  	st.Unlock()
   813  	defer st.Lock()
   814  	body := `{"action":"apply","mode":"enforce"}`
   815  	req, err := http.NewRequest("POST", fmt.Sprintf("/v2/validation-sets/%s/bar", s.dev1acct.AccountID()), strings.NewReader(body))
   816  	c.Assert(err, check.IsNil)
   817  
   818  	rsp := s.syncReq(c, req, nil)
   819  	c.Assert(rsp.Status, check.Equals, 200)
   820  	res := rsp.Result.(daemon.ValidationSetResult)
   821  	c.Check(res, check.DeepEquals, daemon.ValidationSetResult{
   822  		AccountID: s.dev1acct.AccountID(),
   823  		Name:      "bar",
   824  		Mode:      "enforce",
   825  		Sequence:  99,
   826  		Valid:     true,
   827  	})
   828  	c.Check(called, check.Equals, 1)
   829  }
   830  
   831  func (s *apiValidationSetsSuite) TestApplyValidationSetEnforceModeIgnoreValidationOK(c *check.C) {
   832  	st := s.d.Overlord().State()
   833  	st.Lock()
   834  	defer st.Unlock()
   835  
   836  	s.mockValidationSetsTracking(st)
   837  	assertstatetest.AddMany(st, s.dev1acct, s.acct1Key)
   838  	as := s.mockAssert(c, "bar", "99")
   839  	err := assertstate.Add(st, as)
   840  	c.Assert(err, check.IsNil)
   841  
   842  	var called int
   843  	restore := daemon.MockAssertstateFetchEnforceValidationSet(func(st *state.State, accountID, name string, sequence int, userID int, snaps []*snapasserts.InstalledSnap, ignoreValidation map[string]bool) (*assertstate.ValidationSetTracking, error) {
   844  		c.Check(ignoreValidation, check.DeepEquals, map[string]bool{"snap-b": true})
   845  		c.Check(snaps, testutil.DeepUnsortedMatches, []*snapasserts.InstalledSnap{
   846  			snapasserts.NewInstalledSnap("snap-b", "yOqKhntON3vR7kwEbVPsILm7bUViPDzz", snap.R("1"))})
   847  		c.Assert(accountID, check.Equals, s.dev1acct.AccountID())
   848  		c.Assert(name, check.Equals, "bar")
   849  		c.Assert(sequence, check.Equals, 0)
   850  		c.Check(userID, check.Equals, 0)
   851  		called++
   852  		return &assertstate.ValidationSetTracking{AccountID: accountID, Name: name, Mode: assertstate.Enforce, Current: 99}, nil
   853  	})
   854  	defer restore()
   855  
   856  	snapstate.Set(st, "snap-b", &snapstate.SnapState{
   857  		Active:   true,
   858  		Sequence: []*snap.SideInfo{{RealName: "snap-b", Revision: snap.R(1), SnapID: "yOqKhntON3vR7kwEbVPsILm7bUViPDzz"}},
   859  		Current:  snap.R(1),
   860  		Flags:    snapstate.Flags{IgnoreValidation: true},
   861  	})
   862  
   863  	assertstatetest.AddMany(st, s.dev1acct, s.acct1Key)
   864  
   865  	st.Unlock()
   866  	defer st.Lock()
   867  	body := `{"action":"apply","mode":"enforce"}`
   868  	req, err := http.NewRequest("POST", fmt.Sprintf("/v2/validation-sets/%s/bar", s.dev1acct.AccountID()), strings.NewReader(body))
   869  	c.Assert(err, check.IsNil)
   870  
   871  	rsp := s.syncReq(c, req, nil)
   872  	c.Assert(rsp.Status, check.Equals, 200)
   873  	res := rsp.Result.(daemon.ValidationSetResult)
   874  	c.Check(res, check.DeepEquals, daemon.ValidationSetResult{
   875  		AccountID: s.dev1acct.AccountID(),
   876  		Name:      "bar",
   877  		Mode:      "enforce",
   878  		Sequence:  99,
   879  		Valid:     true,
   880  	})
   881  	c.Check(called, check.Equals, 1)
   882  }
   883  
   884  func (s *apiValidationSetsSuite) TestApplyValidationSetEnforceModeSpecificSequence(c *check.C) {
   885  	st := s.d.Overlord().State()
   886  	st.Lock()
   887  	defer st.Unlock()
   888  
   889  	s.mockValidationSetsTracking(st)
   890  	assertstatetest.AddMany(st, s.dev1acct, s.acct1Key)
   891  	as := s.mockAssert(c, "bar", "5")
   892  	err := assertstate.Add(st, as)
   893  	c.Assert(err, check.IsNil)
   894  
   895  	var called int
   896  	restore := daemon.MockAssertstateFetchEnforceValidationSet(func(st *state.State, accountID, name string, sequence int, userID int, snaps []*snapasserts.InstalledSnap, ignoreValidation map[string]bool) (*assertstate.ValidationSetTracking, error) {
   897  		c.Assert(accountID, check.Equals, s.dev1acct.AccountID())
   898  		c.Assert(name, check.Equals, "bar")
   899  		c.Assert(sequence, check.Equals, 5)
   900  		c.Check(userID, check.Equals, 0)
   901  		called++
   902  		return &assertstate.ValidationSetTracking{AccountID: accountID, Name: name, Mode: assertstate.Enforce, PinnedAt: 5, Current: 5}, nil
   903  	})
   904  	defer restore()
   905  
   906  	snapstate.Set(st, "snap-b", &snapstate.SnapState{
   907  		Active:   true,
   908  		Sequence: []*snap.SideInfo{{RealName: "snap-b", Revision: snap.R(1), SnapID: "yOqKhntON3vR7kwEbVPsILm7bUViPDzz"}},
   909  		Current:  snap.R(1),
   910  	})
   911  
   912  	assertstatetest.AddMany(st, s.dev1acct, s.acct1Key)
   913  
   914  	st.Unlock()
   915  	defer st.Lock()
   916  	body := `{"action":"apply","mode":"enforce","sequence":5}`
   917  	req, err := http.NewRequest("POST", fmt.Sprintf("/v2/validation-sets/%s/bar", s.dev1acct.AccountID()), strings.NewReader(body))
   918  	c.Assert(err, check.IsNil)
   919  
   920  	rsp := s.syncReq(c, req, nil)
   921  	c.Assert(rsp.Status, check.Equals, 200)
   922  	res := rsp.Result.(daemon.ValidationSetResult)
   923  	c.Check(res, check.DeepEquals, daemon.ValidationSetResult{
   924  		AccountID: s.dev1acct.AccountID(),
   925  		Name:      "bar",
   926  		Mode:      "enforce",
   927  		PinnedAt:  5,
   928  		Sequence:  5,
   929  		Valid:     true,
   930  	})
   931  	c.Check(called, check.Equals, 1)
   932  }
   933  
   934  func (s *apiValidationSetsSuite) TestApplyValidationSetEnforceModeError(c *check.C) {
   935  	restore := daemon.MockAssertstateFetchEnforceValidationSet(func(st *state.State, accountID, name string, sequence int, userID int, snaps []*snapasserts.InstalledSnap, ignoreValidation map[string]bool) (*assertstate.ValidationSetTracking, error) {
   936  		return nil, fmt.Errorf("boom")
   937  	})
   938  	defer restore()
   939  
   940  	st := s.d.Overlord().State()
   941  	st.Lock()
   942  	defer st.Unlock()
   943  
   944  	snapstate.Set(st, "snap-b", &snapstate.SnapState{
   945  		Active:   true,
   946  		Sequence: []*snap.SideInfo{{RealName: "snap-b", Revision: snap.R(1), SnapID: "yOqKhntON3vR7kwEbVPsILm7bUViPDzz"}},
   947  		Current:  snap.R(1),
   948  	})
   949  
   950  	assertstatetest.AddMany(st, s.dev1acct, s.acct1Key)
   951  
   952  	st.Unlock()
   953  	defer st.Lock()
   954  	body := `{"action":"apply","mode":"enforce"}`
   955  	req, err := http.NewRequest("POST", fmt.Sprintf("/v2/validation-sets/%s/bar", s.dev1acct.AccountID()), strings.NewReader(body))
   956  	c.Assert(err, check.IsNil)
   957  
   958  	rspe := s.errorReq(c, req, nil)
   959  	c.Assert(rspe.Status, check.Equals, 400)
   960  	c.Check(string(rspe.Message), check.Equals, "cannot enforce validation set: boom")
   961  }