github.com/stolowski/snapd@v0.0.0-20210407085831-115137ce5a22/daemon/api_model_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 daemon_test
    21  
    22  import (
    23  	"bytes"
    24  	"encoding/json"
    25  	"io"
    26  	"net/http"
    27  	"net/http/httptest"
    28  	"time"
    29  
    30  	"gopkg.in/check.v1"
    31  
    32  	"github.com/snapcore/snapd/asserts"
    33  	"github.com/snapcore/snapd/asserts/assertstest"
    34  	"github.com/snapcore/snapd/client"
    35  	"github.com/snapcore/snapd/daemon"
    36  	"github.com/snapcore/snapd/overlord/assertstate/assertstatetest"
    37  	"github.com/snapcore/snapd/overlord/auth"
    38  	"github.com/snapcore/snapd/overlord/devicestate"
    39  	"github.com/snapcore/snapd/overlord/devicestate/devicestatetest"
    40  	"github.com/snapcore/snapd/overlord/hookstate"
    41  	"github.com/snapcore/snapd/overlord/state"
    42  )
    43  
    44  var modelDefaults = map[string]interface{}{
    45  	"architecture": "amd64",
    46  	"gadget":       "gadget",
    47  	"kernel":       "kernel",
    48  }
    49  
    50  var _ = check.Suite(&modelSuite{})
    51  
    52  type modelSuite struct {
    53  	apiBaseSuite
    54  }
    55  
    56  func (s *modelSuite) TestPostRemodelUnhappy(c *check.C) {
    57  	s.daemon(c)
    58  
    59  	data, err := json.Marshal(daemon.PostModelData{NewModel: "invalid model"})
    60  	c.Check(err, check.IsNil)
    61  
    62  	req, err := http.NewRequest("POST", "/v2/model", bytes.NewBuffer(data))
    63  	c.Assert(err, check.IsNil)
    64  	rsp := s.errorReq(c, req, nil)
    65  	c.Assert(rsp.Status, check.Equals, 400)
    66  	c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Matches, "cannot decode new model assertion: .*")
    67  }
    68  
    69  func (s *modelSuite) TestPostRemodel(c *check.C) {
    70  	oldModel := s.Brands.Model("my-brand", "my-old-model", modelDefaults)
    71  	newModel := s.Brands.Model("my-brand", "my-old-model", modelDefaults, map[string]interface{}{
    72  		"revision": "2",
    73  	})
    74  
    75  	d := s.daemonWithOverlordMockAndStore(c)
    76  	hookMgr, err := hookstate.Manager(d.Overlord().State(), d.Overlord().TaskRunner())
    77  	c.Assert(err, check.IsNil)
    78  	deviceMgr, err := devicestate.Manager(d.Overlord().State(), hookMgr, d.Overlord().TaskRunner(), nil)
    79  	c.Assert(err, check.IsNil)
    80  	d.Overlord().AddManager(deviceMgr)
    81  	st := d.Overlord().State()
    82  	st.Lock()
    83  	assertstatetest.AddMany(st, s.StoreSigning.StoreAccountKey(""))
    84  	assertstatetest.AddMany(st, s.Brands.AccountsAndKeys("my-brand")...)
    85  	s.mockModel(c, st, oldModel)
    86  	st.Unlock()
    87  
    88  	soon := 0
    89  	var origEnsureStateSoon func(*state.State)
    90  	origEnsureStateSoon, restore := daemon.MockEnsureStateSoon(func(st *state.State) {
    91  		soon++
    92  		origEnsureStateSoon(st)
    93  	})
    94  	defer restore()
    95  
    96  	var devicestateRemodelGotModel *asserts.Model
    97  	defer daemon.MockDevicestateRemodel(func(st *state.State, nm *asserts.Model) (*state.Change, error) {
    98  		devicestateRemodelGotModel = nm
    99  		chg := st.NewChange("remodel", "...")
   100  		return chg, nil
   101  	})()
   102  
   103  	// create a valid model assertion
   104  	c.Assert(err, check.IsNil)
   105  	modelEncoded := string(asserts.Encode(newModel))
   106  	data, err := json.Marshal(daemon.PostModelData{NewModel: modelEncoded})
   107  	c.Check(err, check.IsNil)
   108  
   109  	// set it and validate that this is what we was passed to
   110  	// devicestateRemodel
   111  	req, err := http.NewRequest("POST", "/v2/model", bytes.NewBuffer(data))
   112  	c.Assert(err, check.IsNil)
   113  	rsp := s.asyncReq(c, req, nil)
   114  	c.Assert(rsp.Status, check.Equals, 202)
   115  	c.Check(devicestateRemodelGotModel, check.DeepEquals, newModel)
   116  
   117  	st.Lock()
   118  	defer st.Unlock()
   119  	chg := st.Change(rsp.Change)
   120  	c.Assert(chg, check.NotNil)
   121  
   122  	c.Assert(st.Changes(), check.HasLen, 1)
   123  	chg1 := st.Changes()[0]
   124  	c.Assert(chg, check.DeepEquals, chg1)
   125  	c.Assert(chg.Kind(), check.Equals, "remodel")
   126  	c.Assert(chg.Err(), check.IsNil)
   127  
   128  	c.Assert(soon, check.Equals, 1)
   129  }
   130  
   131  func (s *modelSuite) TestGetModelNoModelAssertion(c *check.C) {
   132  
   133  	d := s.daemonWithOverlordMockAndStore(c)
   134  	hookMgr, err := hookstate.Manager(d.Overlord().State(), d.Overlord().TaskRunner())
   135  	c.Assert(err, check.IsNil)
   136  	deviceMgr, err := devicestate.Manager(d.Overlord().State(), hookMgr, d.Overlord().TaskRunner(), nil)
   137  	c.Assert(err, check.IsNil)
   138  	d.Overlord().AddManager(deviceMgr)
   139  
   140  	req, err := http.NewRequest("GET", "/v2/model", nil)
   141  	c.Assert(err, check.IsNil)
   142  	rsp := s.errorReq(c, req, nil)
   143  	c.Assert(rsp.Status, check.Equals, 404)
   144  	c.Assert(rsp.Result, check.FitsTypeOf, &daemon.ErrorResult{})
   145  	errRes := rsp.Result.(*daemon.ErrorResult)
   146  	c.Assert(errRes.Kind, check.Equals, client.ErrorKindAssertionNotFound)
   147  	c.Assert(errRes.Value, check.Equals, "model")
   148  	c.Assert(errRes.Message, check.Equals, "no model assertion yet")
   149  }
   150  
   151  func (s *modelSuite) TestGetModelHasModelAssertion(c *check.C) {
   152  	// make a model assertion
   153  	theModel := s.Brands.Model("my-brand", "my-old-model", modelDefaults)
   154  
   155  	// model assertion setup
   156  	d := s.daemonWithOverlordMockAndStore(c)
   157  	hookMgr, err := hookstate.Manager(d.Overlord().State(), d.Overlord().TaskRunner())
   158  	c.Assert(err, check.IsNil)
   159  	deviceMgr, err := devicestate.Manager(d.Overlord().State(), hookMgr, d.Overlord().TaskRunner(), nil)
   160  	c.Assert(err, check.IsNil)
   161  	d.Overlord().AddManager(deviceMgr)
   162  	st := d.Overlord().State()
   163  	st.Lock()
   164  	assertstatetest.AddMany(st, s.StoreSigning.StoreAccountKey(""))
   165  	assertstatetest.AddMany(st, s.Brands.AccountsAndKeys("my-brand")...)
   166  	s.mockModel(c, st, theModel)
   167  	st.Unlock()
   168  
   169  	// make a new get request to the model endpoint
   170  	req, err := http.NewRequest("GET", "/v2/model", nil)
   171  	c.Assert(err, check.IsNil)
   172  	rec := httptest.NewRecorder()
   173  	s.req(c, req, nil).ServeHTTP(rec, req)
   174  
   175  	// check that we get an assertion response
   176  	c.Check(rec.Code, check.Equals, 200, check.Commentf("body %q", rec.Body))
   177  	c.Check(rec.HeaderMap.Get("Content-Type"), check.Equals, "application/x.ubuntu.assertion")
   178  
   179  	// check that there is only one assertion
   180  	dec := asserts.NewDecoder(rec.Body)
   181  	m, err := dec.Decode()
   182  	c.Assert(err, check.IsNil)
   183  	_, err = dec.Decode()
   184  	c.Assert(err, check.Equals, io.EOF)
   185  
   186  	// check that one of the assertion keys matches what's in the model we
   187  	// provided
   188  	c.Check(m.Type(), check.Equals, asserts.ModelType)
   189  	arch := m.Header("architecture")
   190  	c.Assert(arch, check.FitsTypeOf, "")
   191  	c.Assert(arch.(string), check.Equals, "amd64")
   192  }
   193  
   194  func (s *modelSuite) TestGetModelJSONHasModelAssertion(c *check.C) {
   195  	// make a model assertion
   196  	theModel := s.Brands.Model("my-brand", "my-old-model", modelDefaults)
   197  
   198  	// model assertion setup
   199  	d := s.daemonWithOverlordMockAndStore(c)
   200  	hookMgr, err := hookstate.Manager(d.Overlord().State(), d.Overlord().TaskRunner())
   201  	c.Assert(err, check.IsNil)
   202  	deviceMgr, err := devicestate.Manager(d.Overlord().State(), hookMgr, d.Overlord().TaskRunner(), nil)
   203  	c.Assert(err, check.IsNil)
   204  	d.Overlord().AddManager(deviceMgr)
   205  	st := d.Overlord().State()
   206  	st.Lock()
   207  	assertstatetest.AddMany(st, s.StoreSigning.StoreAccountKey(""))
   208  	assertstatetest.AddMany(st, s.Brands.AccountsAndKeys("my-brand")...)
   209  	s.mockModel(c, st, theModel)
   210  	st.Unlock()
   211  
   212  	// make a new get request to the model endpoint with json as true
   213  	req, err := http.NewRequest("GET", "/v2/model?json=true", nil)
   214  	c.Assert(err, check.IsNil)
   215  	rsp := s.syncReq(c, req, nil)
   216  	// get the body and try to unmarshal into modelAssertJSON
   217  	c.Assert(rsp.Result, check.FitsTypeOf, daemon.ModelAssertJSON{})
   218  
   219  	jsonResponse := rsp.Result.(daemon.ModelAssertJSON)
   220  
   221  	// get the architecture key from the headers
   222  	arch, ok := jsonResponse.Headers["architecture"]
   223  	c.Assert(ok, check.Equals, true)
   224  
   225  	// ensure that the architecture key is what we set in the model defaults
   226  	c.Assert(arch, check.FitsTypeOf, "")
   227  	c.Assert(arch.(string), check.Equals, "amd64")
   228  }
   229  
   230  func (s *modelSuite) TestGetModelNoSerialAssertion(c *check.C) {
   231  
   232  	d := s.daemonWithOverlordMockAndStore(c)
   233  	hookMgr, err := hookstate.Manager(d.Overlord().State(), d.Overlord().TaskRunner())
   234  	c.Assert(err, check.IsNil)
   235  	deviceMgr, err := devicestate.Manager(d.Overlord().State(), hookMgr, d.Overlord().TaskRunner(), nil)
   236  	c.Assert(err, check.IsNil)
   237  	d.Overlord().AddManager(deviceMgr)
   238  
   239  	req, err := http.NewRequest("GET", "/v2/model/serial", nil)
   240  	c.Assert(err, check.IsNil)
   241  	rsp := s.errorReq(c, req, nil)
   242  	c.Assert(rsp.Status, check.Equals, 404)
   243  	c.Assert(rsp.Result, check.FitsTypeOf, &daemon.ErrorResult{})
   244  	errRes := rsp.Result.(*daemon.ErrorResult)
   245  	c.Assert(errRes.Kind, check.Equals, client.ErrorKindAssertionNotFound)
   246  	c.Assert(errRes.Value, check.Equals, "serial")
   247  	c.Assert(errRes.Message, check.Equals, "no serial assertion yet")
   248  }
   249  
   250  func (s *modelSuite) TestGetModelHasSerialAssertion(c *check.C) {
   251  	// make a model assertion
   252  	theModel := s.Brands.Model("my-brand", "my-old-model", modelDefaults)
   253  
   254  	deviceKey, _ := assertstest.GenerateKey(752)
   255  
   256  	encDevKey, err := asserts.EncodePublicKey(deviceKey.PublicKey())
   257  	c.Assert(err, check.IsNil)
   258  
   259  	// model assertion setup
   260  	d := s.daemonWithOverlordMockAndStore(c)
   261  	hookMgr, err := hookstate.Manager(d.Overlord().State(), d.Overlord().TaskRunner())
   262  	c.Assert(err, check.IsNil)
   263  	deviceMgr, err := devicestate.Manager(d.Overlord().State(), hookMgr, d.Overlord().TaskRunner(), nil)
   264  	c.Assert(err, check.IsNil)
   265  	d.Overlord().AddManager(deviceMgr)
   266  	st := d.Overlord().State()
   267  	st.Lock()
   268  	defer st.Unlock()
   269  	assertstatetest.AddMany(st, s.StoreSigning.StoreAccountKey(""))
   270  	assertstatetest.AddMany(st, s.Brands.AccountsAndKeys("my-brand")...)
   271  	s.mockModel(c, st, theModel)
   272  
   273  	serial, err := s.Brands.Signing("my-brand").Sign(asserts.SerialType, map[string]interface{}{
   274  		"authority-id":        "my-brand",
   275  		"brand-id":            "my-brand",
   276  		"model":               "my-old-model",
   277  		"serial":              "serialserial",
   278  		"device-key":          string(encDevKey),
   279  		"device-key-sha3-384": deviceKey.PublicKey().ID(),
   280  		"timestamp":           time.Now().Format(time.RFC3339),
   281  	}, nil, "")
   282  	c.Assert(err, check.IsNil)
   283  	assertstatetest.AddMany(st, serial)
   284  	devicestatetest.SetDevice(st, &auth.DeviceState{
   285  		Brand:  "my-brand",
   286  		Model:  "my-old-model",
   287  		Serial: "serialserial",
   288  	})
   289  
   290  	st.Unlock()
   291  	defer st.Lock()
   292  
   293  	// make a new get request to the serial endpoint
   294  	req, err := http.NewRequest("GET", "/v2/model/serial", nil)
   295  	c.Assert(err, check.IsNil)
   296  	rec := httptest.NewRecorder()
   297  	s.req(c, req, nil).ServeHTTP(rec, req)
   298  
   299  	// check that we get an assertion response
   300  	c.Check(rec.Code, check.Equals, 200, check.Commentf("body %q", rec.Body))
   301  	c.Check(rec.HeaderMap.Get("Content-Type"), check.Equals, "application/x.ubuntu.assertion")
   302  
   303  	// check that there is only one assertion
   304  	dec := asserts.NewDecoder(rec.Body)
   305  	ser, err := dec.Decode()
   306  	c.Assert(err, check.IsNil)
   307  	_, err = dec.Decode()
   308  	c.Assert(err, check.Equals, io.EOF)
   309  
   310  	// check that the device key in the returned assertion matches what we
   311  	// created above
   312  	c.Check(ser.Type(), check.Equals, asserts.SerialType)
   313  	devKey := ser.Header("device-key")
   314  	c.Assert(devKey, check.FitsTypeOf, "")
   315  	c.Assert(devKey.(string), check.Equals, string(encDevKey))
   316  }
   317  
   318  func (s *modelSuite) TestGetModelJSONHasSerialAssertion(c *check.C) {
   319  	// make a model assertion
   320  	theModel := s.Brands.Model("my-brand", "my-old-model", modelDefaults)
   321  
   322  	deviceKey, _ := assertstest.GenerateKey(752)
   323  
   324  	encDevKey, err := asserts.EncodePublicKey(deviceKey.PublicKey())
   325  	c.Assert(err, check.IsNil)
   326  
   327  	// model assertion setup
   328  	d := s.daemonWithOverlordMockAndStore(c)
   329  	hookMgr, err := hookstate.Manager(d.Overlord().State(), d.Overlord().TaskRunner())
   330  	c.Assert(err, check.IsNil)
   331  	deviceMgr, err := devicestate.Manager(d.Overlord().State(), hookMgr, d.Overlord().TaskRunner(), nil)
   332  	c.Assert(err, check.IsNil)
   333  	d.Overlord().AddManager(deviceMgr)
   334  	st := d.Overlord().State()
   335  	st.Lock()
   336  	defer st.Unlock()
   337  	assertstatetest.AddMany(st, s.StoreSigning.StoreAccountKey(""))
   338  	assertstatetest.AddMany(st, s.Brands.AccountsAndKeys("my-brand")...)
   339  	s.mockModel(c, st, theModel)
   340  
   341  	serial, err := s.Brands.Signing("my-brand").Sign(asserts.SerialType, map[string]interface{}{
   342  		"authority-id":        "my-brand",
   343  		"brand-id":            "my-brand",
   344  		"model":               "my-old-model",
   345  		"serial":              "serialserial",
   346  		"device-key":          string(encDevKey),
   347  		"device-key-sha3-384": deviceKey.PublicKey().ID(),
   348  		"timestamp":           time.Now().Format(time.RFC3339),
   349  	}, nil, "")
   350  	c.Assert(err, check.IsNil)
   351  	assertstatetest.AddMany(st, serial)
   352  	devicestatetest.SetDevice(st, &auth.DeviceState{
   353  		Brand:  "my-brand",
   354  		Model:  "my-old-model",
   355  		Serial: "serialserial",
   356  	})
   357  
   358  	st.Unlock()
   359  	defer st.Lock()
   360  
   361  	// make a new get request to the model endpoint with json as true
   362  	req, err := http.NewRequest("GET", "/v2/model/serial?json=true", nil)
   363  	c.Assert(err, check.IsNil)
   364  	rsp := s.syncReq(c, req, nil)
   365  	// get the body and try to unmarshal into modelAssertJSON
   366  	c.Assert(rsp.Result, check.FitsTypeOf, daemon.ModelAssertJSON{})
   367  
   368  	jsonResponse := rsp.Result.(daemon.ModelAssertJSON)
   369  
   370  	// get the architecture key from the headers
   371  	devKey, ok := jsonResponse.Headers["device-key"]
   372  	c.Assert(ok, check.Equals, true)
   373  
   374  	// check that the device key in the returned assertion matches what we
   375  	// created above
   376  	c.Assert(devKey, check.FitsTypeOf, "")
   377  	c.Assert(devKey.(string), check.Equals, string(encDevKey))
   378  }