github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/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  	s.expectRootAccess()
    60  
    61  	data, err := json.Marshal(daemon.PostModelData{NewModel: "invalid model"})
    62  	c.Check(err, check.IsNil)
    63  
    64  	req, err := http.NewRequest("POST", "/v2/model", bytes.NewBuffer(data))
    65  	c.Assert(err, check.IsNil)
    66  	rspe := s.errorReq(c, req, nil)
    67  	c.Assert(rspe.Status, check.Equals, 400)
    68  	c.Check(rspe.Message, check.Matches, "cannot decode new model assertion: .*")
    69  }
    70  
    71  func (s *modelSuite) TestPostRemodel(c *check.C) {
    72  	s.expectRootAccess()
    73  
    74  	oldModel := s.Brands.Model("my-brand", "my-old-model", modelDefaults)
    75  	newModel := s.Brands.Model("my-brand", "my-old-model", modelDefaults, map[string]interface{}{
    76  		"revision": "2",
    77  	})
    78  
    79  	d := s.daemonWithOverlordMockAndStore(c)
    80  	hookMgr, err := hookstate.Manager(d.Overlord().State(), d.Overlord().TaskRunner())
    81  	c.Assert(err, check.IsNil)
    82  	deviceMgr, err := devicestate.Manager(d.Overlord().State(), hookMgr, d.Overlord().TaskRunner(), nil)
    83  	c.Assert(err, check.IsNil)
    84  	d.Overlord().AddManager(deviceMgr)
    85  	st := d.Overlord().State()
    86  	st.Lock()
    87  	assertstatetest.AddMany(st, s.StoreSigning.StoreAccountKey(""))
    88  	assertstatetest.AddMany(st, s.Brands.AccountsAndKeys("my-brand")...)
    89  	s.mockModel(c, st, oldModel)
    90  	st.Unlock()
    91  
    92  	soon := 0
    93  	var origEnsureStateSoon func(*state.State)
    94  	origEnsureStateSoon, restore := daemon.MockEnsureStateSoon(func(st *state.State) {
    95  		soon++
    96  		origEnsureStateSoon(st)
    97  	})
    98  	defer restore()
    99  
   100  	var devicestateRemodelGotModel *asserts.Model
   101  	defer daemon.MockDevicestateRemodel(func(st *state.State, nm *asserts.Model) (*state.Change, error) {
   102  		devicestateRemodelGotModel = nm
   103  		chg := st.NewChange("remodel", "...")
   104  		return chg, nil
   105  	})()
   106  
   107  	// create a valid model assertion
   108  	c.Assert(err, check.IsNil)
   109  	modelEncoded := string(asserts.Encode(newModel))
   110  	data, err := json.Marshal(daemon.PostModelData{NewModel: modelEncoded})
   111  	c.Check(err, check.IsNil)
   112  
   113  	// set it and validate that this is what we was passed to
   114  	// devicestateRemodel
   115  	req, err := http.NewRequest("POST", "/v2/model", bytes.NewBuffer(data))
   116  	c.Assert(err, check.IsNil)
   117  	rsp := s.asyncReq(c, req, nil)
   118  	c.Assert(rsp.Status, check.Equals, 202)
   119  	c.Check(devicestateRemodelGotModel, check.DeepEquals, newModel)
   120  
   121  	st.Lock()
   122  	defer st.Unlock()
   123  	chg := st.Change(rsp.Change)
   124  	c.Assert(chg, check.NotNil)
   125  
   126  	c.Assert(st.Changes(), check.HasLen, 1)
   127  	chg1 := st.Changes()[0]
   128  	c.Assert(chg, check.DeepEquals, chg1)
   129  	c.Assert(chg.Kind(), check.Equals, "remodel")
   130  	c.Assert(chg.Err(), check.IsNil)
   131  
   132  	c.Assert(soon, check.Equals, 1)
   133  }
   134  
   135  func (s *modelSuite) TestGetModelNoModelAssertion(c *check.C) {
   136  
   137  	d := s.daemonWithOverlordMockAndStore(c)
   138  	hookMgr, err := hookstate.Manager(d.Overlord().State(), d.Overlord().TaskRunner())
   139  	c.Assert(err, check.IsNil)
   140  	deviceMgr, err := devicestate.Manager(d.Overlord().State(), hookMgr, d.Overlord().TaskRunner(), nil)
   141  	c.Assert(err, check.IsNil)
   142  	d.Overlord().AddManager(deviceMgr)
   143  
   144  	req, err := http.NewRequest("GET", "/v2/model", nil)
   145  	c.Assert(err, check.IsNil)
   146  	rspe := s.errorReq(c, req, nil)
   147  	c.Assert(rspe.Status, check.Equals, 404)
   148  	c.Assert(rspe.Kind, check.Equals, client.ErrorKindAssertionNotFound)
   149  	c.Assert(rspe.Value, check.Equals, "model")
   150  	c.Assert(rspe.Message, check.Equals, "no model assertion yet")
   151  }
   152  
   153  func (s *modelSuite) TestGetModelHasModelAssertion(c *check.C) {
   154  	// make a model assertion
   155  	theModel := s.Brands.Model("my-brand", "my-old-model", modelDefaults)
   156  
   157  	// model assertion setup
   158  	d := s.daemonWithOverlordMockAndStore(c)
   159  	hookMgr, err := hookstate.Manager(d.Overlord().State(), d.Overlord().TaskRunner())
   160  	c.Assert(err, check.IsNil)
   161  	deviceMgr, err := devicestate.Manager(d.Overlord().State(), hookMgr, d.Overlord().TaskRunner(), nil)
   162  	c.Assert(err, check.IsNil)
   163  	d.Overlord().AddManager(deviceMgr)
   164  	st := d.Overlord().State()
   165  	st.Lock()
   166  	assertstatetest.AddMany(st, s.StoreSigning.StoreAccountKey(""))
   167  	assertstatetest.AddMany(st, s.Brands.AccountsAndKeys("my-brand")...)
   168  	s.mockModel(c, st, theModel)
   169  	st.Unlock()
   170  
   171  	// make a new get request to the model endpoint
   172  	req, err := http.NewRequest("GET", "/v2/model", nil)
   173  	c.Assert(err, check.IsNil)
   174  	rec := httptest.NewRecorder()
   175  	s.req(c, req, nil).ServeHTTP(rec, req)
   176  
   177  	// check that we get an assertion response
   178  	c.Check(rec.Code, check.Equals, 200, check.Commentf("body %q", rec.Body))
   179  	c.Check(rec.HeaderMap.Get("Content-Type"), check.Equals, "application/x.ubuntu.assertion")
   180  
   181  	// check that there is only one assertion
   182  	dec := asserts.NewDecoder(rec.Body)
   183  	m, err := dec.Decode()
   184  	c.Assert(err, check.IsNil)
   185  	_, err = dec.Decode()
   186  	c.Assert(err, check.Equals, io.EOF)
   187  
   188  	// check that one of the assertion keys matches what's in the model we
   189  	// provided
   190  	c.Check(m.Type(), check.Equals, asserts.ModelType)
   191  	arch := m.Header("architecture")
   192  	c.Assert(arch, check.FitsTypeOf, "")
   193  	c.Assert(arch.(string), check.Equals, "amd64")
   194  }
   195  
   196  func (s *modelSuite) TestGetModelJSONHasModelAssertion(c *check.C) {
   197  	// make a model assertion
   198  	theModel := s.Brands.Model("my-brand", "my-old-model", modelDefaults)
   199  
   200  	// model assertion setup
   201  	d := s.daemonWithOverlordMockAndStore(c)
   202  	hookMgr, err := hookstate.Manager(d.Overlord().State(), d.Overlord().TaskRunner())
   203  	c.Assert(err, check.IsNil)
   204  	deviceMgr, err := devicestate.Manager(d.Overlord().State(), hookMgr, d.Overlord().TaskRunner(), nil)
   205  	c.Assert(err, check.IsNil)
   206  	d.Overlord().AddManager(deviceMgr)
   207  	st := d.Overlord().State()
   208  	st.Lock()
   209  	assertstatetest.AddMany(st, s.StoreSigning.StoreAccountKey(""))
   210  	assertstatetest.AddMany(st, s.Brands.AccountsAndKeys("my-brand")...)
   211  	s.mockModel(c, st, theModel)
   212  	st.Unlock()
   213  
   214  	// make a new get request to the model endpoint with json as true
   215  	req, err := http.NewRequest("GET", "/v2/model?json=true", nil)
   216  	c.Assert(err, check.IsNil)
   217  	rsp := s.syncReq(c, req, nil)
   218  	// get the body and try to unmarshal into modelAssertJSON
   219  	c.Assert(rsp.Result, check.FitsTypeOf, daemon.ModelAssertJSON{})
   220  
   221  	jsonResponse := rsp.Result.(daemon.ModelAssertJSON)
   222  
   223  	// get the architecture key from the headers
   224  	arch, ok := jsonResponse.Headers["architecture"]
   225  	c.Assert(ok, check.Equals, true)
   226  
   227  	// ensure that the architecture key is what we set in the model defaults
   228  	c.Assert(arch, check.FitsTypeOf, "")
   229  	c.Assert(arch.(string), check.Equals, "amd64")
   230  }
   231  
   232  func (s *modelSuite) TestGetModelNoSerialAssertion(c *check.C) {
   233  
   234  	d := s.daemonWithOverlordMockAndStore(c)
   235  	hookMgr, err := hookstate.Manager(d.Overlord().State(), d.Overlord().TaskRunner())
   236  	c.Assert(err, check.IsNil)
   237  	deviceMgr, err := devicestate.Manager(d.Overlord().State(), hookMgr, d.Overlord().TaskRunner(), nil)
   238  	c.Assert(err, check.IsNil)
   239  	d.Overlord().AddManager(deviceMgr)
   240  
   241  	req, err := http.NewRequest("GET", "/v2/model/serial", nil)
   242  	c.Assert(err, check.IsNil)
   243  	rspe := s.errorReq(c, req, nil)
   244  	c.Assert(rspe.Status, check.Equals, 404)
   245  	c.Assert(rspe.Kind, check.Equals, client.ErrorKindAssertionNotFound)
   246  	c.Assert(rspe.Value, check.Equals, "serial")
   247  	c.Assert(rspe.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  }