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