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